【Kaggle挑戦記】Titanic 攻略 #10:モデルの検証。自作フラグは「ノイズ」だったのか?
前回(攻略 #9)では、生存予測の精度を上げるため、以下のデータ加工とモデル構築を行いました。
1. 前回の復習:投入データの構成と加工
- Age(年齢)の精密補完: 単純な平均値ではなく、
Pclass(客室階級)とSex(性別)を掛け合わせたグループごとの中央値で欠損値を補完。 - IsChild_Final(自作フラグ): 補完した「Age(年齢)が12歳以下」という条件に、さらに「Name(名前)に Master を含む」という確実性の高い敬称データを組み合わせて作成。
- 投入した特徴量:
Pclass,Sex,Age,IsChild_Final,SibSp(兄弟・配偶者数),Parch(親子数),Fare(運賃)。 - モデル: ランダムフォレスト(
n_estimators=500)。
しかし、結果は 0.77751 → 0.77511 へと下落。「子供と女性は優先される」という強力なドメイン知識を反映したはずが、なぜ逆効果になるのか。学習済みモデルの内部数値を直接取り出してデバッグします。
2. 思考のステップ:なぜ「子供フラグ」は失敗したのか
- 「Age」と「Name」から導いた子供フラグはダメだった。
実際に精度が下がった以上、このフラグの設定には致命的な「何か」が足りない。 - 「子供と女性は優先されていた」というドメイン知識は揺るがない。
歴史的事実として、避難優先順位の筆頭は「Women and children」である。 - モデルには「子供フラグ」と「Sex(女性)」という2つの軸を与えてある。
理論上、これで優先グループを網羅できているはずだ。 - 【核心】ならば、モデル内部で、この2つの軸が「生存を切り分けるための重要度」としてどう扱われているかを確認すべきではないか?
3. 検証:ランダムフォレストが算出する「重要度」の仕組み
今回使用しているランダムフォレストは、学習(fit)のプロセスで500本の独立した決定木を作成します。その際、各項目の「重要度(Feature Importance)」は以下のステップで計算されています。
- 不純度の減少量を累積: 各決定木がデータを分割する際、「性別」や「子供フラグ」などの軸を使います。その軸で分けた結果、グループ内の「生存・死亡」がきれいに分かれるほど(=不純度が下がるほど)、その軸に高いスコアが与えられ、500本分累積されます。
- 比率の算出: 全項目が稼いだ累積スコアの総和を 1.0 (100%) としたとき、各項目が占める割合を算出します。
4. 【実装】モデルが各項目をどれだけ「信頼」したかを確認する
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
# 1. 前回のデータ準備(攻略 #9の状態を再現)
train_data = pd.read_csv('train.csv')
group_cols = ['Pclass', 'Sex']
# Ageの補完:PclassとSexごとの中央値
train_data['Age'] = train_data['Age'].fillna(train_data.groupby(group_cols)['Age'].transform('median'))
# Age(年齢)とName(敬称Master)から導いた子供フラグ
train_data['IsChild_Final'] = ((train_data['Age'] <= 12) | (train_data['Name'].str.contains('Master'))).astype(int)
# 学習に使用する特徴量の定義
features = ["Pclass", "Sex", "Age", "IsChild_Final", "SibSp", "Parch", "Fare"]
X = pd.get_dummies(train_data[features])
y = train_data["Survived"]
# 2. モデルの学習(500本の決定木を生成し、それぞれの分割性能を記録)
model = RandomForestClassifier(n_estimators=500, max_depth=5, random_state=1)
model.fit(X, y)
# 3. 各項目の重要度(Feature Importance)を抽出
importances = pd.Series(model.feature_importances_, index=X.columns).sort_values(ascending=False)
print("--- [Feature Importance: 500本の木による信頼度の集計結果] ---")
print(importances)
# 4. 補足検証:子供フラグ(IsChild_Final)の実際の生存率
print("\n--- [Actual Survival Rate: 子供フラグ別の生存率(学習データ)] ---")
print(train_data.groupby('IsChild_Final')['Survived'].mean())
5. 実行結果:残酷な現実
検証コードを走らせた結果、以下の数値が返ってきました。
■ 500本の木による信頼度の集計結果(Feature Importance)
Sex_female 0.273813 (27.4%) Sex_male 0.251328 (25.1%) Fare 0.146034 (14.6%) Pclass 0.125932 (12.6%) Age 0.090933 (9.1%) SibSp 0.053964 (5.4%) IsChild_Final 0.029075 (2.9%) ← ★ここ Parch 0.028921 (2.9%)
■ 子供フラグ別の実際の生存率(学習データ)
IsChild_Final 0 (大人など) : 0.366 (36.6%) 1 (子供) : 0.575 (57.5%)
6. 考察:数字が示す「子供フラグ」の敗北原因
- 【データ:累積貢献度 2.9%】子供フラグは「無視」された:
500本の決定木が生存予測の軸として信頼した割合(Feature Importance)は、わずか 0.029 (2.9%) でした。これは、運賃(14.6%)や客室階級(12.6%)、さらには生の年齢(9.1%)よりも遥かに低く、モデルが「このフラグは生存を占う上で、ほとんど信用に値しない」と公式に判定したことを示しています。 - 【データ:生存率 57.5%】「6割・4割」では使えない:
モデルがこのフラグを信頼しなかった直接の根拠は、実行結果のIsChild_Final = 1(子供グループ)の生存率が 57.5% に留まったことです。これは「約6割が生き、42.5%(4割以上)が死亡している」という極めて不純な状態です。モデルが求めているのは生存か死亡かを明確に切り分けられる軸ですが、このフラグで分けても4割以上も死亡者が混ざってしまうため、生存予測の条件(if文)としては不適格と見なされました。 - 【結論】不純な境界線がスコアを落とした:
Age(年齢)とName(敬称Master)から導き出した「12歳以下」という人為的な境界線では、モデルが生存を確信できるほどの「純度」を生み出せませんでした。この「6割生存・4割死亡」という分離性能の低さこそが、累積貢献度を押し下げ、結果として全体の予測精度(スコア)を下落させた正体です。
PR