load
from sklearn.datasets import load_diabetes
from sklearn.model_selection import GridSearchCV, KFold, cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import RandomForestRegressor
X, y = load_diabetes(return_X_y=True)
estimatorのハイパーパラメータの探索
estimator = RandomForestRegressor()
param_grid = {'n_estimators':[10,50,100], 'max_depth':[3,5,10]}
tune_cv = KFold(3, shuffle=True)
evaluate_cv = KFold(5, shuffle=True)
# for cross-validation
gsv_cv = GridSearchCV(estimator, param_grid, cv=tune_cv)
# for double cross-validation
gsv_dcv = GridSearchCV(estimator, param_grid, cv=tune_cv)
クロスバリデーションによるハイパーパラメータ最適化&評価
gsv_cv.fit(X,y)
best_estimator = gsv_cv.best_estimator_
score = cross_val_score(best_estimator , X, y, cv=evaluate_cv)
# 上と下のコードは同じです。
score = cross_val_score(gsv_cv.best_estaimator_, X, y, cv=evaluate_cv)
このコードでは
- GridSearchCV で最適なハイパーパラメータを決定する
- そのハイパーパラメータを用いてcross_val_scoreを求める
という手順になり、不適切な評価結果となる可能性があります。
詳細は以下のサイト等をご参照ください。
クロスバリデーションの結果がよくなるようにハイパーパラメータの値を決めているからです。つまり、クロスバリデーションの結果を実測値にフィッティング (適合) してハイパーパラメータの値を決定しているようなものです。
ダブルクロスバリデーション(モデルクロスバリデーション)でテストデータいらず~サンプルが少ないときのモデル検証~
scikit-learnのドキュメントにも過適合の危険性について記載されています。
https://scikit-learn.org/stable/auto_examples/model_selection/plot_nested_cross_validation_iris.html
ダブルクロスバリデーションによるハイパーパラメータ最適化&評価
情報のleakageを避け、正しくモデルの評価を行うためにはdouble cross-validation (nested cross-validationとも)が必要です。
この場合、「モデル」とは「GridSearchCVでハイパーパラメータを最適化する手順も含めた機械学習モデル(GridSearchCV(estimator, param_grid))」のことを指します。
scikit-learnは上手く設計されているため、cross_val_score()にGridSearchCVを渡すとそれだけでdouble cross validationの評価値を得ることが出来ます。
score = cross_val_score(gsv_dcv , X, y, cv=evaluate_cv)
# 上と下のコードは同じ内容です。
score = cross_val_score(GridSearchCV(RandomForestRegressor(), param_grid={'n_estimators':[10,50,100], 'max_depth':[3,5,10]})) , X, y, cv=KFold(3, shuffle=True))
なぜ cross_val_score(gsv_dcv , X, y, cv=evaluate_cv) の一行で
ダブルクロスバリデーションが評価できるのかは、詳細なscikit-learnの仕様説明が必要になるので割愛しますが、
ざっくり説明すると以下の仕様が効いてきます。
・cross_val_scoreでは各foldごとに独立したestimatorを用い、fit_predictを行うこと
・GridSearchCVはfitにより、best_estimator_を定め、predictではbest_estimator_が用いられること
・cloneは__init__時に定義済のパラメータのみコピーし、best_estimator_などはコピーされないこと
などです。
詳しく知りたい方はドキュメントやコードを参照ください。
GridSearchCV.predict 仕様
GridSearchCV.predict ソース
GridSearchCV.fit
clone
Pipelineを用いる場合
selectorのハイパーパラメータの探索
selector = SelectFromModel(RandomForestRegressor())
selector_param_grid = {'selector__threshold':[0.1,0.2]}
pipe = Pipeline([('selector', selector), ('estimator', estimator)])
param_grid = {**selector_param_grid} #python 3.5以上
tune_cv = KFold(3, shuffle=True)
evaluate_cv = KFold(5, shuffle=True)
# for cross-validation
gsv_cv = GridSearchCV(pipe , param_grid, cv=tune_cv)
# for double cross-validation
gsv_dcv = GridSearchCV(pipe , param_grid, cv=tune_cv)
クロスバリデーションによるハイパーパラメータ最適化&評価
gsv_cv.fit(X,y)
if True:
[setattr(i[1], 'prefit',True) for i in gsv_cv.estimator.steps
if hasattr(i[1],'prefit')]
best_pipe = gsv_cv.best_estimator_
score = cross_val_score(best_pipe , X, y, cv=evaluate_cv)
ダブルクロスバリデーションによるハイパーパラメータ最適化&評価
score = cross_val_score(gsv, X, y, cv=evaluate_cv)
pipelineを用いる場合も同じ様に一行でダブルクロスバリデーションを評価することが出来ます。
TIPS
GridSearchCVでcvにLeaveOneOutCV()を指定するとエラーとなります。
これはデフォルトではmetricsにr2_scoreが指定されており、n=1ではr2を定義できないためです。
代わりにmetricsにneg_mean_squared_errorを指定するとエラー回避できます。
他に指定可能なmetrics:https://qiita.com/shnchr/items/f5066021b7143566f950