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

【Kaggle挑戦記】Spaceship Titanic 攻略 #7:良かれと思った「外れ値除外」でスコアが急降下した話

1. 今回の仮説:外れ値は「毒」である

前回までで 0.79214 という自己ベストを記録していました。しかし、支出項目のデータ分布を見ると、ごく一部の乗客が数万ドルという極端な金額を使っています。 「これほどの外れ値は、モデルの判断を狂わせるノイズ(毒)に違いない」——そう考えた私は、外れ値の基準を厳格化し、上位5%(95パーセンタイル)で数値を一律カットする強硬策に出ました。

2. 実装した「徹底排除」コード

以下が、あえて「失敗」を招くことになったコードの全文です。支出の上限をかなり低く設定しました。

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

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

# 支出項目の欠損値を0で埋め、合計を算出
spend_cols = ["RoomService", "FoodCourt", "ShoppingMall", "Spa", "VRDeck"]
for df in [train, test]:
    df[spend_cols] = df[spend_cols].fillna(0)
    df['TotalSpend'] = df[spend_cols].sum(axis=1)

# 【ここが失敗の種】外れ値を95%タイルで厳格にクリッピング
for col in spend_cols:
    # 上位5%をカット。Spaなら1600ドル程度が上限に
    upper_limit = train[col].quantile(0.95)
    train[col] = train[col].clip(upper=upper_limit)
    test[col] = test[col].clip(upper=upper_limit)
    print(f"✂️ {col} の上限を {upper_limit:.1f} (95%) に設定")

# CryoSleepの論理補完とAgeの中央値補完
for df in [train, test]:
    df.loc[(df['CryoSleep'].isnull()) & (df['TotalSpend'] > 0), 'CryoSleep'] = False
    df.loc[(df['CryoSleep'].isnull()) & (df['TotalSpend'] == 0), 'CryoSleep'] = True
    df['Age'] = df['Age'].fillna(df['Age'].median())

# 特徴量選択と学習
features = ["CryoSleep", "Age", "RoomService", "FoodCourt", "ShoppingMall", "Spa", "VRDeck"]
X = pd.get_dummies(train[features], drop_first=True)
y = train["Transported"]
X_test = pd.get_dummies(test[features], drop_first=True)
X, X_test = X.align(X_test, join='left', axis=1, fill_value=0)

model = RandomForestClassifier(n_estimators=100, random_state=1)
model.fit(X, y)

# 予測・提出
predictions = model.predict(X_test)
pd.DataFrame({'PassengerId': test['PassengerId'], 'Transported': predictions}).to_csv('sub_clip_95.csv', index=False)

3. 実行結果:Macのターミナルに突きつけられた現実

意気揚々とKaggleに提出した結果、リーダーボードに表示された数字に目を疑いました。

 Previous Score : 0.79214
 New Score      : 0.78185 (▲0.01029)

なんと、自己ベストから一気に **0.01 ポイントもの急落**。これまでの積み上げを台無しにするような、手痛い敗戦となりました。

4. 考察:なぜ「外れ値」は必要だったのか?

この失敗から、このコンペティションにおける重要な真実が見えてきました。 「超高額な支出をしている乗客」というのは、単なるノイズではありませんでした。「高額な施設を頻繁に利用していた=事故の瞬間に特定のエリアにいた」 という、転送(Transported)されるか否かを決める極めて重要なシグナルだったのです。

それを95%という低いラインで丸めてしまったことで、モデルは「重要人物」と「普通の客」の区別がつかなくなってしまった。ランダムフォレストは元々外れ値に強いアルゴリズムであり、人間が余計な手出しをするべきではありませんでした。

5. 次なる一手

「外れ値=悪」という先入観は捨てました。次回は、この「数値の大きさ」という情報を残しつつ、まだ手付かずの「Cabin(客室)」データを分解し、物理的な位置関係から0.8の壁に再挑戦します。


Kaggleは、自分の思い込みをデータが粉砕してくれる場所。この敗北を糧に、次はもっと賢いコードを書こう。攻略は続く。

PR