🔷
【14日目】Stackingで精度を高める【2021アドベントカレンダー】
2021年1人アドベントカレンダー(機械学習)、14日目の記事になります。
テーマは Stacking になります。
スタッキング(stacking)とは「積み重ねる」を意味し、複数の学習器を組み合わせて作った学習モデル を指します。
Colab のコードはこちら
1層目 各モデルの予測結果を集計
取り回しが簡単なので、代表的な回帰モデルのうち、.fit() .predict() が使えるものをピックアップしました。
from sklearn.linear_model import Ridge
from sklearn.linear_model import Lasso
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import ElasticNet
from sklearn.svm import SVR
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.ensemble import RandomForestRegressor
import xgboost as xgb
from catboost import CatBoostRegressor
複数のモデルのインスタンスを辞書型に設定します。
見た目の問題なので必須ではないですが、CatBoost はログを非表示にしておきます。
また、XGBoost のエラーログも出ないように設定しておきます。
models = {
"ridge":Ridge(random_state=SEED),
"lasso":Lasso(random_state=SEED),
"linear":LinearRegression(),
"elastic_net":ElasticNet(random_state=SEED),
"svm":SVR(),
"random_forest":RandomForestRegressor(random_state=SEED),
"gradient":GradientBoostingRegressor(random_state=SEED),
"catboost":CatBoostRegressor(random_state=SEED,
silent=True, # ログを非表示
),
"xgboost":xgb.XGBRegressor(
random_state=SEED,
objective='reg:squarederror'
),
"lightgbm":lgb.LGBMRegressor(random_state=SEED),
}
GroupKFold を使っているため、必ずしも 各Fold 毎のデータは index がキレイにそろうわけではありません。
そのため、単純に処理したデータを結合すると、indexがバラバラになってしまい、カラム(各モデル)ごとの整合性がなくなってしまい、Stacking の効果が発揮されません。
そこで、sort_index() によってすべてのカラムの index を揃えてやります。
gkf = GroupKFold(n_splits=5)
groups = X_train_dropna["Genre"]
cv_result_stck = {}
pred_df = pd.DataFrame()
for i, (model_name, model) in enumerate(models.items()):
print(i, model)
each_model_df = pd.DataFrame()
each_model_result = []
for train_index, test_index in gkf.split(X_train_dropna, y_train_dropna, groups):
X_train_gkf, X_test_gkf = X_train_dropna.iloc[train_index], X_train_dropna.iloc[test_index]
y_train_gkf, y_test_gkf = y_train_dropna.iloc[train_index], y_train_dropna.iloc[test_index]
model.fit(X_train_gkf, y_train_gkf)
y_pred = model.predict(X_test_gkf)
tmp_df = pd.DataFrame(
[y_pred],
columns=test_index
)
rmse = mean_squared_error(y_test_gkf, y_pred, squared=False)
each_model_result.append(rmse)
each_model_df = pd.concat([each_model_df , tmp_df.T]) # 各KFold ごとの予測結果をDataFrameに縦に並べる
cv_result_stck[model_name] = each_model_result # 各モデルのRMSEを集計
each_model_df.columns = [model_name] # カラム名をモデル名に変更
pred_df = pd.concat([pred_df, each_model_df.sort_index()], axis=1) # 予測結果集計用DataFrameに各モデルの予測結果をくっつける
pred_df.head()
最も精度 (今回はRMSE) のよいモデルの名前を取得してやります。
2層目の 学習モデルに使うためと、一番精度のよいカラムを Group に設定するためです。
best_model_name = ""
best_rmse = 9999.9999
for model_name, rmse in cv_result_stck.items():
print(model_name, np.mean(rmse))
if best_rmse > np.mean(rmse):
best_rmse = np.mean(rmse)
best_model_name = model_name
print()
print(f"Best model is {best_model_name}. Score is {best_rmse}")
XGBoost が一番よい精度が出たようです。
出力:
Best model is xgboost. Score is 0.03133802749741507
2階層目 各モデルの予測結果をもとに予測
最も良い精度の出たモデルを使って2層目のデータを学習・推論してみます。
# 学習・推論
gkf = GroupKFold(n_splits=5)
groups = pred_df[best_model_name]
cv_result_stck_best_model = []
for train_index, test_index in gkf.split(pred_df, y_train_dropna, groups):
X_train_gkf, X_test_gkf = pred_df.iloc[train_index], pred_df.iloc[test_index]
y_train_gkf, y_test_gkf = y_train_dropna.iloc[train_index], y_train_dropna.iloc[test_index]
# 学習、推論
best_model = models[best_model_name]
best_model.fit(X_train_gkf, y_train_gkf)
y_pred = models[best_model_name].predict(X_test_gkf)
rmse = mean_squared_error(y_test_gkf, y_pred, squared=False)
cv_result_stck_best_model.append(rmse)
次に、Optuna + LightGBM を使ってみます。
# 学習・推論
gkf = GroupKFold(n_splits=5)
params = {
'task': 'train', # タスクを訓練に設定
'boosting_type': 'gbdt', # GBDTを指定
'objective': 'regression', # 回帰を指定
'metric': 'rmse', # 回帰の損失(誤差)
'learning_rate': 0.1, # 学習率
'deterministic':True, # 再現性確保用のパラメータ
'force_row_wise':True, # 再現性確保用のパラメータ
'seed': SEED # シード値
}
groups = pred_df[best_model_name]
cv_result_stck_lgbm = []
for train_index, test_index in gkf.split(pred_df, y_train_dropna, groups):
X_train_gkf, X_test_gkf = pred_df.iloc[train_index], pred_df.iloc[test_index]
y_train_gkf, y_test_gkf = y_train_dropna.iloc[train_index], y_train_dropna.iloc[test_index]
# 学習、推論
lgb_train = opt_lgb.Dataset(X_train_gkf, y_train_gkf)
lgb_test = opt_lgb.Dataset(X_test_gkf, y_test_gkf, reference=lgb_train)
model = opt_lgb.LightGBMTuner(
params, # ハイパーパラメータをセット
lgb_train, # 訓練データを訓練用にセット
valid_sets=[lgb_train, lgb_test], # 訓練データとテストデータをセット
valid_names=['Train', 'Test'], # データセットの名前をそれぞれ設定
num_boost_round=100, # 計算回数
early_stopping_rounds=50, # アーリーストッピング設定
evals_result=lgb_results,
verbose_eval=-1, # ログを最後の1つだけ表示
show_progress_bar = False, # ログの非表示
optuna_seed=SEED,
)
# 訓練の実施
model.run()
best_params = model.best_params
# 損失推移を表示
loss_train = lgb_results['Train']['rmse']
loss_test = lgb_results['Test']['rmse']
fig = plt.figure()
plt.xlabel('Iteration')
plt.ylabel('logloss')
plt.title(f"fold:{fold}")
plt.plot(loss_train, label='train loss')
plt.plot(loss_test, label='test loss')
plt.legend()
plt.show()
# 推論
best_model = model.get_best_booster()
y_pred = best_model.predict(X_test_gkf)
# 評価
rmse = mean_squared_error(y_test_gkf, y_pred, squared=False)
cv_result_stck_lgbm.append(rmse)
print("RMSE:", round(np.mean(cv_result), 4))
print(f"Best Model {best_model_name} Stacking RMSE:", round(np.mean(cv_result_stck_best_model), 4))
print("Optuna LightGBM Stacking RMSE:", round(np.mean(cv_result_stck_lgbm), 4))
項目 | RMSE |
---|---|
通常のOptuna + LightGBM | 0.1799 |
ベストモデルで Stacking | 0.0501 |
Optuna + LightGBM で Stacking | 0.0501 |
14日目は以上になります、最後までお読みいただきありがとうございました。
その他参考サイト
Discussion
Optuna + LightGBMの交差検証なのですが、
(>for train_index, test_index in gkf.split(pred_df, y_train_dropna, groups):)
各モデル(ridgeやlassoなど)を格納したpred_dfを訓練データにするとtestデータで予測すると(predict = best_model.predict(test_df))
「testデータの特徴量数が訓練データと異なっています」、と怒られます。
この場合、交差検証のpred_dfではなく、train_dfを訓練データに入れるべ気だと考えていますが、如何でしょうか?
すいません、追記です。
最も良い精度の出たモデルを使って2層目のデータを学習・推論するという事で、pred_dfを使用するという事は理解できていて、RMSEまでは実行できるのですが、いざテストデータで予測してみようとなるとpred_dfは今回10のモデルが特徴量として入っているので、test_dfで実施した際は「testデータの特徴量数が訓練データと異なっています」、と怒られますとなります。
自身として悩んでいる為、意見交換して頂けると幸甚です。