論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説
📚 用語解説
XGBoost
XGBoost
ライブラリ

🔖 キーワード索引

#GBDT#Boosting#Kaggle#tabular#feature importance#gradient boosting
勾配ブースティング決定木(GBDT)弱学習器と残差学習率 eta正則化 γ・λ分割ゲイン Gainearly_stopping_rounds5-fold CVGridSearchCVfeature_importances_ と permutation importanceSHAP(TreeExplainer)学習曲線LightGBM・CatBoost との違い

💡 30秒で分かる結論

🍰 まずはやさしく

データの予測が得意な道具です。

正解を効率よく当てるために使います。

テストの点数を予想するような時に便利です。

まずは結論を短くまとめました。

XGBoost:勾配ブースティング決定木の高速実装

📍 文脈ボックス

🍰 まずはやさしく

分析に使う便利な道具箱のようなものです。

データを正しく処理するために使います。

スマホのアプリ開発などで活用されています。

この道具をどう使うか詳しく説明します。

このページは、 勾配ブースティングという手法そのものより、 その代表的な実装である Python ライブラリ xgboost の使い方を扱う。 SSDSE-B-2026(2023 年度の 47 都道府県)で合計特殊出生率などを予測しながら、 木の本数・深さ・学習率・正則化をどう決め、 47 行しかないデータで過学習をどう見抜くか(early stopping・交差検証・学習曲線)、 予測の理由を feature_importances_・permutation importance・SHAP でどう読むかを、 実行結果つきで確かめる。 アルゴリズムの考え方は 🎨 と 📐 で、 ライブラリの使い方は 🐍 で読む。

🎨 直感で掴む

🍰 まずはやさしく

小さな間違いを直すリレーのような仕組みです。

予測の精度を高めるために使います。

部活の練習で弱点を一つずつ直す感覚です。

仕組みをイメージしやすい例で解説します。

決定木 1 本ではよく外す。 そこで「外した分を補正する次の木」を順番に作り、 何百本も足し合わせる ── これが 勾配ブースティング。 XGBoost はこのアルゴリズムを C++ で高速実装し、 欠損値対応・並列処理・正則化を全部入りにしたライブラリ。 表形式データ予測の 2010 年代後半のデファクト。

「弱学習器の足し算」の絵

SSDSE で 47 県の出生数を予測する状況を想像する。 1 本目の木は「人口 > 500 万 → 高出生数」程度の単純な分岐。 残った誤差を 2 本目の木が「人口密度 > 1000」で分岐して補正。 3 本目は「高齢化率 < 25% で更に高く」。 ── 100-1000 本積み重ねることで複雑な関係を表現する。 1 本ごとの寄与は小 (学習率 $\eta=0.1$ 程度) なので、 オーバーフィットを抑えつつ高精度に。

特徴XGBoostLightGBMCatBoost
開発元Tianqi Chen (DMLC)MicrosoftYandex
初版201420172017
木分割戦略level-wise (深さ均等)leaf-wise (最大ロス低下)対称木 (oblivious tree)
カテゴリ変数手動エンコード必要対応 (categorical_feature)ネイティブ対応 (target encoding)
GPU 学習対応 (tree_method=gpu_hist)対応 (device=gpu)対応 (task_type=GPU)
速度中高速 (大規模で 2-5倍)中
Kaggle 勝率トップ常連トップ常連特定問題で強

📌 XGBoost は表形式データの標準。 LightGBM は大規模 (数千万行) で高速、 CatBoost はカテゴリ多めの場合に強い。 「迷ったら XGBoost」「速度が必要なら LightGBM」が定石。

なぜ Kaggle で勝てるのか

表形式データに対し、 非線形・特徴量相互作用・欠損値・カテゴリを全部自動で扱える。 線形モデルは特徴量エンジニアリングが必須、 ニューラルネットは大データが必須、 ランダムフォレストは XGBoost に精度で劣る ── 中間のスイートスポットを掴んでいる。 Kaggle トップソリューションは 2015-2022 年に XGBoost (or LightGBM) + アンサンブルが定番だった。

🎮 触って理解する ── 勾配ブースティングを 1 本ずつ育てる

XGBoost の数理的核心は 「浅い木 (弱学習器) を、 残った誤差 (残差) に順番に当てはめて足し合わせる」こと。 下は架空の 1 次元回帰データ (y = sin(2πx) + ノイズ、 シード固定・完全に再現可能) です。 「木を 1 本追加」を押すたびに、 いまの残差に浅い決定株を当てはめて予測へ足し込みます。 予測曲線 (左) が階段状にターゲットへ近づき、 残差 (右) が 0 へ潰れていく様子を体感してください。

学習率 η 0.30 弱学習器
木の本数0
適用中の η0.30
訓練 RMSE–
テスト RMSE–

① 予測 vs ターゲット (階段状に近づく)

図の上をなぞると、その x での ターゲット値・予測値・残差を表示します

② 残差 (次の木が拾う誤り → 0 へ潰れる)

灰=直前の残差 / 橙=追加した木の補正 / 緑=更新後の残差
「木を 1 本追加」を押して開始。 まず全データの平均値から出発します。

🧠 何が起きている?(直感)

出発点は「全部同じ値 (平均)」という一番愚かな予測。 そこから いまの誤り (残差) だけを見て、 それを説明する浅い木を 1 本足す。 足すと残差が少し縮む。 縮んだ残差にまた木を当てる ── この 逐次的な誤り修正の繰り返しが勾配ブースティングです。 二乗誤差では残差そのものが勾配 (の逆符号) なので、 「残差を予測する木を足す」ことは損失を勾配方向に減らすことと等価になります。 1 本の寄与は η 倍に縮小されるので、 1 本ずつは控えめでも 数十〜数百本の合議で複雑な曲線を表現できます。

⚠️ 落とし穴(このウィジェットで確かめられる)

🚀 発展

