【Kaggle挑戦記】Titanic 攻略 #14:0.77990への後退。欠損率77%の「Cabin」が招いた過学習
前回(攻略 #13)、自己ベストの 0.78468 を記録し、ついに波に乗ったかと思われました。次なる一手として投入したのは、船の階層を示す Cabin(客室番号)からのデッキ情報。物理的な生存率の差を捉える決定打になるはずが、結果は 0.77990 への大幅なランクダウンとなりました。
1. 敗因分析:なぜ「強力なヒント」が毒になったのか?
客室番号から抽出した「デッキ(A〜G)」は、理論上はボートへの距離を示す重要な指標です。しかし、そこには機械学習特有の罠が潜んでいました。
- 圧倒的な欠損率: Cabinデータは約77%が欠損しています。この「穴だらけ」のデータを無理にカテゴリ化して学習させたことで、モデルが数少ないサンプルに過剰に反応(過学習)してしまった可能性があります。
- 低頻度カテゴリのノイズ: 「Tデッキ」などの極端に乗客が少ない階層が、予測において「意味のない分岐」を作り出し、未知のデータに対する判断を狂わせたと考えられます。
- 情報の不純度: デッキ情報は Pclass(客室階級)と極めて強い相関があります。すでに Pclass で説明できている情報に、ノイズの多い Cabin 情報を混ぜたことが、モデルの焦点をぼかしてしまったようです。
2. 【実装】敗北の記録:デッキ情報を導入した全コード
結果としてスコアを下げてしまいましたが、検証の記録として、デッキ抽出を組み込んだコードを掲載します。
import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestClassifier
# 1. データの読み込み
train_data = pd.read_csv('train.csv')
test_data = pd.read_csv('test.csv')
# 2. Cabinからデッキ情報を抽出(今回の挑戦ポイント)
for df in [train_data, test_data]:
df['Deck'] = df['Cabin'].apply(lambda x: x[0] if pd.notnull(x) else 'U')
# 3. 安定した前処理(0.78468時と同じ構成)
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())
# 4. 特徴量の選択(Deckを追加)
features = ["Pclass", "Sex", "Age", "SibSp", "Parch", "Fare", "FamilySize", "Embarked", "Deck"]
# 5. ダミー変数化
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)
# 6. モデル学習
model = RandomForestClassifier(n_estimators=500, max_depth=5, random_state=1)
model.fit(X, y)
# 7. 予測と出力
predictions = model.predict(X_test)
pd.DataFrame({'PassengerId': test_data.PassengerId, 'Survived': predictions}).to_csv('submission_deck_failed.csv', index=False)
3. 実験結果:0.78468 → 0.77990 への転落
「垂直の階層」を加えた結果、スコアは改善するどころか、前々回の水準まで後退しました。
- 自己ベスト(攻略 #13): Score 0.78468
- 今回(Deck追加): Score 0.77990
4. 考察:欠損値は「埋めればいい」ものではない
エンジニア的な視点:
今回の学びは、「欠損率があまりに高いカラムは、無理に特徴量化するとモデルの汎化性能を破壊する」ということです。デッキ情報は確かに生存に影響したはずですが、データの密度が薄すぎました。Kaggleにおいては、情報の「正しさ」だけでなく、情報の「密度」と「安定性」がいかに重要かを痛感させられる結果となりました。
自己ベスト更新直後の落とし穴。しかし、この 0.77990 という数字が「Deck情報の生投入は悪手である」と教えてくれました。次は、この Deck 情報をより大胆にグルーピング(例:Cabinの有無だけにする等)してリベンジするか、あるいは再び「引き算」をして別の道を探るか……。戦略の練り直しです。
PR