K_DM Book
用語集
対象者 標準
対象者別の入口 →

対象者の設定はこのブラウザの中にだけ保存されます。

言語 JA

Adaboost(回帰)

中級

02.4.5

Adaboost(回帰)

最終更新 2026-11-01 読了時間 6 分
まとめ
AdaBoost 回帰(AdaBoost.R2)は、回帰木を 1 本ずつ順番に学習し、予測誤差 \(|y_i - h_t(x_i)|\) が大きかった点ほど次のラウンドで選ばれやすくなるように重みを更新する手法です。各木には加重誤差率 \(\epsilon_t\) から決まる信頼度 \(\beta_t\) が付き、最終予測は \(-\ln \beta_t\) を重みにした加重中央値で求めます。scikit-learn の AdaBoostRegressor では loss 引数で誤差を linear / square / exponential のどれで測るかを選べ、この選択が外れ値への重みの集まり方を決めます。このページでは sin と cos を重ねた 500 点のデータで、ラウンドが進むと波の山と谷にどう重みが集まるかを図で確かめます。

AdaBoost 回帰は 決定木回帰 を弱学習器として使います。重みの更新と重み付き多数決の考え方は Adaboost(分類) と共通なので、分類版を先に読んでおくと、このページの \(\beta_t\) と加重中央値が「\(\alpha_t\) と多数決の回帰版」として読めます。

直感 #

波打つ曲線を深さ 5 の回帰木 1 本で近似すると、細かい山と谷の先端ほど予測がずれます。AdaBoost 回帰は、そのずれ \(|y_i - h_t(x_i)|\) が大きかった点を次のラウンドで「出題頻度の高い問題」にします。具体的には、重みに比例した確率で訓練データを引き直し(ブートストラップ)、外れた点が何度も複製されたデータで次の木を学習します。これを 100 回繰り返すと、1 本目が取り逃した山の先端を 2 本目以降が埋め、最後に各木の予測の加重中央値をとることで、1 本の木では描けなかった波形が再現されます。平均ではなく中央値を使うのは、たまたま大きく外した木 1 本に最終予測が引きずられないようにするためです。

何が起きているか(ステップで見る) #

コードを読む前に、この手法が図の上で何をしているかを追ってみましょう。「再生」を押すか、スライダーを動かすと 1 手順ずつ進みます。

見るポイント:間違えた点ほど大きく(重く)なり、次のラウンドで優先して正解させようとする。

式で書くと重みはどう動くか(AdaBoost.R2) #

1 ラウンドで起きる 5 つの計算 #

ステップ再生で見た「外れた点が大きくなる」動きを、Drucker (1997) の AdaBoost.R2 の式で追います。$N$ 個の訓練データに対して初期重みを均等に設定します。

$$w_i^{(1)} = \frac{1}{N}, \quad i = 1, \dots, N$$

各ラウンド $t = 1, \dots, T$ で以下を繰り返します。

Step 1. 弱学習器の学習 — 重み分布 $w^{(t)}$ からブートストラップサンプリングして回帰器 $h_t(x)$ を学習する。

Step 2. 個体誤差の計算

各サンプルの正規化誤差を計算します。線形損失の場合:

$$e_i^{(t)} = \frac{|y_i - h_t(x_i)|}{\max_j |y_j - h_t(x_j)|}$$

Step 3. 加重誤差率の計算

$$\epsilon_t = \sum_{i=1}^{N} w_i^{(t)} \cdot e_i^{(t)}$$

$\epsilon_t \geq 0.5$ ならラウンドを棄却します。

Step 4. 信頼度の計算

$$\beta_t = \frac{\epsilon_t}{1 - \epsilon_t}$$

Step 5. サンプル重みの更新

$$w_i^{(t+1)} = \frac{w_i^{(t)} \cdot \beta_t^{1 - e_i^{(t)}}}{Z_t}$$

$Z_t$ は正規化定数です。$\beta_t$ は $\epsilon_t < 0.5$ のとき 1 より小さいので、誤差が小さいサンプル($e_i \approx 0$)は $\beta_t^1$ 倍されて重みが縮み、最も外れたサンプル($e_i = 1$)は $\beta_t^0 = 1$ 倍で重みがそのまま残ります。分類版のように「間違えた点を増やす」のではなく「当たった点を減らす」書き方ですが、$Z_t$ で合計を 1 に戻すため、相対的には外れた点の重みが増えます。$\epsilon_t = 0.2$ なら $\beta_t = 0.25$ で、ぴったり当たった点の重みは最も外れた点の 4 分の 1 になります。

最終予測 #

最終予測は、$T$ 個の弱学習器の予測 $h_t(x)$ を $-\ln \beta_t$ で重み付けした加重中央値です。

$$\hat{y} = \text{weighted-median}(\{h_t(x)\}_{t=1}^T, \{-\ln \beta_t\}_{t=1}^T)$$

