📚 背景知識(読んでから問題へ)
xgb.train / xgb.cv の「動かし方」を学びました。今日はその中で当たり前のように使っていた params 辞書の中身——max_depth・eta・subsample——が実際に何をコントロールしているのかを理解します。Kaggleノートブックで必ず目にする3大パラメータです。| 用語 | 直感的な意味 |
|---|---|
| max_depth | 1本の決定木が「質問を何回重ねてよいか」の上限。Phase 2 の決定木の深さと同じ概念 |
| eta(学習率) | 新しい木を1本追加するたびに、その木の予測を「どれだけ本気で採用するか」の倍率 |
| subsample | 各木を作る際に訓練データの何%をランダムに使うか。ランダムフォレストのバギングと同じ発想 |
| (関連)colsample_bytree | subsampleの「列版」。各木が使う特徴量の割合をランダムに間引く |
🎛️ 3つのツマミ(大きくすると何が起きるか)
max_depth ↑
1本の木がより細かい条件分岐を持てる。訓練データの細部まで記憶しやすくなり、過学習リスク↑
Tabularでは 3〜8 が典型eta ↓
1本の木の発言力が弱まる分、多くの木を積み上げて慎重に学習。安定するが木の本数が必要
大きめ 0.1〜0.3 で高速探索、小さめ 0.01〜0.05 で本番仕上げsubsample ↓
各木が見るデータを間引き、木ごとの多様性を生む。ランダムフォレストのバギングと同じ発想で過学習抑制
0.6〜1.0 の範囲で試すのが一般的eta と num_boost_round(何本の木を許すか)は強く連動する——etaを下げたら本数の上限も引き上げないと未学習で終わる。🎯 問題
Part A: 理論(自分の言葉で説明してみる)
eta を 0.3 から 0.05 に下げると、必要な num_boost_round(木の本数)はどう変化するか。理由も含めて説明するmax_depth を際限なく大きくすると何が起きるか。「過学習」という言葉を使って説明するsubsample=0.8 がなぜ過学習対策になるのか。ランダムフォレストの「バギング」との共通点から説明するPart B: コーディング(Day 091の家賃データを使用)
import numpy as np import pandas as pd np.random.seed(42) n = 200 area_m2 = np.random.normal(45, 15, n).clip(15, 100) age_years = np.random.randint(0, 35, n) station_dist_min = np.random.normal(8, 4, n).clip(1, 25) rent_man_yen = ( 3.0 + area_m2 * 0.25 - age_years * 0.05 - station_dist_min * 0.15 + np.random.normal(0, 1.0, n) ).clip(3.0, None) df = pd.DataFrame({ "area_m2": area_m2, "age_years": age_years, "station_dist_min": station_dist_min, "rent_man_yen": rent_man_yen, })
タスク
eta を [0.3, 0.1, 0.05]、max_depth を [2, 4, 6] の全9通りについて xgb.cv(5-fold, early_stopping_rounds=30, num_boost_round=1000)を実行し、各組み合わせの「CVベストラウンド」「CVベストRMSE」を記録するDataFrame にまとめ、CVベストRMSEが最小の組み合わせを表示するeta が小さいほど「CVベストラウンド」がどう変化するか確認する(Part A 設問1の裏付け)eta / max_depth に対し subsample=1.0 と 0.8 を比較する📊 eta を下げると必要な木の本数はどう変わるか(出力例)
模範解答コードを実行した際の出力例(乱数シードにより多少前後する)。1歩の大きさ(eta)が小さいほど、目的地に着くまでの歩数(best_round)が増える。
early_stopping_roundsがあるため学習は自動で適切なところで止まる。「小さいetaは遅いが安定」「大きいetaは速いが不安定になりやすい」がこのグラフから読み取れる。💡 ヒント
3つのパラメータはそれぞれ独立した「ツマミ」だが、影響し合う。etaを小さくする=一歩を小さくする、ということは、同じ目的地(最適な精度)にたどり着くまでの歩数(木の本数)が増えるはず。max_depthは「1本の木の複雑さ」、subsampleは「1本の木が見るデータの量」——どちらも大きい/多いほど1本の木が強力になり、過学習しやすくなる。
- 組み合わせの総当たり:
itertools.product(etas, max_depths) - ベストRMSE/ラウンド:
cv_results["test-rmse-mean"].min()/.idxmin() - 結果は辞書のリストに貯めて
pd.DataFrame(results_list)にする - 並べ替え:
sort_values("best_rmse")
import itertools import xgboost as xgb dtrain = xgb.DMatrix(df[["area_m2", "age_years", "station_dist_min"]], label=df["rent_man_yen"]) etas = [0.3, 0.1, 0.05] max_depths = [2, 4, 6] results = [] for eta, depth in itertools.product(___, ___): params = { "objective": "reg:squarederror", "eta": eta, "max_depth": depth, "eval_metric": "rmse", "seed": 42, } cv_results = xgb.cv( params, dtrain, num_boost_round=___, nfold=5, early_stopping_rounds=___, metrics="rmse", as_pandas=True, seed=42, ) best_round = cv_results["test-rmse-mean"].___() best_rmse = cv_results["test-rmse-mean"].___() results.append({"eta": eta, "max_depth": depth, "best_round": best_round, "best_rmse": best_rmse}) results_df = pd.DataFrame(results).sort_values(___) print(results_df)
✅ 模範解答
Part A: 理論の模範解答
| 設問 | 模範解答 |
|---|---|
| 1. eta を下げると本数は? | 1本あたりの補強が弱まる分、同じだけ誤差を減らすのにより多くの木が必要になる(best_iteration増加)。一方、たまたまのクセに引きずられにくく最終精度は安定しやすい |
| 2. max_depth を大きくすると? | 1本の木が訓練データの細部(ノイズ含む)まで記憶できるようになり、訓練スコアは良化するがテストスコアは悪化する=過学習が起きやすい |
| 3. subsample=0.8 が過学習対策になる理由 | ランダムフォレストのバギングと同じ発想。各木が訓練データの80%だけをランダムに見ることで、木ごとの多様性が生まれ、1本が全データの細部を覚えすぎるのを防ぐ |
Part B: コーディングの模範解答
import numpy as np import pandas as pd import xgboost as xgb import itertools # --- データ準備(Day 091と同じ) --- np.random.seed(42) n = 200 area_m2 = np.random.normal(45, 15, n).clip(15, 100) age_years = np.random.randint(0, 35, n) station_dist_min = np.random.normal(8, 4, n).clip(1, 25) rent_man_yen = ( 3.0 + area_m2 * 0.25 - age_years * 0.05 - station_dist_min * 0.15 + np.random.normal(0, 1.0, n) ).clip(3.0, None) df = pd.DataFrame({ "area_m2": area_m2, "age_years": age_years, "station_dist_min": station_dist_min, "rent_man_yen": rent_man_yen, }) dtrain = xgb.DMatrix(df[["area_m2", "age_years", "station_dist_min"]], label=df["rent_man_yen"]) # --- 1&2. eta x max_depth のグリッド探索 --- etas = [0.3, 0.1, 0.05] max_depths = [2, 4, 6] results = [] for eta, depth in itertools.product(etas, max_depths): params = { "objective": "reg:squarederror", "eta": eta, "max_depth": depth, "eval_metric": "rmse", "seed": 42, } cv_results = xgb.cv( params, dtrain, num_boost_round=1000, nfold=5, early_stopping_rounds=30, metrics="rmse", as_pandas=True, seed=42, ) best_round = cv_results["test-rmse-mean"].idxmin() best_rmse = cv_results["test-rmse-mean"].min() results.append({"eta": eta, "max_depth": depth, "best_round": best_round, "best_rmse": best_rmse}) results_df = pd.DataFrame(results).sort_values("best_rmse").reset_index(drop=True) print(results_df) best_row = results_df.iloc[0] print(f"\nベスト組み合わせ: eta={best_row['eta']}, max_depth={best_row['max_depth']}, " f"best_round={best_row['best_round']}, best_rmse={best_row['best_rmse']:.4f}") # --- 3. eta と best_round の関係を確認 --- print("\neta別の平均best_round:") print(results_df.groupby("eta")["best_round"].mean().sort_index(ascending=False)) # --- 4. subsampleの比較 --- for subsample in [1.0, 0.8]: params = { "objective": "reg:squarederror", "eta": best_row["eta"], "max_depth": int(best_row["max_depth"]), "subsample": subsample, "eval_metric": "rmse", "seed": 42, } cv_results = xgb.cv( params, dtrain, num_boost_round=1000, nfold=5, early_stopping_rounds=30, metrics="rmse", as_pandas=True, seed=42, ) print(f"subsample={subsample}: best_rmse={cv_results['test-rmse-mean'].min():.4f}, " f"best_round={cv_results['test-rmse-mean'].idxmin()}") # 出力例: # eta max_depth best_round best_rmse # 0 0.05 2 210 1.0812 # 1 0.10 2 103 1.0839 # 2 0.05 4 95 1.0921 # ...(eta=0.3, max_depth=6 は過学習気味でRMSEが相対的に悪化しやすい) # # eta別の平均best_round: # eta # 0.30 28.0 # 0.10 84.3 # 0.05 168.7 # # subsample=1.0: best_rmse=1.0812, best_round=210 # subsample=0.8: best_rmse=1.07xx, best_round=2xx
🪜 Step-by-Step 解説
1eta x max_depth の全組み合わせをループする
for eta, depth in itertools.product(etas, max_depths): ...
eta=0.05が良いかどうかはmax_depthにも依存するため、複数のパラメータを同時に動かして「組み合わせ」で評価するのがKaggleでの基本姿勢(本格的なチューニングではOptunaを使うが、まずは総当たりの感覚を掴む)。2各組み合わせで xgb.cv を実行し、結果を貯める
best_round = cv_results["test-rmse-mean"].idxmin() best_rmse = cv_results["test-rmse-mean"].min() results.append({...})
xgb.cvは5-foldの平均スコアを返すため、1回のtrain/test分割よりも信頼できる評価が得られる。結果を辞書のリストに貯めてDataFrame化することで、9通りの結果を一覧で比較できる。3eta と best_round の関係を確認する
results_df.groupby("eta")["best_round"].mean().sort_index(ascending=False)
eta=0.3では数十本、eta=0.05ではその数倍の本数が必要になるはず。4subsample を変えて過学習抑制効果を見る
for subsample in [1.0, 0.8]: ...
subsample単体の効果を見るために、他のパラメータ(最良のeta, max_depth)は固定してsubsampleだけを変える。1つの変数だけを動かして純粋な影響を観察する「対照実験」の考え方。🧮 数学・統計の補足(文系向け)
etaは「階段を一段ずつ上るときの一歩の大きさ」だと考えてよい。eta=0.3は大股で一気に上る(速いが着地がズレやすい)、eta=0.05は小股で慎重に上る(時間はかかるが正確に目的の高さに到達しやすい)。max_depthは「1人の専門家が答えられる質問の深さ」——深すぎる専門家は「今回のテストに出た特殊な問題」まで覚えてしまう。身近な例: 抽選で選ばれる80人
subsample=0.8は、100人のクラスから毎回ランダムに80人だけを選んでテストの傾向分析をするようなもの。同じ80人を使わず毎回メンバーを入れ替えることで、「たまたまその80人に多かった特徴」に分析結果が引きずられにくくなる。これが複数の木に渡って行われることで、モデル全体としてより汎用的な判断ができるようになる。
もし数式を見たら
F_m(x) = F_(m-1)(x) + η · h_m(x)
「$m$本目の木を足した後の予測 = $m-1$本目までの予測 + $\eta$(eta)倍した新しい木$h_m$の予測」という意味。$\eta$が1に近いほど新しい木の意見をそのまま採用し、0に近いほど「ちょっとだけ参考にする」形になる。これが「学習率」と呼ばれる理由——木1本あたりの影響力を調整する数値だから。
🏆 Kaggleでの実践的な使い方
よく使われるコンペカテゴリ: ☑ 表形式データ(Tabular) / ☐ 自然言語処理(NLP) / ☐ 画像認識(CV) / ☑ 時系列(Time Series)
Kaggle上位者の典型的な流れは「まずetaをやや大きめ(0.1前後)にして高速に探索 → 有望な特徴量・構造が見えてきたらetaを小さく(0.01〜0.03)してnum_boost_roundを増やし、early_stopping_roundsで自動的に止める」という2段階。ラフスケッチで方向性を決めてから清書は丁寧に描く、のと同じ発想。max_depthはTabularコンペでは3〜8程度が多く使われ、subsample/colsample_bytreeは0.6〜0.9あたりがよく試されるレンジ。
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
etaを小さくすればするほど精度が上がると信じ、num_boost_roundを増やさずに使う | 「小さい=慎重=良い」という単純な連想 | etaを小さくしたらnum_boost_round(とearly_stopping_roundsの許容幅)も増やさないと、木の本数が足りず未学習で終わる |
max_depthを大きくすれば表現力が上がって常に良いと思う | 訓練スコアが上がるのを見て「良くなった」と誤認する | 訓練スコアの向上とテストスコアの向上は別物。max_depthが大きすぎるとテストスコアはむしろ悪化する(過学習) |
subsampleを下げれば下げるほど良いと思う | 「ランダム性=汎化性能向上」という過度な一般化 | データが少なすぎる場合、下げすぎると各木が学習に使えるデータが不足し逆に精度が落ちることがある。0.6〜1.0の範囲で試すのが一般的 |
| パラメータを1つずつ順番に最適化しようとする | 直列に考える方が理解しやすいため | パラメータ同士は相互作用する(特にetaと本数)ため、単独最適化では真の最適解を見逃しやすい。グリッドサーチやOptuna(Day 097-098)で同時に探索するのが望ましい |
🚀 次のステップ
- 発展:
colsample_bytree(列のサブサンプリング)も加えた3パラメータのグリッド探索を試してみる - 次回予告: Day 093「LightGBM① 実装」 — XGBoostとの違いと、なぜLightGBMの方が高速なのかを理論と実装の両面から学ぶ
📝 自己評価(解いた後に記入)
自分の回答・気づき・メモ: