深い森(multi-Grained Cascade Forest)と浅い森(Random Forests)を動かして比較してみた


# はじめに
gcForestを動かしてみる。
## 背景
近年、機械学習のモデルは主にニューラルネットワークを用いる深層学習に注目が集まっている。しかし、一般的に深層学習を十分に利用するには、膨大な計算資源や、ネットワークの構造とパラメーターのチューニング、学習用の大規模データが必要というネガティブなポイントがある。これらの問題点を解決するために、深層学習の代替案として決定木とアンサンブル学習を利用して深く学習するgcForest(multi-Grained Cascade Forest)が提案されている。

## 内容
本ページでは次のような決定木ベースの機械学習モデルを構築します。

- Random Forests
- gcForest

以上の二つのモデルの精度と計算時間を2000枚に限定したMNISTデータを利用して比較する。


## 学銃的な背景
### 論文の引用情報
本ページは、[Deep Forest: Towards an Alternative to Deep Neural Networks](https://www.ijcai.org/proceedings/2017/0497.pdf)を参考にしています。この論文のソースコードは[deep-forest](https://github.com/leopiney/deep-forest)を参考にしています。
また日本語で読みたい方は[Deep Forest :Deep Neural Networkの代替へ向けて](https://qiita.com/de0ta/items/8681b41d858a7f89acb9)を参考にしてください。

著者の目的はとにかく動かすことです。
そのため、その他の解説は[Deep forest](https://www.slideshare.net/naotomoriyama/deep-forest-74148319)や、深くする学習の他の方法[Deep Neural Decision Forests](https://paper.hatenadiary.jp/entry/2017/07/30/033813#Deep%E9%96%A2%E4%BF%82)を参考にしてください。

### ざっくりした gcForest のまとめ
- 深層学習のように深くランダムフォレスト利用して学習を行う  gcForest(multi-Grained Cascade Forest)
- 本当ならすごい gcForest の性能
    - gcForest の計算時間 (CPU動作) は 深層学習の計算時間(GPU計算)と同等
    - パラメーターチューニングが容易
        - Random Forests は細かいパラメーターが不要である。それと同様にgcForestも、問題に依存せず、ほとんど同じパラメーター設定で精度向上が可能らしい。
    - 理論的解析が容易らしい。
- 性能面
    - 画像データ分類(MNIST): LesNet-5よりも精度が高い
    - 感情分類(IMDB),音楽分類(GTZAN): CNNよりも精度が高い
    - 手の動きの分類(sEMG): LSTMよりも精度が高い
    - 低次元データ(UCI-datasets, LETTER): Random Forestsより精度が高い
- gcForest の大まかな構成要素は以下の二つ(イメージで確認する場合は[論文の図4を参照](https://www.ijcai.org/proceedings/2017/0497.pdf))
    - cascade forest
    - multi-grained scanning
        - multi-grained scanning+cascade forestの組み合わせは、特に音楽分類と手の動き分類において、cascade forest だけを用いるよりも性能が高くなるため、効果がある。

## インストール
deep_forest は pip コマンドや git+URLに対応していません。そのため、[ここ](https://github.com/leopiney/deep-forest)からdeep_forest.pyとutils.pyをダウンロードしてください。ダウンロードしたdeep_forest.pyとutils.pyはソースファイルと同一のフォルダに入れて「from deep_forest import MGCForest」とすれば実行することができます。

# 実験
## インポート関係


```python
import numpy as np
import random
import uuid

from sklearn.datasets import make_classification
from sklearn.model_selection import StratifiedKFold, cross_validate
from sklearn.metrics import accuracy_score, cohen_kappa_score, balanced_accuracy_score, make_scorer, f1_score, recall_score

from sklearn.datasets import fetch_mldata
from sklearn.ensemble import ExtraTreesClassifier, RandomForestClassifier
from sklearn.metrics import accuracy_score, f1_score
from sklearn.model_selection import train_test_split
from sklearn.datasets.base import get_data_home 
from deep_forest import MGCForest


```


## データの読み込み
MNISTのデータを利用する。ただし、2000文字に限定する。

- 注意事項
     - gcforest を実行するためには(2000, 28, 28)のような3次元データに変換する必要がある。
     - random forestsを実行するためには2次元データのまま利用する


```python
#https://webbibouroku.com/Blog/Article/fetch-mldata-error
mnist =fetch_mldata('MNIST original', data_home=get_data_home())


X_train, X_test, y_train, y_test = train_test_split(
    mnist.data,
    mnist.target,
    test_size=0.2,
    random_state=42,
)

X_train = X_train.reshape((len(X_train), 28, 28))
X_test = X_test.reshape((len(X_test), 28, 28))

#
# Limit the size of the dataset
#
X_train = X_train[:2000]
y_train = y_train[:2000]
X_test = X_test[:2000]
y_test = y_test[:2000]

print('X_train:', X_train.shape, X_train.dtype)
print('y_train:', y_train.shape, y_train.dtype)
print('X_test:', X_test.shape)
print('y_test:', y_test.shape)
```

    X_train: (2000, 28, 28) uint8
    y_train: (2000,) float64
    X_test: (2000, 28, 28)
    y_test: (2000,)


## gcforestのモデルの定義


```python
mgc_forest = MGCForest(
    estimators_config={
        'mgs': [{
            'estimator_class': ExtraTreesClassifier,
            'estimator_params': {
                'n_estimators': 30,
                'min_samples_split': 21,
                'n_jobs': -1,
            }
        }, {
            'estimator_class': RandomForestClassifier,
            'estimator_params': {
                'n_estimators': 30,
                'min_samples_split': 21,
                'n_jobs': -1,
            }
        }],
        'cascade': [{
            'estimator_class': ExtraTreesClassifier,
            'estimator_params': {
                'n_estimators': 1000,
                'min_samples_split': 11,
                'max_features': 1,
                'n_jobs': -1,
            }
        }, {
            'estimator_class': ExtraTreesClassifier,
            'estimator_params': {
                'n_estimators': 1000,
                'min_samples_split': 11,
                'max_features': 'sqrt',
                'n_jobs': -1,
            }
        }, {
            'estimator_class': RandomForestClassifier,
            'estimator_params': {
                'n_estimators': 1000,
                'min_samples_split': 11,
                'max_features': 1,
                'n_jobs': -1,
            }
        }, {
            'estimator_class': RandomForestClassifier,
            'estimator_params': {
                'n_estimators': 1000,
                'min_samples_split': 11,
                'max_features': 'sqrt',
                'n_jobs': -1,
            }
        }]
    },
    stride_ratios=[1.0 / 4, 1.0 / 9, 1.0 / 16],
)
```

## gcforest の学習と評価


```python
%%time
mgc_forest.fit(X_train, y_train)

y_pred = mgc_forest.predict(X_test)

print('Prediction shape:', y_pred.shape)
print(
    'Accuracy:', accuracy_score(y_test, y_pred),
    'F1 score:', f1_score(y_test, y_pred, average='weighted')
)
```

    <MultiGrainedScanner stride_ratio=0.25> - Scanning and fitting for X ((2000, 28, 28)) and y ((2000,)) started
    <MultiGrainedScanner stride_ratio=0.25> - Window shape: [7, 7] Total windows: 484
    <MultiGrainedScanner stride_ratio=0.25> - Finished scan X ((2000, 28, 28)) and got predictions with shape (2000, 9680)
    <MultiGrainedScanner stride_ratio=0.1111111111111111> - Scanning and fitting for X ((2000, 28, 28)) and y ((2000,)) started
    <MultiGrainedScanner stride_ratio=0.1111111111111111> - Window shape: [3, 3] Total windows: 676
    <MultiGrainedScanner stride_ratio=0.1111111111111111> - Finished scan X ((2000, 28, 28)) and got predictions with shape (2000, 13520)
    <MultiGrainedScanner stride_ratio=0.0625> - Scanning and fitting for X ((2000, 28, 28)) and y ((2000,)) started
    <MultiGrainedScanner stride_ratio=0.0625> - Window shape: [1, 1] Total windows: 784
    <MultiGrainedScanner stride_ratio=0.0625> - Finished scan X ((2000, 28, 28)) and got predictions with shape (2000, 15680)
    <CascadeForest forests=4> - Cascade fitting for X ((2000, 38880)) and y ((2000,)) started
    <CascadeForest forests=4> - Level #1:: X with shape: (2000, 38880)
    <CascadeForest forests=4> - Level 1:: got all predictions
    <CascadeForest forests=4> - Level 1:: got accuracy 0.959
    <CascadeForest forests=4> - Level #2:: X with shape: (2000, 38920)
    <CascadeForest forests=4> - Level 2:: got all predictions
    <CascadeForest forests=4> - Level 2:: got accuracy 0.958
    <MultiGrainedScanner stride_ratio=0.25> - Scanning and fitting for X ((2000, 28, 28)) and y (None) started
    <MultiGrainedScanner stride_ratio=0.25> - Window shape: [7, 7] Total windows: 484
    <MultiGrainedScanner stride_ratio=0.25> - Finished scan X ((2000, 28, 28)) and got predictions with shape (2000, 9680)
    <MultiGrainedScanner stride_ratio=0.1111111111111111> - Scanning and fitting for X ((2000, 28, 28)) and y (None) started
    <MultiGrainedScanner stride_ratio=0.1111111111111111> - Window shape: [3, 3] Total windows: 676
    <MultiGrainedScanner stride_ratio=0.1111111111111111> - Finished scan X ((2000, 28, 28)) and got predictions with shape (2000, 13520)
    <MultiGrainedScanner stride_ratio=0.0625> - Scanning and fitting for X ((2000, 28, 28)) and y (None) started
    <MultiGrainedScanner stride_ratio=0.0625> - Window shape: [1, 1] Total windows: 784
    <MultiGrainedScanner stride_ratio=0.0625> - Finished scan X ((2000, 28, 28)) and got predictions with shape (2000, 15680)
    <CascadeForest forests=4> - Shape of predictions: (4, 2000, 10) shape of X: (2000, 38880)


    Prediction shape: (2000,)
    Accuracy: 0.9615 F1 score: 0.9614975226981488
    CPU times: user 20min 57s, sys: 54.9 s, total: 21min 52s
    Wall time: 19min


## Random forestsのモデルの定義


```python
model = RandomForestClassifier(random_state = 43,
                               n_jobs = -1,
                               n_estimators = 1000,                               
                               max_features = 'log2',
                               oob_score=False)
X_train, X_test, y_train, y_test = train_test_split(
    mnist.data,
    mnist.target,
    test_size=0.2,
    random_state=42,
)
X_train = X_train[:2000,:]
X_test = X_test[:2000,:]
y_train= y_train[:2000]
y_test = y_test[:2000]
```

## Random forests の学習と評価


```python
%%time
#X_train = X_train.reshape(len(X_train), -1).astype(np.float64)
#X_test = X_train.reshape(len(X_test), -1).astype(np.float64)
model.fit(X_train, y_train)

y_pred = model.predict(X_test)

print('Prediction shape:', y_pred.shape)
print(
    'Accuracy:', accuracy_score(y_test, y_pred),
    'F1 score:', f1_score(y_test, y_pred, average='weighted')
)
```

    Prediction shape: (2000,)
    Accuracy: 0.922 F1 score: 0.9217463136989749
    CPU times: user 5.14 s, sys: 467 ms, total: 5.61 s
    Wall time: 2.43 s


# まとめ

- 精度
    - gcforestはrandom forestsよりも精度が高い
        - gcforest
            - Accuracy: 0.9615 F1 score: 0.9614975226981488
        - random forests
            - Accuracy: 0.922 F1 score: 0.9217463136989749
    - 上記の精度の差を埋めるためには？
        - MNISTデータに対するrandom forestsの精度が2000文字分のトレーニングデータを利用したgcforestの精度に追いつくためには、7000文字分のトレーニングデータを利用したrandom forestsのモデルが必要である。
        - つまり学習方法の工夫により、4%ほど精度が向上したことになる。
- 計算時間
    - gcforestはrandom forestsよりも計算時間が必要
        - gcforest: 19min
        - random forests:  2.43 s
    - 計測したPCの性能(一般的なPCよりも少し性能が高い)
        - MacBook Pro (13-inch, 2017, Two Thunderbolt 3 ports)
        - プロセッサ 2.5 GHz Intel Core i7
        - メモリ 16 GB 2133 MHz LPDDR3
        
# 追記(2019/2/18/19:00)

- 画像データとアンケートデータを利用する場合では適切な機械学習モデルが異なる。
    - そこで、[記事: 深い森(multi-Grained Cascade Forest)の苦手なデータを検証してみる。](https://qiita.com/KROYO/items/5a01f65130f8c28512c1)のように検証した。