静かなる名辞

pythonとプログラミングのこと


ランダムフォレストを使うなら変数選択はしなくてもいいのか?

はじめに

 表題の通りの話をたまに聞きます。「ランダムフォレストは内部で変数選択を行う。なので変数選択は必要ない」という主張です。

 しかし個人的には、それはあくまでも

  • 他の手法*1と比べれば変数選択しなかった場合の悪影響が少ない

 ということであって、ランダムフォレストであっても変数選択した方が良いんじゃ? ということを昔からずっと思っていました。

 検証してみます。

思考実験

 実際に検証する前に思考実験を行います。

 まずパターンA(変数選択なし)とパターンB(変数選択あり)の2通りを考えます。

  • パターンA

 有効な変数:10個
 無効な変数:90個

  • パターンB

 有効な変数:10個
 のみ(無効な変数なし)

 ランダムフォレストの弱分類器では、元々の変数の数の平方根くらいの数の変数を使うのが一般的です。そうすると、

  • パターンAの場合

 弱分類器で使う変数は10個。うち有効なもの(の期待値)は1個。

  • パターンBの場合

 弱分類器で使う変数は3個(切り捨てたとする)。うち有効なものは3個。

 となり、どう見てもパターンBの方が良い結果を生みそうです。期待される結果としては、

  • 各弱分類器の性能が上がることで、全体の性能が向上
  • 少ない木の本数で済む
  • 過学習しづらくなる

 などがあり、いいことずくめです。

実験

 データセットや条件を変えてやってみたら、変数選択が効くデータや条件、そうでもないデータや条件がいろいろ出てきてしまいました。

 要するにケースバイケースなのですが、結果がわかりやすかったやつだけ見せることにします(いい加減)。

 やったこと

  • データセットはscikit-learnのdigits
  • 元々の特徴量ベクトルは64次元だが、ノイズを128次元足して192次元にした
  • SelectPercentileで特徴選択して10%ずつ増やしながら見ていく
  • 分類器はランダムフォレスト
  • 訓練データとテストデータに半分ずつ分けて評価した

sklearn.feature_selection.SelectPercentile — scikit-learn 0.21.3 documentation

 コード

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.feature_selection import SelectPercentile
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import Pipeline
from sklearn.metrics import f1_score

def main():
    dataset = load_digits()
    X, y = dataset.data, dataset.target
    X = np.hstack([X, np.random.normal(size=(X.shape[0], X.shape[1]*2))])
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.5, stratify=y, 
        shuffle=True, random_state=0)

    sp = SelectPercentile()
    rfc = RandomForestClassifier(n_jobs=-1)
    pl = Pipeline([("sp", sp), ("rf", rfc)])
    
    fig, ax = plt.subplots()
    ps = [10*x for x in range(1, 11)]
    for n_estimators in [50, 100, 150, 200]:
        f1s = []
        for p in ps:
            pl.set_params(sp__percentile=p, 
                          rf__n_estimators=n_estimators)
            pl.fit(X_train, y_train)
            y_pred = pl.predict(X_test)
            f1s.append(f1_score(y_test, y_pred, average="macro"))
        ax.plot(ps, f1s, label="RF_n:" + str(n_estimators))
    plt.legend()
    plt.savefig("result.png")

if __name__ == "__main__":
    main()

 結果です。

result.png
result.png

 ほら、変数選択した方が結果が良い。一番良いのは30%のときなので、57個くらいの変数が使われたことになります。

考察

 思考実験でも示した通り、理屈の上ではやったほうが良いのですが、これを実験で示そうとするとけっこう難しかったというのが率直な感想です。

 上述の通り、ここに載せていないものも色々試したのですが、まず「明らかに無意味なノイズ特徴量」をわざと入れるくらいの人工的な条件でないと、特徴選択しない方が悪いという結果はほとんど得ることができませんでした。

 ランダムフォレストを使っていると「変数選択しなくても(大真面目に変数選択してちゃんとチューニングしたときに得られる)最高性能に近い」ということはありがちで、「たとえ統計的に有意な関係がなくても、目的変数と何らかの形で関係がある(気がする)」、くらいの説明変数(特徴量)は入れておいてもそうそう悪さはしません。

 適当に変数を取っているのでゴミがたくさん入っている、というようなシチュエーションでは威力を発揮することでしょう。

まとめ

 わざとらしい感じはしますが、一応ランダムフォレストでも特徴選択した方が良いときはあり得る、ということは示せたのでよしとします。

*1:特に個体間の距離や内積に基づくものは変数選択しないとまずいでしょう