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

【Kaggle挑戦記】Spaceship Titanic 攻略 #14:GroupSizeのカテゴリ化。重要度への反映と精度のトレードオフ

1. 実験:人数の「意味」をモデルに教える

前回の分析で判明した「4人グループの異常な転送率(64%)」をモデルに直接認識させるため、GroupSizeを Solo / Small / Large の3カテゴリに分類しました。 連続的な数値としてではなく、独立した属性として扱うことで、モデルの「気付き」を促す狙いです。

2. 【実装】GroupCategory導入版・フルソースコード

分析に基づき、最も転送率が高かった2〜4人を「Small」と定義。これを特徴量として追加したコードです。

import pandas as pd
import numpy as np
import lightgbm as lgb

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

# グループサイズ計算用
all_df = pd.concat([train, test], axis=0)
all_groups = all_df['PassengerId'].apply(lambda x: x.split('_')[0]).value_counts()

# 2. 特徴量エンジニアリング
spend_cols = ["RoomService", "FoodCourt", "ShoppingMall", "Spa", "VRDeck"]

def get_group_category(size):
    if size == 1:
        return 'Solo'
    elif 2 <= size <= 4:
        return 'Small' # 転送率が極めて高い層
    else:
        return 'Large' # 大家族層

for df in [train, test]:
    # --- A. 支出の論理補完 ---
    df[spend_cols] = df[spend_cols].fillna(0)
    total_spend = df[spend_cols].sum(axis=1)
    df.loc[(df['CryoSleep'].isnull()) & (total_spend > 0), 'CryoSleep'] = False
    df.loc[(df['CryoSleep'].isnull()) & (total_spend == 0), 'CryoSleep'] = True
    df['Age'] = df['Age'].fillna(df['Age'].median())

    # --- B. Cabinの物理分解 ---
    df['Cabin'] = df['Cabin'].fillna('U/U/U')
    df['Cabin_Deck'] = df['Cabin'].apply(lambda x: x.split('/')[0])
    df['Cabin_Side'] = df['Cabin'].apply(lambda x: x.split('/')[-1])

    # --- C. GroupSizeのカテゴリ化 ---
    group_id = df['PassengerId'].apply(lambda x: x.split('_')[0])
    df['GroupSize'] = group_id.map(all_groups)
    df['GroupCategory'] = df['GroupSize'].apply(get_group_category)

# 3. 特徴量の選定
features = [
    "CryoSleep", "Age", "RoomService", "FoodCourt", "ShoppingMall", 
    "Spa", "VRDeck", "Cabin_Deck", "Cabin_Side", "GroupCategory"
]

# 4. 整形と学習
X = pd.get_dummies(train[features], drop_first=True)
y = train["Transported"].astype(int)
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 = lgb.LGBMClassifier(n_estimators=100, learning_rate=0.05, random_state=1)
model.fit(X, y)

# 5. 予測と保存
predictions = model.predict(X_test)
output = pd.DataFrame({'PassengerId': test['PassengerId'], 'Transported': predictions.astype(bool)})
output.to_csv('sub_v14_group_cat.csv', index=False)

# 6. 分析ログの出力
print("\n 特徴量寄与度 (Importance) - Top 15")
importances = pd.DataFrame({'Feature': X.columns, 'Importance': model.feature_importances_}).sort_values(by='Importance', ascending=False)
print(importances.head(15))

3. 結果と分析:重要度の浮上と精度の乖離

リーダーボードの結果は 0.79985。惜しくも0.8を下回る結果となりました。 一方で、コンソールの Importance には明らかな変化が現れました。

 特徴量寄与度 (Importance)
...
13. Cabin_Deck_F         : 55
14. Cabin_Deck_U         : 54
15. GroupCategory_Small  : 24 (New!)

前回は圏外だったグループ関連の指標が、上位15項目に食い込んできました。 モデルが「2〜4人組であること」を判断の一助にしたことは確かです。 しかし、スコアが下がった理由は、カテゴリ化したことで「5人組」や「8人組」といった細かな人数の違いによる情報の解像度が失われ、予測がマイルドになりすぎたことにあると考えられます。

4. 結論

特徴量を「意味のある塊」にまとめる手法は、重要度を上げるのには有効でしたが、今回のような複雑なデータセットでは、生の数値が持っていた細かなニュアンスも重要だったようです。 次は、この「人数の意味」を消さずに、さらに情報の密度を高めるアプローチ(グループ内の他者の状態など)への転換が必要です。


一歩下がって、データの解像度を見直す。スコアの変動は、モデルからのフィードバックに他ならない。




PR