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

【Kaggle挑戦記】Titanic 攻略 #8:フラグ追加の試みと、見えてきた「欠損値補完」の罠

前回(攻略 #7)では、性別と客室クラスを掛け合わせた精緻な Age(年齢)補完を行い、スコアは 0.77511 → 0.77990 と微増しました。今回は、さらに「子供」という属性を強調し、0.78 の大台を突破するための特徴量エンジニアリングに挑戦しました。

1. これまでの振り返りと今回の仮説

現在の私たちの戦績と構成は以下の通りです。

  • 攻略 #6: パラメータ調整(木の数)ではスコアに変化なし
  • 攻略 #7: Age(年齢)を補完して投入。スコアが 0.00479 上昇

現在、Age は連続値としてモデルに渡っています。しかし、生存の鍵は「子供か大人か」という二値的な境界線にあるはず。そこで、「12歳以下(Child)」か「それ以外(Adult)」かというフラグ(IsChild)を明示的に作ることで、モデルの判断を助けるという仮説を立てました。

2. 【実装】子供フラグ(IsChild)の導入とコード全文

Age が 12 以下の場合は 1、それ以外を 0 とする新しいカラム IsChild を作成し、特徴量に加えました。前回からの変更・追加箇所がわかるように実装したコードの全文です。

import pandas as pd
from sklearn.ensemble import RandomForestClassifier

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

# 2. 【攻略 #7からの継承】Age の欠損値を「性別×客室クラス」ごとの中央値で補完
# ※ここでの中央値補完が、後のフラグ判定に影響を与えた可能性あり
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())

# 3. 【攻略 #8での追加】子供フラグ(IsChild)の作成
# ---------------------------------------------------------
# 12歳以下を子供(1)、それ以外を大人(0)として新規カラム作成
# ---------------------------------------------------------
train_data['IsChild'] = (train_data['Age'] <= 12).astype(int)
test_data['IsChild'] = (test_data['Age'] <= 12).astype(int)

# 4. 【攻略 #8での変更】特徴量の選択
# ---------------------------------------------------------
# 新たに作成した "IsChild" をリストに追加
# ---------------------------------------------------------
features = ["Pclass", "Sex", "Age", "IsChild", "SibSp", "Parch", "Fare"]

X = pd.get_dummies(train_data[features])
y = train_data["Survived"]
X_test = pd.get_dummies(test_data[features])

# 5. モデルの構築(攻略 #6 で決めた 500本 を維持)
model = RandomForestClassifier(n_estimators=500, max_depth=5, random_state=1)
model.fit(X, y)

# 6. 予測の実行
predictions = model.predict(X_test)

# 7. 提出用ファイルの作成
output = pd.DataFrame({'PassengerId': test_data.PassengerId, 'Survived': predictions})
output.to_csv('submission_ischild_added.csv', index=False)

3. 実験結果:無情にもスコアは 0.77511 へ下落

期待に反し、スコアは前回の Age 投入前と同じ 0.77511 まで落ち込んでしまいました。良かれと思って追加した「子供フラグ」が、なぜ足を引っ張ったのでしょうか。

4. 考察:欠損値補完が「真の子供」を消した可能性

ここで非常に重要な仮説が浮かび上がりました。「Ageが空欄で、適当な中央値で埋められた人たち」の中に、実は助かったはずの子供(Master)が混ざっていたのではないか? という点です。

攻略 #7 で行った「クラス別の中央値補完」では、たとえば3等客のAge欠損値に対し、一律で「20代半ば」といった大人の数値を割り当ててしまいました。その結果、本来は子供であったはずの乗客が、この IsChild フラグによって「確定的な大人(0)」としてモデルに誤学習されてしまった恐れがあります。

つまり、現在の「Ageベースのフラグ」は、補完データの不正確さを増幅させるノイズになってしまったと言えます。


次の一手は、この矛盾を解消するために「名前(Name)」に含まれる情報を使います。年齢が不明でも、名前に "Master." とあればその乗客は確実に男の子です。次回、「名前 + 年齢」による真の子供特定に挑み、0.78 の壁を叩き割ります。



PR