実際の XGBoost はこの素朴なブースティングに正則化 ($\gamma T + \tfrac12\lambda\sum w_j^2$) を加え、 葉の値と葉数そのものを罰して過学習を抑えます (このデモには未実装)。 さらに各木で使う特徴量の列サンプリング (colsample) と行サンプリング (subsample)で木同士を脱相関させ、 汎化を高めます。 分割探索を高速化した派生が 学習率を含む多数のハイパラを持つ LightGBM (leaf-wise 成長・ヒストグラム分割) や CatBoost (カテゴリ変数のネイティブ対応・対称木) です。 基礎となる考え方は 決定木・ランダムフォレスト・木のアンサンブル・勾配降下法・正則化・過学習の各ページも参照してください。

📐 定義・数式

🍰 まずはやさしく

計算のルールを決めた数式です。

正確に予測を計算するために使います。

買い物での合計金額を出すような計算です。

具体的な数式とその意味を説明します。

【勾配ブースティングの更新式】
$$ \hat{y}^{(t)}(\mathbf{x}) = \hat{y}^{(t-1)}(\mathbf{x}) + \eta\, f_t(\mathbf{x}) $$

$t$ 番目の木 $f_t$ を学習率 $\eta$ をかけて加算。 各 $f_t$ は前ステップまでの残差 (勾配) を予測するように学習される。

【XGBoost の目的関数 (Chen & Guestrin, 2016)】
$$ \mathcal{L}^{(t)} = \sum_{i=1}^{n} l(y_i, \hat{y}_i^{(t-1)} + f_t(\mathbf{x}_i)) + \Omega(f_t) $$

第 1 項が予測誤差、 第 2 項 $\Omega$ が木の複雑度ペナルティ。 これが XGBoost と GBDT (Friedman 2001) の最大の違い ── 損失関数自体に正則化が組み込まれている。

【テイラー展開で 2 次近似】
$$ \mathcal{L}^{(t)} \approx \sum_i \left[ g_i f_t(\mathbf{x}_i) + \tfrac{1}{2} h_i f_t(\mathbf{x}_i)^2 \right] + \Omega(f_t) $$

$g_i = \partial l / \partial \hat{y}_i$、 $h_i = \partial^2 l / \partial \hat{y}_i^2$ は 1 階・2 階勾配。 任意の微分可能損失関数 (二乗誤差、 ロジット、 hinge 等) で同じフレームワークが使える ── これが XGBoost が汎用的たる理由。

【木の複雑度ペナルティ $\Omega$】
$$ \Omega(f) = \gamma T + \tfrac{1}{2} \lambda \sum_{j=1}^{T} w_j^2 $$

$T$ は葉数、 $w_j$ は葉 $j$ の予測値。 $\gamma$ で葉数を、 $\lambda$ で葉値を罰する。 $\gamma$ が大きいと木は浅く・少なく、 $\lambda$ が大きいと葉の予測値が抑制される。

【最適な葉重みの閉形式解】
$$ w_j^* = -\frac{\sum_{i \in I_j} g_i}{\sum_{i \in I_j} h_i + \lambda}, \quad \mathcal{L}^* = -\tfrac{1}{2} \sum_{j=1}^{T} \frac{\left(\sum_{i \in I_j} g_i\right)^2}{\sum_{i \in I_j} h_i + \lambda} + \gamma T $$

葉 $j$ に入るサンプル集合 $I_j$ について、 $g$ と $h$ から最適 $w_j^*$ が解析的に計算できる。 $\mathcal{L}^*$ は木の品質スコアで、 分割点候補の評価に使う。

【分割ゲイン (split gain)】
$$ \text{Gain} = \tfrac{1}{2}\left[ \frac{G_L^2}{H_L+\lambda} + \frac{G_R^2}{H_R+\lambda} - \frac{(G_L+G_R)^2}{H_L+H_R+\lambda} \right] - \gamma $$

分割を入れる前後のスコア差。 Gain > 0 なら分割する価値あり、 ≤ 0 なら止める (pre-pruning)。 $\gamma$ が分割の最小利益閾値として働く ── これが XGBoost の枝刈り戦略。

🔬 数式を言葉で読み解く

数式に出てくる記号の意味を 1 つずつ確認しましょう。

$f_t \in \mathcal{F}$
$t$ 番目の決定木 (CART)。 木構造 + 葉の予測値からなる関数。
$\hat{y}^{(t)}$
$t$ ステップ目までの累積予測値。 $\hat{y}^{(t)} = \sum_{k=1}^{t} \eta f_k$。
$\eta$ (learning_rate)
学習率 / 縮約率 (shrinkage)。 0.01〜0.3 が典型。 小さくして $T$ を増やすと精度が出やすい。
$T$ (n_estimators)
木の総数。 数百〜数千。 early_stopping_rounds で自動決定が推奨。
max_depth
1 本の木の最大深さ。 3〜8 が典型。 大きすぎると過学習、 小さすぎるとアンダーフィット。
$g_i, h_i$
サンプル $i$ における 1 階・2 階勾配。 損失関数を 2 次近似する係数。
$\gamma$ (gamma, min_split_loss)
葉の追加コスト。 Gain $> \gamma$ でないと分割しない。 過学習抑制に効く。
$\lambda$ (reg_lambda, L2)
葉重みの L2 正則化。 $\lambda$ 大で葉値が小さく抑制される。 既定 1.0。
$\alpha$ (reg_alpha, L1)
葉重みの L1 正則化。 スパース化に効く。 既定 0。
subsample, colsample_bytree
行・列のサンプリング率 (0<・≤1)。 確率的ブースティングで多様性確保。 推奨 0.7-0.9。
min_child_weight
葉の最小 $h$ 和。 分類なら「最小サンプル数 × 確率」相当。 大きくして過学習防止。

具体例 ── 二乗誤差損失の場合

