AdaBoostの理論と導出をわかりやすく解説して実装する

アンサンブル学習の代表的手法の1つである AdaBoost(Adaptive Boosting)は、単体では精度の低い「弱学習器」を逐次的に組み合わせて、高精度な「強学習器」を構築するブースティングの代表アルゴリズムです。1995年に Freund と Schapire によって提案され、理論的な裏付けの美しさと実用的な性能の高さから、機械学習の歴史に大きな足跡を残しました。

バギングが各学習器を独立に訓練するのに対し、ブースティングは前の学習器が間違えたサンプルに重点を置いて次の学習器を訓練するという逐次的な戦略を取ります。本記事では、AdaBoost のアルゴリズムを数式で丁寧に導出し、指数損失関数との関係、訓練誤差の理論的上界、マージン理論による汎化性能の説明まで踏み込み、Python でのスクラッチ実装を行います。

本記事の内容

  • ブースティングの基本思想と AdaBoost アルゴリズム
  • 重み更新式と学習器の重み $\alpha_t$ の導出
  • 前向き段階的加法モデルとしての AdaBoost の導出
  • 指数損失関数 $L = E[\exp(-yf(\bm{x}))]$ の最小化との関係
  • 訓練誤差の指数的減少の証明
  • マージン理論と汎化性能
  • Python での Decision Stump ベース AdaBoost のスクラッチ実装

前提知識

この記事を読む前に、以下の記事を読んでおくと理解が深まります。

ブースティングの基本思想

弱学習器と強学習器

ブースティングの出発点は、以下の問いにあります。

「ランダムよりわずかに良い程度の弱い学習器を組み合わせて、強い学習器を作ることができるか?」

形式的に定義しましょう。二値分類問題を考え、ラベルを $y \in \{-1, +1\}$ とします。弱学習器 $h(\bm{x})$ は、誤り率が $1/2$ より小さいもの、すなわち

$$ \epsilon = P(h(\bm{x}) \neq y) < \frac{1}{2} $$

を満たす学習器です。ブースティングの理論は、このような弱学習器を適切に組み合わせることで、誤り率をいくらでも小さくできることを保証します。

逐次的な学習戦略

バギングでは各学習器をブートストラップサンプルで独立に訓練しましたが、ブースティングは以下の戦略を取ります。

  1. まず全サンプルに均等な重みを与えて弱学習器を訓練する
  2. 誤分類されたサンプルの重みを増加させる
  3. 更新された重み付きデータで次の弱学習器を訓練する
  4. これを繰り返し、全学習器の重み付き多数決で最終予測を行う

この「前のステップの失敗に適応(Adaptive)する」という性質が、AdaBoost の名前の由来です。

AdaBoost アルゴリズム

問題設定

訓練データ $\{(\bm{x}_i, y_i)\}_{i=1}^{N}$ が与えられたとします。ここで $\bm{x}_i \in \mathbb{R}^d$、$y_i \in \{-1, +1\}$ です。

AdaBoost は $T$ 個の弱学習器 $h_1, h_2, \dots, h_T$ を逐次的に訓練し、最終的な分類器を

$$ H(\bm{x}) = \text{sign}\left(\sum_{t=1}^{T} \alpha_t h_t(\bm{x})\right) $$

として構築します。ここで $\alpha_t > 0$ は各弱学習器の「信頼度」を表す重みです。

アルゴリズムの手順

入力: 訓練データ $\{(\bm{x}_i, y_i)\}_{i=1}^{N}$、弱学習器の数 $T$

初期化: サンプル重みを均等に設定

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

For $t = 1, 2, \dots, T$:

Step 1: 重み分布 $\{w_i^{(t)}\}$ のもとで弱学習器 $h_t$ を訓練する。

Step 2: 重み付き誤り率を計算する。

$$ \epsilon_t = \sum_{i=1}^{N} w_i^{(t)} \mathbb{1}[h_t(\bm{x}_i) \neq y_i] $$

Step 3: 学習器の重みを計算する。

$$ \alpha_t = \frac{1}{2} \ln \frac{1 – \epsilon_t}{\epsilon_t} $$

Step 4: サンプル重みを更新する。

$$ w_i^{(t+1)} = \frac{w_i^{(t)} \exp(-\alpha_t y_i h_t(\bm{x}_i))}{Z_t} $$

ここで $Z_t$ は正規化定数

$$ Z_t = \sum_{i=1}^{N} w_i^{(t)} \exp(-\alpha_t y_i h_t(\bm{x}_i)) $$

出力: $H(\bm{x}) = \text{sign}\left(\sum_{t=1}^{T} \alpha_t h_t(\bm{x})\right)$

重み更新の直感的理解

$y_i h_t(\bm{x}_i)$ の符号に着目しましょう。

  • 正しく分類された場合: $y_i h_t(\bm{x}_i) = +1$ なので $\exp(-\alpha_t) < 1$ が掛かり、重みが減少します。
  • 誤分類された場合: $y_i h_t(\bm{x}_i) = -1$ なので $\exp(+\alpha_t) > 1$ が掛かり、重みが増加します。

これにより、次のラウンドでは誤分類されたサンプルにより多くの注意が払われます。

$\alpha_t$ の直感的理解

$\alpha_t = \frac{1}{2} \ln \frac{1 – \epsilon_t}{\epsilon_t}$ の性質を確認しましょう。

  • $\epsilon_t \to 0$(ほぼ完璧な学習器)のとき $\alpha_t \to +\infty$:大きな信頼度
  • $\epsilon_t = 1/2$(ランダム推測)のとき $\alpha_t = 0$:信頼度ゼロ
  • $\epsilon_t \to 1$(ほぼ逆を予測)のとき $\alpha_t \to -\infty$:反転して利用

$\alpha_t$ の導出:指数損失関数の最小化

ここでは、$\alpha_t$ の式がどのように導かれるかを厳密に示します。AdaBoost は「前向き段階的加法モデル(Forward Stagewise Additive Modeling)」として解釈できます。

前向き段階的加法モデル

加法モデルを次のように定義します。

$$ f(\bm{x}) = \sum_{t=1}^{T} \alpha_t h_t(\bm{x}) $$

このモデルを段階的に構築します。ラウンド $t$ 開始時の累積モデルを

$$ f_{t-1}(\bm{x}) = \sum_{s=1}^{t-1} \alpha_s h_s(\bm{x}) $$

とすると、ラウンド $t$ では指数損失関数

$$ L(\alpha_t, h_t) = \sum_{i=1}^{N} \exp\left(-y_i \left(f_{t-1}(\bm{x}_i) + \alpha_t h_t(\bm{x}_i)\right)\right) $$

を最小化する $\alpha_t$ と $h_t$ を求めます。

指数損失の展開

上式を展開します。

$$ \begin{align} L(\alpha_t, h_t) &= \sum_{i=1}^{N} \exp\left(-y_i f_{t-1}(\bm{x}_i)\right) \exp\left(-y_i \alpha_t h_t(\bm{x}_i)\right) \end{align} $$

ここで $w_i^{(t)} = \exp\left(-y_i f_{t-1}(\bm{x}_i)\right)$ と置くと(これは前のラウンドまでの結果で決まる定数)、

$$ L(\alpha_t, h_t) = \sum_{i=1}^{N} w_i^{(t)} \exp\left(-y_i \alpha_t h_t(\bm{x}_i)\right) $$

$y_i, h_t(\bm{x}_i) \in \{-1, +1\}$ なので $y_i h_t(\bm{x}_i) \in \{-1, +1\}$ です。正分類の集合と誤分類の集合に分けます。

$$ \begin{align} L(\alpha_t, h_t) &= \sum_{i: y_i = h_t(\bm{x}_i)} w_i^{(t)} e^{-\alpha_t} + \sum_{i: y_i \neq h_t(\bm{x}_i)} w_i^{(t)} e^{\alpha_t} \end{align} $$

$h_t$ の最適化

$\alpha_t > 0$ を固定したとき、$L$ を最小化する $h_t$ は、$e^{\alpha_t}$ の係数(誤分類の重み和)を最小化する学習器です。これは重み付き誤分類率

$$ \epsilon_t = \frac{\sum_{i: y_i \neq h_t(\bm{x}_i)} w_i^{(t)}}{\sum_{i=1}^{N} w_i^{(t)}} $$

を最小化することと等価です。

$\alpha_t$ の最適化

$h_t$ を固定して $\alpha_t$ で微分します。重みの総和を $W = \sum_{i} w_i^{(t)}$ とおくと

$$ L(\alpha_t) = W \left[(1 – \epsilon_t) e^{-\alpha_t} + \epsilon_t e^{\alpha_t}\right] $$

$\alpha_t$ で微分して 0 とおきます。

$$ \frac{\partial L}{\partial \alpha_t} = W \left[-(1 – \epsilon_t) e^{-\alpha_t} + \epsilon_t e^{\alpha_t}\right] = 0 $$

整理すると

$$ \epsilon_t e^{\alpha_t} = (1 – \epsilon_t) e^{-\alpha_t} $$

$$ e^{2\alpha_t} = \frac{1 – \epsilon_t}{\epsilon_t} $$

両辺の対数を取ると

$$ \alpha_t = \frac{1}{2} \ln \frac{1 – \epsilon_t}{\epsilon_t} $$

これが AdaBoost の $\alpha_t$ の公式です。指数損失関数を前向き段階的に最小化することから、$\alpha_t$ の式が自然に導かれることがわかりました。

訓練誤差の上界と指数的減少

AdaBoost の理論的な強みの1つは、訓練誤差が指数的に減少することが証明できる点です。

訓練誤差の上界

最終分類器 $H(\bm{x}) = \text{sign}(f_T(\bm{x}))$ の訓練誤差は

$$ \frac{1}{N} \sum_{i=1}^{N} \mathbb{1}[H(\bm{x}_i) \neq y_i] $$

です。ここで、$\mathbb{1}[y_i f_T(\bm{x}_i) \leq 0] \leq \exp(-y_i f_T(\bm{x}_i))$ が成り立つことを利用します($y_i f_T(\bm{x}_i) \leq 0$ のとき右辺は $\geq 1$、$y_i f_T(\bm{x}_i) > 0$ のとき左辺は 0)。

$$ \frac{1}{N} \sum_{i=1}^{N} \mathbb{1}[H(\bm{x}_i) \neq y_i] \leq \frac{1}{N} \sum_{i=1}^{N} \exp(-y_i f_T(\bm{x}_i)) $$

右辺を展開します。$f_T(\bm{x}_i) = \sum_{t=1}^{T} \alpha_t h_t(\bm{x}_i)$ なので

$$ \frac{1}{N} \sum_{i=1}^{N} \exp\left(-y_i \sum_{t=1}^{T} \alpha_t h_t(\bm{x}_i)\right) $$

正規化定数 $Z_t$ との関係

初期重み $w_i^{(1)} = 1/N$ から再帰的に重みを追跡します。重みの更新式は

$$ w_i^{(t+1)} = \frac{w_i^{(t)} \exp(-\alpha_t y_i h_t(\bm{x}_i))}{Z_t} $$

これを $t = 1$ から $T$ まで展開すると

$$ w_i^{(T+1)} = \frac{w_i^{(1)} \prod_{t=1}^{T} \exp(-\alpha_t y_i h_t(\bm{x}_i))}{\prod_{t=1}^{T} Z_t} = \frac{\frac{1}{N} \exp(-y_i f_T(\bm{x}_i))}{\prod_{t=1}^{T} Z_t} $$

$w_i^{(T+1)} \geq 0$ であり、$\sum_i w_i^{(T+1)} = 1$ なので

$$ \frac{1}{N} \sum_{i=1}^{N} \exp(-y_i f_T(\bm{x}_i)) = \prod_{t=1}^{T} Z_t $$

したがって、訓練誤差の上界は

$$ \frac{1}{N} \sum_{i=1}^{N} \mathbb{1}[H(\bm{x}_i) \neq y_i] \leq \prod_{t=1}^{T} Z_t $$

$Z_t$ の評価

各 $Z_t$ を計算します。

$$ \begin{align} Z_t &= \sum_{i=1}^{N} w_i^{(t)} \exp(-\alpha_t y_i h_t(\bm{x}_i)) \\ &= (1 – \epsilon_t) e^{-\alpha_t} + \epsilon_t e^{\alpha_t} \end{align} $$

ここで重みは正規化されているとしました。$\alpha_t = \frac{1}{2}\ln\frac{1-\epsilon_t}{\epsilon_t}$ を代入すると

$$ e^{-\alpha_t} = \sqrt{\frac{\epsilon_t}{1 – \epsilon_t}}, \quad e^{\alpha_t} = \sqrt{\frac{1 – \epsilon_t}{\epsilon_t}} $$

よって

$$ \begin{align} Z_t &= (1 – \epsilon_t) \sqrt{\frac{\epsilon_t}{1 – \epsilon_t}} + \epsilon_t \sqrt{\frac{1 – \epsilon_t}{\epsilon_t}} \\ &= \sqrt{(1 – \epsilon_t)\epsilon_t} + \sqrt{\epsilon_t(1 – \epsilon_t)} \\ &= 2\sqrt{\epsilon_t(1 – \epsilon_t)} \end{align} $$

指数的減少

$\gamma_t = 1/2 – \epsilon_t > 0$(弱学習器の「エッジ」)とおくと

$$ \epsilon_t = \frac{1}{2} – \gamma_t, \quad 1 – \epsilon_t = \frac{1}{2} + \gamma_t $$

$$ Z_t = 2\sqrt{\left(\frac{1}{2} – \gamma_t\right)\left(\frac{1}{2} + \gamma_t\right)} = 2\sqrt{\frac{1}{4} – \gamma_t^2} = \sqrt{1 – 4\gamma_t^2} $$

$\sqrt{1 – x} \leq e^{-x/2}$($x \geq 0$)を用いると

$$ Z_t \leq e^{-2\gamma_t^2} $$

したがって

$$ \frac{1}{N} \sum_{i=1}^{N} \mathbb{1}[H(\bm{x}_i) \neq y_i] \leq \prod_{t=1}^{T} Z_t \leq \prod_{t=1}^{T} e^{-2\gamma_t^2} = \exp\left(-2\sum_{t=1}^{T} \gamma_t^2\right) $$

各弱学習器がランダムより少しでも良ければ($\gamma_t > 0$)、ラウンド数 $T$ の増加に伴い訓練誤差は指数的に 0 に収束します。特に $\gamma_t \geq \gamma > 0$(一様にエッジが下から抑えられる場合)には

$$ \text{訓練誤差} \leq \exp(-2T\gamma^2) $$

となり、$T$ に対して指数的に減少します。

マージン理論と汎化性能

マージンの定義

AdaBoost が過学習しにくい理由は、マージン理論によって説明されます。サンプル $(\bm{x}_i, y_i)$ のマージンを

$$ \text{margin}(\bm{x}_i, y_i) = \frac{y_i \sum_{t=1}^{T} \alpha_t h_t(\bm{x}_i)}{\sum_{t=1}^{T} \alpha_t} $$

と定義します。マージンは $[-1, +1]$ の値を取り、正のマージンは正しく分類されていることを意味します。マージンの絶対値が大きいほど、分類の確信度が高いことを表します。

Schapire らの汎化誤差上界

Schapire, Freund, Bartlett, Lee (1998) は、任意のマージン閾値 $\theta > 0$ に対して、確率 $1 – \delta$ 以上で

$$ P(yf(\bm{x}) \leq 0) \leq \hat{P}(\text{margin} \leq \theta) + O\left(\sqrt{\frac{\log N / \delta}{N}} \cdot \frac{1}{\theta}\right) $$

が成り立つことを示しました。ここで $\hat{P}$ は経験分布です。

重要なのは、この上界がブースティングのラウンド数 $T$ に直接は依存しないという点です。AdaBoost は $T$ を増やしてもマージンを拡大し続ける傾向があり、これが過学習しにくさの理論的根拠となっています。

AdaBoost がマージンを最大化する理由

AdaBoost が最小化する指数損失 $\exp(-y_i f(\bm{x}_i))$ は、マージンが大きくなるほど小さくなります。そのため、訓練誤差が 0 になった後も、ラウンドを続けることでマージンを拡大し、汎化性能を向上させ続ける効果があります。

ただし、ノイズの多いデータでは、外れ値に過度にフィットしようとして過学習が起こり得ます。これは指数損失が外れ値に対して敏感であるためで、後述の勾配ブースティングでは対数損失等の代替損失関数を用いることでこの問題を緩和できます。

Python での AdaBoost スクラッチ実装

Decision Stump(決定株)

AdaBoost の弱学習器として最もよく使われるのが Decision Stump(深さ1の決定木)です。1つの特徴量に対する単純な閾値分類器で、「$x_j \leq \theta$ なら +1、そうでなければ -1」という形をしています。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap

class DecisionStump:
    """決定株(深さ1の決定木)"""
    def __init__(self):
        self.feature_index = None  # 使用する特徴量のインデックス
        self.threshold = None      # 閾値
        self.polarity = None       # 分類の向き(+1 or -1)

    def fit(self, X, y, sample_weight):
        """重み付きデータに対して最適な決定株を学習"""
        n_samples, n_features = X.shape
        min_error = float('inf')

        for feature_idx in range(n_features):
            # この特徴量の値を取得してソート
            feature_values = X[:, feature_idx]
            thresholds = np.unique(feature_values)

            for threshold in thresholds:
                for polarity in [1, -1]:
                    # 予測
                    predictions = np.ones(n_samples)
                    if polarity == 1:
                        predictions[feature_values <= threshold] = -1
                    else:
                        predictions[feature_values > threshold] = -1

                    # 重み付き誤り率
                    error = np.sum(sample_weight[predictions != y])

                    if error < min_error:
                        min_error = error
                        self.feature_index = feature_idx
                        self.threshold = threshold
                        self.polarity = polarity

    def predict(self, X):
        """予測"""
        n_samples = X.shape[0]
        predictions = np.ones(n_samples)
        feature_values = X[:, self.feature_index]
        if self.polarity == 1:
            predictions[feature_values <= self.threshold] = -1
        else:
            predictions[feature_values > self.threshold] = -1
        return predictions


class AdaBoostClassifier:
    """AdaBoostのスクラッチ実装"""
    def __init__(self, n_estimators=50):
        self.n_estimators = n_estimators
        self.alphas = []       # 各学習器の重み
        self.stumps = []       # 弱学習器のリスト
        self.errors = []       # 各ラウンドの重み付き誤り率
        self.train_errors = [] # 各ラウンドの訓練誤差

    def fit(self, X, y):
        """AdaBoostの学習"""
        n_samples = X.shape[0]
        # 初期重み
        w = np.full(n_samples, 1.0 / n_samples)

        for t in range(self.n_estimators):
            # Step 1: 弱学習器の学習
            stump = DecisionStump()
            stump.fit(X, y, w)
            predictions = stump.predict(X)

            # Step 2: 重み付き誤り率
            incorrect = (predictions != y)
            epsilon_t = np.sum(w[incorrect])

            # 誤り率が0.5以上なら打ち切り
            if epsilon_t >= 0.5:
                break

            # Step 3: 学習器の重み
            alpha_t = 0.5 * np.log((1 - epsilon_t) / (epsilon_t + 1e-10))

            # Step 4: サンプル重みの更新
            w = w * np.exp(-alpha_t * y * predictions)
            w = w / np.sum(w)  # 正規化

            self.alphas.append(alpha_t)
            self.stumps.append(stump)
            self.errors.append(epsilon_t)

            # 現時点での訓練誤差を記録
            cumulative_pred = self.predict(X)
            train_error = np.mean(cumulative_pred != y)
            self.train_errors.append(train_error)

    def predict(self, X):
        """最終予測(重み付き多数決)"""
        n_samples = X.shape[0]
        f = np.zeros(n_samples)
        for alpha, stump in zip(self.alphas, self.stumps):
            f += alpha * stump.predict(X)
        return np.sign(f)

    def decision_function(self, X):
        """決定関数の値(マージン計算用)"""
        n_samples = X.shape[0]
        f = np.zeros(n_samples)
        for alpha, stump in zip(self.alphas, self.stumps):
            f += alpha * stump.predict(X)
        return f

学習過程の可視化

合成データで AdaBoost を訓練し、学習過程を可視化しましょう。

# 合成データの生成(月型データ)
from sklearn.datasets import make_moons

np.random.seed(42)
X, y = make_moons(n_samples=200, noise=0.25, random_state=42)
y = 2 * y - 1  # {0,1} -> {-1,+1}

# AdaBoostの学習
ada = AdaBoostClassifier(n_estimators=100)
ada.fit(X, y)

# --- 可視化1: 訓練誤差と重み付き誤り率の推移 ---
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# 訓練誤差
axes[0].plot(range(1, len(ada.train_errors) + 1), ada.train_errors, 'b-', linewidth=2)
axes[0].set_xlabel('Number of estimators', fontsize=12)
axes[0].set_ylabel('Training error', fontsize=12)
axes[0].set_title('Training Error vs Number of Estimators', fontsize=14)
axes[0].grid(True, alpha=0.3)

# 理論上界(Zt の積)
Z_products = []
z_prod = 1.0
for eps in ada.errors:
    z_t = 2 * np.sqrt(eps * (1 - eps))
    z_prod *= z_t
    Z_products.append(z_prod)
axes[0].plot(range(1, len(Z_products) + 1), Z_products, 'r--', linewidth=2, label='Upper bound $\\prod Z_t$')
axes[0].legend(fontsize=11)

# 各ラウンドの alpha_t
axes[1].bar(range(1, len(ada.alphas) + 1), ada.alphas, color='steelblue', alpha=0.7)
axes[1].set_xlabel('Round $t$', fontsize=12)
axes[1].set_ylabel('$\\alpha_t$', fontsize=12)
axes[1].set_title('Weight of Each Weak Learner', fontsize=14)
axes[1].grid(True, alpha=0.3, axis='y')

plt.tight_layout()
plt.savefig('adaboost_training_process.png', dpi=150, bbox_inches='tight')
plt.show()
# --- 可視化2: 決定境界の変化 ---
fig, axes = plt.subplots(2, 3, figsize=(16, 10))

x_min, x_max = X[:, 0].min() - 0.5, X[:, 0].max() + 0.5
y_min, y_max = X[:, 1].min() - 0.5, X[:, 1].max() + 0.5
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 200),
                      np.linspace(y_min, y_max, 200))
X_grid = np.c_[xx.ravel(), yy.ravel()]

rounds_to_show = [1, 3, 5, 10, 30, 100]

for idx, n_est in enumerate(rounds_to_show):
    ax = axes[idx // 3, idx % 3]
    # n_est ラウンドまでの累積予測
    f = np.zeros(X_grid.shape[0])
    actual_rounds = min(n_est, len(ada.alphas))
    for t in range(actual_rounds):
        f += ada.alphas[t] * ada.stumps[t].predict(X_grid)

    Z = np.sign(f).reshape(xx.shape)
    ax.contourf(xx, yy, Z, alpha=0.3, cmap=ListedColormap(['#FF9999', '#9999FF']))
    ax.scatter(X[y == -1, 0], X[y == -1, 1], c='red', edgecolors='k', s=20, label='$y=-1$')
    ax.scatter(X[y == 1, 0], X[y == 1, 1], c='blue', edgecolors='k', s=20, label='$y=+1$')
    ax.set_title(f'T = {actual_rounds}', fontsize=13)
    ax.set_xlim(x_min, x_max)
    ax.set_ylim(y_min, y_max)

plt.suptitle('Decision Boundary Evolution of AdaBoost', fontsize=16, y=1.02)
plt.tight_layout()
plt.savefig('adaboost_decision_boundary.png', dpi=150, bbox_inches='tight')
plt.show()
# --- 可視化3: マージン分布の変化 ---
fig, axes = plt.subplots(1, 3, figsize=(16, 4))

for idx, n_est in enumerate([5, 20, 100]):
    ax = axes[idx]
    actual_rounds = min(n_est, len(ada.alphas))

    # マージンの計算
    f = np.zeros(X.shape[0])
    alpha_sum = 0
    for t in range(actual_rounds):
        f += ada.alphas[t] * ada.stumps[t].predict(X)
        alpha_sum += ada.alphas[t]

    margins = y * f / alpha_sum

    ax.hist(margins, bins=30, edgecolor='black', alpha=0.7, color='steelblue')
    ax.axvline(x=0, color='red', linestyle='--', linewidth=2)
    ax.set_xlabel('Margin', fontsize=12)
    ax.set_ylabel('Count', fontsize=12)
    ax.set_title(f'Margin Distribution (T={actual_rounds})', fontsize=13)
    ax.set_xlim(-1.1, 1.1)
    ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('adaboost_margin_distribution.png', dpi=150, bbox_inches='tight')
plt.show()

print(f"最終訓練誤差: {ada.train_errors[-1]:.4f}")
print(f"使用した弱学習器の数: {len(ada.alphas)}")
print(f"理論上界 (prod Z_t): {Z_products[-1]:.6f}")

マージン分布の図を見ると、ラウンド数 $T$ を増やすにつれてマージンが右(正の方向)にシフトし、分布が広がっていく様子がわかります。これは、AdaBoost が単に訓練誤差を 0 にするだけでなく、マージンを拡大し続けていることを示しています。

まとめ

本記事では、AdaBoost の理論と導出を詳しく解説しました。

  • ブースティングの基本思想: 弱学習器を逐次的に組み合わせ、前のステップで誤分類されたサンプルに重点を置いて次の学習器を訓練します
  • AdaBoost アルゴリズム: サンプル重みの更新と学習器の重み $\alpha_t = \frac{1}{2}\ln\frac{1-\epsilon_t}{\epsilon_t}$ の計算を反復します
  • 前向き段階的加法モデル: 指数損失関数 $\exp(-y f(\bm{x}))$ を段階的に最小化することから、AdaBoost が自然に導かれます
  • 訓練誤差の指数的減少: $\text{訓練誤差} \leq \exp(-2\sum_t \gamma_t^2)$ により、弱学習器の条件さえ満たせば訓練誤差は指数的に 0 に収束します
  • マージン理論: 汎化誤差上界がラウンド数 $T$ に直接依存せず、マージンによって制御されるため、AdaBoost は過学習しにくい傾向があります

次のステップとして、以下の記事も参考にしてください。