たった1つのモデルで満足してない？精度を劇的に上げるアンサンブル学習の実力とは

たった1つのモデルで満足していませんか？
実は、複数のモデルを組み合わせるだけで、予測精度を劇的にアップさせる方法があるんです！この記事では、その秘密兵器「アンサンブル学習」の仕組みをわかりやすく解説。さらに、Pythonでの実装例も交えて、あなたのモデルを一段上のレベルへ引き上げます。

本記事では以下の流れで説明する。
- [1 アンサンブル学習とは](#1-アンサンブル学習とは)
    - [1.1 多数決によるアンサンブル学習の実装](#11-多数決によるアンサンブル学習の実装)
    - [1.2 ハイパーパラメータチューニングとアンサンブル学習の実装](#12-ハイパーパラメータチューニングとアンサンブル学習の実装)
- [2 バギング](#2-バギング)
    - [2.1 バギングの実装](#21-バギングの実装)
- [3 ブースティング](#3-ブースティング)
    - [3.1 ブースティングとバギングの比較](#31-ブースティングとバギングの比較)
    - [3.2 アダブーストの実装](#32-アダブーストの実装)
    - [3.3 その他のブースティング手法](#33-その他のブースティング手法)
        - [3.3.1 Gradient Boosting（勾配ブースティング）](#331-gradient-boosting勾配ブースティング)
        - [3.3.2 XGBoost（Extreme Gradient Boosting）](#332-xgboostextreme-gradient-boosting)
        - [3.3.3 LightGBM（Light Gradient Boosting Machine）](#333-lightgbmlight-gradient-boosting-machine)
        - [3.3.4 CatBoost（Categorical Boosting）](#334-catboostcategorical-boosting)



# 1 アンサンブル学習とは
1人の専門家の予測よりも10人の専門家の予測を合わせた方が、良い予測ができそうですよね。これを機械学習で実践するのが**アンサンブル学習です**。
アンサンブル学習は様々な分類アルゴリズムまたは、同じ分類アルゴリズムに訓練データの異なる部分を使って、モデルを学習させ、それらの予測結果を組み合わせる手法です。このアプローチの典型例として、様々な分類決定木を組み合わせたランダムフォレストアルゴリズムがあります。
アンサンブル学習で最もよく使われるのが多数決の原理で、二値問題でも他クラスの分類問題でも、最も得票数の多いクラスラベルが最終的な予測として選択されます。下図では、Layer2に至る過程の学習の部分で多数決を行なっています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4072443/d2247c11-0563-4642-b63d-80ec52e50a9a.png" width="100%">
出典：https://datawokagaku.com/ensemble/

1つの予測よりも複数の予測を組み合わせた方が、予測精度が良くなることを数学的に証明しましょう。

まず、分類器の数を$T$個（ここでは奇数と仮定)として、各分類器の単体での誤分類率を$\epsilon$とします。分類器の誤類率は独立であり、これらの分類器を用いたアンサンブル学習の結果の多数決によって最終予測を決めるとします。
各分類器が独立に誤分類する確率が$\epsilon$のとき、$T$個の分類器のうちちょうど$k$個が誤分類する確率は、二項分布に従います。これを確率質量関数（PMF）を使って表すと、

$$
P(Y = k) = \binom{T}{k} \epsilon^k (1-\epsilon)^{T-k}, \quad k=0,1,\ldots,T
$$

ここで、$Y$はアンサンブル内で誤分類した分類器の数（確率変数）です。

次にアンサンブル全体の誤分類確率を考えます。アンサンブルが誤分類するのは、「誤分類した分類器の数$Y$が過半数を超える」場合です。  
つまり、以下のように書けます。

$$
P(\text{アンサンブル誤分類}) = P\left(Y > \frac{T}{2}\right) = \sum_{k=\lceil \frac{T}{2} + 1 \rceil}^{T} \binom{T}{k} \epsilon^k (1-\epsilon)^{T-k}
$$

ここで、$\lceil$$x$$\rceil$は$x$の切り上げを表します。

例として、分類器が3個ある（$T$=3）ときの誤分類率を考えましょう。分類器が3個ある時、アンサンブルが誤分類をするには、2個以上の分類器が誤分類をする必要があります（$k\geq2$）。それぞれの分類器の誤分類率を0.25（$\epsilon=0.25$）とすると、この場合の誤分類率は、

$$
P(\text{誤分類}) = P(Y \geq 2) = P(Y=2) + P(Y=3)
$$

$$
\begin{aligned}
&= \binom{3}{2} \epsilon^{2} (1-\epsilon)^{1} + \binom{3}{3} \epsilon^{3} (1-\epsilon)^{0}= 3 \epsilon^2 (1-\epsilon) + \epsilon^3 \\\\
&= 0.15625
\end{aligned}
$$

その結果、アンサンブル学習なしでは0.25の誤分類率だったが、アンサンブルを使うことで0.15まで誤分類率を下げることができました。理論上、分類器の数を増やすほど、誤分類率を低下させることができます。

さらに、アンサンブル学習ではそれぞれの分類器の予測結果に重み付けを行うこともできます。例えば、3つの分類器（$C_1, C_2, C_3$）の予測に基づいて、最終結果が得られる時、最終予測結果$\hat{y}$は以下のように表せます。

$$
\hat{y} = \mathrm{mode}\lbrace C_1(x), C_2(x), C_3(x) \rbrace
$$

ここで、それぞれの分類結果が以下のような時、

$$
C_1(x)→0, C_2(x)→0, C_3(x)→1
$$

これらの分類器の予測に等しく重みをつけた場合は、多数決によりデータ点はクラス0に所属すると予測される。
そこで、$C_3$の重み係数を0.6、そのほかの重み係数を0.2とすると、

$$
\hat{y} = \underset{i}{\mathrm{argmax}} \sum_{j=1}^{m} w_j \chi_{A}(C_j(x) = i)
$$

$$
= \underset{i}{\mathrm{argmax}} \left[ 0.2 \cdot i_0 + 0.2 \cdot i_0 + 0.6 \cdot i_1 \right] = 1
$$

このように重みを調整することで、予測結果が変化します。

## 1.1 多数決によるアンサンブル学習の実装
まず、実装で使うデータをインポートします。今回は、`scikit-learn`の`dataset` モジュールから、Irisデータセットを使います。分類問題を難しくするために、「がく片の幅」と「花びらの長さ」の2つの特徴量のみを使います。このデータは3つの分類を持つ多クラス問題で、モデルの評価指標としては単純な正解率を用います。

<details><summary>Irisデータの読み込みと前処理</summary>

```python
# Iris dataを読み込む
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
import numpy as np
# データセットを読み込む
iris = load_iris()
# 特徴量とターゲットを取得
X = iris.data[50:, [1,2]] # 2つの特徴量を選択
y = iris.target[50:,]
print(X.shape, y.shape)  # 特徴量とターゲットの形状を表示
# (150, 2) (150,)
print("yのユニークな値:", np.unique(y))
# yのユニークな値: [0 1]
# データセットを訓練用とテスト用に分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5, random_state=42)
```
</details>

今回の実装では、以下の3つの分類器を使用します。
- ロジスティック回帰分類き
- 決定木分類器
- k最近傍法分類器

まずは、それぞれの分類器の性能を見ていきましょう。k=5の交差検証法を使って、検証データにおける正解率の平均を計算します。
```python
# ロジスティック回帰、決定木、k最近傍法の3つの分類器を作成する
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import cross_val_score

# ロジスティック回帰のパイプライン
lr_pipe = Pipeline([
    ('scaler', StandardScaler()),
    ('classifier', LogisticRegression(
        penalty='l2',
        C=0.001,
        solver='liblinear',
        random_state=1
    ))
])
# 決定木のパイプライン
dt_pipe = Pipeline([
    ('scaler', StandardScaler()),
    ('classifier', DecisionTreeClassifier(
        criterion='entropy',
        max_depth=1,
        random_state=1
    ))
])
# k最近傍法のパイプライン
knn_pipe = Pipeline([
    ('scaler', StandardScaler()),
    ('classifier', KNeighborsClassifier(
        n_neighbors=2,
        metric='euclidean'
    ))
])
# 分類器の辞書
classifiers = {
    'Logistic Regression': lr_pipe,
    'Decision Tree': dt_pipe,
    'K-Nearest Neighbors': knn_pipe
}

# 交差検証と結果表示
for name, clf in classifiers.items():
    scores = cross_val_score(clf, X_train, y_train, cv=5, scoring='accuracy')
    print(f"{name} - Accuracy: {scores.mean():.2f} ± {scores.std():.2f}")

# Logistic Regression - Accuracy: 0.81 ± 0.10
# Decision Tree - Accuracy: 0.67 ± 0.00
# K-Nearest Neighbors - Accuracy: 0.87 ± 0.04
```

次に、これら3つの分類器の予測結果を組み合わせて、多数決により最終的な予測を決定するアンサンブル学習の分類器を作り、その性能を評価してみます。

```python
from sklearn.ensemble import VotingClassifier
# Voting Classifierの作成
voting_clf = VotingClassifier(
    estimators=[
        ('lr', lr_pipe),
        ('dt', dt_pipe),
        ('knn', knn_pipe)
    ],
    voting='soft' 
)
# 評価指標：Accuracy
scores = cross_val_score(voting_clf, X_train, y_train, cv=5, scoring='accuracy')
print(f"Voting Ensemble - Accuracy: {scores.mean():.2f} ± {scores.std():.2f}")
# Voting Ensemble - Accuracy: 0.89 ± 0.07
```

以上のモデルの正解率を比較すると、3つの予測結果を組み合わせたアンサンブル学習が最もモデルの性能が高いことが分かります。

## 1.2 ハイパーパラメータチューニングとアンサンブル学習の実装
上記では、訓練データの交差検証のコードに入れることで、モデルの性能を評価しました。すなわち、上記で得られた性能は、訓練データを5つに分割し、そのうちの4つを使って学習させたモデルを1つの検証データで性能評価した値です。次に、より実務に近いように、元データを訓練データ、検証データ、テストデータに分けます。検証データを用いて、ハイパーパラメータのチューニングを行い、未知のテストデータにおける性能を評価します。評価指標としては、上記でも用いた正解率を用います。

まず、チューニング対象のハイパーパラメータとそれらへのアクセス方法を確認するために、以下のコードを実行します。

```python
# パラメータ一覧を表示（検索用キーワード付き）
voting_clf.get_params()

# {'estimators': [('lr',
#    Pipeline(steps=[('scaler', StandardScaler()),
#                    ('classifier',
#                     LogisticRegression(C=0.001, random_state=1,
#                                        solver='liblinear'))])),
#   ('dt',
#    Pipeline(steps=[('scaler', StandardScaler()),
#                    ('classifier',
#                     DecisionTreeClassifier(criterion='entropy', max_depth=1,
#                                            random_state=1))])),
#   ('knn',
#    Pipeline(steps=[('scaler', StandardScaler()),
#                    ('classifier',
#                     KNeighborsClassifier(metric='euclidean', n_neighbors=2))]))],
#  'flatten_transform': True,
#  'n_jobs': None,
#  'verbose': False,
#  'voting': 'soft',
#  'weights': None,
#  'lr': Pipeline(steps=[('scaler', StandardScaler()),
#                  ('classifier',
#                   LogisticRegression(C=0.001, random_state=1,
#                                      solver='liblinear'))]),
#  'dt': Pipeline(steps=[('scaler', StandardScaler()),
#                  ('classifier',
# ...
#  'knn__classifier__metric_params': None,
#  'knn__classifier__n_jobs': None,
#  'knn__classifier__n_neighbors': 2,
#  'knn__classifier__p': 2,
#  'knn__classifier__weights': 'uniform'}
```

続いて、訓練データに対して、`GridSearchCV`を用いることで、ロジスティック回帰の逆正則化パラメータCの値、決定木分類器の深さ、k最近傍法分類器の近傍データ数のパラメータチューニングを行います。その後、テストデータを用いて、アンサンブル分類器の性能を評価します。

```python
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import accuracy_score

# グリッドサーチによるアンサンブルモデルのパラメータチューニング
param_grid = {
    'lr__classifier__C': [0.001, 0.01, 0.1],
    'dt__classifier__max_depth': [1, 2, 3, 4],
    'knn__classifier__n_neighbors': [1, 3, 5, 7]
}

grid = GridSearchCV(voting_clf, param_grid, cv=5, scoring='accuracy')
grid.fit(X_train, y_train)

# テストデータで評価
best_model = grid.best_estimator_
y_pred = best_model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)

print(f"Best VotingClassifier Test Accuracy: {accuracy:.2f}")
# Best VotingClassifier Test Accuracy: 0.91

print("Best Parameters:")
for key, val in grid.best_params_.items():
    print(f"  {key}: {val}")
# Best Parameters:
#   dt__classifier__max_depth: 2
#   knn__classifier__n_neighbors: 3
#   lr__classifier__C: 0.001
```

# 2 バギング
ブートストラップ標本を使った分類器アンサンブル学習法を**バギング**と呼びます。まず、**ブートストラップ**（Bootstrap）について説明します。限られたデータから統計的推定やモデル評価の信頼性を高めるために、データを再利用する手法を再標本化と呼びます（resampling）。ブートストラップは、再標本化の方法の1つで、元のデータからと同じサイズのサンプルを復元抽出（with replacement）でランダムに抽出する方法です。バギングでは、個々の分類器の学習にブートストラップ標本を使います。
いくつかあるアンサンブル学習の方法の中で、バギングが適している時は以下のような時です。
- 不安定な学習器を使う時（決定木、ニューラルネットワークなど）
- 学習データにノイズが多い時
- バイアス（学習不足）よりバリアンス（過学習）の問題が強い時

バギングと決定木を組み合わせた手法を特にランダムフォレストと呼びます。

# 2.1 バギングの実装
ここからは、[UCI Machine Learning Repository](https://archive.ics.uci.edu/dataset/109/wine)のWineデータセットを使いって、実際にバギングの実装を行なっていきましょう。今回は、AlcoholとAshのみを特徴量として使い、単一の決定木と決定木＋バギングのモデルの性能を比較します。

<details><summary>Wineデータセットをインポート</summary>

```python
import pandas as pd

# UCI Machine Learning RepositoryからWineデータセットを取得
df_wine = pd.read_csv('https://archive.ics.uci.edu/'
                      'ml/machine-learning-databases/wine/wine.data',
                      header=None)

df_wine.columns = ['Class label', 'Alcohol', 'Malic acid', 'Ash',
                   'Alcalinity of ash', 'Magnesium', 'Total phenols',
                   'Flavanoids', 'Nonflavanoid phenols', 'Proanthocyanins',
                   'Color intensity', 'Hue', 'OD280/OD315 of diluted wines',
                   'Proline']

print('Class labels', np.unique(df_wine['Class label']))
# Class labels [1 2 3]
# 特徴量として、AlcoholとAshのみを利用する
X = df_wine[['Alcohol', 'Ash']].values
y = df_wine['Class label'].values
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, stratify=y, random_state=42)
```
</details>

```python
from sklearn.ensemble import BaggingClassifier
from sklearn.metrics import accuracy_score
#  Bagging Classifierの作成
tree = DecisionTreeClassifier(criterion='entropy', max_depth=None, random_state=1)
bagging_clf = BaggingClassifier(
    estimator=tree,
    n_estimators=500,
    max_samples=0.8,
    max_features=0.8,
    bootstrap=True,
    n_jobs=-1,
    random_state=1
)

# モデルの学習
tree = tree.fit(X_train, y_train)
bagging_clf = bagging_clf.fit(X_train, y_train)
# テストデータでの精度を評価
y_pred_tree = tree.predict(X_test)
y_pred_bagging = bagging_clf.predict(X_test)
print(f"Decision Tree Test Accuracy: {accuracy_score(y_test, y_pred_tree):.2f}")
# Decision Tree Test Accuracy: 0.56
print(f"Bagging Classifier Test Accuracy: {accuracy_score(y_test, y_pred_bagging):.2f}")
# Bagging Classifier Test Accuracy: 0.64

# それぞれのモデルの訓練データに対する精度を表示
print(f"Decision Tree Train Accuracy: {accuracy_score(y_train, tree.predict(X_train)):.2f}")
# Decision Tree Train Accuracy: 1.00
print(f"Bagging Classifier Train Accuracy: {accuracy_score(y_train, bagging_clf.predict(X_train)):.2f}")
# Bagging Classifier Train Accuracy: 　0.94
```
以上の結果から、単一の決定木のモデルは過学習の傾向にあったが、バギングを用いることで、過学習が抑制され、テストデータに対する予測精度が向上したことがわかります。

# 3 ブースティング
**ブースティング**（Boosting）は、複数の弱学習器（weak learners）を順番に学習させて精度の高い強い学習器（strong learner）を構築するアンサンブル学習手法です。特徴的なのは、「前の学習器が間違えたサンプルに次の学習器が重点的に取り組む」というプロセスを通じて、誤りを段階的に修正していく点です。ここで使われる弱学習器は当て数量（AかBかを当てる問題に対してランダムにどちらかを選択すれば、50%の確率で正解できる）を僅かに上回る程度で作られます。
基本的なブースティングの流れは以下です。
1. 初回はすべてのサンプルに同じ重みを割り当てて弱学習器を訓練する
1. 誤分類されたサンプルの重みを増やす
1. 次の弱学習器は、その重みを考慮して訓練される
1. これを繰り返して、最終的に全学習器を加重平均（または加重多数決）して1つの強力な予測器を得る

これらの手順をより詳しく数式と具体例を用いて説明します。
1. 訓練データの重みをwとして、全てのデータ点の重みの合計が1になるようにする
$$
\sum_iw_i = 1
$$

2. 全体でm回するブースティングのうち、j回目のステップで手順a~fを繰り返す（j=1,...,m）
a. 重み付けされた弱学習器$C_j$を訓練する

$$
C_j = train(X, y, w)
$$

b. クラスラベル$\hat{y}$を予測する

$$
\hat{y}= predict(C_j, X)
$$

c. 重み付けされた誤分類率$\epsilon$を計算する

$$
\epsilon =  w_i \cdot \left( \hat{y} \neq y_i \right)
$$

d. 重みの更新に用いる係数$\alpha_j$を計算する

$$
\alpha_j = \frac{1}{2} \log\left( \frac{1 - \epsilon_j}{\epsilon_j} \right)
$$

e. 重みを更新する

$$
w \leftarrow w \cdot \exp\left( -\alpha_j \cdot y \cdot \hat{y} \right)
$$

f. 重みを正規化して合計が1になるようにする

$$
w_i \leftarrow \frac{w_i}{\sum_{i=1}^{n} w_i}
$$


3. 入力された特徴量行列がXに対する最終予測$\hat{y}$を計算する。各手順で推定した重み$\alpha_j$で予測結果を重み付けた平均が0よりも大きければ$y_i=1$、平均が0以下ならば$y_i=-1$とする。

$$
\hat{y} = \left( \sum_{j=1}^{m} \alpha_j \cdot predict(C_j, X) \right) > 0
$$

それでは、具体例として以下のデータに対してどのように重みを更新するか説明します。


| インデックス | X   | y   | 重み  | $\hat{y} | 正しいか | 今された重み |
|--------------|------|------|--------|--------------------------|-----------|----------------|
| 1            | 1.0  | 1    | 0.1    | 1                        | Yes       | 0.072          |
| 2            | 2.0  | 1    | 0.1    | 1                        | Yes       | 0.072          |
| 3            | 3.0  | 1    | 0.1    | 1                        | Yes       | 0.072          |
| 4            | 4.0  | -1   | 0.1    | -1                       | Yes       | 0.072          |
| 5            | 5.0  | -1   | 0.1    | -1                       | Yes       | 0.072          |
| 6            | 6.0  | -1   | 0.1    | -1                       | Yes       | 0.072          |
| 7            | 7.0  | 1    | 0.1    | -1                       | No        | 0.167          |
| 8            | 8.0  | 1   | 0.1    | -1                       | No        | 0.167          |
| 9            | 9.0  | 1    | 0.1    | -1                       | No        | 0.167          |
| 10           | 10.0 | -1   | 0.1    | -1                       | Yes       | 0.072          |

重み付けされた誤分類率$\epsilon$を手順2cで説明したように計算します。
誤分類されたインデックスは 7, 8, 9 の3つです。各点の初期重みは $0.1$ なので、誤分類された点の重みの合計は：

$$
\epsilon = w_7 + w_8 + w_9 = 0.1 + 0.1 + 0.1 = 0.3
$$

次に手順2dに従って係数$\alpha_j$を計算し、手順$2e$の重みの更新と、多数決予測での重みとして使います（手順3）。

$$
\alpha_j = \frac{1}{2} \log\left(\frac{1 - \epsilon}{\epsilon}\right) = \frac{1}{2} \ln\left(\frac{1 - 0.3}{0.3}\right) \approx 0.4236
$$

手順2eの式を使って重みを更新します。ここでは、誤分類されたサンプルの重みを増加させ、正しく分類されたサンプルの重みを減少させます：

- 正解したデータ点 $i$ の重み更新：

$$
w_i \leftarrow 0.1 \cdot \exp(-0.4236) \approx 0.1 \cdot 0.6547 \approx 0.0655
$$
- 誤分類したデータ点 $i$ の重み更新：

$$
w_i \leftarrow 0.1 \cdot \exp(0.4236) \approx 0.1 \cdot 1.528 \approx 0.1528
$$

次に、これらの重みをすべて正規化して合計が1になるようにします（手順2f）。

- 正しく分類された点（7つ）: $0.0655 \times 7 \approx 0.4585$
- 誤分類された点（3つ）: $0.1528 \times 3 \approx 0.4584$
- 合計: $0.4585 + 0.4584 = 0.9169$

よって、正規化後の重みは：

- 正しく分類された点：

$$
w_i \leftarrow \frac{0.0655}{0.9169} \approx 0.072
$$

- 誤分類された点：

$$
w_i \leftarrow \frac{0.1528}{0.9169} \approx 0.167
$$

したがって、正しく分類されたデータ点の重みは0.1から減少している一方、誤って分類したデータ点の重みは増加しています。これにより、次の分類器では誤ったデータ点に対してより注意した分類器を作成することになります。

## 3.1 ブースティングとバギングの比較
アンサンブル学習の方法である、ブースティングとバギングの比較をした表が以下です。

| 特徴      | ブースティング             | バギング（例：ランダムフォレスト） |
| ------- | ------------------- | ----------------- |
| 学習の流れ   | 逐次（順番に依存）           | 並列（各モデルが独立）       |
| 重みの扱い   | 誤りに重点を置く（重みを調整）     | データのサブサンプリング      |
| 過学習への強さ | 過学習しやすいが、チューニングで抑制可 | 比較的過学習に強い         |
| 弱学習器の精度 | 弱くてもOK（例：決定株）       | 比較的高めの学習器が必要      |
| 処理速度    | 遅い（逐次処理）            | 速い（並列処理可能）        |

特に、以下のケースでアダブーストは有効な手段です。
- 単純なモデルで精度を改善したいとき（例：決定木1本では不十分なとき）
- ノイズが少ないデータセット（ノイズに敏感なので）
- 少量のデータでも過学習しにくい設計にしたい場合

## 3.2 アダブーストの実装

```python
from sklearn.ensemble import AdaBoostClassifier
ada = AdaBoostClassifier(
    estimator=tree,
    n_estimators=500,
    learning_rate=0.01,
    random_state=1
)

# モデルの学習
tree = tree.fit(X_train, y_train)
ada = ada.fit(X_train, y_train)
# それぞれのモデルの訓練データに対する精度を表示
print(f"Decision Tree Train Accuracy: {accuracy_score(y_train, tree.predict(X_train)):.2f}")
# Decision Tree Train Accuracy: 1.00
print(f"AdaBoost Classifier Train Accuracy: {accuracy_score(y_train, ada.predict(X_train)):.2f}")
# AdaBoost Classifier Train Accuracy: 1.00
# テストデータでの精度を評価
y_pred_tree = tree.predict(X_test)
y_pred_ada = ada.predict(X_test)
print(f"Decision Tree Test Accuracy: {accuracy_score(y_test, y_pred_tree):.2f}")
# Decision Tree Test Accuracy: 0.56
print(f"AdaBoost Classifier Test Accuracy: {accuracy_score(y_test, y_pred_bagging):.2f}")
# AdaBoost Classifier Test Accuracy: 0.64
```

## 3.3 その他のブースティング手法
AdaBoostはブースティングの代表例ですが、近年ではさらに高性能なブースティングアルゴリズムが数多く登場しています。ここでは、代表的な以下の4つについて紹介します。
- Gradient Boosting（勾配ブースティング）
- XGBoost（Extreme Gradient Boosting）
- LightGBM（Light Gradient Boosting Machine）
- CatBoost（Categorical Boosting）

### 3.3.1 Gradient Boosting（勾配ブースティング）
損失関数を最小化する方向（＝負の勾配）に従って、弱学習器を1つずつ追加していく手法です。各ステップでは残差（誤差）を予測するモデルを学習させていきます。

アルゴリズムのステップ：
1. 初期予測値を設定する（例：回帰なら平均値、分類ならロジット変換など）

2. 各ステップmにおいて：
a. 既存のモデルでの予測誤差（損失関数の負の勾配）を計算 → これが「残差」に相当
b. 残差を目的変数として、新しい回帰木$h_m(x)$を学習
c. 学習器に重み（学習率$\eta$）をかけてモデルに加算：
$$
F_m(x) = F_{m-1}(x) + \eta \cdot h_m(x)
$$
3. 最終予測値は全モデルの出力の加算で得られる


特徴：
- 残差（誤差）を直接学習していく形式
- 自由な損失関数を使える（回帰・分類・順位付けなど）
- 各ステップが損失関数の勾配に基づく修正を行うため、理論的にしっかりしている
- 実装は `sklearn.ensemble.GradientBoostingClassifier` など

```python
from sklearn.ensemble import GradientBoostingClassifier

gb = GradientBoostingClassifier(
    n_estimators=100,
    learning_rate=0.1,
    max_depth=3,
    random_state=1
)

gb.fit(X_train, y_train)
```

### 3.3.2 XGBoost（Extreme Gradient Boosting）
Gradient Boosting を高速・高精度に改良したライブラリです。正則化項を含む損失関数を採用し、過学習を抑制することができる。

特徴：
- L1・L2正則化により過学習に強い
- 欠損値処理・並列処理・疎行列対応など、多機能
- コンペ・実務でも定番の手法
- `xgboost` ライブラリで使用

```python
import xgboost as xgb

model = xgb.XGBClassifier(
    n_estimators=100,
    learning_rate=0.1,
    max_depth=3,
    use_label_encoder=False,
    eval_metric='logloss',
    random_state=1
)

model.fit(X_train, y_train)
```

### 3.3.3 LightGBM（Light Gradient Boosting Machine）
高速・高精度な勾配ブースティングライブラリです。大量データや高次元特徴量でも学習可能です。

特徴：
- 全ての葉ノードの中で最も損失関数の改善が大きくなるノードだけを選んで分割する、Leaf-wiseの成長戦略（他の手法はLevel-wise）
- Categorical変数を直接扱える
- GPU学習・並列学習に対応
- 欠損値処理も自動
- `lightgbm`ライブラリで使用

```python
import lightgbm as lgb

model = lgb.LGBMClassifier(
    n_estimators=100,
    learning_rate=0.1,
    max_depth=-1,
    random_state=1
)

model.fit(X_train, y_train)
```

### 3.3.4 CatBoost（Categorical Boosting）
カテゴリカル変数に特化した勾配ブースティング手法です。

特徴：
- カテゴリ変数を前処理なしで直接扱える（One-hot不要）
- 過学習抑制のためのOrdered Boostingを採用
- テキスト・画像の埋め込みにも強い
- `catboost`ライブラリで使用

```python
from catboost import CatBoostClassifier

model = CatBoostClassifier(
    iterations=100,
    learning_rate=0.1,
    depth=3,
    verbose=0,
    random_seed=1
)

model.fit(X_train, y_train)
```
それぞれの手法の特徴をまとめると、

| 手法                | 勾配使用 | 正則化      | カテゴリ対応    | 木の成長戦略     | 特徴            |
| ----------------- | ---- | -------- | --------- | ---------- | ------------- |
| Gradient Boosting | ○    | ×        | ×         | Level-wise | シンプルな理論ベース    |
| XGBoost           | ○    | ◎（L1/L2） | △（事前処理必要） | Level-wise | 正則化で過学習耐性・高性能 |
| LightGBM          | ○    | △        | ◎（自動処理）   | Leaf-wise  | 超高速・大規模対応     |
| CatBoost          | ○    | ◎        | ◎（最強）     | Ordered    | カテゴリ特化・過学習に強い |

それぞれのブースティング手法は、以下のように使い分けるのが良いです。

| 使いたい状況              | おすすめ手法            |
| ------------------- | ----------------- |
| 汎用性・精度・実績重視         | XGBoost           |
| データが多く学習時間も短くしたい    | LightGBM          |
| カテゴリ変数が多く・前処理を楽にしたい | CatBoost          |

Gradient BoostingはXGBoostingよりも性能は劣ることが多いので、特別な理由でXGBoostが使えないといった状態ではない限り、Gradient Boostingは使わないことが多いです。


# 出典
[Python機械学習プログラミング 達人データサイエンティストによる理論と実践](https://amzn.to/4ktOMjQ)の第7章を参考にしている。

本記事で用いたPythonとライブラリのバージョン

```python
Python version: 3.10.4
pandas version: 2.2.3
scikit-learn version: 1.6.1
matplotlib version: 3.10.1
```