$l(y, \hat{y}) = (y - \hat{y})^2 / 2$ なら、 $g_i = \hat{y}_i - y_i$ (= 残差の符号反転)、 $h_i = 1$。 → $w_j^* = -\sum_i g_i / (n_j + \lambda)$ で「葉内サンプルの残差平均」に近い値。 これが「次の木は残差をフィットする」直感の正体。

🔬 数式を言葉で読み解く (2):「分割ゲイン」の意味

XGBoost が「どこで分割するか」を決める指標 Gain を分解する。

  1. 「分割前のスコア」 ── $- \frac{1}{2} \cdot \frac{(G_L+G_R)^2}{H_L+H_R+\lambda}$ (親ノード)。
  2. 「分割後の左子・右子のスコア合計」 ── $- \frac{1}{2} \left[ \frac{G_L^2}{H_L+\lambda} + \frac{G_R^2}{H_R+\lambda} \right]$。
  3. 「差を取って $\gamma$ を引く」 ── Gain = (親 - 子) - $\gamma$。 これがプラスなら分割する価値あり。
  4. 「すべての候補分割点を試して最大 Gain を選ぶ」 ── 連続値特徴量はソート + 累積和で $O(n \log n)$ で高速計算。
  5. 「分位点近似 (approximate algorithm)」 ── 大規模データでは全候補でなく分位点候補のみ評価 (XGBoost の高速化テクの一つ)。

この式が「データ量の足し算」(累積 $G, H$) だけで計算できるため、 ヒストグラムベース実装 (LightGBM・XGBoost hist tree_method) に展開できる ── これが GPU 学習や大規模対応の基盤。

🧮 実値で計算してみる

SSDSE-B-2026 の 47 都道府県データを使った 2 つの計算: (A) 単一木の分割 Gain を手計算する小例、 (B) XGBoost で人口階層 (大/中/小県) の 3 クラス分類を実行し精度・特徴量重要度を確認する。

A. Gain を手計算 (分割の意思決定を追体験)

10 県のミニデータで「人口 > 500 万人」分割の Gain を計算する。 各サンプルに 1 階勾配 $g_i$、 2 階勾配 $h_i$ を割り当て、 公式 $G_L^2/(H_L+\lambda) + G_R^2/(H_R+\lambda) - (G_L+G_R)^2/(H_L+H_R+\lambda)$ を電卓で叩く。 $\lambda=1$、 $\gamma=0$。

STEP A1 10 県データを準備
人口 [万人]: 東京 1410, 神奈川 924, 大阪 877, 愛知 750, 埼玉 734 (左=大県群), 静岡 358, 京都 254, 茨城 282, 鳥取 53.9, 高知 67.6 (右=小県群)。
STEP A2 勾配を計算 (二乗誤差、 初期予測値 0)
$g_i = -y_i$ (残差符号反転)、 $h_i = 1$ (恒常)。 ターゲット = 出生数千人で代用。
STEP A3 「人口 > 500」分割で Gain を計算
$G_L = -286.1, H_L = 5$ (大県側 5 サンプル)。 $G_R = -54.5, H_R = 5$ (小県側 5 サンプル)。 下の 10 県 (2023 年度) の出生数 (千人) を足した値。
STEP A4 代入して Gain 算出
$\text{Gain} = \frac{1}{2}\left[\frac{286.1^2}{6} + \frac{54.5^2}{6} - \frac{340.6^2}{11}\right] = \frac{1}{2}[13642.2 + 495.0 - 10546.2] = 1795.5$ → かなり大きく、 分割する価値がある。
SSDSE-B-2026 の 10 県データ (2023 年度、 出生数 = ターゲット) 県 人口万 出生千 東京 1408.6 86.3 神奈川 922.9 54.0 大阪 876.3 55.3 愛知 747.7 48.4 埼玉 733.1 42.1 (大県群 5) ----- 分割 (人口 > 500 万) ----- 静岡 355.5 19.0 京都 253.5 13.9 茨城 282.5 14.9 鳥取 53.7 3.3 高知 66.6 3.4 (小県群 5) g_i = -y_i, h_i = 1 G_L = -(86.3+54.0+55.3+48.4+42.1) = -286.1, H_L = 5 (大県側) G_R = -(19.0+13.9+14.9+3.3+3.4) = -54.5, H_R = 5 (小県側) λ = 1, γ = 0 Gain = 0.5 * [G_L^2/(H_L+λ) + G_R^2/(H_R+λ) - (G_L+G_R)^2/(H_L+H_R+λ)] = 0.5 * [286.1^2/6 + 54.5^2/6 - 340.6^2/11] = 0.5 * [13642.2 + 495.0 - 10546.2] = 1795.5 ← Gain > 0、 分割する価値あり

B. XGBoost で 47 県を 3 クラス分類

47 県を人口 3 分位 (大県/中県/小県) に分け、 15 歳未満人口・出生数・死亡数など 8 列の特徴量から階層を予測する。 5-fold CV で精度評価。

STEP B1 ターゲット作成
A1101 (人口) を qcut で 3 分位 → 0 (小)、 1 (中)、 2 (大)。 2023 年度は 117 万人・198 万人が境目で、 16・15・16 県に分かれる。
STEP B2 特徴量選択
A1301 (15 歳未満人口)、 A4101 (出生数)、 A4200 (死亡数)、 A5101 (転入者数)、 A9101 (婚姻件数)、 E1101 (幼稚園数)、 C3301 (着工建築物数)、 F3101 (新規求職申込件数) の 8 列。 どれも総人口との相関が 0.96 以上ある「人口の言い換え」なので、 精度が高く出るのはほぼ当然である。
STEP B3 XGBoost で学習 + 5-fold CV
XGBClassifier(n_estimators=200, max_depth=4, learning_rate=0.05) で fit。
STEP B4 特徴量重要度を確認
feature_importances_ で寄与順を見る → 15 歳未満人口・死亡数・出生数が上位。 人口と直接相関なので妥当。

🧮 数式に値を入れて手で計算する: XGBoost のステップ

合成データで 3 段の boost で残差が減少する様子を計算する。

Step 1: 真値と段別予測

段y予測残差
F0105 (平均)5
F1 (+0.5tree)105 + 2.5 = 7.52.5
F2 (+0.5tree)107.5 + 1.25 = 8.751.25
F3108.75 + 0.625 = 9.3750.625

Step 2: 収束

残差が半減ずつ → 学習率 0.5 無限段で残差 0 に収束

🐍 Python で再現

1
2
3
4
5
6
y = 10
pred = 5
lr = 0.5
for i in range(4):
    print(f"段 {i}: pred={pred}, 残差={y-pred}")
    pred = pred + lr * (y - pred)

📤 実行結果

段 0: pred=5, 残差=5 段 1: pred=7.5, 残差=2.5 段 2: pred=8.75, 残差=1.25 段 3: pred=9.375, 残差=0.625

💬 手計算 (Step 1) と Python 出力が完全一致。

🐍 Python 実装

7 つのハンズオン: (1) 47 県を 3 階層分類する基本コード、 (2) early stopping で過学習防止、 (3) 5-fold CV による評価、 (4) GridSearchCV でハイパラ探索、 (5) feature_importances_ と permutation importance、 (6) SHAP 値による個別説明、 (7) 早期停止カーブのプロット用データ取得。 全例で data/raw/SSDSE-B-2026.csv を使用。

📝 より正確な分析(教材補足)
以下のハンズオンは すべて SSDSE-B-2026 に実在する列コードのみで構成している。 使用する主な列は次の通り: A1101 総人口 / A1301 15歳未満人口 / A1303 65歳以上人口 / A4101 出生数 / A4103 合計特殊出生率 / B4101 年平均気温 / L3221 消費支出(二人以上の世帯) / I510120 一般病院数 / I5102 一般診療所数 / J2503 保育所等数。 多変量回帰+SHAP の例では「合計特殊出生率 A4103 を 総人口・65歳以上人口・一般病院数・保育所等数 から予測」する。 なお読み込みは pd.read_csv(..., encoding='cp932', skiprows=[1]) で 2 行目の日本語見出しを除き、 先頭列 SSDSE-B-2026(=年度)で 2023 を抽出する点に注意。

① 基本コード: 47 県を 3 階層分類

🎯 このコードでやること: SSDSE-B-2026 から 47 県の特徴量を取り出し、 人口 3 分位ターゲットで XGBClassifier を学習。 80/20 分割で精度評価。

📥 入力データ: data/raw/SSDSE-B-2026.csv の 2023 年度 47 県。 ターゲットは A1101 (総人口) の 3 分位、 特徴量は下の 8 列。

特徴量 (実在列、 コードの feats): A1301 (15歳未満人口) A4101 (出生数) A4200 (死亡数) A5101 (転入者数(日本人移動者)) A9101 (婚姻件数) E1101 (幼稚園数) C3301 (着工建築物数) F3101 (新規求職申込件数(一般)) ターゲット (3 階層、 pd.qcut): 0 = 小県 (人口 117 万人以下、 16 県) 1 = 中県 (117 万〜198 万人、 15 県) 2 = 大県 (人口 198 万人超、 16 県)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import pandas as pd
import xgboost as xgb
from sklearn.model_selection import train_test_split

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
last = df.query('`SSDSE-B-2026` == `SSDSE-B-2026`.max()').copy()

feats = ['A1301', 'A4101', 'A4200', 'A5101', 'A9101', 'E1101', 'C3301', 'F3101']
X = last[feats]
y = pd.qcut(last['A1101'], q=3, labels=[0, 1, 2]).astype(int)

Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.2, random_state=0, stratify=y)

model = xgb.XGBClassifier(
    n_estimators=200, max_depth=4, learning_rate=0.05,
    objective='multi:softprob', num_class=3, eval_metric='mlogloss',
)
model.fit(Xtr, ytr)
print(f'Train acc: {model.score(Xtr,ytr):.4f}')
print(f'Test  acc: {model.score(Xte,yte):.4f}')

📤 実行結果:

Train acc: 1.0000 Test acc: 0.9000

💬 結果の読み方: 訓練 100% / テスト 90% (テスト 10 県中 9 県正解) で 10pt の汎化ギャップ。 テストが 10 県しかないので 1 県の当たり外れで 10pt 動き、 この差だけで過学習の程度は測れない。 特徴量がどれも人口の言い換えなので、 200 木でも大きくは崩れない。 次のステップ ② で early stopping を導入。

② Early Stopping で過学習防止

🎯 このコードでやること: 検証セットの mlogloss が連続 20 ラウンド改善しなければ学習打ち切り。 最適木数を自動決定する。

📥 入力データ: ①と同じ特徴量とターゲット。 さらに eval_set として検証セットを渡す。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import pandas as pd
import xgboost as xgb
from sklearn.model_selection import train_test_split

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
last = df.query('`SSDSE-B-2026` == `SSDSE-B-2026`.max()').copy()
feats = ['A1301', 'A4101', 'A4200', 'A5101', 'A9101', 'E1101', 'C3301', 'F3101']
X, y = last[feats], pd.qcut(last['A1101'], 3, labels=[0,1,2]).astype(int)
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.3, random_state=0, stratify=y)

model = xgb.XGBClassifier(
    n_estimators=500, max_depth=3, learning_rate=0.05,
    objective='multi:softprob', num_class=3, eval_metric='mlogloss',
    early_stopping_rounds=20,
)
model.fit(Xtr, ytr, eval_set=[(Xtr,ytr), (Xte,yte)], verbose=False)
print(f'Best iter: {model.best_iteration}')
print(f'Train acc: {model.score(Xtr,ytr):.4f}  Test acc: {model.score(Xte,yte):.4f}')

📤 実行結果:

Best iter: 107 Train acc: 1.0000 Test acc: 0.9333

💬 結果の読み方: 検証の mlogloss が最小になったのは best_iteration = 107 (0 始まりの round 番号) で、 そこから 20 ラウンド改善しなかったので 500 本の手前で止まった。 テスト 93.3% は 15 県中 14 県の正解で、 ① とは分割 (test_size=0.3) が違うので精度をそのまま比べることはできない。 なお eval_set の最後 (テストセット) で止め時を決めているので、 このテスト精度はやや楽観的になる。 「ラウンド数を 500 とり、 early_stopping_rounds 20」がベストプラクティス。

③ 5-fold CV で精度の信頼区間取得

🎯 このコードでやること: cross_val_score で 5-fold CV。 47 県を 5 つに分けて、 各 fold で 80% 学習 / 20% 検証。 平均 ± SD で精度を報告。

📥 入力データ: ①と同じ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import pandas as pd
import numpy as np
import xgboost as xgb
from sklearn.model_selection import cross_val_score, StratifiedKFold

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
last = df.query('`SSDSE-B-2026` == `SSDSE-B-2026`.max()').copy()
feats = ['A1301', 'A4101', 'A4200', 'A5101', 'A9101', 'E1101', 'C3301', 'F3101']
X, y = last[feats], pd.qcut(last['A1101'], 3, labels=[0,1,2]).astype(int)

model = xgb.XGBClassifier(n_estimators=100, max_depth=3, learning_rate=0.05,
                          objective='multi:softprob', num_class=3, eval_metric='mlogloss')
cv     = StratifiedKFold(n_splits=5, shuffle=True, random_state=0)
scores = cross_val_score(model, X, y, cv=cv, scoring='accuracy')
print(f'CV accuracy: {scores.mean():.4f} ± {scores.std():.4f}')
print(f'各 fold: {scores.round(3)}')

📤 実行結果:

CV accuracy: 0.9378 ± 0.0812 各 fold: [0.8 1. 1. 0.889 1. ]

💬 結果の読み方: 5 fold 平均 93.8% (±8.1%)。 5 つの fold のうち 3 つが 100%、 残りが 80% (10 県中 8 県) と 88.9% (9 県中 8 県) で、 1 fold が 9〜10 県しかないため 1 県の誤りで 10pt 前後動く。 単発の分割の数字 (90%・93%) より、 「80〜100% の幅がある」と報告するほうが誠実である。

④ GridSearchCV でハイパラ探索

🎯 このコードでやること: max_depth・learning_rate・n_estimators の 3 つを 3-fold CV で網羅探索。 ベスト構成を取得。

📥 入力データ: ①と同じ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import pandas as pd
import xgboost as xgb
from sklearn.model_selection import GridSearchCV

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
last = df.query('`SSDSE-B-2026` == `SSDSE-B-2026`.max()').copy()
feats = ['A1301', 'A4101', 'A4200', 'A5101', 'A9101', 'E1101', 'C3301', 'F3101']
X, y = last[feats], pd.qcut(last['A1101'], 3, labels=[0,1,2]).astype(int)

grid = {
    'n_estimators':  [50, 100, 200],
    'max_depth':     [2, 3, 4, 6],
    'learning_rate': [0.01, 0.05, 0.1],
}
model = xgb.XGBClassifier(objective='multi:softprob', num_class=3, eval_metric='mlogloss', random_state=0)
gs = GridSearchCV(model, grid, cv=3, scoring='accuracy', n_jobs=-1)
gs.fit(X, y)
print(f'Best params: {gs.best_params_}')
print(f'Best score : {gs.best_score_:.4f}')

📤 実行結果:

Best params: {'learning_rate': 0.01, 'max_depth': 2, 'n_estimators': 50} Best score : 0.8931

💬 結果の読み方: 36 通り探索の結果、 最も弱い構成に近い lr=0.01 / depth=2 / 50 木が最良 (3-fold CV で 89.3%)。 47 県の小データでは木を浅く・少なくしても精度が落ちず、 同点に近い構成が多いので、 最良の組み合わせは fold の切り方で入れ替わりやすい。 探索した CV の最高値そのものは楽観的な見積もりなので、 最終報告は別の CV で測り直す。

⑤ feature_importances_ と permutation importance

🎯 このコードでやること: XGBoost 標準の feature_importances_ (gain ベース) と、 scikit-learn の permutation_importance (シャッフルで予測精度低下を測る) を比較。 後者のほうがフェアな指標とされる。

📥 入力データ: ① と同じ 8 列・3 階層ターゲットの 47 県すべて。 モデルは lr=0.1 / depth=3 / 100 木 (④ の探索結果とは別の構成) で、 学習に使った 47 県そのもので重要度を測る。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import pandas as pd
import xgboost as xgb
from sklearn.inspection import permutation_importance

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
last = df.query('`SSDSE-B-2026` == `SSDSE-B-2026`.max()').copy()
feats = ['A1301', 'A4101', 'A4200', 'A5101', 'A9101', 'E1101', 'C3301', 'F3101']
X, y = last[feats], pd.qcut(last['A1101'], 3, labels=[0,1,2]).astype(int)

model = xgb.XGBClassifier(n_estimators=100, max_depth=3, learning_rate=0.1,
                          objective='multi:softprob', num_class=3, eval_metric='mlogloss').fit(X,y)

gain_imp = pd.Series(model.feature_importances_, index=feats).sort_values(ascending=False)
perm     = permutation_importance(model, X, y, n_repeats=10, random_state=0)
perm_imp = pd.Series(perm.importances_mean, index=feats).sort_values(ascending=False)

print('feature_importances_ (gain):')
print(gain_imp.to_string())
print()
print('permutation_importance:')
print(perm_imp.to_string())

📤 実行結果:

feature_importances_ (gain): A1301 0.449619 A4200 0.324509 A4101 0.124745 A5101 0.027090 F3101 0.022144 E1101 0.018841 C3301 0.017570 A9101 0.015482 permutation_importance: A4200 0.455319 A1301 0.229787 A4101 0.004255 A5101 0.000000 A9101 0.000000 E1101 0.000000 C3301 0.000000 F3101 0.000000

💬 結果の読み方: gain では 15 歳未満人口 (A1301) 0.450、 死亡数 (A4200) 0.325、 出生数 (A4101) 0.125 の順だが、 permutation では死亡数 0.455 が 1 位に上がり、 転入者数以下の 5 列は 0 になる。 8 列はどれも総人口と r≥0.96 で相関するので、 木は最初に選んだ数列だけで分け切ってしまい、 残りの列はシャッフルしても予測が変わらない。 「0 = 無関係」ではなく「代わりの列があるので使われなかった」と読む。

⑥ SHAP 値による個別予測の説明

🎯 このコードでやること: SHAP (SHapley Additive exPlanations) で各サンプルの予測根拠を分解。 「東京がなぜ大県と分類されたか」を特徴量寄与で説明する。

📥 入力データ: ⑤のモデル + 全 47 県。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pandas as pd
import numpy as np
import xgboost as xgb
import shap

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
last = df.query('`SSDSE-B-2026` == `SSDSE-B-2026`.max()').reset_index(drop=True)
feats = ['A1301', 'A4101', 'A4200', 'A5101', 'A9101', 'E1101', 'C3301', 'F3101']
X, y = last[feats], pd.qcut(last['A1101'], 3, labels=[0,1,2]).astype(int)

model     = xgb.XGBClassifier(n_estimators=100, max_depth=3, learning_rate=0.1,
                              objective='multi:softprob', num_class=3, eval_metric='mlogloss', random_state=0).fit(X,y)
explainer = shap.TreeExplainer(model)
# shap 0.4x 以降、多クラスの shap_values は list ではなく (サンプル, 特徴量, クラス) の 3 次元配列
sv        = np.asarray(explainer.shap_values(X))
print('sv.shape =', sv.shape)

# skiprows=[1] で読むと列名は英語コードなので、県名の列は 'Prefecture'
tokyo_pos = last.index[last['Prefecture'] == '東京都'][0]
print('東京の クラス 2 (大県) SHAP 寄与:')
for i, f in enumerate(feats):
    print(f'  {f:8s} SHAP={sv[tokyo_pos, i, 2]:+.3f}  値={X.iloc[tokyo_pos, i]:.1f}')

📤 実行結果:

sv.shape = (47, 8, 3) 東京の クラス 2 (大県) SHAP 寄与: A1301 SHAP=+0.000 値=1513000.0 A4101 SHAP=+0.000 値=86348.0 A4200 SHAP=+2.431 値=137241.0 A5101 SHAP=+0.000 値=406749.0 A9101 SHAP=+0.000 値=71774.0 E1101 SHAP=+0.000 値=959.0 C3301 SHAP=+0.000 値=41817.0 F3101 SHAP=+0.000 値=270954.0

💬 結果の読み方: 東京がクラス 2 (大県) と判定された寄与は死亡数 (A4200) の +2.431 だけで、 残り 7 列は 0.000。 クラス 2 の木は死亡数だけで大県を切り分けており、 東京の死亡数 137,241 人は大県の境目をはるかに超えているので、 他の列を見る必要が無い。 sv.shape = (47, 8, 3) は 47 県 × 8 特徴量 × 3 クラスで、 クラスごとに別の寄与が出る。 → SHAP で「県別の推定根拠」が定量化される。

⑦ 学習曲線の取得 (n_estimators sweep)

🎯 このコードでやること: 学習途中の各 round で訓練・検証ロスを取得し、 「いつ過学習が始まるか」を可視化用データとして抽出する。

📥 入力データ: ①と同じ。 evals_result_ から自動取得。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import pandas as pd
import xgboost as xgb
from sklearn.model_selection import train_test_split

df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1])
last = df.query('`SSDSE-B-2026` == `SSDSE-B-2026`.max()').copy()
feats = ['A1301', 'A4101', 'A4200', 'A5101', 'A9101', 'E1101', 'C3301', 'F3101']
X, y = last[feats], pd.qcut(last['A1101'], 3, labels=[0,1,2]).astype(int)
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.3, stratify=y, random_state=0)

model = xgb.XGBClassifier(n_estimators=150, max_depth=3, learning_rate=0.1,
                          objective='multi:softprob', num_class=3, eval_metric='mlogloss')
model.fit(Xtr, ytr, eval_set=[(Xtr,ytr), (Xte,yte)], verbose=False)

hist = model.evals_result_
print(f'最初 5 round:')
for i in range(5):
    print(f'  iter {i:3d}  train={hist["validation_0"]["mlogloss"][i]:.4f}  test={hist["validation_1"]["mlogloss"][i]:.4f}')
print(f'最終 round 150:  train={hist["validation_0"]["mlogloss"][-1]:.4f}  test={hist["validation_1"]["mlogloss"][-1]:.4f}')

📤 実行結果:

最初 5 round: iter 0 train=0.9850 test=0.9982 iter 1 train=0.8873 test=0.9180 iter 2 train=0.8024 test=0.8493 iter 3 train=0.7278 test=0.7823 iter 4 train=0.6619 test=0.7308 最終 round 150: train=0.0506 test=0.2049

💬 結果の読み方: 訓練ロスは 0.985 → 0.051、 検証ロスも 0.998 → 0.205 と、 150 round まで両方とも下がり続けている。 検証ロスの最小は最後の 150 round 目なので、 この設定ではまだ過学習は始まっておらず、 train と test の差 (0.051 と 0.205) が少しずつ開いていく段階にある。 本数をさらに増やして検証ロスが上がり始める round を探し、 そこで early stopping をかける。 学習曲線の可視化は XGBoost 運用の基本デバッグ。

⚠️ よくある落とし穴

🧭 重要度や SHAP 値を「因果効果」と読まない

XGBoost の feature_importances_ や SHAP 値が示すのは、学習したモデルの予測がどの特徴量にどれだけ頼っているかであって、その特徴量を動かしたときに目的変数が実際にどれだけ変わるか(因果効果)ではない。たとえば SSDSE-B の都道府県データでは、人口規模に連動する列(総人口・出生数・死亡数・一般診療所数など)が互いに強く相関しているため、どの列に重要度が集まるかは木の分割の順序や乱数の種でも入れ替わる。重要度が高い列を政策で動かせば結果が変わる、という結論はこの数字からは出せない。

因果を問いたいときは、介入の割り当てが分かるデザイン(ランダム化、差の差、回帰不連続など)や、交絡を明示したモデルが必要になる。予測モデルの説明は「このモデルはこう判断している」という診断として使い、「こうすればこうなる」という主張には使わない、と区別しておくとよい。

この用語を使うときに陥りがちな失敗パターン。 経験者ほどここに 1 度はハマっています。

❌ 過学習しやすい (n_estimators 暴走)
デフォルト 100 木でも小データなら過学習。 必ず eval_set + early_stopping_rounds=10〜50 を併用。 訓練ロスと検証ロスの乖離を学習曲線で監視する。
❌ ハイパーパラメータが多すぎて手動が不可能
学習率・max_depth・L1/L2・subsample・colsample・min_child_weight・gamma の 7 つは要調整。 Optuna・Hyperopt・Ray Tune など自動探索を使う。 手動チューニングは時間と精度の両方を失う。
❌ 解釈性は限定的
「100 本の木で予測する」 → 1 本ずつ見ても分からない。 SHAP・LIME・feature importance を併用しないと「なぜそう予測したか」が説明不能。 規制対象 (金融・医療) では特に注意。
❌ カテゴリ変数の事前エンコーディング忘れ
XGBoost 1.5 以降は enable_categorical=True でネイティブ対応するが、 古い版は LabelEncoder/One-Hot/Target Encoding を要する。 高基数カテゴリ (例: 都市名 1000 種) は target encoding 推奨。
❌ feature_importances_ を盲信
gain ベースは「分割使用回数 × 平均 gain」で、 高基数特徴量に偏る。 公平に評価したいなら permutation_importance または SHAP を使う。 「人口列」と「人口の log」を両方入れたら片方しか出ない問題も。
❌ 訓練データ漏洩 (Data Leakage)
target に直接相関する特徴量 (例: ターゲットが「人口階層」なのに「総人口列」を入れる) があると精度 100%。 「妥当な精度」が出たら逆に疑う。 リーケージは見つけにくいバグの代表。
❌ 不均衡データを scale_pos_weight 抜きで学習
2 値分類で正例が 1% の場合、 default だと全部「負」と予測しても 99% 精度になり学習が進まない。 scale_pos_weight = 負例数 / 正例数 で重み補正、 または class_weight='balanced'。
❌ GPU 学習で OOM (Out of Memory)
tree_method='gpu_hist' はメモリを大量消費。 large depth + 大データだと OOM。 max_bin 削減 (256 → 128) や subsample で対処。

🎛 ハイパーパラメータ早見表 (実務向け)

XGBoost のハイパラ数百種類の中で、 まず押さえるべき 10 個。 デフォルト値と推奨範囲。

パラメータデフォルト推奨範囲役割
n_estimators100100-1000木の本数。 early stopping と組み合わせて自動決定
learning_rate (eta)0.30.01-0.3縮約率。 小さくして本数増やすと精度↑
max_depth63-10木の深さ。 小データなら 3-5、 大データなら 6-10
min_child_weight11-10葉の最小重み和。 大きく ⇒ 過学習防止
gamma00-1分割の最小利益閾値。 大きく ⇒ シンプル木
subsample1.00.7-1.0行サンプリング率。 確率的ブースティングで分散低減
colsample_bytree1.00.6-1.0特徴量サンプリング率。 RF 的多様性
reg_alpha (L1)00-1L1 正則化。 特徴量選択効果
reg_lambda (L2)10-10L2 正則化。 滑らかさ確保
scale_pos_weight1負例数/正例数2 値不均衡データの補正

チューニング手順 (上から順に)

  1. learning_rate=0.1 固定、 max_depth と min_child_weight を grid search で最適化
  2. 続いて gamma を [0, 0.1, 0.2, 0.3] で探索
  3. subsample と colsample_bytree を [0.6, 0.8, 1.0] で探索
  4. reg_alpha・reg_lambda を log-uniform で探索
  5. 最後に learning_rate を下げて (0.01-0.05) n_estimators を増やす

📌 「全部同時に Optuna で 1000 trial 回す」が現代風だが、 上記手順は計算リソース節約と説明可能性の点で今も有効。

📝 理解度チェック

  1. このページの SHAP の例で、 沖縄県の基準値は 1.2928、 寄与は 総人口 +0.130・保育所等数 +0.110・65 歳以上人口 +0.063・一般病院数 +0.003 だった。 予測値はいくつか。
    答え1.2928 + 0.130 + 0.110 + 0.063 + 0.003 = 1.599。 SHAP の寄与は足し算で予測値に戻る(加法性)。 実測 1.60 とほぼ同じなのは、 沖縄県自身を学習に使ったからでもある。
  2. early stopping の例で best_iteration = 39、 検証 RMSE 0.107 だった。 検証に使った 15 県の合計特殊出生率の標準偏差は 0.116。 この結果から何が言えるか。
    答えRMSE が標準偏差とほぼ同じなので、 検証県の平均値で当てるのと大差ない。 木を 2000 本まで伸ばすと訓練 RMSE は 0.0014 まで下がるが検証 RMSE は 0.125 に悪化する。 小さな訓練誤差は予測力の証拠にならない。
  3. 「県民幸福度」の例では、 5 分割 CV の平均 R² が −0.556 なのに、 全 47 県で学習したモデルの予測は元のスコアをほぼなぞった。 どちらを予測力として報告すべきか。
    答えCV の −0.556。 学習に使った県を予測しても木が値を覚えているだけで、 見たことのない県は平均値より悪くしか当てられない。
  4. max_depth を 1・3・6 と深くすると、 1 変数の予測曲線はどう変わるか(演習 Q2)。 自分で確かめる前に予想を書いてみよう。
    答え木 1 本の分割が細かくなり、 予測曲線の段(階段)が細かくなる。 47 県では深い木ほど個々の県に合わせすぎ、 交差検証の成績は下がりやすい。

🗺 概念マップ

関連概念を視覚的に整理した概念マップ。

XGBoost ライブラリ 前提: 勾配ブースティング 並列: LightGBM/CatBoost 発展: SHAP・Optuna 調整 応用: Kaggle 表形式コンペ 対比: Random Forest 統合: sklearn 互換 API

このマップは XGBoost ライブラリの位置づけを表す。 中央に「XGBoost」、 上方に「前提 (勾配ブースティング理論・決定木)」、 並列に「LightGBM / CatBoost (高速代替)」、 発展に「XGBoost + SHAP / Optuna ハイパラ最適化」、 応用に「Kaggle 表形式コンペ・SSDSE 都道府県予測」、 対比に「Random Forest (Bagging 系)」、 統合に「scikit-learn API (XGBClassifier/Regressor)」が伸びる。

XGBoost は 2014 年に Tianqi Chen が公開し、 Kaggle の表形式データ系コンペで圧倒的シェアを獲得。 SSDSE-B-2026 のような表形式かつ標本小 (47 件) のデータでも、 適切な正則化 (early_stopping_rounds, max_depth=3-5) で高い予測精度を出せる。

🔗 隣接手法への橋渡し

XGBoost ライブラリは ML パイプラインの中核モデルとして、 前後の手法と接続する。

SSDSE-B-2026 で「出生数・出生率などの予測」を XGBoost で行う標準フロー: 欠損値処理 → 特徴量作成 → XGBRegressor (early_stopping_rounds=10) → CV → SHAP で重要特徴量解釈。

🌳 手法選択フロー

XGBoost を使うべきかは「データの形」と「予測 vs 解釈」の優先で決まる。

  1. データ形式は? 表形式 (CSV、 pandas DataFrame) → XGBoost が強い、 画像・テキスト → DNN を選ぶ
  2. 標本サイズ N は? N<100 → 線形回帰 / 決定木の方が安定、 100<=N<10万 → XGBoost が最適、 N>100万 → LightGBM で高速化
  3. カテゴリ変数が多いか? Yes (>10 列) → CatBoost が自動処理で便利、 No → XGBoost で十分
  4. 解釈性の要求は? 高 → XGBoost + SHAP で feature 重要度 + 個別予測の説明、 低 → XGBoost 単独で OK
  5. 本番デプロイ環境は? Python → xgboost (sklearn API)、 C++/Java → XGBoost4J、 GPU → tree_method='gpu_hist'

SSDSE-B-2026 (N=47、 表形式、 カテゴリ少) では「N が小さい」が最大の論点。 XGBoost は max_depth=3-4、 n_estimators=50-200、 early_stopping_rounds=10 程度の控えめ設定で過学習を回避する。

🧪 深掘り: 「n=47 実測ショーダウン」で過学習を体感する

姉妹ページ(木のアンサンブル・ランダムフォレスト・決定木)がアルゴリズムの仕組みを扱うのに対し、 ここでは SSDSE-B-2026 の 2023 年・47 都道府県だけを使い、 「小標本で XGBoost が線形回帰に完敗する」瞬間を実測 RMSE で見せる。 数値はすべて下記コードの実出力(捏造なし、 合成データも不使用)。

🧭 直感

XGBoost の本質は「前の木の間違い(残差)を、 次の木が謝りに行く」反復。 1 本目は大外し、 木を足すほど訓練誤差は 0 に吸い込まれる。 これは強みだが、 標本が小さいと「答えを丸暗記した優等生が初見問題で崩れる」現象そのものになる。 訓練誤差 0 はゴールではなく警告灯だと覚えておく。

⚠️ 落とし穴(重要): 訓練 RMSE=0 でもテストは線形回帰の 10 倍

実験設定: 目的変数=出生数(A4101)、 説明変数 7 列=15歳未満人口(A1301)・生産年齢人口(A1302)・65歳以上人口(A1303)・婚姻件数(A9101)・離婚件数(A9201)・転入者数(A5101)・転出者数(A5102)。 train_test_split(test_size=0.3, random_state=42) で 訓練 32 / テスト 15 に分割。 参考: 出生数の平均 15,474 / 標準偏差 16,972。

モデル 訓練 RMSE テスト RMSE ひとこと
線形回帰218.7915.8出生数は人口とほぼ線形 → 圧勝
XGBoost(木500 / 深6 / η0.3)0.09190.3訓練を丸暗記 → テストで完敗
XGBoost 早期停止(深3 / η0.05 / early_stopping_rounds=20)—9172.4best_iteration=749 でも救えず

「木を増やせば直る」も幻想。 木の本数だけ変えた実測(同分割):

n_estimators訓練 RMSEテスト RMSE
110069.020582.9
53828.113966.6
20254.69532.5
1000.09190.3
5000.09190.3

訓練 RMSE は 100 本で 0 に到達する一方、 テスト RMSE は 100 本以降まったく改善せず(9190 で頭打ち)、 線形回帰の 915.8 の約 10 倍で止まる。 過学習は「木を足す」では直らないという決定的証拠。

🚀 発展

🔗 関連ページ