scikit-learnのVotingClassifier/Regressorでお手軽にrandom seed average

金曜日, 12月 20, 2019

Python 機械学習

t f B! P L
アンサンブルは、テーブルデータ系の機械学習でよく用いられる手法です。
アンサンブルの基本は多数決で、複数のモデルの予測結果を統合します。

ひとつのモデルだと間違えてしまう場合でも、他のモデルと補い合うことで間違え(予測ミス)を減らそうという考えですね。
クラスを予測する分類問題では多数決、数値を直接予測する回帰問題では平均をとるのが一般的です。

ソロ活動が多い私にはなんだかグサグサくる話です(笑)

random seedでモデルを水増し

複数のモデルの予測で多数決/平均といっても、足を引っ張るレベルでダメダメのモデルが混入していると、平均の場合は逆に精度が悪化するのでは・・・?という事態が容易に想像できます。
また、多数決であっても、ダメダメなモデルが混入しているメリットはないです。

scikit-learnには、ロジスティクス回帰、ランダムフォレスト、SVM、ナイーブベイズなどメジャーなモデルがよりどりミドリで実装されています。
しかし、これらの予測平均を取るとして、これらひとつひとつを個別に最低限足を引っ張らないレベルまでパラメータチューニングするのは結構大変だったりします。

そこでモデルとハイパーパラメータは同じものを使いつつも、random seedだけ変えてモデルの出力に多様性をもたせるのが、random seed averageまたはseed averagingと呼ばれる手法です。

random seedは、その名の通り、モデルのアルゴリズムのうち乱数に依存する処理の乱数シードになります。
そのため、この値を変えると乱数に依存する処理に微妙な違いが出るため、同じモデル(アルゴリズム)を使っていたとしても、結果的にその出力(予測値)に一定の差がでる場合があります。
そのため、同じモデルを使いながらも擬似的に複数のモデルを作り出すことができるのです。

この乱数に依存する処理というのは、よくあるものとしてはデータや特徴量のサンプリングがあげられます。
例えば、ランダムフォレストでは、ひとつの決定木をつくる際の、データサンプリングと着目する特徴量集合を決定する部分で利用します。

つまり、三人よれば文殊の知恵的なことをしたいが、3人(違うモデル3つ)を集めるのが大変なので、ひとつのモデルで乱数を変えて3パターンのモデルを作るテクニックというわけです。

乱数シードはsklearnの場合はモデルのインスタンスを宣言するときに、引数random_stateで指定できます。
乱数シードを変えるだけなのでかなりコストは低いですよね。

VotingClassifierで実装できる

さらに楽をできるのが昨今の機械学習です。
ただでさえ楽なsklearnのAPIを使うと、seed averagingもラクラクにできます。
VotingClassifierというAPIを使います。

まずふつうにランダムフォレスト+手書き文字分類をやってみます。

from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score

digits = load_digits()
X = digits.data
y = digits.target

train_x, test_x, train_y, test_y = train_test_split(X, y, random_state=123)

clf = RandomForestClassifier(random_state=1)
clf.fit(train_x, train_y)
pred_y = clf.predict(test_x)
print(accuracy_score(test_y, pred_y))

この単体モデルの精度は0.9444でした。

from sklearn.ensemble import VotingClassifier

estimators = [
  ("rf1", RandomForestClassifier(random_state=1)),
  ("rf2", RandomForestClassifier(random_state=2)),
  ("rf3", RandomForestClassifier(random_state=3)),
]

clf = VotingClassifier(estimators=estimators, voting="soft")

clf.fit(train_x, train_y)
pred_y = clf.predict(test_x)
print(accuracy_score(test_y, pred_y))

一方で多数決を使うと0.9688まであがります!

しかも、モデルを定義した配列をVotingClassifierに渡すだけで、VC自体がふつうのsklearnのAPIとして簡単に使えます。
すごい。

さらにすごいことに、これ、random_state=2とrandom_state=3のモデルの精度は、それぞれ0.9244と0.9444なんですね。
一匹のすごいモデルが頑張ってくれているわけではなくて、みんな同じくらいの精度で、弱い部分を補い合って精度を上げられていることがわかります。

今日のベストプラクティス

テーブルデータ系の機械学習コンペではもう常識化しているようです。
一方で、同じモデルを複数作るので、学習時間はそれだけ伸びます。
特に分析初期で、特徴量をごりごり作っている段階では、seed averagingはしません。
ある程度単体モデルが完成した後、最後のひと押しに使うのが良いです。

QooQ