信頼度の高い($\beta_t$ が小さい)弱学習器ほど、中央値計算で大きな重みを持ちます。

損失関数のバリエーション #

scikit-learn の loss パラメータで個体誤差 $e_i^{(t)}$ の計算を切り替えられます:

loss個体誤差 $e_i^{(t)}$
linear$\frac{|y_i - h_t(x_i)|}{\max_j |y_j - h_t(x_j)|}$
square$\left(\frac{|y_i - h_t(x_i)|}{\max_j |y_j - h_t(x_j)|}\right)^2$
exponential$1 - \exp!\left(-\frac{|y_i - h_t(x_i)|}{\max_j |y_j - h_t(x_j)|}\right)$

linear では誤差が最大の半分の点に $e_i = 0.5$ が付きますが、square では $0.25$ に下がります。中程度の誤差の点が「ほぼ当たり」扱いになるので、square と exponential では重みが最も外れた少数の点に集中し、後半の図で見るように重みの集まり方が変わります。


Python で確かめる #

ここからは、sin と cos を重ねた 500 点の波形データに AdaBoostRegressor を当て、まず最終モデルの当てはまりを確認し、次に loss="linear" と loss="square" で各ラウンドにどの点が何回引き直されたかを散布図の丸の大きさで比べます。

1
2
3
4
import numpy as np
import matplotlib.pyplot as plt
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import AdaBoostRegressor

scikit-learn の AdaBoostRegressor は、各ラウンドで弱学習器に渡した訓練データを外から見られません。そこで、本物の回帰木を抱えつつ fit に渡された X と y をコピーして保存するだけのラッパー DummyRegressor を作ります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# NOTE: モデルのsample_weightを確認するために作成したモデル
class DummyRegressor:
    def __init__(self):
        self.model = DecisionTreeRegressor(max_depth=5)
        self.error_vector = None
        self.X_for_plot = None
        self.y_for_plot = None

    def fit(self, X, y):
        self.model.fit(X, y)
        y_pred = self.model.predict(X)

        # 重みは回帰の誤差を元に計算する
        # https://github.com/scikit-learn/scikit-learn/blob/main/sklearn/ensemble/_weight_boosting.py#L1130
        self.error_vector = np.abs(y_pred - y)
        self.X_for_plot = X.copy()
        self.y_for_plot = y.copy()
        return self.model

    def predict(self, X, check_input=True):
        return self.model.predict(X)

    def get_params(self, deep=False):
        return {}

    def set_params(self, deep=False):
        return {}

訓練データに回帰モデルを当てはめる #

まず $y = 10(\sin x + \cos 4x) + 10$ に緩やかな傾きを足した 500 点を作り、DummyRegressor を 100 本積んだ AdaBoostRegressor で当てはめます。最終モデルが波形をどこまで再現できるかを散布図と赤い線で確認します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 訓練データ
X = np.linspace(-10, 10, 500)[:, np.newaxis]
y = (np.sin(X).ravel() + np.cos(4 * X).ravel()) * 10 + 10 + np.linspace(-2, 2, 500)

## 回帰モデルを作成
reg = AdaBoostRegressor(
    DummyRegressor(),
    n_estimators=100,
    random_state=100,
    loss="linear",
    learning_rate=0.8,
)
reg.fit(X, y)
y_pred = reg.predict(X)

# 訓練データへのフィッティング具合を確認する
plt.figure(figsize=(10, 5))
plt.scatter(X, y, c="k", marker="x", label="訓練データ")
plt.plot(X, y_pred, c="r", label="最終的に作成されたモデルの予測", linewidth=1)
plt.xlabel("x")
plt.ylabel("y")
plt.title("訓練データへのフィッティング具合")
plt.legend()
plt.show()

png

赤い線は 12 個以上の山と谷のほぼすべてに追従しています。深さ 5 の木 1 本なら階段状に粗く近似されるところを、100 本の加重中央値で滑らかに埋めている点がブースティングの効果です。

標本の重みを可視化(loss=“linear” の場合) #

AdaBoost 回帰では、直前の木の誤差 $|y_i - h_t(x_i)|$ から次のラウンドの重みを決め、その重みに比例した確率で訓練点を引き直します。重みが大きい点は何回も複製されて次の木に渡るので、「何回選ばれたか」を数えれば重みの大きさが見えます。ここでは loss="linear" で 0 ラウンド目と 100 ラウンド目の選ばれ方をオレンジの丸の大きさとして描きます。

