
XGBoostでグリッドサーチとクロスバリデーション2
前回はクロスバリデーション(CV)までやりました。今回はグリッドサーチ(GS)と組み合わせて最適なパラメーターを探していきます。
GridSearchCVでGSCV
forで書いてもいいんですが、sklearnにGridSearchCVというとても便利な関数があります。
GSをループさせながらCVでmean_best_scoreを探してそのパラメーターで全体fitまで行ってくれる凄いやつ。
train_test_splitでtrainとtestデータを分割する前にデータを全体に対してGSCVを行います。*1
import xgboost as xgb
import matplotlib.pyplot as plt
from sklearn.datasets import load_boston
from sklearn import metrics as met
import numpy as np
from sklearn.model_selection import train_test_split, GridSearchCV
dataset = load_boston()
x = dataset.data
t = dataset.target
mod3 = xgb.XGBRegressor(max_depth = 6, learning_rate = 0.1, n_estimators=10000, objective='reg:squarederror', gamma=0, min_child_weight=1, subsample=1, colsample_bytree=1)
# モデルを作成。xgb.trainにおけるparamの部分
params3 = {'max_depth': [8, 7, 6, 5, 4, 3], 'learning_rate': [0.1, 0.2, 0.3], 'gamma': [0.1, 0.2, 0.3], 'min_child_weight': [0.8, 1, 1.2], 'subsample': [1, 0.9, 0.8], 'colsample_bytree': [1, 0.9, 0.8]}
gscv3 = GridSearchCV(mod3, params3, cv = 5, refit=True, scoring='r2', verbose = 1, n_jobs=-1)
# gscvをこれからやるよっていう宣言だけ。グリッドサーチは.fitで実行される。
# n_jobs=-1にするとCPU100%で全コア並列計算。とても速い。
evallist = [(x, t)]
gscv3.fit(x, t, eval_metric='rmse', eval_set=evallist, early_stopping_rounds=100)
# 全データに対して学習を行う。evallistの値に対してRMSEで評価を行い、100round後も変化がなければ終了。
print(gscv3.best_params_)
# {'colsample_bytree': 0.9, 'gamma': 0.3, 'learning_rate': 0.1, 'max_depth': 3, 'min_child_weight': 0.8, 'subsample': 0.9}
x_train, x_test, t_train, t_test = train_test_split(x, t, test_size=0.2, random_state=0)
train_pred = gscv3.predict(x_train)
test_pred = gscv3.predict(x_test)
print('train_RMSE_score_is_{:.4f}, test_RMSE_score_is_{:.4f}'.format(np.sqrt(met.mean_squared_error(t_train, train_pred)), np.sqrt(met.mean_squared_error(t_test, test_pred))))
# 0.2281, 0.2537
print('train_R2_score_is_{:.4f}, test_R2_score_is_{:.4f}'.format(met.r2_score(t_train, train_pred), met.r2_score(t_test, test_pred)))
# 0.9994, 0.9992
5*(3^5)*5回学習するのでかなりの計算量になります。Colabじゃとても実行できなかったのですがローカルで3分ぐらいで終わりました。
新しいPC作っといてよかった。
得られたbest_params_は
'colsample_bytree': 0.9, 'gamma': 0.3, 'learning_rate': 0.1, 'max_depth': 3, 'min_child_weight': 0.8, 'subsample': 0.9
で、その時のmean_test_scoreはR^2 = 0.7ぐらいとかなり控えめ。
にもかかわらず分割データでpredictすると精度0.999以上と流石におかしい、、、
のでfitを新たにやり直しました。
パラメーターは**gscv3.best_params_で引き継いでます。
x_train, x_test, t_train, t_test = train_test_split(x, t, test_size=0.2, random_state=0)
mod_all = xgb.XGBRegressor(n_estimators=10000, objective='reg:squarederror', **gscv3.best_params_)
evallist = [(x_train, t_train)]
mod_all.fit(x_train, t_train, eval_metric='rmse', eval_set=evallist, early_stopping_rounds=100)
train_pred = mod_all.predict(x_train)
test_pred = mod_all.predict(x_test)
print('train_RMSE_score_is_{:.4f}, test_RMSE_score_is_{:.4f}'.format(np.sqrt(met.mean_squared_error(t_train, train_pred)), np.sqrt(met.mean_squared_error(t_test, test_pred))))
# _0.2311, 4.6010
print('train_R2_score_is_{:.4f}, test_R2_score_is_{:.4f}'.format(met.r2_score(t_train, train_pred), met.r2_score(t_test, test_pred)))
# 0.9994, 0.7400
これはちゃんとできてる感じがします。
test_scoreがGSCVによってR^2 = 0.7377から0.7400へとわずかに改善されました。
cv_results_でプロット
fig = plt.figure()
ax = fig.add_subplot(ylabel='test_score', xlabel='rank', title='Scores of 5-fold CV')
ax.scatter(gscv3.cv_results_['rank_test_score'], gscv3.cv_results_['split0_test_score'], s=5, label='s0')
ax.scatter(gscv3.cv_results_['rank_test_score'], gscv3.cv_results_['split1_test_score'], s=5, label='s1')
ax.scatter(gscv3.cv_results_['rank_test_score'], gscv3.cv_results_['split2_test_score'], s=5, label='s2')
ax.scatter(gscv3.cv_results_['rank_test_score'], gscv3.cv_results_['split3_test_score'], s=5, label='s3')
ax.scatter(gscv3.cv_results_['rank_test_score'], gscv3.cv_results_['split4_test_score'], s=5, label='s4')
plt.legend()
5-foldでのそれぞれのスコアをranking別にプロットしてみました。
分け方によって全然scoreが違いますね、、
今回random_state=0で分けたtestデータのスコアはデフォルトのパラメーターを使ってR^2が0.73程度でしたが、random_state=1にするとR^2=0.9ぐらいになりますからね。
サンプルごとにかなりまとまってばらつきがあるような気がします。
fig = plt.figure(figsize=(20,10))
ax1 = fig.add_subplot(231, ylabel='friq.', xlabel='values', title='colsample_bytree')
ax1.hist([rank_sorted.iloc[0:200, 4], rank_sorted.iloc[200:400, 4], rank_sorted.iloc[400:600, 4]], label=['Rank: 0-200', '200-400', '400-600'])
ax1.set_ylim(0, 150)
plt.legend()
ax2 = fig.add_subplot(232, ylabel='friq.', xlabel='values', title='gamma')
ax2.hist([rank_sorted.iloc[0:200, 5], rank_sorted.iloc[200:400, 5], rank_sorted.iloc[400:600, 5]], label = ['Rank: 0-200', '200-400', '400-600'])
ax2.set_ylim(0, 150)
plt.legend()
ax3 = fig.add_subplot(233, ylabel='friq.', xlabel='values', title='learning_rate')
ax3.hist([rank_sorted.iloc[0:200, 6], rank_sorted.iloc[200:400, 6], rank_sorted.iloc[400:600, 6]], label = ['Rank: 0-200', '200-400', '400-600'])
ax3.set_ylim(0, 150)
plt.legend()
ax4 = fig.add_subplot(234, ylabel='friq.', xlabel='values', title='max_depth')
ax4.hist([rank_sorted.iloc[0:200, 7], rank_sorted.iloc[200:400, 7], rank_sorted.iloc[400:600, 7]], label = ['Rank: 0-200', '200-400', '400-600'])
ax4.set_ylim(0, 150)
plt.legend()
ax5 = fig.add_subplot(235, ylabel='friq.', xlabel='values', title='min_child_weight')
ax5.hist([rank_sorted.iloc[0:200, 8], rank_sorted.iloc[200:400, 8], rank_sorted.iloc[400:600, 8]], label = ['Rank: 0-200', '200-400', '400-600'])
ax5.set_ylim(0, 150)
plt.legend()
ax6 = fig.add_subplot(236, ylabel='friq.', xlabel='values', title='subsample')
ax6.hist([rank_sorted.iloc[0:200, 9], rank_sorted.iloc[200:400, 9], rank_sorted.iloc[400:600, 4]], label = ['Rank: 0-200', '200-400', '400-600'])
ax6.set_ylim(0, 150)
plt.legend()
rankごとにパラメーターの頻度を表したのが上のヒストグラムです。
青棒が最もrankが良かったデータにおける頻度ですが、colsample_bytree, gamma, min_child_weightは値と頻度に相関がないのに対し、
learning_rate, max_depth, subsampleは明らかに値が小さいほうが高いrank(良いscore)となることが分かります。
つまりこれらのパラメーターは小さいほうが良い、ということになります。実際にbest_params_もlearning_rateとmax_depthは最小値を採用しています。
------------------------------------------------------------------------------------
*1. trainデータとtestデータに分けてからGSCVを行うべきか
全体でCVを行うと、testデータも含めてfittingを行っているため、卑怯な気もします。
しかしsplitした後に同様のparameterでGSCVを行うと、、、
import xgboost as xgb
import matplotlib.pyplot as plt
from sklearn.datasets import load_boston
from sklearn import metrics as met
import numpy as np
from sklearn.model_selection import train_test_split, GridSearchCV
dataset = load_boston()
x = dataset.data
t = dataset.target
x_train, x_test, t_train, t_test = train_test_split(x, t, test_size=0.2, random_state=0)
mod = xgb.XGBRegressor(max_depth = 6, learning_rate = 0.1, n_estimators=10000, objective='reg:squarederror', gamma=0, min_child_weight=1, subsample=1, colsample_bytree=1)
# モデルを作成。xgb.trainにおけるparamの部分
params = {'max_depth': [8, 7, 6, 5, 4, 3], 'learning_rate': [0.1, 0.2, 0.3], 'gamma': [0.1, 0.2, 0.3], 'min_child_weight': [0.8, 1, 1.2], 'subsample': [1, 0.9, 0.8], 'colsample_bytree': [1, 0.9, 0.8]}
gscv = GridSearchCV(mod, params, cv = 5, refit=True, scoring='r2', verbose = 1, n_jobs=-1)
evallist = [(x_train, t_train)]
gscv.fit(x_train, t_train, eval_metric='rmse', eval_set=evallist, early_stopping_rounds=100)
print(gscv.best_params_)
# {'colsample_bytree': 1, 'gamma': 0.3, 'learning_rate': 0.2, 'max_depth': 5, 'min_child_weight': 0.8, 'subsample': 0.9}
train_pred = gscv.predict(x_train)
test_pred = gscv.predict(x_test)
print('train_RMSE_score_is_{:.4f}, test_RMSE_score_is_{:.4f}'.format(np.sqrt(met.mean_squared_error(t_train, train_pred)), np.sqrt(met.mean_squared_error(t_test, test_pred))))
# 0.2052, 4.7584
print('train_R2_score_is_{:.4f}, test_R2_score_is_{:.4f}'.format(met.r2_score(t_train, train_pred), met.r2_score(t_test, test_pred)))
# 0.9995, 0.7219
print('Best Score:{:.4f}'.format(gscv.best_score_))
# Best Score:0.9036 (R2)
testデータのR^2が0.72とGSCVを行う前よりも悪くなります。
これはrandom_split = 0で分けたtestデータが残りのtrainデータに対して偏ったデータとなってしまっているからなんだろうか。。
ちなみにrandom_split = 0と1の時はこれでやるとscoreが悪くなるがrandom_split = 2だと良くなる。
こういうもんなんでしょうかねえ。
もしお分かりの方いらっしゃいましたらコメントの方よろしくお願いします。