📚 背景知識(読んでから問題へ)
sklearnの LinearRegression と同じ感覚で使える sklearn API(XGBRegressor)もありますが、今日はより細かく制御できる Native API(xgb.train, xgb.cv)を使います。Kaggleノートブックでは Native API の方がよく使われるため、最初に慣れておくと後々有利です。
| 用語 | 直感的な意味 |
|---|---|
| DMatrix | XGBoost専用の内部データ構造。DataFrameより高速・省メモリに学習できるよう最適化されている |
| watchlist | 学習中に「訓練データ」と「検証データ」のスコアを両方モニタリングする仕組み |
| early stopping(早期終了) | 検証スコアが指定ラウンド数だけ改善しなくなったら学習を自動で打ち切る仕組み |
| xgb.cv | K-Fold CVを内部で自動実行し、各ラウンドの平均スコアを返す関数。最適な木の本数を安定して見積もれる |
🔀 実装フローの全体像
DataFrame → DMatrix → 学習/CV → 評価、の一本道。CVは「最適な木の本数」を確かめる別ルート。
🎯 問題
以下は「賃貸物件の家賃予測」のサンプルデータです(Day 090 の家賃の例を拡張したもの)。このデータで XGBoost の回帰モデルを構築してください。
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) # 専有面積(m2) 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, })
| カラム名 | 意味 | 型 |
|---|---|---|
area_m2 | 専有面積(平米) | float |
age_years | 築年数 | int |
station_dist_min | 最寄り駅からの徒歩時間(分) | float |
rent_man_yen | 家賃(万円・目的変数) | float |
タスク
df を特徴量 X と目的変数 y に分け、train_test_split で訓練80%・テスト20%に分割するxgb.DMatrix に変換するparams 辞書を定義し、xgb.train を watchlist・early_stopping_rounds=20 付きで実行する。best_iteration を確認するxgb.cv で5-fold CVを実行し、CVベースの最適ラウンド数と平均RMES(test-rmse-mean)を求める📊 ベースライン vs XGBoost(RMSE比較イメージ)
数値は模範解答コードを実行した際の出力例。乱数シードにより多少前後する。
💡 ヒント
DMatrixは「numpy配列やDataFrameをXGBoost専用の高速フォーマットに変換したもの」というだけで、中身の考え方はsklearnのX, yと同じ。xgb.trainとxgb.cvはどちらも同じparams辞書を受け取るが、xgb.trainは1回の学習、xgb.cvはデータをK分割して複数回学習・評価を繰り返す点が違う。early stoppingは「検証スコアの改善が止まったら止める」というシンプルなルール。
- DMatrix変換:
xgb.DMatrix(X_train, label=y_train) - watchlist:
[(dtrain, "train"), (dtest, "eval")] - 学習:
xgb.train(params, dtrain, num_boost_round=500, evals=watchlist, early_stopping_rounds=20, verbose_eval=False) - 学習後の本数確認:
bst.best_iteration - 予測:
bst.predict(dtest, iteration_range=(0, bst.best_iteration + 1)) - CV実行:
xgb.cv(params, dtrain, num_boost_round=500, nfold=5, early_stopping_rounds=20, metrics="rmse", as_pandas=True, seed=42) - 最適ラウンド:
cv_results["test-rmse-mean"].idxmin()
import xgboost as xgb from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error X = df[["area_m2", "age_years", "station_dist_min"]] y = df["rent_man_yen"] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) dtrain = xgb.DMatrix(___, label=___) dtest = xgb.DMatrix(___, label=___) params = { "objective": "reg:squarederror", "eta": 0.1, "max_depth": 4, "eval_metric": "rmse", "seed": 42, } watchlist = [(___, "train"), (___, "eval")] bst = xgb.train( params, dtrain, num_boost_round=500, evals=watchlist, early_stopping_rounds=___, verbose_eval=False, ) print("best_iteration:", bst.best_iteration) cv_results = xgb.cv( params, dtrain, num_boost_round=500, nfold=___, early_stopping_rounds=20, metrics="rmse", as_pandas=True, seed=42, ) best_round = cv_results["test-rmse-mean"].___() print("CVベスト:", best_round, cv_results.loc[best_round, "test-rmse-mean"])
✅ 模範解答
import numpy as np import pandas as pd import xgboost as xgb from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error # --- データ準備 --- 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, }) # --- 1. train/testに分割 --- X = df[["area_m2", "age_years", "station_dist_min"]] y = df["rent_man_yen"] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # --- 2. DMatrixに変換 --- dtrain = xgb.DMatrix(X_train, label=y_train) dtest = xgb.DMatrix(X_test, label=y_test) # --- 3. パラメータ定義と学習(watchlist + early stopping) --- params = { "objective": "reg:squarederror", "eta": 0.1, "max_depth": 4, "eval_metric": "rmse", "seed": 42, } watchlist = [(dtrain, "train"), (dtest, "eval")] bst = xgb.train( params, dtrain, num_boost_round=500, evals=watchlist, early_stopping_rounds=20, verbose_eval=False, ) print("best_iteration:", bst.best_iteration) print("best_score (eval RMSE):", bst.best_score) # --- 4. xgb.cvで5-fold CV --- cv_results = xgb.cv( params, dtrain, num_boost_round=500, nfold=5, early_stopping_rounds=20, metrics="rmse", as_pandas=True, seed=42, ) best_round = cv_results["test-rmse-mean"].idxmin() print("CVベストラウンド:", best_round) print("CVベストRMSE:", cv_results.loc[best_round, "test-rmse-mean"]) # --- 5. テストRMSE vs ベースラインRMSE --- y_pred = bst.predict(dtest, iteration_range=(0, bst.best_iteration + 1)) xgb_rmse = mean_squared_error(y_test, y_pred, squared=False) baseline_pred = np.full_like(y_test, fill_value=y_train.mean(), dtype=float) baseline_rmse = mean_squared_error(y_test, baseline_pred, squared=False) print(f"ベースラインRMSE(平均値予測): {baseline_rmse:.3f} 万円") print(f"XGBoost RMSE: {xgb_rmse:.3f} 万円") print(f"改善率: {(1 - xgb_rmse / baseline_rmse) * 100:.1f}%") # 出力例: # best_iteration: 34 # best_score (eval RMSE): 1.07... # CVベストラウンド: 29 # CVベストRMSE: 1.15... # ベースラインRMSE(平均値予測): 6.9xx 万円 # XGBoost RMSE: 1.0x 万円 # 改善率: 8x.x%
🪜 Step-by-Step 解説
1DMatrixへの変換
dtrain = xgb.DMatrix(X_train, label=y_train) dtest = xgb.DMatrix(X_test, label=y_test)
XGBRegressor.fit(X, y)を使えばこの変換は自動で行われるが、Native APIでは明示的に変換することでメモリ使用量や前処理を細かく制御できる。2watchlistとearly stoppingで学習を制御する
watchlist = [(dtrain, "train"), (dtest, "eval")] bst = xgb.train(params, dtrain, num_boost_round=500, evals=watchlist, early_stopping_rounds=20, verbose_eval=False)
num_boost_round=500と多めに設定しても、early_stopping_rounds=20によって「evalスコアが20ラウンド連続で改善しなければ自動で止める」ため、過学習が始まる直前で学習を打ち切れる。bst.best_iterationが実際に使われた木の本数。3xgb.cvで「何本の木が最適か」を安定して見積もる
cv_results = xgb.cv(params, dtrain, num_boost_round=500, nfold=5, early_stopping_rounds=20, metrics="rmse", as_pandas=True, seed=42) best_round = cv_results["test-rmse-mean"].idxmin()
early_stopping_roundsは「1回のtrain/test分割」だけを見て止め時を決めているため、たまたまその分割のクセに引っ張られる可能性がある。xgb.cvはデータを5分割して5回学習・評価を繰り返し、各ラウンドの平均スコアを返すため、より信頼できる「最適な木の本数」の見積もりが得られる。Kaggleでは、このCV結果をもとに最終的なnum_boost_roundを決め、全訓練データで再学習する流れがよく使われる。4ベースラインとの比較で「モデルの価値」を数値化する
baseline_pred = np.full_like(y_test, fill_value=y_train.mean(), dtype=float) baseline_rmse = mean_squared_error(y_test, baseline_pred, squared=False)
🧮 数学・統計の補足(文系向け)
early_stopping_rounds=20 は「20回連続で成績が伸びなかったら、そこで練習をやめる」というルールだと考えてよい。ずっと練習を続けると、そのうち「本番では出ない過去問の細かいクセ」まで覚え始めてしまう(=過学習)。伸びが止まった時点でやめることで、ちょうど良いところで練習を打ち切れる。身近な例: 模擬試験の伸び止まり
模擬試験の点数が5回連続で伸びなければ、「今のやり方はもう頭打ちだから、この時点の実力を採用しよう」と判断するのに似ている。XGBoostは「評価用データでの点数(eval RMSE)」を模擬試験のスコアとして、それが伸びなくなった時点の木の本数を採用する。
もし数式を見たら
RMSE = √( (1/n) Σ (yᵢ − ŷᵢ)² )
「予測と正解のズレ(誤差)を2乗して平均し、最後にルートを取ったもの」。2乗することで正負の誤差が打ち消し合わないようにし、ルートを取ることで単位を元の家賃(万円)のスケールに戻す。「平均してだいたい何万円ズレているか」の目安になる。
🏆 Kaggleでの実践的な使い方
よく使われるコンペカテゴリ: ☑ 表形式データ(Tabular) / ☐ 自然言語処理(NLP) / ☐ 画像認識(CV) / ☑ 時系列(Time Series)
Kaggleの多くのTabularコンペでは、まず「素の特徴量 + XGBoostのデフォルトに近いパラメータ + early stopping」でベースラインスコアを素早く作り、そこから特徴量エンジニアリング(Phase 4後半で学ぶAggregation・TargetEncodingなど)を積み重ねてスコアを伸ばしていくのが定石。xgb.cvによるCVベースの木の本数決定は、Public LBに過剰適合しないための基本動作でもある。
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
num_boost_roundを大きくすればするほど精度が上がると思う | 木の本数=モデルの複雑さ、という単純な連想 | early stoppingがなければ過学習して逆にテストスコアが悪化する。num_boost_roundは「上限」であり、実際に使う本数はbest_iterationで決まる |
xgb.trainのearly stoppingとxgb.cvの結果を混同する | どちらも「最適な木の本数」を出すため似て見える | xgb.trainは1回の分割に基づく結果、xgb.cvは5分割の平均に基づく結果。CVの方が信頼性が高く、最終的な本数決定にはCV結果を使うのが安全 |
DMatrix変換を忘れてDataFrameをそのままxgb.trainに渡そうとする | sklearn APIとの混同 | Native API(xgb.train, xgb.cv)は必ずDMatrixが必要。sklearn API(XGBRegressor)なら内部で自動変換される |
| RMSEの数値だけを見て「良いモデルだ」と判断してしまう | 絶対値だけでは基準がない | 必ずベースライン(平均値予測など単純な手法)と比較し、相対的な改善度で評価する |
🚀 次のステップ
- 発展:
max_depthやetaを変えてCV結果(test-rmse-mean)がどう変化するか試してみる - 次回予告: Day 092「XGBoost② パラメータ」 —
max_depth・eta・subsampleなど主要ハイパーパラメータの意味と役割を理論と実装の両面から学ぶ
📝 自己評価(解いた後に記入)
自分の回答・気づき・メモ: