論文一覧に戻る 📚 用語集トップ 🗺 概念マップ
📚 用語解説(ジャストインタイム型データサイエンス教育)
決定木・アンサンブル学習
Decision Trees & Ensemble Learning
弱い学習器を組み合わせて強い学習器を作る — テーブルデータ最強の伝統
教師あり学習分類・回帰両用テーブルデータ向け解釈性中

📍 あなたが今見ているもの

本ページは、 決定木を出発点として、 バギング・ランダムフォレストブースティング(AdaBoost / 勾配ブースティング / XGBoost / LightGBM / CatBoost)スタッキングまでを一気通貫で解説する統合ページです。

これらは「弱い学習器を多数組み合わせて強い学習器を作る」というアンサンブル学習の発想に基づきます。 SSDSE-B のような中規模テーブルデータでは、 深層学習よりこれらが第一選択となることが多いです。

🔖 🔖 キーワード索引(チップから該当箇所へジャンプ)

論文記事から各用語のリンクをクリックすると、 該当箇所が開きます:

決定木 ジニ不純度 情報エントロピー 情報利得 剪定 アンサンブル学習 バギング ランダムフォレスト 特徴量重要度 OOB誤差 ブースティング AdaBoost 勾配ブースティング XGBoost LightGBM CatBoost スタッキング SHAP

💡 30秒で分かる結論

📚 このページの章構成

内容 主要手法
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

🌲 1. 決定木 (Decision Tree)

1.1 直観

「もし所得が 500万円以上なら → 持ち家率高い、 そうでなければ次の質問へ…」のように、 yes/no で枝分かれする木構造で予測する。 ノード(条件)から葉(予測値)まで辿れば判断根拠が明確で、 業務でそのまま使える説明力がある。

1.2 分割の評価指標

あるノード $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$ で分割したときの情報利得。 大きいほど良い分岐。

1.3 実値で計算

10サンプル(正例 6・負例 4)のノードを「年齢 < 30」で分けたら、 左ノード(5件中正例 4・負例 1)、 右ノード(5件中正例 2・負例 3)になったとする。

1.4 剪定 (Pruning)

木を深く育てるほど訓練データに過学習する。 事前剪定(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()

🎯 2. アンサンブル学習の発想

1本の弱い木より「ばらつきの違う多数の木の多数決」の方が、 ノイズに強く正確になる、 という洞察。

主な戦略

方式 並列 / 直列 下げるのは 代表手法
バギング並列主に分散RF, ExtraTrees
ブースティング直列主にバイアスAdaBoost, GBM, XGBoost
スタッキング階層バイアス/分散の両方StackingClassifier

👜 3. バギング (Bagging = Bootstrap AGGregatING)

訓練データから復元抽出で多数のサブセットを作り、 それぞれで弱学習器を訓練し、 予測を平均(回帰)または多数決(分類)する。

3.1 数式

$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$ より分散が小さい。

3.2 OOB誤差 (Out-of-Bag Error)

復元抽出で取られなかった約 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_)

🌳 4. Random Forest

バギング + 各分岐で特徴量もランダムサンプリング。 木同士の相関が下がり、 平均化効果がさらに高まる。

4.1 主要ハイパーパラメータ

4.2 特徴量重要度

分岐に使った特徴量が平均でどれだけ不純度を下げたかで重要度を算出(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 バイアス

カテゴリ数の多い・連続変数の特徴量ほど重要度が大きく出る傾向(MDI バイアス)。 より信頼性の高い順列重要度(Permutation Importance)SHAP を併用するのが実務標準。

🚀 5. ブースティング (Boosting)

弱学習器を直列に並べ、 前の学習器の誤りを次が補正するように学習を進める。

5.1 AdaBoost (Adaptive Boosting, 1995)

誤分類したサンプルの重みを大きくし、 次の弱学習器がそれを優先的に学ぶ。

$$\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$ が小さい学習器ほど大きな影響を持つ。

5.2 勾配ブースティング (Gradient Boosting Machine)

損失関数 $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))

⚡ 6. XGBoost / LightGBM / CatBoost

6.1 XGBoost (eXtreme Gradient Boosting, 2014)

6.2 LightGBM (2017)

6.3 CatBoost (2018)

 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))

6.4 三者比較

項目 XGBoost LightGBM CatBoost
速度速い最速やや遅
カテゴリ事前エンコード必要対応最強対応
既定値の精度
過学習耐性過学習しやすい強い
エコシステム豊富豊富

🥞 7. スタッキング (Stacking)

複数の異質モデル(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))

🔍 8. SHAP — モデルを説明する標準ツール

ツリー系モデルの予測寄与をサンプル単位で分解する。 「なぜこの個体は『高い』と予測されたのか」が分かる。

1
2
3
4
import shap
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_test)
shap.summary_plot(shap_values, X_test)

📊 9. 手法横断比較

手法 精度 速度 解釈性 過学習 推奨用途
決定木低〜中しやすい説明用途・ルール抽出
Random Forest中〜高強い汎用ベースライン
XGBoost / LightGBM中〜高CV必須本番モデル(テーブル)
CatBoost既定値で強いカテゴリ多めデータ
Stacking最高CV慎重に最後の伸ばし

⚠️ 10. よくある落とし穴

落とし穴 対処
1本の木を信用しすぎるアンサンブルで分散低減。 訓練 data の僅かな違いで木は大きく変わる。
特徴量重要度を絶対視MDI バイアス。 Permutation Importance / SHAP を併用。
外挿(範囲外予測)ツリーは外挿できない。 訓練データの範囲外は予測しないかドメイン知識で補正。
ブースティングの早期停止忘れearly_stopping_rounds を必ず設定。 過学習防止と高速化に必須。
時系列データに通常CVTimeSeriesSplit を使用。 リークを避ける。
クラス不均衡scale_pos_weight / class_weight / is_unbalance を設定。
前処理過剰ツリー系はスケーリング不要。 単調変換でも結果は不変。

⚠️ 詳説:落とし穴 10 連発(各 100 文字以上の詳細解説)

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 上は超高性能なのに本番運用で性能が出ない現象が頻発します。 TimeSeriesSplitGroupKFold(被験者・地域 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_ratenum_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 回の平均±標準偏差を併記するのが推奨です。

🐍 Python 実装バリエーション(scikit-learn / xgboost / lightgbm / catboost)

同じ「持ち家比率の二値分類」を 4 つのライブラリで実装する例。 API の違いと共通点が分かります。

A. scikit-learn の HistGradientBoosting(依存なし・最速ベースライン)

 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}')
Q2. Random Forest で特徴量重要度を出し、 さらに Permutation Importance と比較しなさい。
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)
Q3. LightGBM で early stopping を使い、 訓練・検証損失曲線をプロットしなさい。
 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()

📝 12. 報告フォーマット

❌ NG例

「Random Forest を使った結果、 accuracy 0.85 でした。」

✅ OK例

「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 値による個別解釈でも同じ傾向を確認した。」

🐍 13. ライブラリ早見表

手法 主要パッケージ・クラス 主要引数
決定木sklearn.tree.DecisionTree{Classifier,Regressor}criterion, max_depth, min_samples_leaf, ccp_alpha
Random Forestsklearn.ensemble.RandomForest{Classifier,Regressor}n_estimators, max_features, oob_score, class_weight
Extra Treessklearn.ensemble.ExtraTrees{Classifier,Regressor}RFと同じ + 分岐閾値ランダム
AdaBoostsklearn.ensemble.AdaBoostClassifiern_estimators, learning_rate, algorithm
GBMsklearn.ensemble.GradientBoosting{Classifier,Regressor}n_estimators, learning_rate, max_depth, subsample
HistGBMsklearn.ensemble.HistGradientBoosting{Classifier,Regressor}LightGBM相当の高速実装
XGBoostxgboost.XGB{Classifier,Regressor}n_estimators, eta, max_depth, lambda, alpha, gamma
LightGBMlightgbm.LGBM{Classifier,Regressor}n_estimators, learning_rate, num_leaves, feature_fraction
CatBoostcatboost.CatBoost{Classifier,Regressor}iterations, learning_rate, depth, cat_features
Stackingsklearn.ensemble.StackingClassifierestimators, final_estimator, cv, passthrough
SHAPshap.TreeExplainerツリー系専用の高速 SHAP

📜 14. ツリー系・アンサンブル法の歴史

💼 15. 実務応用例

⚠️ 重要な注意点(誤解を避ける)

「特徴量重要度」「SHAP」は予測への寄与であり、 因果効果ではない。