scikit-learnでバギングとブースティングをやってみる

# はじめに
複数のモデルを組み合わせて学習する方法をアンサンブル学習といいます。様々な分類器を組み合わせることで汎化性能を上げることが出来ます。

ここではバギングとブースティング（Adaboost）について見ていきます。

# 参考
- ［第2版］Python機械学習プログラミング 達人データサイエンティストによる理論と実践 impress top gearシリーズ(https://www.amazon.co.jp/dp/B07BF5QZ41/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1)
- [sklearn.ensemble.BaggingRegressor](https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.BaggingRegressor.html)

# 環境
- MacOS Mojave 10.14.2
- scikit-learn==0.19.1

# 手順

## バギング
元の訓練データからランダムにn個のデータを重複を許して抽出する、ということを繰り返してデータセットをn_estimators個作ります。これをブートストラップといいます。作成した各データそれぞれに対してモデルを作成します。予測はこの多数のモデルを集約して予測します。結果の集約は分類であれば多数決、回帰であれば平均値を取るなどします。

それぞれのモデルが学習の際に使用するデータは少しづつ異なるため、汎化性能を向上させられる可能性があります。

分類問題であれば`BaggingClassifier`を、回帰問題であれば`BaggingRegressor`を使用します。`n_estimators`が作成するデータセットの数＝モデルの数で、`max_samples`は抽出する際のデータ数の上限、`max_features`は抽出する際の特徴量の数の上限を指定できます。

```python
import pandas as pd
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import BaggingClassifier

loaded_data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
    loaded_data.data, loaded_data.target, random_state=0)

models = {
    'not bagging': DecisionTreeClassifier(random_state=0),
    'bagging': BaggingClassifier(DecisionTreeClassifier(random_state=0), n_estimators=100, random_state=0) 
}

scores = {}
for model_name, model in models.items():
    model.fit(X_train, y_train)
    scores[(model_name, 'train_score')] = model.score(X_train, y_train)
    scores[(model_name, 'test_score')] = model.score(X_test, y_test)

pd.Series(scores).unstack()
```

|  | test_score | train_score |
|:--|--:|--:|
| bagging | 0.986014 | 1.0 |
| not bagging | 0.881119 | 1.0 |


## ブースティング
訓練データに対しモデルを作成し、学習します。学習後、予測と正解を比較し、誤ったデータに重み付けをして、重み付けしたデータを用いて新たなモデルで学習します。そして誤ったデータに、、、と繰り返し逐次的にモデルを作成していきます。最後はバギング同様に、多数のモデルを集約して予測します。

```python
import pandas as pd
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import AdaBoostRegressor

loaded_data = load_boston()
X_train, X_test, y_train, y_test = train_test_split(
    loaded_data.data, loaded_data.target, random_state=0)

models = {
    'tree': DecisionTreeRegressor(random_state=0),
    'AdaBoost': AdaBoostRegressor(DecisionTreeRegressor(random_state=0), n_estimators=100, random_state=0) 
}

scores = {}
for model_name, model in models.items():
    model.fit(X_train, y_train)
    scores[(model_name, 'train_score')] = model.score(X_train, y_train)
    scores[(model_name, 'test_score')] = model.score(X_test, y_test)

pd.Series(scores).unstack()
```

|  | test_score | train_score |
|:--|--:|--:|
| AdaBoost | 0.741108 | 0.99971 |
| tree | 0.605167 | 1.00000 |

## バギング&ブースティング
バギングとブースティングの代表的なモデルとしてランダムフォレストと勾配ブースティングが存在します。これらと、上記の決定木のバギング&ブースティングを比較してみます。モデルのチューンをしていないので、下記がモデルの優劣を表すわけではありませんが、大体の目安になります。

```python
# バギング＆ブースト
import pandas as pd
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import AdaBoostRegressor
from sklearn.ensemble import BaggingRegressor
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor

loaded_data = load_boston()
X_train, X_test, y_train, y_test = train_test_split(
    loaded_data.data, loaded_data.target, random_state=0)

models = {
    'tree': DecisionTreeRegressor(random_state=0),
    'AdaBoost': AdaBoostRegressor(DecisionTreeRegressor(random_state=0), random_state=0),
    'Bagging': BaggingRegressor(DecisionTreeRegressor(random_state=0), n_estimators=100, random_state=0),
    'Bagging & AdaBoost': AdaBoostRegressor(BaggingRegressor(DecisionTreeRegressor(random_state=0),
                                                   n_estimators=100,
                                                   random_state=0
                                                  ),
                                  random_state=0
                                 ),
    'RandomForest': RandomForestRegressor(random_state=0),
    'GradientBoost': GradientBoostingRegressor(random_state=0)
}

scores = {}
for model_name, model in models.items():
    model.fit(X_train, y_train)
    scores[(model_name, 'train_score')] = model.score(X_train, y_train)
    scores[(model_name, 'test_score')] = model.score(X_test, y_test)

pd.Series(scores).unstack()
```

|  | test_score | train_score |
|:--|--:|--:|
| AdaBoost | 0.740901 | 0.999597 |
| Bagging | 0.786746 | 0.982702 |
| Bagging & AdaBoost | 0.763452 | 0.994583 |
| GradientBoost | 0.816288 | 0.983166 |
| RandomForest | 0.757792 | 0.972705 |
| tree | 0.605167 | 1.000000 |

# おわりに
scikit-learnでバギングとブースティングを試してみました。お手軽に試せて、精度も良いモデルとして、ランダムフォレストと勾配ブースティングが人気です。とりあえず使ってみるというのであれば、これらのモデルがおすすめです。