loss{‘linear’, ‘square’, ‘exponential’}, default=’linear’ The loss function to use when updating the weights after each boosting iteration.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def visualize_weight(reg, X, y, y_pred):
    """標本の重みに相当する値(サンプリングされた数)をプロットするための関数

    Parameters
    ----------
    reg : sklearn.ensemble._weight_boosting
        ブースティングモデル
    X : numpy.ndarray
        訓練データ
    y : numpy.ndarray
        教師データ
    y_pred:
        モデルの予測値
    """
    assert reg.estimators_ is not None, "len(reg.estimators_) > 0"

    for i, estimators_i in enumerate(reg.estimators_):
        if i % 100 == 0:
            # i番目のモデル作成に使用したデータに、データが何回出現するかをカウント
            weight_dict = {xi: 0 for xi in X.ravel()}
            for xi in estimators_i.X_for_plot.ravel():
                weight_dict[xi] += 1

            # 出現回数をグラフにオレンジ円でプロットする(多いほど大きい円)
            weight_x_sorted = sorted(weight_dict.items(), key=lambda x: x[0])
            weight_vec = np.array([s * 100 for xi, s in weight_x_sorted])

            # グラフをプロット
            plt.figure(figsize=(10, 5))
            plt.title(f"{i}番目のモデル作成後の重み付き標本の可視化, loss={reg.loss}")
            plt.scatter(X, y, c="k", marker="x", label="訓練データ")
            plt.scatter(
                estimators_i.X_for_plot,
                estimators_i.y_for_plot,
                marker="o",
                alpha=0.2,
                c="orange",
                s=weight_vec,
            )
            plt.plot(X, y_pred, c="r", label="最終的に作成されたモデルの予測", linewidth=2)
            plt.legend(loc="upper right")
            plt.show()

## loss="linear"で回帰モデルを作成
reg = AdaBoostRegressor(
    DummyRegressor(),
    n_estimators=101,
    random_state=100,
    loss="linear",
    learning_rate=1,
)
reg.fit(X, y)
y_pred = reg.predict(X)
visualize_weight(reg, X, y, y_pred)

png

png

0 番目の図では、ブートストラップの偶然で 0〜3 回選ばれた点がほぼ一様に散っています。100 番目の図では、波の山と谷の先端や右端の点に大きな丸が集まり、曲線の中腹の点は丸が小さいか消えています。深さ 5 の木が苦手な「先端」に重みが寄った結果です。

loss=“square” にすると重みはどう変わるか #

同じデータと同じ random_state で、loss だけを square に替えます。個体誤差が $e_i^2$ になるので、中程度の誤差の点はほぼ当たり扱いになり、重みは最も外れた少数の点に集まるはずです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
## loss="square"で回帰モデルを作成
reg = AdaBoostRegressor(
    DummyRegressor(),
    n_estimators=101,
    random_state=100,
    loss="square",
    learning_rate=1,
)
reg.fit(X, y)
y_pred = reg.predict(X)
visualize_weight(reg, X, y, y_pred)

png

png

square の 100 番目の図では、linear よりも大きな丸の数が減り、その代わりに 1 つ 1 つがさらに大きくなっています。誤差を 2 乗して測ると「少し外れた点」は軽視され、「大きく外れた点」だけが何十回も引き直されるためです。外れ値が混ざったデータでは、この集中が外れ値への過剰適合につながるので、まず loss="linear" を試し、重みの偏りを見てから切り替えるのが安全です。

使いどころと落とし穴 #

AdaBoost 回帰が向くのは、上の波形のように 1 本の木では取り切れない細かい構造があり、ラベルのノイズが少ないデータです。弱点は外れ値です。1 点でも大きく外れた値があると、その点は毎ラウンド最大誤差 $e_i = 1$ を取り続けて重みが縮まず、後半の木がその 1 点を当てるためだけに費やされます。対策は loss="linear" を使う、learning_rate を 0.5 以下に落とす、あるいは Huber 損失を選べる 勾配ブースティング に替えることです。n_estimators は 100 本程度から始め、評価データの誤差が下がり止まる手前で止めます。

まとめ #

AdaBoost 回帰(AdaBoost.R2)は、回帰木を順番に学習し、誤差 $|y_i - h_t(x_i)|$ が大きい点が次のラウンドで多く引き直されるように重みを更新する手法です。木ごとの信頼度 $\beta_t = \epsilon_t / (1 - \epsilon_t)$ から当たった点の重みを $\beta_t^{1-e_i}$ 倍に縮め、最終予測は $-\ln \beta_t$ を重みにした加重中央値で求めます。AdaBoostRegressor の loss を linear から square に替えると、重みは中腹の点から最も外れた少数の点へ移り、可視化ではオレンジの丸が少数・大型になりました。visualize_weight のような図で重みがどこに集まるかを一度確かめると、外れ値に引きずられているかどうかを判断できます。

推定器数と AdaBoost 回帰 #

スライダーで n_estimators を 1 本から 200 本まで動かすと、予測の階段状の線がどう滑らかになり、train_mse と test_mse がどこで離れ始めるかを確認できます。