忍者ブログ
統計、機械学習、AIを学んでいきたいと思います。 お役に立てば幸いです。

【Kaggle挑戦記】Titanic 攻略 #15:0.78468 不変。ハイパーパラメータ最適化が証明した「モデルの限界」

前回(攻略 #14)、デッキ情報の追加でスコアを落とした反省を活かし、今回はベストスコア 0.78468 を出した最強の布陣に対し、科学的なメスを入れました。GridSearchCV(交差検証)による「木の深さ」の最適化。しかし、返ってきたスコアは驚くほど正確に前回と同じ 0.78468 でした。

1. なぜ「最適化」したのにスコアが変わらなかったのか?

勘に頼っていた max_depth=5 という設定を、交差検証によって [3, 4, 5, 6, 7, 8] の中から最も優れたものへ自動選択させました。それにも関わらずスコアが不変だった理由。そこにはエンジニアとして納得のいく理由が隠れています。

  • 「深さ5」がすでに黄金比だった: 交差検証の結果、実はこれまでの「深さ5」が、学習データとテストデータのバランスを保つ上で既に最適な値であった可能性が高いです。
  • 特徴量の表現力の限界: パラメータという「火加減」を調整してもスコアが動かないのは、材料である「特徴量」が持つ情報の限界に達していることを意味します。
  • 高い汎化性能の証明: スコアが落ちなかったということは、モデルが変に過学習せず、安定した予測能力を維持できている証拠でもあります。

2. 【実装】GridSearchCV による最適化の全記録

結果は維持でしたが、今後の試行錯誤において「確信」を持ってパラメータを設定するための必須ステップです。

import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV

# 1. データの読み込み
train_data = pd.read_csv('train.csv')
test_data = pd.read_csv('test.csv')

# 2. 前処理(ベストスコア時の構成を維持)
train_data['Embarked'] = train_data['Embarked'].fillna('S')
test_data['Embarked'] = test_data['Embarked'].fillna('S')

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'))

for df in [train_data, test_data]:
    df['FamilySize'] = df['SibSp'] + df['Parch'] + 1
test_data['Fare'] = test_data['Fare'].fillna(test_data['Fare'].median())

# 3. 特徴量のダミー変数化
features = ["Pclass", "Sex", "Age", "SibSp", "Parch", "Fare", "FamilySize", "Embarked"]
X = pd.get_dummies(train_data[features])
y = train_data["Survived"]
X_test = pd.get_dummies(test_data[features])
X, X_test = X.align(X_test, join='left', axis=1, fill_value=0)

# 4. パラメータの探索
param_grid = {
    'n_estimators': [500],
    'max_depth': [3, 4, 5, 6, 7, 8],
    'min_samples_leaf': [1, 3, 5],
    'random_state': [1]
}

# 5. グリッドサーチによる交差検証
grid_search = GridSearchCV(
    estimator=RandomForestClassifier(),
    param_grid=param_grid,
    cv=5,
    n_jobs=-1,
    verbose=1
)
grid_search.fit(X, y)

# 最適なモデルを抽出
best_model = grid_search.best_estimator_
predictions = best_model.predict(X_test)

# 6. 出力
output = pd.DataFrame({'PassengerId': test_data.PassengerId, 'Survived': predictions})
output.to_csv('submission_optimized.csv', index=False)

print(f"Best Params found: {grid_search.best_params_}")

3. 考察:次のフェーズは「材料そのもの」の変革

エンジニア的な視点:
今回の実験で、「今の特徴量の組み合わせ(Embarked, FamilySizeなど)を、今のランダムフォレストで回す限り、これ以上の伸び代はない」ということがはっきりしました。0.78468 は一つの完成形です。ここから 0.79、0.80 を目指すには、微調整(チューニング)ではなく、革新(イノベーション)が必要です。


スコアが変わらなかったことは、決して無駄ではありません。「迷い」が「確信」に変わった瞬間です。次回からは、これまでの安定した布陣をベースにしつつ、全く新しいアルゴリズム(XGBoostやLightGBM)を試すか、あるいは「チケット番号の重複」など、より高度な特徴量生成(Feature Engineering)の深淵へと足を踏み入れます!

PR