📚 背景知識(読んでから問題へ)
Playground Series とは?
Kaggle が毎月開催する「練習用コンペ」です。実際の業務データではなく、合成データ(Synthetic Data)を使用するため、初心者でもメダルを狙いやすい特徴があります。
| 項目 | 詳細 |
|---|---|
| 開催頻度 | 毎月(Season 5 = 2025〜2026年) |
| データ種類 | 合成データ(実データから生成 → 分布が安定) |
| Bronzeメダル | 上位40%(初心者でも狙える!) |
| Silverメダル | 上位20% |
| Goldメダル | 上位1%(Expert〜Master層の目標) |
| 評価指標例 | AUC, RMSE, F1スコア, Accuracy |
合成データは実データより分布が綺麗で欠損も少なく、CV≈LBの精度が高い(作った評価が本番でもほぼ同じスコアになる)。Discussion で上位ノートブックが共有されるため学習材料も豊富です。
Bronze メダルを取る3ステップ戦略
🗂️ データスキーマ — Bank Customer Churn
今月のPlayground Series: 顧客離脱予測(評価指標: ROC AUC)
| カラム名 | 型 | 説明 | 使い方 |
|---|---|---|---|
id | int | 提出用顧客ID | 識別子のみ(学習に使わない) |
CustomerId | int | 顧客番号 | 識別子のみ(学習に使わない) |
Surname | str | 苗字 | 通常は使わない(ただしレアケースで有効な場合も) |
CreditScore | int | 信用スコア 300〜850 | 数値特徴量として使用 |
Geography | str | 国(France/Germany/Spain) | OneHot or Label Encoding |
Gender | str | 性別 | Label Encoding (Male=0/Female=1) |
Age | int | 年齢 | 重要な特徴量(高年齢→離脱傾向) |
Tenure | int | 在籍年数 0〜10 | 数値特徴量として使用 |
Balance | float | 口座残高 | BalanceSalaryRatio など派生特徴量が有効 |
NumOfProducts | int | 保有商品数 1〜4 | カテゴリとして扱うことも有効 |
HasCrCard | int | クレカ保有 0/1 | バイナリ特徴量 |
IsActiveMember | int | アクティブ会員 0/1 | 非アクティブは離脱の予兆 |
EstimatedSalary | float | 推定年収 | Balance との比率が有効 |
Exited | int | 離脱フラグ(目的変数) | 0=継続, 1=離脱 |
🎯 問題
要件
数値変数の比率・ゼロフラグ・交互作用などを活用
なぜ LightGBM が AUC 評価のコンペで強いのかを理解する
OOF予測でCV AUC を計算 → submission.csv 生成
📊 AUCスコア目標(Bank Churn PS 参考値)
参考: Playground Series のChurn系コンペでの典型的なスコア分布
CV AUC ≈ 0.875 を達成してBronzeメダル圏内に入る。特徴量エンジニアリングを丁寧に行うだけで LR の 0.760 から 0.840〜0.880 まで引き上げられます。💡 ヒント
Playground Series の合成データは「元の実データ」の分布を模倣して生成されます。実データで有効だった特徴量エンジニアリングは PS でも有効なことが多いです。
Churn 予測では以下のパターンが特に効きやすい:
- 残高ゼロフラグ(Balance == 0 は解約の前兆)
- 高年齢 × 非アクティブの組み合わせ
- 保有商品数 == 1 かつ非アクティブ
ROC AUC は分類の「閾値非依存」の指標です。予測確率の精度が高いほど AUC が上がります。LightGBM は predict_proba の確率精度が高いため AUC 評価のコンペで強いモデルです。
early_stopping を使うと最適な n_estimators を自動で見つけられます。最初は n_estimators=1000 と大きく設定しておけばOKです。
import lightgbm as lgb from sklearn.model_selection import StratifiedKFold from sklearn.metrics import roc_auc_score cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) oof_preds = np.zeros(len(X)) test_preds = np.zeros(len(X_test)) for fold, (tr_idx, val_idx) in enumerate(cv.split(X, y)): X_tr, X_val = X.iloc[tr_idx], X.iloc[val_idx] y_tr, y_val = y.iloc[tr_idx], y.iloc[val_idx] model = lgb.LGBMClassifier(n_estimators=1000, learning_rate=0.05) model.fit(X_tr, y_tr, eval_set=[(X_val, y_val)], callbacks=[lgb.early_stopping(50, verbose=False)]) oof_preds[val_idx] = model.predict_proba(X_val)[:, 1] test_preds += model.predict_proba(X_test)[:, 1] / 5 print(f"CV AUC: {roc_auc_score(y, oof_preds):.5f}")
✅ 模範解答
import pandas as pd import numpy as np import lightgbm as lgb from sklearn.model_selection import StratifiedKFold from sklearn.metrics import roc_auc_score from sklearn.preprocessing import LabelEncoder # ── 1. データ読み込み ────────────────────────────────────── train = pd.read_csv('train.csv') test = pd.read_csv('test.csv') # ── 2. 特徴量エンジニアリング ───────────────────────────── def engineer_features(df): df = df.copy() # 残高比率: 財務状況の相対的な評価 df['BalanceSalaryRatio'] = df['Balance'] / (df['EstimatedSalary'] + 1) # 残高ゼロフラグ(0残高は解約の前兆かもしれない) df['ZeroBalance'] = (df['Balance'] == 0).astype(int) # 年齢 × アクティブ度(高年齢かつ非アクティブが離脱しやすい) df['AgeActivity'] = df['Age'] * df['IsActiveMember'] # 信用スコアのバケット化(低/中/高/優) df['CreditBucket'] = pd.cut(df['CreditScore'], bins=[0, 450, 600, 750, 1000], labels=[0, 1, 2, 3]).astype(int) # 商品数 × アクティブ度 df['ProductActivity'] = df['NumOfProducts'] * df['IsActiveMember'] return df train = engineer_features(train) test = engineer_features(test) # ── 3. カテゴリ変数エンコーディング ────────────────────── cat_cols = ['Geography', 'Gender'] for col in cat_cols: le = LabelEncoder() train[col] = le.fit_transform(train[col]) test[col] = le.transform(test[col]) # ── 4. 特徴量選択 ───────────────────────────────────────── drop_cols = ['id', 'CustomerId', 'Surname', 'Exited'] feature_cols = [c for c in train.columns if c not in drop_cols] X = train[feature_cols] y = train['Exited'] X_test = test[feature_cols] # ── 5. LightGBM + Stratified K-Fold CV ────────────────── cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) oof_preds = np.zeros(len(X)) test_preds = np.zeros(len(X_test)) lgb_params = { 'n_estimators': 1000, 'learning_rate': 0.05, 'num_leaves': 31, 'max_depth': -1, 'min_child_samples': 20, 'subsample': 0.8, 'colsample_bytree': 0.8, 'reg_alpha': 0.1, 'reg_lambda': 1.0, 'random_state': 42, 'n_jobs': -1, 'verbose': -1, } for fold, (tr_idx, val_idx) in enumerate(cv.split(X, y)): X_tr, X_val = X.iloc[tr_idx], X.iloc[val_idx] y_tr, y_val = y.iloc[tr_idx], y.iloc[val_idx] model = lgb.LGBMClassifier(**lgb_params) model.fit( X_tr, y_tr, eval_set=[(X_val, y_val)], callbacks=[lgb.early_stopping(50, verbose=False), lgb.log_evaluation(-1)], ) oof_preds[val_idx] = model.predict_proba(X_val)[:, 1] test_preds += model.predict_proba(X_test)[:, 1] / 5 fold_auc = roc_auc_score(y_val, oof_preds[val_idx]) print(f" Fold {fold+1}: AUC = {fold_auc:.5f} (best iter: {model.best_iteration_})") cv_auc = roc_auc_score(y, oof_preds) print(f"\nCV AUC: {cv_auc:.5f}") print(f"Bronze 目標: AUC ≈ 0.875 (上位40%)") print(f"Silver 目標: AUC ≈ 0.890 (上位20%)") # ── 6. 提出ファイル生成 ─────────────────────────────────── submission = pd.DataFrame({ 'id': test['id'], 'Exited': test_preds, # 確率を提出(クラスではない!) }) submission.to_csv('submission.csv', index=False) print("\nsubmission.csv を生成しました!")
🪜 Step-by-Step 解説
1 特徴量エンジニアリング:比率と交互作用
# 残高比率: 財務状況の相対的な評価 df['BalanceSalaryRatio'] = df['Balance'] / (df['EstimatedSalary'] + 1) # 年齢 × アクティブ度(高年齢かつ非アクティブが離脱しやすい) df['AgeActivity'] = df['Age'] * df['IsActiveMember']
「残高÷給与」という比率は、残高そのものより顧客の財務状況を表します。絶対値より比率・割合が特徴量として強いのはKaggleの鉄則です。
2 LightGBM を選ぶ理由
| モデル | AUC得意? | 理由 |
|---|---|---|
| LightGBM | 非常に得意 | leaf-wise 成長で高精度な確率を出せる |
| XGBoost | 得意 | depth-wise だが正則化が強力 |
| CatBoost | 得意 | カテゴリ変数が多いデータで強い |
| ロジスティック回帰 | 普通 | 線形モデルなので非線形な関係を捉えにくい |
3 OOF(Out-of-Fold)予測の仕組み
oof_preds[val_idx] = model.predict_proba(X_val)[:, 1]
OOFとは「その折で検証に使ったデータへの予測をつなぎ合わせたもの」です。全データについて「学習に使っていないときの予測」を集めるため、情報リークのない正確なCV評価ができます。
Fold 1 の様子: 20%をvalidationとして「学習なし状態」で予測
4 テスト予測を5つのモデルで平均
test_preds += model.predict_proba(X_test)[:, 1] / 5
各Foldで学習した5つのモデルの予測を平均(アンサンブル)することで、単一モデルより安定した予測ができます。これをOOF + テスト平均と呼びます。
5 CV と LB の乖離を防ぐ3つの手法
| # | 手法 | 説明 |
|---|---|---|
| ① | Pipeline を使って CV する | 前処理を CV ループの中で行い情報リークを防ぐ |
| ② | Stratified K-Fold を使う | 各 Fold で離脱率(正例比率)を均等に保つ |
| ③ | 識別子(ID)を学習から除外する | id/CustomerId は目的変数と無関係のため必ず除く |
📅 Playground Series 参戦タイムライン(1ヶ月間)
📐 数学・統計の補足(文系向け)— ROC AUC とは何か?
AUC(Area Under the Curve)は「ランダムな正例と負例を比べたとき、正例の方が高いスコアになる確率」です。
🏆 Kaggleでの実践的な使い方
| 状況 | 戦略 |
|---|---|
| Day 1 提出 | EDA + デフォルト LGB → LBの位置を確認 |
| Discussion 活用 | 「EDA」「Feature Engineering」タグの投稿を重点確認 |
| スコア停滞時 | 特徴量を削って過学習を疑う / CV と LB の乖離を確認 |
| 最終提出選択 | CV 重視 1 + LB 重視 1 の2つを Selected Submission に設定 |
| メダル圏外の場合 | Discussion で上位解法を読んで次回に活かす(学習が最優先) |
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
| AUC ≈ 1.0 が出た → 良いモデルだ! | データリークを見落とす | ID やタイムスタンプが混入している可能性を必ず疑う |
| CV が良いのに LB スコアが悪い | 過学習 or 分布の違い | 特徴量の数を減らす・正則化を強める |
| predict で 0/1 を提出してしまった | AUC は確率が必要と知らない | AUC 評価は predict_proba()[:, 1] を提出する |
| Public LB を信じて提出を選ぶ | LB の shake-up を考慮しない | Private LB では CV スコアが強い提出が逆転しやすい |
| 全データをシャッフルしてから時系列で CV | 時系列データに不適切な CV | 時系列コンペは time-based split を使う(PS では通常不要) |
🚀 次のステップ
次回予告: House Prices EDA — 回帰コンペでの特徴量工学を実践(RMSE最小化)
📝 自己評価(解いた後に記入)
自分の回答・実装メモ:
気づき・メモ: