本ページは、 決定木を出発点として、 バギング・ランダムフォレスト・ブースティング(AdaBoost / 勾配ブースティング / XGBoost / LightGBM / CatBoost)・スタッキングまでを一気通貫で解説する統合ページです。
これらは「弱い学習器を多数組み合わせて強い学習器を作る」というアンサンブル学習の発想に基づきます。 SSDSE-B のような中規模テーブルデータでは、 深層学習よりこれらが第一選択となることが多いです。
論文記事から各用語のリンクをクリックすると、 該当箇所が開きます:
| 章 | 内容 | 主要手法 |
|---|---|---|
| 1. 決定木 | アンサンブルの土台。 分岐基準・剪定。 | CART / ID3 / C4.5 |
| 2. バギング | 並列アンサンブル。 分散を下げる。 | Bagging, Random Forest, ExtraTrees |
| 3. Random Forest | バギング+特徴量サンプリング。 万能ベースライン。 | RandomForestClassifier/Regressor |
| 4. ブースティング | 直列アンサンブル。 バイアスを下げる。 | AdaBoost, Gradient Boosting |
| 5. XGBoost等 | 勾配ブースティング高速実装。 テーブル王者。 | XGBoost / LightGBM / CatBoost |
| 6. スタッキング | 複数モデル予測をメタモデルで統合 | StackingClassifier |
「もし所得が 500万円以上なら → 持ち家率高い、 そうでなければ次の質問へ…」のように、 yes/no で枝分かれする木構造で予測する。 ノード(条件)から葉(予測値)まで辿れば判断根拠が明確で、 業務でそのまま使える説明力がある。
あるノード $t$ の不純度を最小化するように分岐を作る。
$$\mathrm{Gini}(t) \;=\; 1 - \sum_{k=1}^{K} p_k^2$$
記号読み:$p_k$ は「ピー・サブ・ケー」、 ノード $t$ におけるクラス $k$ の割合。 すべてのサンプルが同一クラスなら $\mathrm{Gini}=0$。
$$H(t) \;=\; -\sum_{k=1}^{K} p_k \log_2 p_k$$
記号読み:$H$ は「エイチ」、 確率分布の不確実性。 等確率に近いほど大きく、 偏るほど小さい。
分割で減ったエントロピー:
$$\mathrm{IG}(t, a) \;=\; H(t) - \sum_{v} \frac{|t_v|}{|t|} H(t_v)$$
記号読み:$\mathrm{IG}$ は「アイ・ジー」、 属性 $a$ で分割したときの情報利得。 大きいほど良い分岐。
10サンプル(正例 6・負例 4)のノードを「年齢 < 30」で分けたら、 左ノード(5件中正例 4・負例 1)、 右ノード(5件中正例 2・負例 3)になったとする。
木を深く育てるほど訓練データに過学習する。 事前剪定(max_depth、 min_samples_leaf 等の制約)と事後剪定(Cost-Complexity Pruning, ccp_alpha)の 2 系統がある。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import pandas as pd from sklearn.tree import DecisionTreeClassifier, plot_tree import matplotlib.pyplot as plt df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1) df['持家率高い'] = (df['持ち家比率'] >= df['持ち家比率'].median()).astype(int) X = df[['一人当たり県民所得', '世帯人員', '高齢化率']] y = df['持家率高い'] clf = DecisionTreeClassifier(max_depth=3, criterion='gini', random_state=42) clf.fit(X, y) plt.figure(figsize=(14, 6)) plot_tree(clf, feature_names=X.columns, class_names=['低', '高'], filled=True, rounded=True) plt.show() |
1本の弱い木より「ばらつきの違う多数の木の多数決」の方が、 ノイズに強く正確になる、 という洞察。
| 方式 | 並列 / 直列 | 下げるのは | 代表手法 |
|---|---|---|---|
| バギング | 並列 | 主に分散 | RF, ExtraTrees |
| ブースティング | 直列 | 主にバイアス | AdaBoost, GBM, XGBoost |
| スタッキング | 階層 | バイアス/分散の両方 | StackingClassifier |
訓練データから復元抽出で多数のサブセットを作り、 それぞれで弱学習器を訓練し、 予測を平均(回帰)または多数決(分類)する。
$M$ 個のブートストラップサンプル $D_1, \dots, D_M$ から得た予測器 $\hat{f}_m$ で:
$$\hat{f}_{\text{bag}}(x) \;=\; \frac{1}{M}\sum_{m=1}^{M}\hat{f}_m(x)$$
記号読み:$\hat{f}_{\text{bag}}$ は「エフ・バッグのハット」、 バギング全体の予測。 個々の $\hat{f}_m$ より分散が小さい。
復元抽出で取られなかった約 37% のサンプルを検証データとして使える。 別途 CV しなくても汎化誤差を推定できる。
1 2 3 4 | from sklearn.ensemble import RandomForestClassifier rf = RandomForestClassifier(n_estimators=300, oob_score=True, random_state=42) rf.fit(X_train, y_train) print('OOB Accuracy:', rf.oob_score_) |
バギング + 各分岐で特徴量もランダムサンプリング。 木同士の相関が下がり、 平均化効果がさらに高まる。
n_estimators:木の本数。 多いほど安定(100〜1000)max_depth:木の深さ上限(過学習防止)max_features:各分岐で使う特徴量数。 分類は $\sqrt{p}$、 回帰は $p/3$ が標準min_samples_leaf:葉ノード最小サンプル数(過学習防止)class_weight='balanced':不均衡クラス対応分岐に使った特徴量が平均でどれだけ不純度を下げたかで重要度を算出(mean decrease in impurity)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import pandas as pd import matplotlib.pyplot as plt from sklearn.ensemble import RandomForestRegressor df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1) X = df[['一人当たり県民所得', '世帯人員', '高齢化率', '人口密度', '就業率']] y = df['持ち家比率'] rf = RandomForestRegressor(n_estimators=500, max_depth=8, random_state=42, n_jobs=-1) rf.fit(X, y) importance = pd.Series(rf.feature_importances_, index=X.columns).sort_values() importance.plot(kind='barh', color='#43A047') plt.title('特徴量重要度(RF)') plt.xlabel('Importance') plt.tight_layout() plt.show() |
カテゴリ数の多い・連続変数の特徴量ほど重要度が大きく出る傾向(MDI バイアス)。 より信頼性の高い順列重要度(Permutation Importance)や SHAP を併用するのが実務標準。
弱学習器を直列に並べ、 前の学習器の誤りを次が補正するように学習を進める。
誤分類したサンプルの重みを大きくし、 次の弱学習器がそれを優先的に学ぶ。
$$\alpha_m = \frac{1}{2}\log\frac{1-\epsilon_m}{\epsilon_m}, \quad w_i \leftarrow w_i \exp(\alpha_m \cdot \mathbb{1}[y_i \ne \hat{y}_i])$$
記号読み:$\alpha_m$ は「アルファ・サブ・エム」、 第 $m$ 弱学習器の重み。 誤分類率 $\epsilon_m$ が小さい学習器ほど大きな影響を持つ。
損失関数 $L$ の負の勾配(残差に相当)に新しい木をフィットさせていく。
$$F_m(x) = F_{m-1}(x) + \nu \cdot h_m(x), \quad h_m \approx -\left.\frac{\partial L(y, F)}{\partial F}\right|_{F=F_{m-1}}$$
記号読み:$F_m$ は「エフ・サブ・エム」、 $m$ 段目までの合計予測。 $\nu$ は「ニュー」と読み、 学習率(0.01〜0.1)。 $h_m$ は新たに加える弱木。
1 2 3 4 5 6 | from sklearn.ensemble import GradientBoostingClassifier gbm = GradientBoostingClassifier( n_estimators=500, learning_rate=0.05, max_depth=3, random_state=42 ) gbm.fit(X_train, y_train) print('Test acc:', gbm.score(X_test, y_test)) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import lightgbm as lgb from sklearn.metrics import accuracy_score train = lgb.Dataset(X_train, label=y_train) val = lgb.Dataset(X_val, label=y_val, reference=train) params = dict( objective='binary', learning_rate=0.05, num_leaves=31, feature_fraction=0.9, bagging_fraction=0.8, bagging_freq=5, verbose=-1, ) model = lgb.train(params, train, num_boost_round=1000, valid_sets=[val], callbacks=[lgb.early_stopping(50)]) pred = (model.predict(X_test) >= 0.5).astype(int) print('LGB Accuracy:', accuracy_score(y_test, pred)) |
| 項目 | XGBoost | LightGBM | CatBoost |
|---|---|---|---|
| 速度 | 速い | 最速 | やや遅 |
| カテゴリ | 事前エンコード必要 | 対応 | 最強対応 |
| 既定値の精度 | 中 | 中 | 高 |
| 過学習耐性 | 中 | 過学習しやすい | 強い |
| エコシステム | 豊富 | 豊富 | 中 |
複数の異質モデル(RF・LGB・ロジスティック等)の予測を、 さらに別のメタモデル(ロジスティック等)が統合する 2 段構成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | from sklearn.ensemble import StackingClassifier, RandomForestClassifier from sklearn.linear_model import LogisticRegression from sklearn.svm import SVC base = [ ('rf', RandomForestClassifier(n_estimators=300, random_state=42)), ('svm', SVC(probability=True, random_state=42)), ] stack = StackingClassifier( estimators=base, final_estimator=LogisticRegression(max_iter=1000), cv=5, n_jobs=-1, ) stack.fit(X_train, y_train) print(stack.score(X_test, y_test)) |
ツリー系モデルの予測寄与をサンプル単位で分解する。 「なぜこの個体は『高い』と予測されたのか」が分かる。
1 2 3 4 | import shap explainer = shap.TreeExplainer(model) shap_values = explainer.shap_values(X_test) shap.summary_plot(shap_values, X_test) |
| 手法 | 精度 | 速度 | 解釈性 | 過学習 | 推奨用途 |
|---|---|---|---|---|---|
| 決定木 | 低〜中 | 速 | 高 | しやすい | 説明用途・ルール抽出 |
| Random Forest | 中〜高 | 中 | 中 | 強い | 汎用ベースライン |
| XGBoost / LightGBM | 高 | 中〜高 | 中 | CV必須 | 本番モデル(テーブル) |
| CatBoost | 高 | 中 | 中 | 既定値で強い | カテゴリ多めデータ |
| Stacking | 最高 | 遅 | 低 | CV慎重に | 最後の伸ばし |
| 落とし穴 | 対処 |
|---|---|
| 1本の木を信用しすぎる | アンサンブルで分散低減。 訓練 data の僅かな違いで木は大きく変わる。 |
| 特徴量重要度を絶対視 | MDI バイアス。 Permutation Importance / SHAP を併用。 |
| 外挿(範囲外予測) | ツリーは外挿できない。 訓練データの範囲外は予測しないかドメイン知識で補正。 |
| ブースティングの早期停止忘れ | early_stopping_rounds を必ず設定。 過学習防止と高速化に必須。 |
| 時系列データに通常CV | TimeSeriesSplit を使用。 リークを避ける。 |
| クラス不均衡 | scale_pos_weight / class_weight / is_unbalance を設定。 |
| 前処理過剰 | ツリー系はスケーリング不要。 単調変換でも結果は不変。 |
1. 「1本の決定木の解釈」をそのまま現実のロジックだと信じてしまう。決定木は訓練データの微小な揺らぎ(行の1〜2件入れ替え)で分岐構造が大幅に変わる「高分散モデル」です。 同じ問題でランダム種を変えると、 ルート分岐の特徴量自体が入れ替わることも珍しくありません。 解釈用には Random Forest / GBM の SHAP・Permutation Importance で頑健性を確認してから語るのが安全です。
2. feature_importances_ (MDIベース)を変数間で単純比較する。MDI は「分岐に何回使われたか」を反映するため、 カテゴリ水準数や連続値の細かさが大きい変数ほど膨張する既知バイアスを持ちます。 ID 列やゼロ膨張ダミーを混ぜると、 ノイズが上位に来ます。 Permutation Importance(テストセット)や SHAP の平均絶対値で再検証する習慣を持ちましょう。
3. 訓練データの範囲外を予測させる(外挿)。決定木・Random Forest・GBM は本質的に区分定数関数なので、 訓練データに無かった領域(極端な最小値より小さい、 最大値より大きい)では、 直近の葉ノードの定数を返すだけで、 トレンドを延長しません。 時系列の右端予測や、 都道府県別人口の外れ値領域では、 線形モデルや指数族の併用が安全です。
4. early_stopping_rounds を設定し忘れる。LightGBM / XGBoost / CatBoost は n_estimators を大きく取りつつ early stopping で実際の木数を決めるのが標準運用です。 これを怠ると、 巨大な学習率と小さな n_estimators で「学習し切れない」か、 逆に「過学習」のどちらかに必ず転びます。 検証セットを明示的に渡し、 best_iteration を尊重しましょう。
5. 時系列なのに通常の KFold でランダム分割する。SSDSE-A のような時間的構造を持つデータでは、 未来→過去のリークが発生し、 CV 上は超高性能なのに本番運用で性能が出ない現象が頻発します。 TimeSeriesSplit、 GroupKFold(被験者・地域 ID 単位)、 Walk-Forward Validation のいずれかを必ず使ってください。
6. クラス不均衡を無視して accuracy を最適化する。陽性 1% のデータで全件を陰性と予測すると accuracy 99% に達するため、 ツリー系でも勾配計算は陰性に偏ります。 class_weight='balanced'(sklearn)、 scale_pos_weight(XGB)、 is_unbalance=True(LGB)を使うか、 PR-AUC・F1・Cohen κ などの不均衡耐性指標で評価しましょう。
7. ハイパーパラメータをグリッドで全数探索する。木の本数 × 学習率 × 深さ × サブサンプル × 正則化 …と組合せると数万通りに爆発します。 Optuna / Hyperopt のベイズ最適化、 あるいは Random Search 100 回で十分到達可能なケースが多いです。 まず learning_rate と num_leaves/max_depth から探索する優先順位戦略が定石です。
8. データリーク(target leakage)に気付かない。「将来のアウトカム」が特徴量に紛れ込むと、 ツリーは即座にそれを最重要変数として採用し、 CV 性能が異常に高くなります。 完了日・支払日・退院日など「結果と同時に決まる」列、 ID 由来のハッシュ、 後処理で生成された集計値などは慎重に除外しましょう。
9. SHAP の解釈を「介入効果」と混同する。SHAP は「現在のモデルがその予測値に到達するまでにどの特徴量がどれだけ寄与したか」のシャープレイ分解であり、 「この特徴量を変えたら結果はこう変わる」という因果ではありません。 因果が知りたい場合は DiD / IV / RCT などのデザイン的アプローチを併用しましょう。
10. random_state を固定せずに結果を比較する。ブートストラップ・特徴量サブサンプル・初期分岐の揺らぎで、 同じデータでも accuracy が 0.01〜0.03 ぶれます。 モデル間比較や論文用の数値報告では random_state=42 等を固定し、 さらに乱数を変えた 5〜10 回の平均±標準偏差を併記するのが推奨です。
同じ「持ち家比率の二値分類」を 4 つのライブラリで実装する例。 API の違いと共通点が分かります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import pandas as pd from sklearn.model_selection import cross_val_score from sklearn.tree import DecisionTreeClassifier from sklearn.ensemble import RandomForestClassifier import lightgbm as lgb df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1) df['y'] = (df['持ち家比率'] >= df['持ち家比率'].median()).astype(int) X = df[['一人当たり県民所得', '世帯人員', '高齢化率', '人口密度']] y = df['y'] for name, model in [ ('Tree', DecisionTreeClassifier(max_depth=5, random_state=42)), ('RF', RandomForestClassifier(n_estimators=500, random_state=42)), ('LGB', lgb.LGBMClassifier(n_estimators=500, learning_rate=0.05, verbose=-1)), ]: sc = cross_val_score(model, X, y, cv=5, scoring='accuracy') print(f'{name}: {sc.mean():.3f} ± {sc.std():.3f}') |
1 2 3 4 5 6 7 8 9 | from sklearn.inspection import permutation_importance rf.fit(X, y) perm = permutation_importance(rf, X, y, n_repeats=30, random_state=42, n_jobs=-1) import pandas as pd result = pd.DataFrame({ 'MDI': rf.feature_importances_, 'Perm': perm.importances_mean, }, index=X.columns).sort_values('Perm', ascending=False) print(result) |
1 2 3 4 5 6 7 8 9 10 11 | import lightgbm as lgb import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split X_tr, X_va, y_tr, y_va = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y) model = lgb.LGBMClassifier(n_estimators=1000, learning_rate=0.05, verbose=-1) model.fit(X_tr, y_tr, eval_set=[(X_tr, y_tr), (X_va, y_va)], eval_metric='binary_logloss', callbacks=[lgb.early_stopping(50), lgb.log_evaluation(0)]) lgb.plot_metric(model) plt.show() |
「Random Forest を使った結果、 accuracy 0.85 でした。」
「LightGBM(n_estimators=500, learning_rate=0.05, early_stopping_rounds=50)で 5-fold Stratified CV を行い、 macro-F1 = 0.78 ± 0.03 を得た。 ベースラインのロジスティック回帰(0.71)に対し +0.07。 特徴量重要度(Permutation)では『一人当たり県民所得』が最も寄与し、 SHAP 値による個別解釈でも同じ傾向を確認した。」
| 手法 | 主要パッケージ・クラス | 主要引数 |
|---|---|---|
| 決定木 | sklearn.tree.DecisionTree{Classifier,Regressor} | criterion, max_depth, min_samples_leaf, ccp_alpha |
| Random Forest | sklearn.ensemble.RandomForest{Classifier,Regressor} | n_estimators, max_features, oob_score, class_weight |
| Extra Trees | sklearn.ensemble.ExtraTrees{Classifier,Regressor} | RFと同じ + 分岐閾値ランダム |
| AdaBoost | sklearn.ensemble.AdaBoostClassifier | n_estimators, learning_rate, algorithm |
| GBM | sklearn.ensemble.GradientBoosting{Classifier,Regressor} | n_estimators, learning_rate, max_depth, subsample |
| HistGBM | sklearn.ensemble.HistGradientBoosting{Classifier,Regressor} | LightGBM相当の高速実装 |
| XGBoost | xgboost.XGB{Classifier,Regressor} | n_estimators, eta, max_depth, lambda, alpha, gamma |
| LightGBM | lightgbm.LGBM{Classifier,Regressor} | n_estimators, learning_rate, num_leaves, feature_fraction |
| CatBoost | catboost.CatBoost{Classifier,Regressor} | iterations, learning_rate, depth, cat_features |
| Stacking | sklearn.ensemble.StackingClassifier | estimators, final_estimator, cv, passthrough |
| SHAP | shap.TreeExplainer | ツリー系専用の高速 SHAP |
「特徴量重要度」「SHAP」は予測への寄与であり、 因果効果ではない。
feature_importances_、 SHAP 値は「モデルがその特徴量をどれくらい予測に使ったか」を示すだけです。