📚 背景知識(読んでから問題へ)
これは料理でいう「練習した包丁さばき・下ごしらえ・火加減を1皿のフルコースにまとめる」段階。個々の技術を知っていることと、締切までに動くパイプラインを組んで提出できることは別のスキルです。
🧭 Bronze を取る「型」(9ステップ)
上位陣も初心者も、最初のサブミッションは同じ型から始まる。
🗄️ データ仕様(架空の二値分類コンペ)
| 項目 | 内容 |
|---|---|
train.csv | 10,000 行 × 特徴量 12 個 + target(0/1) |
test.csv | 5,000 行 × 特徴量 12 個(target なし) |
| 数値特徴量 | 8 個(一部に欠損あり) |
| カテゴリ特徴量 | 4 個(うち 1 個は高カーディナリティ) |
| 評価指標 | ROC-AUC |
| 提出形式 | id,target(target は 0〜1 の確率) |
| 目標 | Bronze 圏内 = 上位 40% |
🎯 問題
タスク1: EDA チェックリストの実行(分析)
各項目で「何を見て、何を判断するか」を述べる
タスク2: 一気通貫パイプラインの実装(コーディング)
submission.csv を出力タスク3: CV と LB の乖離診断(戦略)
📊 評価指標 ROC-AUC を可視化で理解する
AUC =「ランダムに選んだ陽性1件と陰性1件で、陽性のほうに高いスコアを付けられる確率」。
AUC の値と「並べ替え能力」
💡 ヒント
新しい手法は不要。「fold の外に情報を漏らさない」ことだけを最優先に、Phase 3 の各ピースを順番につなぐ。TargetEncoding とスケーリングは必ず fold 内の train でのみ fit する。
- CV:
StratifiedKFold(n_splits=5, shuffle=True, random_state=42) - TE: 各 fold で train から
category→target平均を計算し valid/test に適用。Smoothing で安定化 - OOF 配列
oof = np.zeros(len(train))に valid 予測を格納 → 最後に全体 AUC - test 予測は各 fold の平均(
test_pred / n_splits)
import numpy as np, pandas as pd from sklearn.model_selection import StratifiedKFold from sklearn.metrics import roc_auc_score import lightgbm as lgb skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) oof = np.zeros(len(train)) test_pred = np.zeros(len(test)) for tr_idx, va_idx in skf.split(X, y): X_tr, X_va = X.iloc[tr_idx].copy(), X.iloc[va_idx].copy() y_tr, y_va = y.iloc[tr_idx], y.iloc[va_idx] # ★ここで TargetEncoding を X_tr から fit → X_va, test に transform model = lgb.LGBMClassifier(...) model.fit(X_tr, y_tr) oof[va_idx] = model.predict_proba(X_va)[:, 1] test_pred += model.predict_proba(test_X)[:, 1] / skf.n_splits print("OOF AUC:", roc_auc_score(y, oof))
✅ 模範解答
タスク1: 最初の30分でやる EDA チェックリスト(7項目)
| # | 見ること | 何を判断するか |
|---|---|---|
| 1 | train.shape / test.shape | データ量・train と test のサイズ比 |
| 2 | target.value_counts(normalize=True) | クラス不均衡の有無(→ Stratified CV の要否) |
| 3 | df.isna().mean() | 欠損率の高い列・欠損パターン |
| 4 | df.describe() と歪度 | 外れ値・対数変換すべき列 |
| 5 | df.nunique() | 高カーディナリティ変数の特定(→ TE候補) |
| 6 | 数値×target 相関 / カテゴリ×target 平均 | 効きそうな特徴量の見当 |
| 7 | train と test の分布比較 | 分布ズレ(covariate shift)・リークの兆候 |
タスク2: 一気通貫パイプライン
import numpy as np import pandas as pd from sklearn.model_selection import StratifiedKFold from sklearn.metrics import roc_auc_score import lightgbm as lgb # 0. データ読み込み train = pd.read_csv('train.csv') test = pd.read_csv('test.csv') target_col, id_col = 'target', 'id' y = train[target_col] train_X = train.drop(columns=[target_col, id_col]) test_X = test.drop(columns=[id_col]) num_cols = train_X.select_dtypes(include='number').columns.tolist() cat_cols = train_X.select_dtypes(exclude='number').columns.tolist() high_card = [c for c in cat_cols if train_X[c].nunique() > 20] low_card = [c for c in cat_cols if c not in high_card] # 1. 欠損値処理(単純代入はリーク影響が小さいので全体でOK) for c in num_cols: med = train_X[c].median() train_X[c] = train_X[c].fillna(med) test_X[c] = test_X[c].fillna(med) for c in cat_cols: train_X[c] = train_X[c].fillna('__MISSING__').astype(str) test_X[c] = test_X[c].fillna('__MISSING__').astype(str) for c in low_card: train_X[c] = train_X[c].astype('category') test_X[c] = test_X[c].astype('category') # 2. fold 内 TargetEncoding(Smoothing 付き・リーク防止) def target_encode(tr, va, te, col, y_tr, m=10): prior = y_tr.mean() stats = y_tr.groupby(tr[col]).agg(['mean', 'count']) smooth = (stats['mean']*stats['count'] + prior*m) / (stats['count'] + m) return (tr[col].map(smooth).fillna(prior), va[col].map(smooth).fillna(prior), te[col].map(smooth).fillna(prior)) # 3-5. Stratified K-Fold で OOF + test 予測 skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) oof = np.zeros(len(train_X)) test_pred = np.zeros(len(test_X)) params = dict(n_estimators=1000, learning_rate=0.03, num_leaves=31, subsample=0.8, colsample_bytree=0.8, random_state=42, n_jobs=-1) for fold, (tr_idx, va_idx) in enumerate(skf.split(train_X, y)): X_tr, X_va, X_te = train_X.iloc[tr_idx].copy(), train_X.iloc[va_idx].copy(), test_X.copy() y_tr, y_va = y.iloc[tr_idx], y.iloc[va_idx] # ★ TargetEncoding は fold 内 train でのみ fit for c in high_card: e_tr, e_va, e_te = target_encode(X_tr, X_va, X_te, c, y_tr) X_tr[c+'_te'], X_va[c+'_te'], X_te[c+'_te'] = e_tr, e_va, e_te X_tr, X_va, X_te = (d.drop(columns=high_card) for d in (X_tr, X_va, X_te)) model = lgb.LGBMClassifier(**params) model.fit(X_tr, y_tr, eval_set=[(X_va, y_va)], eval_metric='auc', callbacks=[lgb.early_stopping(50), lgb.log_evaluation(0)]) oof[va_idx] = model.predict_proba(X_va)[:, 1] test_pred += model.predict_proba(X_te)[:, 1] / skf.n_splits print(f"fold{fold} AUC = {roc_auc_score(y_va, oof[va_idx]):.4f}") print(f"\n=== OOF AUC = {roc_auc_score(y, oof):.4f} ===") # 6. 提出ファイル submission = pd.DataFrame({id_col: test[id_col], target_col: test_pred}) submission.to_csv('submission.csv', index=False)
タスク3: CV=0.842 / LB=0.798 の乖離診断
| # | 仮説 | 見分け方 | 対処 |
|---|---|---|---|
| 1 | リーク(CVが甘い) | TE を fold 外で fit していないか。OOF が異常に高くないか | fold 内 fit を徹底。時系列なら時間分割 CV へ |
| 2 | train/test の分布ズレ | 各列の分布比較。Adversarial Validation の AUC が 0.5 から離れる | ズレる特徴量を除外/重要度低下。CV を test 分布に寄せる |
| 3 | Public LB が小さくノイズ | Public LB の対象行数を確認(例: test の 20%) | LB を過信せず CV を信じる。最終提出は CV ベース |
🪜 Step-by-Step 解説
1目的変数を最初に見る
不均衡(例: 陽性 5%)なら Stratified K-Fold 必須。fold ごとにクラス比率が崩れると CV スコアが暴れる。AUC は不均衡に比較的頑健だが、CV 設計は必ず層化する。
2欠損値は「単純代入」から始める
3TargetEncoding は fold 内で fit(最重要)
# ❌ リーク: 全 train で category→target 平均を計算してから CV # ✅ 正しい: fold の train 部分だけで計算し、valid と test に適用
Day 077「データリーク」の実戦適用。Smoothing(m=10)は出現回数が少ないカテゴリの平均を全体平均に寄せ、過学習を防ぐ。
4OOF で「正直な」スコアを測る
out-of-fold 予測は「学習に使っていないデータへの予測」を全 train 分そろえたもの。これで計算した AUC が「LB に最も近い自分だけの評価指標」。パラメータ変更の良し悪しは常に OOF で判断する。
5test 予測は fold 平均
各 fold のモデルは少しずつ違うデータで学習しているため、5 モデルの予測を平均すると分散が下がり安定する。軽いアンサンブル(bagging 効果)でもある。
6CV と LB が食い違ったら CV を信じる
🧮 数学・統計の補足(文系向け)
AUC を直感で理解する
AUC = 1.0 → 完璧に並べられる AUC = 0.5 → コイン投げと同じ(無意味) AUC = 0.8 → 8割の確率で 陽性 > 陰性 に並べられる
Smoothing の式の意味
補正平均 = (カテゴリ平均 × 件数 + 全体平均 × m) / (件数 + m)
「件数が多いカテゴリは自分の平均を信頼、少ないカテゴリは全体平均に寄せる」加重平均。m は全体平均を何件分の重みとして混ぜるか。件数が m を大きく超えるとほぼ自分の平均、少ないと全体平均に近づく。
🏆 Kaggleでの実践的な使い方
この総復習パイプラインが「土台」になる
今日組んだ枠組みは Phase 4 以降でそのまま拡張される。OOF と test_pred の枠組みは変わらず、中身を差し替えるだけでスコアが伸びる。
| 拡張ポイント | Phase 4 で学ぶこと |
|---|---|
| モデルを差し替え | XGBoost / CatBoost / 複数モデル |
| ハイパラ最適化 | Optuna で params を自動探索 |
| 特徴量を増やす | Aggregation / Lag / 高度な TargetEncoding |
| アンサンブル | 複数モデルの OOF を Stacking |
Bronze 圏内に入るための実務チェック
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
| 全 train で TE してから CV | 「前処理は先にまとめて」の習慣 | fold 内 fit しないと OOF が甘くなりリーク |
| LB が上がるまでパラメータを回す | LB が正義だと思う | LB probing は Private で崩れる。CV を信じる |
| test 予測を 1 fold だけで作る | 実装が楽 | 5 fold 平均のほうが安定して伸びる |
| AUC なのに 0/1 に丸めて提出 | 分類=ラベル出力だと思う | AUC は確率(順位)で提出。丸めるとスコア低下 |
| 不均衡データで通常の KFold | Stratified を忘れる | fold 間でクラス比が崩れ CV が暴れる |
🚀 次のステップ
- 発展: パイプラインに Adversarial Validation(train と test を見分ける分類器で分布ズレ検出)を追加し、CV と LB の乖離を実際に診断する
- 次回予告: Phase 4(青帯)突入 — 「Gradient Boosting とは何か」(Day 090)。決定木を「順番に弱点を補強していく」Boosting の直感を文系向けに解き明かす
📝 自己評価(解いた後に記入)
自分の回答・気づき・メモ: