【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