【Kaggle挑戦記】Titanic 攻略 #16:LightGBMの洗礼。CVスコアの「罠」と過学習の恐怖
前回、最強の刺客 LightGBM を投入し、手元の交差検証(CV)で 0.8418 という驚異的なスコアを叩き出しました。「ついに0.8の大台か?」と期待に胸を膨らませて提出した結果、待っていたのは 0.76315 という非情な現実。自己ベスト(0.78468)から大きく後退する結果となりました。
1. なぜ「手元の高スコア」が「本番の惨敗」を招いたのか?
今回の敗因は、機械学習において最も警戒すべき 過学習(オーバーフィッティング) です。原因をエンジニア的に分析すると、以下の3点に集約されます。
- モデルが「賢すぎた」: LightGBMは非常に強力なため、約890件という少ない訓練データの「偶然の偏り」まで完璧に学習してしまいました。
- CVスコアの信憑性: CVスコア 0.84 というのは、訓練データ内での「予行演習」に過ぎません。本番のテストデータとの間に、学習しきれないギャップが存在していました。
- パラメータの攻めすぎ:
max_depth: -1(無制限)やnum_leaves: 20という設定が、少数のデータに対しては複雑すぎた可能性があります。
2. 【実装】光と影を記録した LightGBM コード
CVスコア 0.8418 を出しながらも、本番で 0.76315 に沈んだ「教訓」としてのコードです。
import pandas as pd
import numpy as np
import lightgbm as lgb
from sklearn.model_selection import GridSearchCV
# 1. データの読み込みと前処理(ベスト布陣を維持)
train_data = pd.read_csv('train.csv')
test_data = pd.read_csv('test.csv')
for df in [train_data, test_data]:
df['Embarked'] = df['Embarked'].fillna('S')
df['FamilySize'] = df['SibSp'] + df['Parch'] + 1
group_cols = ['Pclass', 'Sex']
train_data['Age'] = train_data['Age'].fillna(train_data.groupby(group_cols)['Age'].transform('median'))
test_data['Age'] = test_data['Age'].fillna(test_data.groupby(group_cols)['Age'].transform('median'))
test_data['Fare'] = test_data['Fare'].fillna(test_data['Fare'].median())
# 2. 特徴量の準備(ダミー変数化)
features = ["Pclass", "Sex", "Age", "SibSp", "Parch", "Fare", "FamilySize", "Embarked"]
X = pd.get_dummies(train_data[features], drop_first=True)
y = train_data["Survived"]
X_test = pd.get_dummies(test_data[features], drop_first=True)
X, X_test = X.align(X_test, join='left', axis=1, fill_value=0)
# 3. LightGBMの設定(ここでの最適化が本番で裏目に出た)
param_grid = {
'num_leaves': [10, 20, 31],
'learning_rate': [0.01, 0.05, 0.1],
'n_estimators': [100, 500],
'max_depth': [-1, 3, 5],
'random_state': [1]
}
gbm = lgb.LGBMClassifier(verbosity=-1)
grid_search = GridSearchCV(gbm, param_grid, cv=5, n_jobs=-1, verbose=0)
grid_search.fit(X, y)
# 結果出力(CVスコアは 0.8418 をマーク)
print(f"LGBM Best Params: {grid_search.best_params_}")
print(f"LGBM Best Score (CV): {grid_search.best_score_:.4f}")
# 予測実行
best_model = grid_search.best_estimator_
predictions = best_model.predict(X_test)
pd.DataFrame({'PassengerId': test_data.PassengerId, 'Survived': predictions}).to_csv('submission_lgbm_overfit.csv', index=False)
3. 実験結果:期待と現実のギャップ
アルゴリズムを変えたことで、数字に大きな「動き」が出ましたが、今回は悪い方へ転がりました。
- 自己ベスト(ランダムフォレスト): Score 0.78468
- 今回(LightGBM): Score 0.76315(CVスコアとの乖離:-0.078)
4. 考察:高すぎるCVスコアを疑え
エンジニア的な視点:
「手元で完璧なモデルが、外の世界で通用するとは限らない」。今回の結果は、AI開発における本質的な難しさを教えてくれました。CVスコアが 0.84 まで跳ね上がった時点で、「学習しすぎではないか?」と疑うべきだったのです。Titanicのような少人数データでは、LightGBMのような強力なモデルを「いかに抑え込むか(正則化)」が次の鍵となります。
スコアダウンは失敗ではなく、モデルの特性を理解するための貴重なデータです。次は、LightGBMをあえて「弱く」する(パラメータを厳しく制限する)か、あるいはランダムフォレストの安定感を見直すか。この 0.02 の差を埋めるための戦いは、さらに深化していきます。
PR