一般的な機械学習のアルゴリズムでは、パラメタチューニングにはグリッドサーチ・交差検証を組み合わせて使うのが割と普通だと思います。sklearnにはそれ専用のGridSearchCVというクラスまで用意されています。
実際問題としては、GridSearchは良いとしても交差検証をやったのでは計算コストをたくさん食います。なので、他の方法で代替できるなら、そうしたいところです。
そして、RandomForestはOOB誤り率という、いかにも強そうなスコアを計算できます。これの良いところは一回fitしただけで計算でき、交差検証と同じ(ような)結果が得られることです。
なので、OOB誤り率を使ったパラメタチューニングを試してみたいと思います。
OOB誤り率とはなんぞ?
OOB誤り率、OOBエラー、OOB error等と呼ばれます。これを理解するためにはランダムフォレストの学習過程を(少なくとも大雑把には)理解する必要があります。
ランダムフォレストは木を一本作る度に、データをランダムサンプリングします(「ランダム」の所以です)。サンプリング方法はブートストラップサンプリングです。詳細はググって頂くとして、とにかくサンプリングの結果、各決定木に対して「訓練に使われなかったデータ」が存在することになります。逆に言えば、各データに対して「そのデータを使っていない決定木の集合」があります。このことを利用して、「そのデータを使っていない決定木」だけ利用して推定し、汎化性能を見ようというのがOOB誤り率のコンセプトです。
実験
以下のようなコードを書きました。
# coding: UTF-8 import time from itertools import product from sklearn.datasets import load_digits from sklearn.ensemble import RandomForestClassifier as RFC from sklearn.model_selection import train_test_split from sklearn.model_selection import GridSearchCV from sklearn.metrics import precision_recall_fscore_support as prf def oob_tune(clf, params, X, y): key_index = list(params.keys()) result_dict = dict() for ps in product(*[params[k] for k in key_index]): clf.set_params(**dict(zip(key_index, ps))) clf.fit(X, y) result_dict[ps] = clf.oob_score_ return dict(zip(key_index, sorted(result_dict.items(), key=lambda x:x[1], reverse=True)[0][0])) def main(): digits = load_digits() X_train, X_test, y_train, y_test = train_test_split( digits.data, digits.target) params = {"n_estimators":[50, 100, 500], "max_features":[4, 8, 12], "min_samples_leaf":[1, 2]} rfc = RFC(oob_score=False, n_jobs=-1) t1 = time.time() clf = GridSearchCV(rfc, params, cv=6, n_jobs=-1) clf.fit(X_train, y_train) t2 = time.time() best_params = clf.best_params_ print("GridSearchCV") print("time:{0:.1f}".format(t2-t1)) print("best params") print(best_params) rfc = RFC(**best_params) rfc.fit(X_train, y_train) y_pred = rfc.predict(X_test) score = prf(y_test, y_pred, average="macro") print("p:{0:.3f} r:{0:.3f} f:{0:.3f}".format(*score)) rfc = RFC(oob_score=True, n_jobs=-1) t1 = time.time() best_params = oob_tune(rfc, params, X_train, y_train) t2 = time.time() print("oob") print("time:{0:.1f}".format(t2-t1)) print("best params") print(best_params) rfc = RFC(**best_params) rfc.fit(X_train, y_train) y_pred = rfc.predict(X_test) score = prf(y_test, y_pred, average="macro") print("p:{0:.3f} r:{0:.3f} f:{0:.3f}".format(*score)) if __name__ == "__main__": main()
sklearnの機能でありそうな気がしましたが、見つからなかったので*1、OOB誤り率を使ったパラメタチューニングのプログラムは自分で書きました。
同じパラメタ候補に対してのグリッドサーチで、GridSearchCVと比較しています。
細かい説明は抜きで(気になる人はプログラムを追ってください。簡単なので)、結果を貼ります。
GridSearchCV time:41.8 best params {'n_estimators': 500, 'max_features': 4, 'min_samples_leaf': 1} p:0.977 r:0.977 f:0.977 oob time:13.1 best params {'n_estimators': 500, 'max_features': 8, 'min_samples_leaf': 1} p:0.979 r:0.979 f:0.979
やはりOOB誤り率を使った方が速いです。最適パラメータは困ったことに両者で食い違っています。テストデータでのスコアはOOB誤り率で計算した最適パラメータを用いた方がびみょ~に高いですが、これくらいだとほとんど誤差かもしれません(1データか2データ程度の違い)。
まとめ
交差検証でやるのと同程度の結果が得られる・・・と言い切るには微妙な部分がありますが、とにかくやればできます。
自分でパラメタチューニングのプログラムを書くと、sklearnのインターフェースから逸脱するデメリットがあるので、実際にやるべきかどうかは微妙。scoringの関数と実質的にcross validateしないcv(ダミー)を渡せば、GridSearchCVを利用してもやれそうではありますが。ちょっと悩ましいところです*2。
とにかくさっさとパラメタチューニングしたいんだ! というときは使えるでしょう。