- AI ⚙️
アンサンブル学習とは|バギング・ブースティング・スタッキングを同じデータで比較
結論
アンサンブル学習とは、複数のモデルの予測を組み合わせて、1つのモデルより当たりやすく、ぶれにくい予測を出す機械学習の方法の総称です。組み合わせ方は、投票(多数決・確率の平均)、バギング(同じモデルを並列に作って平均)、ブースティング(前のモデルの誤りを次のモデルが直す)、スタッキング(予測を入力にして別のモデルで束ねる)の4つが基本です。手元のワインのデータでは、決定木1本の正解率 0.893 が、スタッキングで 0.983 に上がりました。
この記事でわかること
- アンサンブル学習の仕組みと、なぜ混ぜると強くなるのか(多数決の計算と、モデルどうしの誤りの相関の実測)
- バギング・ブースティング・スタッキングの違いを、AdaBoost と勾配ブースティングの numpy 自作(scikit-learn と誤差0で一致)と、リークしないスタッキングの作り方で
- 投票・バギング・ブースティング・スタッキングを同じデータで比べた精度・学習時間・予測時間・サイズの表と、どれを選ぶかの判断表
この記事で実際に動かしたもの:scikit-learn の VotingClassifier・BaggingClassifier・RandomForestClassifier・AdaBoostClassifier・GradientBoostingClassifier/Regressor・HistGradientBoostingClassifier・StackingClassifier と、numpy で自作した多数決の確率計算・ソフト投票・AdaBoost(SAMME)・勾配ブースティング・スタッキング。データは scikit-learn 同梱の load_wine・load_breast_cancer と、make_classification・自作の架空データだけです。XGBoost・LightGBM はこのマシンに入っていないため動かしていません。
flowchart LR
D[データ] --> V[投票<br>多数決・平均]
D --> B[バギング<br>並列に平均]
D --> O[ブースティング<br>誤りを直列に直す]
D --> S[スタッキング<br>メタモデル]
V --> P[予測]
B --> P
O --> P
S --> P
よく調べられることの答えを先にまとめます。
| 知りたいこと | 答え | 詳しい節 |
|---|---|---|
| アンサンブル学習とは | 複数のモデルの予測を組み合わせて1つの予測を出す方法の総称。投票・バギング・ブースティング・スタッキングの4つが基本 | アンサンブル学習とは |
| なぜ精度が上がるのか | モデルどうしが別々のところで間違えると、多数決や平均で誤りが打ち消し合う。誤りが似ていると効果は小さい | なぜ混ぜると強くなるのか |
| バギング ブースティング 違い | バギングは並列に作って平均し、主にばらつき(バリアンス)を減らす。ブースティングは直列に誤りを直し、主に外れ方の偏り(バイアス)を減らす | バイアスとバリアンス |
| ハード投票とソフト投票 | ハードはクラスの多数決、ソフトは予測確率の平均。確率が極端なモデルが混ざるとソフトが負けることがある | 投票(ハード・ソフト) |
| スタッキングとは | 各モデルの予測を特徴量にして、別のモデル(メタモデル)が最終の予測を出す。メタモデルは交差検証で作った予測で学習させる | スタッキングとは |
| AdaBoost と勾配ブースティング | AdaBoost は間違えたデータの重みを上げる。勾配ブースティングは残り(残差・勾配)に次の木を当てはめる | AdaBoost を自作/勾配ブースティング |
| アンサンブル学習 python | scikit-learn の sklearn.ensemble に全部そろう。まずは HistGradientBoostingClassifier と、強いモデル数個の StackingClassifier | 最初の5分 |
| どれを使えばよいか | 表のデータで精度を詰めるならブースティング、手早く安定した基準ならランダムフォレスト、性質の違う強いモデルが複数あるならスタッキング | どれを使うかの判断表 |
確認環境:Ubuntu 24.04.5 LTS(CPU 2コア・メモリ8GB、ほかの作業と同時に動いている共有のマシン)で、Python 3.13.18、scikit-learn 1.9.1、numpy 2.5.3(OpenBLAS 0.3.34、OPENBLAS_NUM_THREADS=1)、scipy 1.18.1 を使い、すべて n_jobs=1 で実行しました。データは scikit-learn 同梱のデータセットと乱数で作った架空のデータで、ネットからの取得はしていません。時間は共有のマシンで1回測った値のため、同じ処理でも2〜3倍ぶれることがあり、順位を見る目安です。警告・エラーの文言は手元の scikit-learn 1.9.1 で出したもので、仕組みは公式ドキュメント(scikit-learn 1.9.1 の User Guide「Ensembles」と各クラスの API リファレンス)、scikit-learn 1.9.1 のソースコード(sklearn/ensemble/_weight_boosting.py・_gb.py・_stacking.py)、各手法の原論文で確かめました。XGBoost・LightGBM は動かしておらず、勾配ブースティングの比較には scikit-learn の HistGradientBoostingClassifier を使いました。図は mermaid で描画を確かめています(確認日 2026年11月5日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「アンサンブル学習とは|バギング・ブースティング・スタッキングを同じデータで比較」
アンサンブル学習とは(4つの組み合わせ方と言葉の意味)
この節では、アンサンブル学習が何をするのかと、4つの組み合わせ方の違いが分かります。
アンサンブル学習(ensemble learning)は、複数のモデル(学習器)を組み合わせて1つの予測を出す方法の総称です。1つ1つのモデルは「弱学習器」「ベースモデル」と呼ばれます。「三人寄れば文殊の知恵」とよく例えられますが、3人が同じ思い込みをしていれば答えは変わりません。アンサンブル学習で効くのは、違うところで間違えるモデルを集めることです。この点はあとの節で数字で確かめます。
言葉の意味が取りにくいので、ふだんの意味と機械学習での意味を並べます。
| 言葉 | ふだんの意味 | 機械学習での意味 |
|---|---|---|
| アンサンブル | 合奏・衣装のひとそろい | 複数のモデルを組み合わせた1つのモデル |
| 投票(voting) | 選挙の投票 | 各モデルの予測クラスの多数決(ハード)か、予測確率の平均(ソフト) |
| バギング(bagging) | 袋に詰めること、と連想しやすい | bootstrap aggregating の略。復元抽出したデータで同じ種類のモデルを並列に作り、平均する(Breiman 1996) |
| ブースティング(boosting) | 押し上げること | 弱いモデルを1つずつ足し、前のモデルの誤りを次のモデルが直していく(AdaBoost は Freund・Schapire 1997) |
| スタッキング(stacking) | 積み重ねること | 各モデルの予測を入力にして、上に置いた別のモデル(メタモデル)が最終の予測を出す(Wolpert 1992 の stacked generalization) |
1投票・平均
種類の違うモデル(ロジスティック回帰・k近傍法・SVM など)を別々に学習し、多数決か確率の平均をとる。いちばん簡単で、重みを付けることもできる。
2バギング
同じ種類のモデルを、復元抽出で作った別々のデータで並列に学習して平均する。深い決定木と相性がよく、ランダムフォレストはこの発展形。
3ブースティング
浅い木を1本ずつ足し、前までの誤りを次の木が直す。直列なので1本ずつしか作れないが、表のデータでは最も精度が出やすい。
4スタッキング
各モデルの予測を特徴量にして、メタモデルが「どのモデルをどれだけ信じるか」を学ぶ。メタモデルの学習には交差検証の予測を使う。
scikit-learn の公式 User Guide は、これらを「平均の方法(バギング・スタッキング・投票)」と「ブースティング」の2つに分けて説明しています。バギングの節には「バギングは強く複雑なモデル(十分に育てた決定木など)で最もよく働き、ブースティングはふつう弱いモデル(浅い決定木など)で最もよく働く」とあります。この違いは、あとの「バイアスとバリアンス」の節で実測します。
近年の大規模言語モデルの話題で出てくる「モデルマージ」「MoE(Mixture of Experts)」「知識蒸留」も複数のモデルを使いますが、アンサンブル学習とは組み合わせる場所が違います。
| 方法 | 何を組み合わせるか | 予測のときに動くモデル | 推論のコスト |
|---|---|---|---|
| アンサンブル学習 | 各モデルの予測(出力) | 全部のモデル | モデルの数に比例して増える |
| モデルマージ | 同じ形のモデルの重み(パラメーター) | 混ぜ合わせた1つのモデル | 1つ分 |
| MoE | 1つのモデルの中の専門家(部分ネットワーク) | 入力ごとに選ばれた一部の専門家 | 選ばれた分だけ |
| 知識蒸留 | 大きなモデル(やアンサンブル)の出力を、小さなモデルに学ばせる | 小さな1つのモデル | 小さなモデル1つ分 |
最初の5分:Pythonでアンサンブル学習を同じデータで動かす
この節では、scikit-learn で4つの方法を同じデータに当て、決定木1本と精度を比べる最小のコードが分かります。
scikit-learn には、アンサンブル学習のクラスが sklearn.ensemble にまとまっています。ワインの分類(178件・13特徴量・3クラス)で、決定木1本とアンサンブル4種類を5分割交差検証で比べます。
from sklearn.datasets import load_wine
from sklearn.model_selection import cross_val_score
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import (VotingClassifier, BaggingClassifier,
HistGradientBoostingClassifier, StackingClassifier)
X, y = load_wine(return_X_y=True) # 178件・13特徴量・3クラス
base = [("lr", make_pipeline(StandardScaler(), LogisticRegression())),
("knn", make_pipeline(StandardScaler(), KNeighborsClassifier(7))),
("tree", DecisionTreeClassifier(max_depth=3, random_state=0))]
models = {
"決定木1本": DecisionTreeClassifier(max_depth=3, random_state=0),
"ソフト投票": VotingClassifier(base, voting="soft"),
"バギング": BaggingClassifier(DecisionTreeClassifier(), n_estimators=100, random_state=0),
"ブースティング": HistGradientBoostingClassifier(random_state=0),
"スタッキング": StackingClassifier(base, final_estimator=LogisticRegression()),
}
for name, m in models.items():
s = cross_val_score(m, X, y, cv=5)
print(f"{name:8s} {s.mean():.3f} ± {s.std():.3f}")
決定木1本 0.893 ± 0.066
ソフト投票 0.972 ± 0.018
バギング 0.955 ± 0.033
ブースティング 0.955 ± 0.022
スタッキング 0.983 ± 0.022
どの方法も決定木1本より高く、分割ごとのぶれ(± の値)も小さくなりました。使い方はふつうのモデルと同じで、fit・predict・predict_proba がそのまま使えます。投票とスタッキングは「どのモデルを組み合わせるか」を estimators に名前つきのリストで渡し、バギングは1つのモデルを estimator に渡します。
ただし、どのデータでもアンサンブルが勝つわけではありません。乳がんのデータ(569件・30特徴量)では、標準化したロジスティック回帰1つで 0.979 になり、アンサンブルのどれもこれを上回りませんでした(スタッキングが同じ 0.979)。アンサンブルを試す前に、単体のモデルの精度を基準として出しておくのが最初の一歩です。
なぜ混ぜると強くなるのか(多数決の計算とモデルどうしの相関)
この節では、多数決で正解率が上がる理由を確率の計算で示し、モデルどうしの誤りが似ているとその効果が消える様子を実測で分かります。
正解率 p のモデルが n 個あり、互いに独立に間違えるとします。多数決が正しくなるのは、過半数が正解する場合です。その確率は二項分布の和 Σk>n/2 C(n,k) pk(1−p)n−k で計算できます。Python の math.comb で計算した値です。
| 1つの正解率 p | 3個 | 11個 | 21個 | 101個 |
|---|---|---|---|---|
| 0.6 | 0.648 | 0.753 | 0.826 | 0.979 |
| 0.7 | 0.784 | 0.922 | 0.974 | 1.000 |
| 0.8 | 0.896 | 0.988 | 0.999 | 1.000 |
正解率0.7のモデルが11個あれば、多数決は0.922まで上がる計算です。ただし、これは「独立に間違える」という強い仮定の上の話です。同じデータで学習したモデルは、難しいデータで一緒に間違えがちです。そこで、正解率0.7のモデル11個について、間違え方の相関を変えて20万回の多数決を乱数で再現しました(共通の乱数をどれだけ混ぜるかで相関を作りました)。
| 2つのモデルの正誤の相関 | 1つの正解率 | 11個の多数決の正解率 |
|---|---|---|
| 0.00(独立) | 0.700 | 0.922 |
| 0.12 | 0.701 | 0.829 |
| 0.32 | 0.699 | 0.755 |
| 0.58 | 0.701 | 0.719 |
| 1.00(全く同じ) | 0.699 | 0.699 |
相関が0.12あるだけで 0.922 が 0.829 に下がり、0.58 ではほぼ1つのモデルと変わりません。では、実際のモデルどうしの相関はどのくらいでしょうか。合成データ(make_classification・2,000件・20特徴量・ラベルの5%を反転)で、ロジスティック回帰・k近傍法・SVM・決定木(深さ5)・ガウシアンナイーブベイズの5つを5分割交差検証で予測させ、「間違えたかどうか」の相関を測りました。
実際のモデルの誤りの相関は 0.46〜0.70 と高く、上の表でいえば「多数決の効果がかなり小さい」範囲です。最も強い SVM 単体の正解率は 0.878 で、5つのハード投票は 0.862 でした。独立の計算どおりには伸びず、弱いモデルを混ぜると最良の単体を下回ることもあるというのが実測の結論です。アンサンブル学習の各手法は、この相関をどう下げるか(バギング・ランダムフォレスト)、あるいは相関に頼らずどう誤りを直すか(ブースティング)、どう重みを学ぶか(スタッキング)の工夫だと読むと整理しやすくなります。
自分のモデルで確かめる手順は1行です。交差検証の予測から誤りの相関を出します(P は各モデルの cross_val_predict の結果を並べた辞書)。
E = {n: (p != y) for n, p in P.items()} # 間違えたら True
print(np.corrcoef([E[n] for n in E]).round(2)) # 誤りの相関の行列
バイアスとバリアンス:バギングとブースティングが減らすもの
この節では、予測の誤差を「外れ方の偏り(バイアス)」と「データによるばらつき(バリアンス)」に分け、バギングとブースティングがそれぞれどちらを減らすのかを実測で分かります。
回帰の二乗誤差は、バイアスの2乗+バリアンス+取り除けない雑音に分けられます。バイアスは「何度学習し直しても平均として真の値からずれる量」、バリアンスは「学習データが変わると予測がどれだけ揺れるか」です。真の関数を y = 2 sin(x) + 0.5x とし、標準偏差0.5の雑音(分散0.25)を足したデータを300件作って学習する、を30回くり返し、200点で予測の平均と分散を測りました。
| モデル | バイアスの2乗 | バリアンス | 期待される誤差(雑音0.25を含む) |
|---|---|---|---|
| 深い木1本(深さ制限なし) | 0.0079 | 0.2328 | 0.491 |
| 浅い木1本(深さ3) | 0.0259 | 0.0657 | 0.342 |
| バギング(深い木50本) | 0.0038 | 0.1091 | 0.363 |
| バギング(浅い木50本) | 0.0219 | 0.0249 | 0.297 |
| 勾配ブースティング(深さ3の木30本) | 0.0044 | 0.0237 | 0.278 |
バギング(並列・独立)
深い木のバリアンス 0.233 を 0.109 に半減。バイアスはほぼそのまま
ばらつく強いモデルを平均して落ち着かせる
浅い木に使うと、もともと小さいバリアンスが減るだけ
ブースティング(直列・逐次)
浅い木のバイアス 0.0259 を 0.0044 に約6分の1
足りない部分を次の木が足していく
バリアンスも小さいまま(学習率0.1で少しずつ足すため)
公式 User Guide の「バギングは強く複雑なモデルで、ブースティングは弱いモデルでよく働く」という説明が、そのまま数字に出ています。深い木はバイアスが小さくバリアンスが大きいのでバギング向き、浅い木はその逆なのでブースティング向きです。この例では、勾配ブースティングの期待誤差 0.278 が雑音の下限 0.25 に最も近くなりました。
投票(ハード・ソフト)の違いと、弱いモデルを混ぜると下がる例
この節では、ハード投票とソフト投票の計算の違い、どちらが勝つかがデータとモデルで変わる理由、重み付けの考え方が分かります。
VotingClassifier の voting="hard" は各モデルの予測クラスの多数決(同数のときはクラスを昇順に並べた先のほう)、voting="soft" は predict_proba の平均が最大のクラスです(公式 User Guide)。ソフト投票は自作でも数行で、scikit-learn と差0で一致しました。
vc = VotingClassifier(base, voting="soft").fit(Xtr, ytr)
my = np.mean([clone(m).fit(Xtr, ytr).predict_proba(Xte) for _, m in base], axis=0)
print(np.abs(vc.predict_proba(Xte) - my).max()) # 0.0
ロジスティック回帰・k近傍法・決定木(深さ5)の3つで、2,000件のうちハードとソフトの答えが食い違ったのは77件でした。そのうち正解したのはハード44件、ソフト33件で、この組み合わせではハードが勝ちました。食い違った例を見ると理由が分かります。
| データ | 3モデルの陽性の確率(lr・knn・木) | 平均 | ハード/ソフト/正解 |
|---|---|---|---|
| 5番 | 0.54・0.73・0.00 | 0.425 | 1/0/1 |
| 50番 | 0.47・0.40・1.00 | 0.624 | 0/1/0 |
| 64番 | 0.62・0.60・0.17 | 0.464 | 1/0/1 |
決定木の葉はデータが少なく、確率が 0.00 や 1.00 と極端に出ます。ソフト投票では、その1つの自信過剰な確率がほかの2つの票を押し切ってしまいます。実際、決定木の確率の当てはまり(対数損失、小さいほどよい)は 0.922 で、ロジスティック回帰の 0.394 より大きく悪い値でした。ソフト投票は、どのモデルも確率がよく校正されているときに強い方法です。
一方で、ソフト投票の確率そのもの(3つの平均)の対数損失は 0.363 で、どの単体(最良 0.394)よりもよくなりました。正解率では負けても、確率の当てはまりは平均すると改善しやすい、という点も押さえておくと選び方を誤りません。対数損失(交差エントロピー)の意味は、関連記事「エントロピーとは|意味・計算・交差エントロピーをPythonで実演」で扱っています。
組み合わせるモデルを変えて、5分割交差検証の予測から投票の正解率を計算しました(SVM は CalibratedClassifierCV で確率を出しています)。
| 組み合わせ | ハード投票 | ソフト投票 |
|---|---|---|
| SVM 単体(基準) | 0.878 | |
| 5つ全部(lr・knn・SVM・木・NB) | 0.862 | 0.856 |
| 強い3つ(lr・knn・SVM) | 0.866 | 0.866 |
| SVM・knn・木 | 0.866 | 0.864 |
| SVM・knn・NB | 0.864 | 0.852 |
どの投票も SVM 単体を下回りました。最も強いモデルが1つだけ抜けていて、ほかのモデルとの誤りの相関が高いと、対等な1票ずつの投票はむしろ足を引っ張ります。対策は、weights=[1, 1, 3] のように強いモデルの重みを上げるか、重みをデータから学ぶスタッキングにすることです。重みを手で決めると検証データに合わせすぎるので、交差検証の中で選ぶか、スタッキングに任せるほうが安全です。
バギングとは(ランダムフォレストとの関係を短く)
この節では、バギングの手順とランダムフォレストとの違いを短く確かめます。
バギングは、n 件の訓練データから同じものを何度選んでもよいとして n 件を選び(ブートストラップ)、そのデータで同じ種類のモデルを学習する、を B 回くり返して平均(分類は確率の平均)する方法です。1つ1つのデータに約37%の「選ばれなかった行」ができ、それで精度を測るのが OOB(out-of-bag)スコアです。前の節の表のとおり、深い木のバリアンスを半分に減らしました。
| データ(5分割交差検証) | 決定木1本(深さ5) | バギング(深い木100本) | ランダムフォレスト(200本) |
|---|---|---|---|
| 乳がん | 0.938 | 0.960 | 0.967 |
| 合成2,000件 | 0.810 | 0.863 | 0.868 |
ランダムフォレストは、バギングに「分割のたびに一部の特徴量だけを候補にする」工夫を足し、木どうしの相関を下げたものです。前の節の「相関が下がるほど多数決が効く」が、そのまま狙いになっています。BaggingClassifier は木以外のモデル(k近傍法・SVM など)も包めるのが違いです。ブートストラップ・OOB・特徴量重要度の自作と実測は、この記事と同時期に書いた「ランダムフォレストとは|バギングを自作し特徴量重要度の偏りを実測」で詳しく扱います。
ブースティングとは:AdaBoostをnumpyで自作してscikit-learnと一致させる
この節では、ブースティングの代表である AdaBoost の手順を手計算と自作コードで追い、scikit-learn 1.9.1 の AdaBoostClassifier と重み・予測が完全に一致することが分かります。
AdaBoost(Adaptive Boosting)は、弱いモデル(ここでは深さ1の決定木=決定株)を1本ずつ作り、前の株が間違えたデータの重みを上げて、次の株にそこを重点的に学ばせる方法です。scikit-learn 1.9.1 の AdaBoostClassifier は、多クラスに広げた SAMME(Zhu ほか 2009)という版だけを実装しています(以前の版にあった algorithm="SAMME.R" は削除済み)。
全データに同じ重み 1/n を付ける
重みは「どのデータをどれだけ重視するか」。合計は1。
重み付きで弱いモデルを学習し、重み付きの誤り率 err を出す
err = 間違えたデータの重みの合計。
そのモデルの発言力 α を決める
α = 学習率 ×(log((1−err)/err) + log(K−1))。K はクラス数で、2クラスなら後ろの項は0。誤りが小さいほど α が大きい。
間違えたデータの重みを eα 倍して合計1に戻す
正解したデータの重みは相対的に下がる。
2〜4をくり返し、予測は α で重み付けした多数決
2クラスでは Σ αm hm(x)(h は +1 か −1)の符号で決める。
手計算の例:10件で最初の株が3件を間違えたとします。err = 0.3、α = log(0.7/0.3) = 0.847、eα = 2.333 です。間違えた3件の重みは 0.1 → 0.233、正解した7件は 0.1 のまま。合計 1.4 で割り直すと、間違えた3件は各 0.167、正解した7件は各 0.071 になります。間違えたデータの重みの合計がちょうど0.5になるのがこの更新の性質で、「前の株は、新しい重みのもとではでたらめと同じ」状態から次の株を作ることになります。
この手順を numpy で書きます。弱いモデルの決定株も自作し、scikit-learn の木と同じく重み付きのジニ不純度が最小になる分割を選び、しきい値は隣り合う値の中点にしました(scikit-learn の木は内部で X を float32 にするので、比べるために同じ変換をしています)。
import numpy as np
def stump_fit(X, y, w): # 重み付きジニで最良の1分割(y は 0/1)
best = (np.inf, None)
W = w.sum()
for j in range(X.shape[1]):
o = np.argsort(X[:, j], kind="stable")
x, yy, ww = X[o, j], y[o], w[o]
L, L1 = np.cumsum(ww)[:-1], np.cumsum(ww * yy)[:-1]
R, R1 = W - L, (ww * yy).sum() - L1
gini = (L * (1 - (L1/L)**2 - ((L-L1)/L)**2)
+ R * (1 - (R1/R)**2 - ((R-R1)/R)**2)) / W
gini = np.where(x[1:] > x[:-1], gini, np.inf) # 同じ値の間では切らない
i = np.argmin(gini)
if gini[i] < best[0] - 1e-12:
best = (gini[i], (j, (x[i] + x[i+1]) / 2,
int(L1[i] > L[i] - L1[i]), int(R1[i] > R[i] - R1[i])))
return best[1]
def stump_pred(s, X):
j, t, left, right = s
return np.where(X[:, j] <= t, left, right)
def my_adaboost(X, y, T, lr=1.0, K=2):
n = len(y); w = np.full(n, 1 / n); eps = np.finfo(float).eps
S, A, ERR = [], [], []
for m in range(T):
w = np.clip(w, eps, None)
s = stump_fit(X, y, w); inc = stump_pred(s, X) != y
err = np.average(inc, weights=w)
a = lr * (np.log((1 - err) / err) + np.log(K - 1))
S.append(s); A.append(a); ERR.append(err)
if m < T - 1:
w = np.exp(np.log(w) + a * inc); w /= w.sum()
return S, np.array(A), np.array(ERR)
def my_decision(S, A, X): # scikit-learn と同じ尺度(2倍)にそろえる
return 2 * sum(a * np.where(stump_pred(s, X) == 1, 1, -1) for s, a in zip(S, A)) / A.sum()
乳がんのデータを7:3に分け、200本で scikit-learn と比べた結果です。
最初の5回 自作 err [0.0678 0.1078 0.1698 0.2069 0.2257] alpha [2.6204 2.1138 1.5871 1.344 1.2327]
最初の5回 sk err [0.0678 0.1078 0.1698 0.2069 0.2257] alpha [2.6204 2.1138 1.5871 1.344 1.2327]
選んだ特徴量 自作 [22, 27, 21, 22, 7] sk [22, 27, 21, 22, 7]
しきい値 自作 [106.1, 0.1417, 23.35] sk [106.1, 0.1417, 23.35]
200本の α の最大の差 0.0 decision_function の最大の差 0.0
テストの正解率 自作 0.9591 sk 0.9591
200回分の誤り率・α・分割・予測がすべて差0で一致しました。1つ注意点があり、scikit-learn の 2クラスの decision_function は、各株の票を「正しいクラスに +α、もう一方に −α」で数えてから差をとるため、教科書の Σα h(x)/Σα の2倍の値になります。符号(予測)は同じです。
最初の株は特徴量22(worst perimeter=腫瘍の輪郭の長さの最大値)を 106.1 で切りました。4本目で同じ特徴量22がまた選ばれているのは、重みが変わって「前とは違うデータを重視した切り方」が必要になったからです。本数ごとの正解率と誤り率を並べます。
| 株の本数 | 訓練の正解率 | テストの正解率 | その回の株の重み付き誤り率 |
|---|---|---|---|
| 1 | 0.932 | 0.889 | 0.068 |
| 5 | 0.972 | 0.930 | 0.226 |
| 10 | 0.977 | 0.942 | 0.256 |
| 20 | 0.997 | 0.936 | 0.286 |
| 50 | 1.000 | 0.942 | 0.344 |
| 200 | 1.000 | 0.959 | 0.355 |
1本の株はテスト 0.889 の「弱い」モデルですが、200本で 0.959 まで上がりました。株の誤り率が回を追うごとに0.5に近づくのは、重みが難しいデータに集まり、1本の株では切り分けにくくなっていくためです。訓練の正解率が50本で1.000になったあともテストの正解率が上がり続けるのは、AdaBoost でよく知られた性質です(Schapire ほか 1998 はマージンの広がりで説明しています)。
反対に、ラベルの誤り(雑音)には弱いのが AdaBoost の弱点です。ラベルの5%を反転させた合成データでは、誤ったラベルのデータに重みが集まり続け、200本の正解率は 0.844 と、ロジスティック回帰 0.838 とほぼ同じにとどまりました(勾配ブースティングは 0.869)。
勾配ブースティングとは:残差に木を足す様子を自作で見る
この節では、勾配ブースティングが「残り(残差)に次の木を当てはめて足す」仕組みであることを、1次元の回帰で予測が少しずつ真の値に近づく様子と自作コードで分かります。
勾配ブースティング(Friedman 2001)は、今の予測 F(x) と正解 y の差を、次の木が予測するように学習させ、学習率 ν を掛けて足していく方法です。二乗誤差を損失にすると、損失の負の勾配がちょうど残差 y − F(x) になるため「勾配」ブースティングと呼ばれます。分類では対数損失の勾配(正解 − 予測確率)を使います。最小二乗法で残差を小さくする考え方は、関連記事「最小二乗法とは|公式・手計算の例とPythonでの求め方を実例で解説」で扱っています。
from sklearn.tree import DecisionTreeRegressor
def my_gbr(X, y, M, lr=0.1, depth=3):
F0 = y.mean() # 最初の予測は平均
F = np.full(len(y), F0); trees = []
for m in range(M):
r = y - F # 二乗誤差の負の勾配 = 残差
t = DecisionTreeRegressor(max_depth=depth, random_state=0).fit(X, r)
F = F + lr * t.predict(X) # 少しだけ足す
trees.append(t)
return F0, trees
def my_pred(F0, trees, X, lr=0.1):
return F0 + lr * sum(t.predict(X) for t in trees)
弱いモデルには scikit-learn の回帰木を使い、足していく部分を自作しました。真の関数 y = 2 sin(x) + 0.5x に雑音(標準偏差0.5)を足した300件で、GradientBoostingRegressor(200本・学習率0.1・深さ3)と比べると、テスト1,000件の予測の最大の差は 4.9×10−15、200回分の訓練の損失(train_score_)は差0で一致しました。木を足すごとに、x=1.5 と x=4.7 の予測が真の値に近づく様子です。
| 木の本数 | 訓練の二乗誤差 | テストの二乗誤差 | x=1.5 の予測(真 2.745) | x=4.7 の予測(真 0.350) |
|---|---|---|---|---|
| 0(平均だけ) | 0.984 | 1.054 | 1.453 | 1.453 |
| 1 | 0.845 | 0.906 | 1.562 | 1.350 |
| 5 | 0.501 | 0.553 | 1.912 | 1.035 |
| 10 | 0.315 | 0.372 | 2.201 | 0.748 |
| 20 | 0.209 | 0.283 | 2.518 | 0.496 |
| 50 | 0.162 | 0.287 | 2.669 | 0.389 |
| 200 | 0.076 | 0.326 | 2.638 | 0.483 |
1本目の木は残差の1割(学習率0.1)しか足さないので、予測はゆっくり動きます。20本ほどで真の値の近くまで来て、テストの二乗誤差は 0.283(雑音の分散 0.25 が下限)になりました。その先も訓練の誤差は 0.076 まで下がり続けますが、テストは 0.326 に悪化します。木が雑音まで覚え始めたためで、x=4.7 の予測も真の 0.350 から 0.483 へ離れていきました。ブースティングは本数を増やすと過学習するので、次の節の早期打ち切りが要ります。この点は、本数を増やしても過学習しないバギング・ランダムフォレストとの大きな違いです。
なお scikit-learn 1.9.1 では、回帰木の criterion="friedman_mse" を指定すると次の警告が出ます。GradientBoostingClassifier/Regressor の criterion 引数も 1.9.1 では既定が "deprecated" になり、内部では squared_error で木を作ります。
FutureWarning: Value `"friedman_mse"` for `criterion` is deprecated and will be removed in 1.11. It maps to `"squared_error"` as both were always equivalent. Use `criterion="squared_error"` to remove this warning.
学習率と本数・早期打ち切り(勾配ブースティングの過学習を防ぐ)
この節では、学習率と木の本数の関係と、早期打ち切りで本数を自動で決める方法が分かります。
同じ300件のデータで、学習率を変えて1,000本まで育て、テストの二乗誤差が最小になった本数を測りました。
| 学習率 | テストが最小の本数 | そのときのテスト誤差 | 1,000本のテスト誤差 | 1,000本の訓練誤差 |
|---|---|---|---|---|
| 1.0 | 2 | 0.303 | 0.485 | 0.000 |
| 0.3 | 8 | 0.279 | 0.485 | 0.000 |
| 0.1 | 29 | 0.277 | 0.449 | 0.003 |
| 0.03 | 98 | 0.278 | 0.352 | 0.050 |
学習率を3分の1にすると、最良の本数はおよそ3倍になりました(8 → 29 → 98)。最良の誤差は学習率0.3以下でほぼ同じで、1.0 だけが少し悪い値でした。公式 User Guide は『The Elements of Statistical Learning』の推奨として、学習率を小さい定数(0.1以下など)にし、早期打ち切りが働く程度に本数を大きく取る方法を紹介しています。小さい学習率は「行きすぎ」に強い代わりに、本数(時間)が増えます。
本数を決めるには、訓練データの一部を検証用に取り分け、改善が止まったら打ち切る早期打ち切り(early stopping)を使います。
g = GradientBoostingRegressor(n_estimators=1000, learning_rate=0.1, max_depth=3,
n_iter_no_change=10, validation_fraction=0.2,
random_state=0).fit(X, y)
print(g.n_estimators_) # 29(実際に作った本数)
1,000本を上限にしても29本で止まり、テストの二乗誤差は 0.278 と、上の表の最良とほぼ同じになりました。HistGradientBoostingClassifier/Regressor は、公式ドキュメントによると1万件を超えるデータでは早期打ち切りが既定で有効です。それより少ないデータでは early_stopping=True を明示します。
learning_rate(学習率)
1本の木の予測をどれだけ足すか。小さいほど慎重で、本数が必要になる。
0.05〜0.1 から始める
n_estimators/max_iter(本数)
多すぎると過学習する。上限を大きめにして早期打ち切りに決めさせる。
n_iter_no_change と組で使う
max_depth/max_leaf_nodes(木の大きさ)
特徴量どうしの組み合わせをどこまで表すか。ブースティングでは浅い木が基本。
深さ3〜6・葉31前後
subsample(行の抜き取り)
1.0 未満にすると木ごとに一部の行だけで学習する(確率的勾配ブースティング)。バリアンスが下がることがある。
GradientBoosting で 0.5〜0.8
XGBoost や LightGBM は、この勾配ブースティングに正則化・ヒストグラム化・欠損値の扱いなどを加えて速くしたライブラリです。使い方と LightGBM との比較は「XGBoostとは・使い方|Pythonで分類・回帰とLightGBM比較を実測」で扱っています。
スタッキングとは:メタモデルとリークしない作り方
この節では、スタッキングの仕組みと、メタモデルを訓練データの予測で学習させると何が起きるか(リーク)、交差検証で防ぐ方法が分かります。
スタッキングは、ベースモデルの予測を新しい特徴量として並べ、その上でメタモデル(最終のモデル、多くはロジスティック回帰や線形回帰)を学習させる方法です。メタモデルは「どのベースモデルを、どれだけ信じるか」を学びます。投票の重みをデータから決めるもの、と考えると分かりやすくなります。
前の節の5モデルを StackingClassifier で束ね、メタモデルの係数を見ました。
メタモデルの係数 lr 0.48 knn 2.17 svc 4.66 tree -0.02 nb -0.74 切片 -3.32
テストの正解率 0.890(SVM 単体の5分割交差検証 0.878、ソフト投票 0.856)
メタモデルは SVM に最も大きな重み 4.66 を付け、決定木はほぼ0、ナイーブベイズはマイナスにしました。投票では足を引っ張っていた弱いモデルを、スタッキングは自分で軽くしたことになります。
ここで最も多い失敗がリークです。ベースモデルを訓練データで学習し、同じ訓練データに対する予測でメタモデルを学習させると、訓練データを丸暗記するモデル(1近傍法・深さ制限なしの木)の予測が「完璧」に見えてしまいます。実際に比べました(合成2,000件を1,500件と500件に分割)。
| 作り方 | メタモデルの係数(lr・1近傍・木・森) | メタの訓練の正解率 | テストの正解率 |
|---|---|---|---|
| リークあり(訓練データの予測で学習) | 0.97・3.96・3.96・2.84 | 1.000 | 0.850 |
| 交差検証の予測で学習(自作) | 1.83・0.54・−0.11・4.71 | 0.855 | 0.876 |
| StackingClassifier(cv=5) | 1.83・0.54・−0.11・4.71 | — | 0.876 |
| 参考:ランダムフォレスト単体 | — | 1.000 | 0.876 |
リークありでは、訓練の正解率 1.000 の1近傍法と木に大きな重み(3.96)が付き、テストでは 0.850 に落ちました。交差検証の予測(各データについて、そのデータを見ていないモデルが出した予測)で学習させると、1近傍法と木の重みは小さくなり、テストは 0.876 になりました。自作と StackingClassifier の係数は完全に同じです。
from sklearn.model_selection import StratifiedKFold, cross_val_predict
from sklearn.base import clone
cv = StratifiedKFold(5) # cv=5 の既定(分類はシャッフルなしの層化)
Z = np.column_stack([cross_val_predict(clone(m), Xtr, ytr, cv=cv,
method="predict_proba")[:, 1] for _, m in base])
meta = LogisticRegression(max_iter=2000).fit(Z, ytr) # メタは交差検証の予測で
fitted = [clone(m).fit(Xtr, ytr) for _, m in base] # ベースは全訓練データで
Zte = np.column_stack([f.predict_proba(Xte)[:, 1] for f in fitted])
print(np.abs(meta.predict_proba(Zte) - st.predict_proba(Xte)).max()) # 0.0
公式 User Guide も「過学習を避けるため、final_estimator は内部で cross_val_predict を使った外側のサンプルで学習される」「estimators は訓練データ全体で学習される」と書いています。2クラスでは、ベースモデルごとに陽性の確率1列だけが使われます(多クラスでは全クラスの列)。
スタッキングのメタモデルの学習
手元ではリークありが訓練1.000・テスト0.850、交差検証の予測ではテスト0.876だった
似た方法にブレンディングがあります。訓練データを7:3に分け、7でベースモデルを、3への予測でメタモデルを学習させる簡易版です。手元ではテスト 0.868 で、交差検証で作るスタッキング 0.876 より少し下がりました。ベースモデルが訓練データの7割しか使えないためです。また passthrough=True にすると、元の特徴量もメタモデルに渡され、ここでは 0.884 に上がりました。
同じデータで全部比べた結果(精度・学習時間・予測時間・サイズ)
この節では、投票・バギング・ブースティング・スタッキングと単体のモデルを、同じデータで比べた精度・確率の当てはまり・時間・モデルのサイズが分かります。
5分割交差検証(層化・シャッフルあり・random_state=0)の正解率と対数損失です。ベースモデルは前の節と同じ5つ(標準化つきのロジスティック回帰・15近傍法・SVM(CalibratedClassifierCV で確率化)・深さ5の決定木・ガウシアンナイーブベイズ)で、投票とスタッキングはこの5つを束ねました。
| モデル | 乳がん 正解率 | 乳がん 対数損失 | 合成2,000件 正解率 | 合成2,000件 対数損失 |
|---|---|---|---|---|
| ロジスティック回帰 | 0.979 | 0.074 | 0.838 | 0.394 |
| SVM | 0.975 | 0.079 | 0.878 | 0.317 |
| 決定木(深さ5) | 0.938 | 1.733 | 0.810 | 0.944 |
| ハード投票(5つ) | 0.975 | — | 0.862 | — |
| ソフト投票(5つ) | 0.970 | 0.093 | 0.856 | 0.351 |
| バギング(深い木100本) | 0.960 | 0.171 | 0.863 | 0.397 |
| ランダムフォレスト(200本) | 0.967 | 0.115 | 0.868 | 0.353 |
| AdaBoost(株200本) | 0.975 | 0.433 | 0.844 | 0.601 |
| 勾配ブースティング(既定) | 0.965 | 0.110 | 0.869 | 0.331 |
| HistGradientBoosting(既定) | 0.970 | 0.105 | 0.879 | 0.347 |
| スタッキング(5つ+ロジスティック回帰) | 0.979 | 0.073 | 0.881 | 0.313 |
読み取れることは3つです。1つ目、スタッキングは両方のデータで正解率・対数損失とも最良かそれに並んだこと。公式 User Guide の「ベース層の最良の予測器と同じくらい、ときにはそれを上回る」という説明どおりです。2つ目、乳がんのように線形モデルで分けやすいデータでは、木のアンサンブルはロジスティック回帰に届かないこと。3つ目、AdaBoost は正解率はよくても対数損失が大きく(0.433)、確率をそのまま使う用途には向かないことです。ハード投票は predict_proba を持たないため、対数損失は測れません。
次に、合成データを5,000件に増やし、4,000件で学習・1,000件で予測したときの時間とサイズです(pickle で保存したときのバイト数)。
| モデル | テストの正解率 | 学習時間 | 1件の予測(中央値) | サイズ |
|---|---|---|---|---|
| ロジスティック回帰 | 0.837 | 0.01秒 | 0.3ms | 2KiB |
| SVM | 0.913 | 0.96秒 | 1.2ms | 247KiB |
| ソフト投票(5つ) | 0.872 | 1.01秒 | 2.7ms | 913KiB |
| バギング(深い木100本) | 0.908 | 5.64秒 | 11.1ms | 3,959KiB |
| ランダムフォレスト(200本) | 0.916 | 2.37秒 | 10.5ms | 10,474KiB |
| AdaBoost(株200本) | 0.824 | 2.21秒 | 20.7ms | 109KiB |
| 勾配ブースティング(100本) | 0.901 | 2.22秒 | 0.3ms | 132KiB |
| HistGradientBoosting | 0.918 | 0.30秒 | 0.8ms | 390KiB |
| スタッキング(5つ) | 0.909 | 5.53秒 | 7.1ms | 913KiB |
スタッキングの学習時間は、ベースモデルを交差検証の分割数+1回(全体で学習し直す分)学習するため、ソフト投票の約5倍でした。予測時には全部のベースモデルが動くので、1件の予測もモデルの数だけ遅くなります。AdaBoost と森は1件ずつの予測が 10〜20ms と遅く、木を1本ずつ呼び出す手間が件数に関係なくかかるためです。API で1件ずつ答える用途では、この列が効いてきます。なお、このデータでの順位は前の表と少し変わり、SVM 単体(0.913)が弱いモデルを混ぜたスタッキング(0.909)を上回りました。データの分け方1回分の差なので、選ぶときは交差検証の値を使います。
よくあるエラーとつまずき(原文つき)
この節では、アンサンブル学習のクラスで実際に出たエラー・警告の原文と、直し方が分かります。
| 状況 | 原文(scikit-learn 1.9.1) | 直し方 |
|---|---|---|
| ハード投票で確率を出そうとした | AttributeError: This 'VotingClassifier' has no attribute 'predict_proba' | voting="soft" にする。ハード投票は確率を持たない |
| ハード投票を対数損失や AUC で評価した | AttributeError: VotingClassifier has none of the following attributes: predict_proba. | scoring を accuracy にするか、ソフト投票にする |
| ソフト投票に SVC() を入れた | AttributeError: This 'SVC' has no attribute 'predict_proba' | CalibratedClassifierCV(SVC(), ensemble=False) で包む |
| SVC(probability=True) を使った | FutureWarning: The `probability` parameter was deprecated in 1.9 and will be removed in version 1.11. Use `CalibratedClassifierCV(SVC(), ensemble=False)` instead of `SVC(probability=True)` | 警告の指示どおり CalibratedClassifierCV に置き換える |
| 以前の版の AdaBoost のコードを動かした | TypeError: AdaBoostClassifier.__init__() got an unexpected keyword argument 'algorithm' | algorithm= を消す(SAMME だけになった) |
| 以前の版の Bagging のコードを動かした | TypeError: BaggingClassifier.__init__() got an unexpected keyword argument 'base_estimator' | estimator= に書き換える |
| 投票・スタッキングで名前が重複した | ValueError: Names provided are not unique: ['a', 'a'] | estimators の名前を全部違うものにする |
| weights の数がモデルの数と違う | ValueError: Number of `estimators` and weights must be equal; got 3 weights, 2 estimators | weights をモデルと同じ数にする |
| 確率を出せないモデルで stack_method="predict_proba" | ValueError: Underlying estimator b does not implement the method predict_proba. | 既定の stack_method="auto" にする(decision_function が使われる) |
| GradientBoosting に欠損値(NaN)を入れた | ValueError: Input X contains NaN.(GradientBoostingClassifier does not accept missing values encoded as NaN natively.) | HistGradientBoosting を使う(NaN をそのまま扱える)か、補完してから渡す |
| AdaBoost の学習率が大きすぎた(50) | UserWarning: Sample weights have reached infinite values, at iteration 1, causing overflow. Iterations stopped. Try lowering the learning rate. | learning_rate を 1.0 以下にする |
エラーにならないつまずきもあります。手元で確かめた4つです。
- 標準化を Pipeline の外でしてから交差検証する:テストの分割の平均・分散が訓練に漏れる。投票・スタッキングのベースモデルは、この記事のように1つずつ
make_pipeline(StandardScaler(), …)で包む。 - 弱いモデルを数合わせで入れる:投票の節のとおり、最良の単体を下回る。入れる前に単体の交差検証の値と、誤りの相関を見る。
- ブースティングの本数を増やし続ける:勾配ブースティングの節のとおり、テストの誤差は途中から悪化する。早期打ち切りを使う。
- スタッキングの評価を、メタモデルの訓練の正解率で見る:リークの表のとおり、1.000 と出ても意味がない。外側の交差検証かテストデータで評価する。
どれを使うかの判断表(メリット・デメリット・使われる場面)
この節では、データと目的に応じてどのアンサンブル学習を選ぶかと、アンサンブル学習全体のメリット・デメリットが分かります。
| 状況 | まず選ぶもの | 理由(この記事の実測) |
|---|---|---|
| 表形式のデータで精度を詰めたい | HistGradientBoosting(か XGBoost・LightGBM) | 5,000件で正解率0.918・学習0.30秒。欠損値もそのまま扱える |
| 調整せずに安定した基準がほしい | ランダムフォレスト | 本数を増やしても過学習しない。標準化も要らない |
| 性質の違う強いモデルが複数ある | スタッキング | 両データで最良か同等。弱いモデルの重みも自動で下がった |
| 手軽に2〜3個を混ぜたい | ソフト投票(確率がよく校正されたモデルどうし) | 確率の対数損失は単体より改善(0.394→0.363)。自信過剰なモデルが混ざるとハードが勝つ |
| 深い木や k近傍法の結果がデータで揺れる | バギング | 深い木のバリアンスを 0.233→0.109 に半減 |
| ラベルの誤りが多いデータ | AdaBoost は避け、ランダムフォレストか勾配ブースティング | ラベル5%反転で AdaBoost 0.844、勾配ブースティング 0.869 |
| 1件ずつ速く答える API | 勾配ブースティング(本数・深さを抑える)か単体モデル | 1件の予測が GradientBoosting 0.3ms、森 10.5ms、AdaBoost 20.7ms |
| 線形で分けやすいデータ・説明が必要 | ロジスティック回帰などの単体 | 乳がんではロジスティック回帰0.979にアンサンブルが並ぶだけだった |
アンサンブル学習全体のメリット・デメリットを、上位の解説記事でよく挙がる項目と実測で並べます。
| 観点 | メリット/デメリット | この記事で確かめたこと |
|---|---|---|
| 予測精度 | メリット:単体より上がりやすい | ワインで決定木0.893→スタッキング0.983。ただし誤りの相関が高いと伸びない |
| ばらつき・雑音への強さ | メリット:データが変わっても予測が揺れにくい | バギングでバリアンス半減。交差検証の ± も小さくなった |
| 計算コスト | デメリット:学習・予測・保存がモデルの数だけ重い | スタッキングの学習はソフト投票の約5倍、森は10MiB |
| 過学習 | デメリット:ブースティングとスタッキングは作り方を誤ると過学習する | 本数を増やした勾配ブースティング、リークしたスタッキング |
| 解釈のしやすさ | デメリット:1本の木のように理由を読み下せない | 重要度や SHAP などの別の道具が要る |
| モデル選び | デメリット:どれを混ぜるかの試行が増える | 弱いモデルを混ぜた投票は最良の単体を下回った |
使われる場面としては、金融の与信・不正検知、医療の診断支援、製造の不良予測、小売の需要予測など、表形式のデータを扱う分野が中心です。データ分析のコンペティション(Kaggle など)では、上位の解法で勾配ブースティングと複数モデルのスタッキング・平均がよく使われます。一方で、ふだんの業務のシステムでは、予測時間・保守の手間・説明のしやすさから、勾配ブースティング1つか、2〜3個の平均に絞ることも多くあります。
学ぶ順番は次のとおりです。決定木と過学習の考え方は、同時期に書いた「決定木とは|ジニ不純度・剪定・過学習をPythonで自作して実測」「過学習とは|原因と対策、交差検証・正則化・リークをPythonで実測」で扱います。
- 1決定木1本とロジスティック回帰で基準を出す交差検証の平均と ± を記録する
- 2ランダムフォレストと HistGradientBoosting を既定で動かす最初の5分のコード。多くの表データはここで高い水準に届く
- 3ブースティングの学習率・本数・早期打ち切りを調整する学習率を下げたら本数を増やす
- 4性質の違う強いモデルをスタッキングで束ねる誤りの相関を見てから選ぶ
- 5AdaBoost・勾配ブースティングを自作して仕組みを確かめるこの記事のコードで scikit-learn と一致させる
ニューラルネットワークでも、複数のモデルの予測の平均や、学習の途中で一部の素子を休ませるドロップアウト(アンサンブルに近い効果があると説明される)が使われます。仕組みは関連記事「ニューラルネットワークとは|仕組み・学習・AttentionをNumPyで実演」で扱っています。この記事の自作コードで使った numpy の配列操作(cumsum・argsort・column_stack)は、関連記事「NumPyの使い方 基本一覧|配列の作成・演算・形状変更」で扱っています。E資格の試験範囲にもアンサンブル学習やバイアスとバリアンスの考え方が含まれます。試験の内容は関連記事「E資格とは|試験内容・合格率・G検定との違いとNumPyで解く要点」で扱っています。
よくある質問
この節では、アンサンブル学習について検索でよく聞かれる疑問への短い答えが分かります。
Q. アンサンブル学習とは、ひとことで言うと何ですか。
複数のモデルの予測を組み合わせて、1つのモデルより当たりやすく、ぶれにくい予測を出す方法です。組み合わせ方は投票・バギング・ブースティング・スタッキングの4つが基本です。
Q. バギングとブースティングの違いは何ですか。
バギングはモデルを並列に独立に作って平均し、主にバリアンス(ばらつき)を減らします。ブースティングはモデルを直列に作り、前の誤りを次が直して、主にバイアス(外れ方の偏り)を減らします。手元の実測では、バギングが深い木のバリアンスを半分にし、ブースティングが浅い木のバイアスを約6分の1にしました。
Q. スタッキングとブレンディングの違いは何ですか。
どちらもメタモデルで予測を束ねますが、スタッキングは交差検証で全訓練データの予測を作り、ブレンディングは訓練データの一部を取り分けてメタモデル用にします。手元ではスタッキング 0.876、ブレンディング 0.868 でした。
Q. XGBoost や LightGBM はどの種類ですか。
勾配ブースティングのライブラリです。この記事で自作した「残差に木を足す」仕組みに、正則化や高速化の工夫を加えたものです。scikit-learn の HistGradientBoosting も同じ考え方(特徴量の値を256個程度のビンにまとめる)で速くしています。
Q. 混ぜれば必ず精度は上がりますか。
上がりません。手元では、弱いモデルを含む5つの投票(0.862)が SVM 単体(0.878)を下回り、乳がんのデータではロジスティック回帰1つ(0.979)をどのアンサンブルも超えませんでした。誤りの相関が高いモデルや弱いモデルを混ぜると、効果は小さいか逆効果です。
Q. ハード投票とソフト投票はどちらがよいですか。
どのモデルも確率がよく校正されているならソフト投票、確率が極端に出るモデル(浅くない決定木など)が混ざるならハード投票が勝つことがあります。手元の lr・knn・木では、食い違った77件でハード44件・ソフト33件が正解でした。
Q. アンサンブル学習で過学習はしますか。
バギングとランダムフォレストは本数を増やしても過学習しにくい方法です。勾配ブースティングは本数を増やすと過学習し、手元ではテストの誤差が 0.283(20本)から 0.326(200本)に悪化しました。スタッキングはメタモデルの学習でリークすると過学習します。
Q. AdaBoost はいまでも使われますか。
仕組みを学ぶ題材としてはよく使われますが、表形式のデータの実務では勾配ブースティングが選ばれることが多くなっています。手元でも、ラベルの雑音があるデータでは AdaBoost が 0.844 で、勾配ブースティングの 0.869 を下回り、確率の当てはまり(対数損失)も悪い値でした。
Q. 回帰(数値の予測)にも使えますか。
使えます。VotingRegressor・BaggingRegressor・AdaBoostRegressor・GradientBoostingRegressor・HistGradientBoostingRegressor・StackingRegressor がそろっています。投票は予測値の平均になります。
Q. ディープラーニングの時代にアンサンブル学習を学ぶ意味はありますか。
あります。表形式のデータでは勾配ブースティングがいまも有力な選択肢で、ニューラルネットワークでも複数モデルの平均はよく使われます。バイアスとバリアンス、誤りの相関、リークの考え方は、モデルの種類によらず評価の基本になります。
まとめ
この節では、記事全体の要点が分かります。
- アンサンブル学習は、複数のモデルの予測を組み合わせる方法の総称で、投票・バギング・ブースティング・スタッキングの4つが基本。ワインでは決定木1本 0.893 がスタッキングで 0.983 になった。
- 効くかどうかはモデルどうしの誤りの相関で決まる。独立なら正解率0.7の11個で0.922になる計算だが、実際のモデルの相関は0.46〜0.70で、弱いモデルを混ぜた投票は最良の単体を下回った。
- バギングはバリアンス、ブースティングはバイアスを減らす。深い木のバリアンスは 0.233→0.109、浅い木のバイアスは 0.0259→0.0044 になった。
- AdaBoost と勾配ブースティングは numpy の自作で scikit-learn と差0で一致。勾配ブースティングは本数を増やすと過学習するので、学習率と早期打ち切り(手元では29本で停止)を組で使う。
- スタッキングはメタモデルを交差検証の予測で学習させる。訓練データの予測で学習させるとテストが 0.876 から 0.850 に落ちた。両データで最良か同等だったが、学習と予測の時間はモデルの数だけ増える。
参考・出典
この節では、この記事で根拠にした論文と公式ドキュメントが分かります(確認日 2026年11月5日)。
- Leo Breiman「Bagging Predictors」Machine Learning 24, 123–140(1996)
- Yoav Freund, Robert E. Schapire「A Decision-Theoretic Generalization of On-Line Learning and an Application to Boosting」Journal of Computer and System Sciences 55(1), 119–139(1997)
- Ji Zhu, Hui Zou, Saharon Rosset, Trevor Hastie「Multi-class AdaBoost」Statistics and Its Interface 2, 349–360(2009)
- Jerome H. Friedman「Greedy Function Approximation: A Gradient Boosting Machine」The Annals of Statistics 29(5), 1189–1232(2001)
- David H. Wolpert「Stacked Generalization」Neural Networks 5(2), 241–259(1992)
- Robert E. Schapire, Yoav Freund, Peter Bartlett, Wee Sun Lee「Boosting the Margin: A New Explanation for the Effectiveness of Voting Methods」The Annals of Statistics 26(5), 1651–1686(1998)
- Trevor Hastie, Robert Tibshirani, Jerome Friedman『The Elements of Statistical Learning』第2版 7章・10章・16章
- scikit-learn 1.9.1 User Guide「Ensembles: Gradient boosting, random forests, bagging, voting, stacking」:https://scikit-learn.org/stable/modules/ensemble.html
- scikit-learn 1.9.1 API リファレンス「VotingClassifier」「StackingClassifier」「AdaBoostClassifier」「GradientBoostingRegressor」「HistGradientBoostingClassifier」:https://scikit-learn.org/stable/api/sklearn.ensemble.html
- scikit-learn 1.9.1 のソースコード(sklearn/ensemble/_weight_boosting.py・_gb.py・_stacking.py・_voting.py)
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「アンサンブル学習とは|バギング・ブースティング・スタッキングを同じデータで比較」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。