アンサンブル学習を改めて勉強し直す


- [記事の概要](#記事の概要)
- [アンサンブル学習とは](#アンサンブル学習とは)
- [ランダムフォレストを使った分類を行う](#ランダムフォレストを使った分類を行う)
- [アンサンブル学習を使った分類を行う](#アンサンブル学習を使った分類を行う)
- [スタッキングを行う](#スタッキングを行う)
- [終わりに](#終わりに)

## 記事の概要

簡単なデータセットを使ってランダムフォレスト、アンサンブル学習を用いた分類を行ってみる記事。Extra-Treesやブレンダなどで高い精度を確認することができた。参考にした本は、[scikit-learn、Keras、TensorFlowによる実践機械学習 第2版](https://www.oreilly.co.jp/books/9784873119281/)(以下、実践機械学習)。

※ pythonのバージョンは3.12を使用している。

## アンサンブル学習とは

複数の別のモデルを使って、それぞれの予測を組み合わせて、最終的な予測を行う手法。分類では多数決、回帰では平均を取る方法などがある。予測に用いるものが決定木の場合は、ランダムフォレストと呼ばれる。使うメリットとしては、予測に用いたモデルが別であれば、組み合わせるだけで基本的に高い結果を出すことができるというシンプルなものである。また、訓練に使うデータモデルごとにランダムに選べば、より性能が上がる可能性が上がる。デメリットとしては、単純に計算量が大きくなってしまうことが挙げられる。

実際に色々使って実験してみる。

## ランダムフォレストを使った分類を行う

実践機械学習を参考に、scikit-learnのmnistデータセットを使って分類を行う。これはよく知られる手書き数字のデータセットで、0から9までの数字が含まれている。以下で訓練データとテストデータを用意する。

```python
from sklearn.datasets import fetch_openml

mnist = fetch_openml('mnist_784', version=1, as_frame=False)
X,y = mnist['data'], mnist['target']
X_train, X_test, y_train, y_test = X[:60000], X[60000:], y[:60000], y[60000:]
```

次に、ランダムフォレストを使って分類を行う。

```python
from sklearn.metrics import accuracy_score
from sklearn.ensemble import RandomForestClassifier


random_forest_clf = RandomForestClassifier()
random_forest_clf.fit(X_train, y_train)
y_pred = random_forest_clf.predict(X_test)

accuracy_score(y_test, y_pred)
```

私が実行した場合は、0.9706という結果が出た。この時点でかなり良い結果が出ている。ランダムフォレストはデフォルトで100個の決定木を使っているというのと、良い結果が出るように洗練されている。特にパラメータを調整しなくてもここまで良い結果が出てくれるのは感動する。

少し補足っぽいが、ランダムフォレストの似た手法で、Extra-Treesというものがある。これは、ランダムフォレストと似ているが、訓練データの一部を使って閾値を決めるのではなく、ランダムに閾値を決める。これで高い精度が出ることは正直信じられないが、個々のランダム性が集合することで相殺されるらしい。集合知の強みなのだろうか。
以下で実際に試してみる。

```python
from sklearn.ensemble import ExtraTreesClassifier


extra_trees_clf = ExtraTreesClassifier()

extra_trees_clf.fit(X_train, y_train)

y_pred = extra_trees_clf.predict(X_test)

accuracy_score(y_test, y_pred)
```

結果は0.9727となった。むしろランダムフォレストより高い精度が出ていることに驚きで、計算速度も、ランダムフォレストでは35.1s、Extra-Treesでは27.6sと、Extra-Treesの方が速いことも確認できた。これはランダムに閾値を選んでいるので、その分の計算量分早く計算が終わることは納得だ。

## アンサンブル学習を使った分類を行う

訓練データは同じものを使ってしまっているが、複数のモデルで分類を行っているので、アンサンブル学習を使ったら高い結果が出るのかを確認してみる。
使うモデルは、上記で使った2つのモデルと、Multi-layer Perceptronとした(ちなみに実践機械学習ではSVMが使われていたが、これは計算に時間がかかるので除いた)。

```python
from sklearn.metrics import accuracy_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.ensemble import VotingClassifier


random_forest_clf = RandomForestClassifier()
extra_trees_clf = ExtraTreesClassifier()
mlp_clf = MLPClassifier()


voting_clf = VotingClassifier(
    estimators=[('rf', random_forest_clf), ('et', extra_trees_clf), ('mlp', mlp_clf)],
    voting='hard'
)


for clf in (random_forest_clf, extra_trees_clf, mlp_clf, voting_clf):
    clf.fit(X_train, y_train)
    y_pred = clf.predict(X_test)
    print(f"className: {clf.__class__.__name__} accuracy: {accuracy_score(y_test, y_pred)}")
```

モデルそれぞれの精度と、それらを組み合わせたアンサンブル学習の精度を出した。結果は以下となった。

```
className: RandomForestClassifier accuracy: 0.9678
className: ExtraTreesClassifier accuracy: 0.9739
className: MLPClassifier accuracy: 0.9653
className: VotingClassifier accuracy: 0.9749
```

Extra-Treesと0.001しか変わらないが、少なくともアンサンブル学習は他のモデルと比べても高い精度を出していることが確認できた。例え3つでも、モデルを組み合わせるだけで高い精度を出せるということを実感できた。

## スタッキングを行う

最後にスタッキングを行って、さらに高い結果が出るかを確かめてみる。スタッキングはブレンダとも呼ばれ、複数のモデルを使って予測を行い、その予測結果をまた訓練データとして使って、最終的な予測を行う手法である。ただの多数決ではなくまた新たなモデルを用意して最終的な結果を出すことで、より良い結果となることがあるようだ。以下がそのコードである。

```python
import numpy as np
from sklearn.metrics import accuracy_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import ExtraTreesClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.ensemble import VotingClassifier


random_forest_clf = RandomForestClassifier()
extra_trees_clf = ExtraTreesClassifier()
mlp_clf = MLPClassifier()


voting_clf = VotingClassifier(
    estimators=[('rf', random_forest_clf), ('et', extra_trees_clf), ('mlp', mlp_clf)],
    voting='hard'
)


new_X_train = []


for clf in (random_forest_clf, extra_trees_clf, mlp_clf, voting_clf):
    clf.fit(X_train, y_train)
    y_pred = clf.predict(X_test)

    # ここで予測結果を新しい訓練データとして追加
    new_X_train.append(y_pred)
    print(f"className: {clf.__class__.__name__} accuracy: {accuracy_score(y_test, y_pred)}")

# 訓練データとして使えるように転置
new_X_train = np.array(new_X_train).T

randam_forest_blender = RandomForestClassifier()

randam_forest_blender.fit(new_X_train, y_test)

y_pred = randam_forest_blender.predict(new_X_train)

print(f"blender accuracy: {accuracy_score(y_test, y_pred)}")

```

先ほど行ったプログラムにて、それぞれのモデルから出てきたデータを訓練データとして使って、新たなモデル(ランダムフォレスト)で予測を行った。結果は以下。

```
className: RandomForestClassifier accuracy: 0.9689
className: ExtraTreesClassifier accuracy: 0.9715
className: MLPClassifier accuracy: 0.9647
className: VotingClassifier accuracy: 0.9739
blender accuracy: 0.9842
```

なんとブレンダの結果は0.9842と、他のモデルと比べて0.1以上高い結果が出た。こんなにも結果がよくなるのかと驚いた。

## 終わりに

良い結果が出ている複数のモデルを組み合わせることで結果が良くなるのであれば、非常にシンプルで効果的な手法であると感じた。最近のMLコンテストでは、アンサンブル学習を使っていることが多いと実践機械学習の本には書いてあったが、その理由が理解できた気がする。これは単純ながらとても応用が効く手法であると感じた。