ライブラリを使ってディープラーニングを構築する　～scikit-learn編

# **■0.はじめに**

[ディープラーニングを基本から学ぶ](https://qiita.com/nvtomo1029/items/551a28f40d092265047c)

こちらで、ゼロからディープラーニングを実装することを学びました。
実務的にはライブラリを使ってとなるのが現実的かと思い、ライブラリを使ったディープラーニングを構築してみようと考えました。
まずはscikit-learnから始めます。

# **■1.scikit-learn**

>scikit-learn (旧称：scikits.learn) はPythonのオープンソース機械学習ライブラリである。サポートベクターマシン、ランダムフォレスト、Gradient Boosting、k近傍法、DBSCANなどを含む様々な分類、回帰、クラスタリングアルゴリズムを備えており、Pythonの数値計算ライブラリのNumPyとSciPyとやり取りするよう設計されている。
[Wikipediaより](https://ja.wikipedia.org/wiki/Scikit-learn)

# **■2.環境**
・**Windows 10**
・**Anaconda 4.4.1**
・**Python 3.6.1**
・**scikit-learn 0.18.1**

# **■3.使用データ**

**MNIST**
・28x28ピクセルからなる手書き数字(0～9)のデータセット
・機械学習のサンプルデータとしてよく使われる
・データ数は70,000枚

# **■4.実装**

```py3
# coding: utf-8
import numpy as np
import timeit
from sklearn import datasets, model_selection, metrics
from sklearn.neural_network import MLPClassifier

# mnistの手書き数字データをロード
# data_homeにない場合は、
# Webから自動的にダウンロードされる（時間がかかる）
# 70000サンプル、28x28ピクセル
mnist = datasets.fetch_mldata('MNIST original', data_home='data/mnist/')

# データとラベルを取り出す
mnist_data = mnist.data / 255 # 8bitのデータを0.0～1.0に正規化
mnist_label = mnist.target

# 学習用とテスト用に分割
data_train, data_test, label_train, label_test = model_selection.train_test_split(mnist_data, mnist_label, test_size=0.05, train_size=0.3) # 学習用は全データの30%、テスト用は5%に設定
print('train_size:', data_train.shape[0]) # 学習データ数
print('test_size:', data_test.shape[0]) # テストデータ数

# 識別器
classifiers = [
    (('neural_network Adam', MLPClassifier(solver="adam", early_stopping=True, verbose=True))), 
    (('neural_network Adam(2 layer)', MLPClassifier(solver="adam", early_stopping=True, hidden_layer_sizes=(100, 10)))), 
    (('neural_network Adam(3 layer)', MLPClassifier(solver="adam",  early_stopping=True, hidden_layer_sizes=(100, 100, 10)))), 
    (('neural_network Adam(4 layer)', MLPClassifier(solver="adam",  early_stopping=True, hidden_layer_sizes=(100, 100, 100, 10)))) 
]

classifier_names = [
    'neural_network Adam', 
    'neural_network Adam(2 layer)', 
    'neural_network Adam(3 layer)', 
    'neural_network Adam(4 layer)' 
]

# 識別器ごとに繰り返す
for name, clf in classifiers:
    print(name, 'start')
    # 学習
    print('Learning Time(s):', timeit.timeit(lambda: clf.fit(data_train, label_train), number=1))
    # 予測
    pre = clf.predict(data_test)
    ### 評価 ###
    # 正解率取得
    ac_score = metrics.accuracy_score(label_test, pre)
    print('AC Score:', ac_score)
    # 混同行列の出力
    co_mat = metrics.confusion_matrix(label_test, pre)
    print('confusion matrix:')
    print(co_mat)
    # precision, recall, f1-score の評価
    cl_repo = metrics.classification_report(label_test, pre)
    print('classification report:')
    print(cl_repo)

    print(name, 'end')
```

## **▼4.1.前処理**

まずは前処理としてデータのダウンロード、正規化、学習用／テスト用への分割を行います。

```py3
# mnistの手書き数字データをロード
# data_homeにない場合は、
# Webから自動的にダウンロードされる（時間がかかる）
# 70000サンプル、28x28ピクセル
mnist = datasets.fetch_mldata('MNIST original', data_home='data/mnist/')
```

最初にMNISTのデータをダウンロードします。
scikit-learnでは、MNISTをダウンロードするI/Fが用意されています。

[sklearn.datasets.fetch_mldata — scikit-learn 0.19.1 documentation](http://scikit-learn.org/stable/modules/generated/sklearn.datasets.fetch_mldata.html)

```py3
# データとラベルを取り出す
mnist_data = mnist.data / 255 # 8bitのデータを0.0～1.0に正規化
mnist_label = mnist.target
```

続いて、データとラベルを取り出すとともに正規化を行っています。
正規化は下記のようにすると汎用的になります。

```py3
mnist_data = mnist.data
mnist_data = mnist_data.astype(np.float64)
mnist_data =mnist_data / mnist_data.max()
```

続いて、データを学習用とテスト用に分割します。

```py3
# 学習用とテスト用に分割
data_train, data_test, label_train, label_test = model_selection.train_test_split(mnist_data, mnist_label, test_size=0.05, train_size=0.3) # 学習用は全データの30%、テスト用は5%に設定
print('train_size:', data_train.shape[0]) # 学習データ数
print('test_size:', data_test.shape[0]) # テストデータ数
```

scikit-learnでは、sklearn.model_selection.train_test_split で学習用とテスト用への分割を行ってくれます。

[sklearn.model_selection.train_test_split — scikit-learn 0.19.1 documentation](http://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html)

## **▼4.2.学習、予測、評価**

```py3
# 識別器
classifiers = [
    (('neural_network Adam', MLPClassifier(solver="adam", early_stopping=True, verbose=True))), 
    (('neural_network Adam(2 layer)', MLPClassifier(solver="adam", early_stopping=True, hidden_layer_sizes=(100, 10)))), 
    (('neural_network Adam(3 layer)', MLPClassifier(solver="adam",  early_stopping=True, hidden_layer_sizes=(100, 100, 10)))), 
    (('neural_network Adam(4 layer)', MLPClassifier(solver="adam",  early_stopping=True, hidden_layer_sizes=(100, 100, 100, 10)))) 
]

# 識別器ごとに繰り返す
for name, clf in classifiers:
    print(name, 'start')
    # 学習
    print('Learning Time(s):', timeit.timeit(lambda: clf.fit(data_train, label_train), number=1))
    # 予測
    pre = clf.predict(data_test)
    ### 評価 ###
    # 正解率取得
    ac_score = metrics.accuracy_score(label_test, pre)
    print('AC Score:', ac_score)
    # 混同行列の出力
    co_mat = metrics.confusion_matrix(label_test, pre)
    print('confusion matrix:')
    print(co_mat)
    # precision, recall, f1-score の評価
    cl_repo = metrics.classification_report(label_test, pre)
    print('classification report:')
    print(cl_repo)

    print(name, 'end')
```

順番に見ていきましょう

```py3
MLPClassifier(solver="adam", early_stopping=True, verbose=True)
```

sklearn.neural_network.MLPClassifierクラスがニューラルネットワークで一般的に用いられる多層パーセプトロンが実装されたクラスです。

```py3
MLPClassifier(solver="adam",  early_stopping=True, hidden_layer_sizes=(100, 100, 100, 10))
```

上記のようにhidden_layer_sizesを指定すると多層のニューラルネットワークが構築できます。
その他パラメータの詳細は公式ドキュメントを参照してください。

[sklearn.neural_network.MLPClassifier — scikit-learn 0.19.1 documentation](http://scikit-learn.org/stable/modules/generated/sklearn.neural_network.MLPClassifier.html)

```py3
# 学習
print('Learning Time(s):', timeit.timeit(lambda: clf.fit(data_train, label_train), number=1))
```

scikit-learnでおなじみのfit関数。
MLPClassifierクラスにもあります。
この関数で学習を行います。
timeit関数と無名関数を組み合わせて、学習時間を計測しています。

```py3
# 予測
pre = clf.predict(data_test)
```

こちらもscikit-learnでおなじみのpredict関数。
テストデータを入力として、fit関数で作られた学習器を使って予測をしています。

```py3
### 評価 ###
# 正解率取得
ac_score = metrics.accuracy_score(label_test, pre)
print('AC Score:', ac_score)
# 混同行列の出力
co_mat = metrics.confusion_matrix(label_test, pre)
print('confusion matrix:')
print(co_mat)
# precision, recall, f1-score の評価
cl_repo = metrics.classification_report(label_test, pre)
print('classification report:')
print(cl_repo)
```

最後にpredict関数での予測結果を元に評価を行います。
accuracy_score関数で正解率が求まります。

[sklearn.metrics.accuracy_score — scikit-learn 0.19.1 documentation](http://scikit-learn.org/stable/modules/generated/sklearn.metrics.accuracy_score.html)

confusion_matrix関数では、テストデータの正解ラベルとそれの予測結果を混同行列として見ることができます。

```
# 縦軸がテストデータの正解ラベル(0～9)
# 横軸が予測結果(0～9)
[[356   0   0   0   0   2   2   2   3   0]
 [  0 421   0   2   1   0   0   0   0   2]
 [  1   0 319   2   7   0   2   7   3   0]
 [  2   0   6 335   0   5   2   2   2   4]
 [  1   1   1   0 328   0   7   0   1  10]
 [  1   2   1   5   1 293   7   1   0   1]
 [  1   2   2   0   0   1 331   0   2   0]
 [  1   1   5   1   1   1   0 344   2   2]
 [  1   3   2   8   1   3   1   1 294   5]
 [  1   0   0   6   6   1   0   7   0 312]]

```

[sklearn.metrics.confusion_matrix — scikit-learn 0.19.1 documentation](http://scikit-learn.org/stable/modules/generated/sklearn.metrics.confusion_matrix.html)


classification_report関数では、主要な指標を見ることができます。

```
# 縦軸がテストデータの正解ラベル(0～9)
# precision : 適合率(予測値ごとの正解率)
# recall :再現率(正解ラベルごとの正解率)
# f1-score :F値(適合率と再現率の調和平均)
# support : データ数
               precision    recall  f1-score   support

        0.0       0.98      0.98      0.98       365
        1.0       0.98      0.99      0.98       426
        2.0       0.95      0.94      0.94       341
        3.0       0.93      0.94      0.93       358
        4.0       0.95      0.94      0.95       349
        5.0       0.96      0.94      0.95       312
        6.0       0.94      0.98      0.96       339
        7.0       0.95      0.96      0.95       358
        8.0       0.96      0.92      0.94       319
        9.0       0.93      0.94      0.93       333

avg / total       0.95      0.95      0.95      3500

```

[sklearn.metrics.classification_report — scikit-learn 0.19.1 documentation](http://scikit-learn.org/stable/modules/generated/sklearn.metrics.classification_report.html)

## **▼4.3.実行結果**

単層～4層までの実行結果です。

**正解率**

```
# 単層
AC Score: 0.952285714286
# 2層
AC Score: 0.959142857143
# 3層
AC Score: 0.962857142857
#4層
AC Score: 0.962
```

**混同行列**

```
# 単層
confusion matrix:
[[356   0   0   0   0   2   2   2   3   0]
 [  0 421   0   2   1   0   0   0   0   2]
 [  1   0 319   2   7   0   2   7   3   0]
 [  2   0   6 335   0   5   2   2   2   4]
 [  1   1   1   0 328   0   7   0   1  10]
 [  1   2   1   5   1 293   7   1   0   1]
 [  1   2   2   0   0   1 331   0   2   0]
 [  1   1   5   1   1   1   0 344   2   2]
 [  1   3   2   8   1   3   1   1 294   5]
 [  1   0   0   6   6   1   0   7   0 312]]
# 2層
confusion matrix:
[[355   0   1   0   0   3   1   1   3   1]
 [  0 420   1   3   0   0   0   1   1   0]
 [  0   2 319   1   5   1   1   6   6   0]
 [  1   0   7 331   0   8   1   1   6   3]
 [  0   1   3   0 329   0   5   0   2   9]
 [  0   0   1   2   0 299   4   0   5   1]
 [  0   4   1   0   1   1 327   0   5   0]
 [  0   1   4   2   0   1   0 348   1   1]
 [  1   1   1   4   0   0   0   1 309   2]
 [  0   0   1   2   2   2   0   3   3 320]]
# 3層
confusion matrix:
[[354   1   0   0   0   4   1   1   2   2]
 [  0 421   1   1   1   0   0   0   1   1]
 [  0   0 326   0   0   0   7   3   3   2]
 [  1   0   8 332   0   5   1   2   5   4]
 [  0   0   1   0 332   0   6   0   1   9]
 [  0   1   0   1   1 303   4   0   1   1]
 [  1   3   0   0   1   2 331   0   1   0]
 [  0   1   7   2   2   2   0 341   1   2]
 [  0   4   1   1   2   3   0   1 304   3]
 [  0   0   1   2   2   1   0   1   0 326]]
#4層
confusion matrix:
[[357   0   0   0   0   2   2   2   2   0]
 [  0 421   1   3   0   0   0   0   1   0]
 [  1   0 320   3   4   0   4   5   3   1]
 [  1   0   5 332   0   5   0   3   4   8]
 [  0   0   1   0 331   0   3   0   0  14]
 [  0   0   0   2   1 304   2   0   3   0]
 [  1   2   0   0   1   0 332   0   3   0]
 [  0   0   5   2   0   1   0 344   1   5]
 [  0   5   2   5   1   2   0   1 302   1]
 [  0   0   1   1   3   1   0   3   0 324]]
```

**主要指標**

```
# 単層
classification report:
             precision    recall  f1-score   support

        0.0       0.98      0.98      0.98       365
        1.0       0.98      0.99      0.98       426
        2.0       0.95      0.94      0.94       341
        3.0       0.93      0.94      0.93       358
        4.0       0.95      0.94      0.95       349
        5.0       0.96      0.94      0.95       312
        6.0       0.94      0.98      0.96       339
        7.0       0.95      0.96      0.95       358
        8.0       0.96      0.92      0.94       319
        9.0       0.93      0.94      0.93       333

avg / total       0.95      0.95      0.95      3500
# 2層
classification report:
             precision    recall  f1-score   support

        0.0       0.99      0.97      0.98       365
        1.0       0.98      0.99      0.98       426
        2.0       0.94      0.94      0.94       341
        3.0       0.96      0.92      0.94       358
        4.0       0.98      0.94      0.96       349
        5.0       0.95      0.96      0.95       312
        6.0       0.96      0.96      0.96       339
        7.0       0.96      0.97      0.97       358
        8.0       0.91      0.97      0.94       319
        9.0       0.95      0.96      0.96       333

avg / total       0.96      0.96      0.96      3500
# 3層
classification report:
             precision    recall  f1-score   support

        0.0       0.99      0.97      0.98       365
        1.0       0.98      0.99      0.98       426
        2.0       0.94      0.96      0.95       341
        3.0       0.98      0.93      0.95       358
        4.0       0.97      0.95      0.96       349
        5.0       0.95      0.97      0.96       312
        6.0       0.95      0.98      0.96       339
        7.0       0.98      0.95      0.96       358
        8.0       0.95      0.95      0.95       319
        9.0       0.93      0.98      0.95       333

avg / total       0.96      0.96      0.96      3500
#4層
classification report:
             precision    recall  f1-score   support

        0.0       0.99      0.98      0.98       365
        1.0       0.98      0.99      0.99       426
        2.0       0.96      0.94      0.95       341
        3.0       0.95      0.93      0.94       358
        4.0       0.97      0.95      0.96       349
        5.0       0.97      0.97      0.97       312
        6.0       0.97      0.98      0.97       339
        7.0       0.96      0.96      0.96       358
        8.0       0.95      0.95      0.95       319
        9.0       0.92      0.97      0.94       333

avg / total       0.96      0.96      0.96      3500
```

# **■5.ハイパーパラメータを最適化する**

機械学習で肝となるのは、いかに適切なハイパーパラメータを設定するかと言うこと。
scikit-learnでは、GridSearchCV関数で最適なハイパーパラメータを設定することができます。

[sklearn.model_selection.GridSearchCV — scikit-learn 0.19.1 documentation](http://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html)

```py3
# ハイパーパラメータのリスト
tuned_parameters = [
    {
        # 最適化手法
        "solver":["lbfgs", "sgd", "adam"], 
        # 隠れ層の層の数と、各層のニューロンの数
        "hidden_layer_sizes":[(100,), (100, 10), (100, 100, 10), (100, 100, 100, 10)], 
    }
]

# ニューラルネットワークの分類器とハイパーパラメータのリストを定義
licv=model_selection.GridSearchCV(MLPClassifier(early_stopping=True), param_grid=tuned_parameters, scoring="accuracy")
```

GridSearchCV関数の第一引数で、評価対象の関数と固定するハイパーパラメータを定義します。
上記の場合、MLPClassifier関数が評価対象で、
early_stopping=True を固定し、それ以外のハイパーパラメータをリストで定義し、リストの中の全組み合わせを実行し、最適値を見つけます。
ハイパーパラメータのリストはparam_gridで定義し、scoringで最適値の評価基準を定義します。

```py3
# 最適なハイパーパラメータを探索する
print('Learning Time(s):', timeit.timeit(lambda: licv.fit(data_train, label_train), number=1))

# 最適なパラメータを表示(リストで指定している項目のみ)
print('licv.best_params_ : ', licv.best_params_)

# 最適なパラメータを表示(リストで指定していない項目も含め全項目表示)
print('licv.best_estimator_ : ', licv.best_estimator_)

# 最適なパラメータ指定時の評価値
print('licv.best_score_ : ', licv.best_score_)
```

fit関数で全パターンの学習を行い、最適なパラメータとその時の評価値を表示しています。

```
Learning Time(s): 263.4227878254683
licv.best_params_ :  {'hidden_layer_sizes': (100,), 'solver': 'lbfgs'}
licv.best_estimator_ :  MLPClassifier(activation='relu', alpha=0.0001, batch_size='auto', beta_1=0.9,
       beta_2=0.999, early_stopping=True, epsilon=1e-08,
       hidden_layer_sizes=(100,), learning_rate='constant',
       learning_rate_init=0.001, max_iter=200, momentum=0.9,
       nesterovs_momentum=True, power_t=0.5, random_state=None,
       shuffle=True, solver='lbfgs', tol=0.0001, validation_fraction=0.1,
       verbose=False, warm_start=False)
licv.best_score_ :  0.954904761905
```

実行結果は以上の通りです。
単層が最も良い結果になってしまいましたねｗ

```py3
# 求められた最適なパラメータで予測、評価
pre = licv.predict(data_test)
# 正解率取得
ac_score = metrics.accuracy_score(label_test, pre)
print('AC Score:', ac_score)
# 混同行列の出力
co_mat = metrics.confusion_matrix(label_test, pre)
print('confusion matrix:')
print(co_mat)
# precision, recall, f1-score の評価
cl_repo = metrics.classification_report(label_test, pre)
print('classification report:')
print(cl_repo)
```

predict関数を使うと、求められた最適なパラメータで予測を行います。
評価についてはこれまでと同様です。

ハイパーパラメータの最適化～最適化されたハイパーパラメータでの学習・評価を行う実装は以下になります。

```py3
# coding: utf-8
import numpy as np
import timeit
from sklearn import datasets, model_selection, metrics
from sklearn.neural_network import MLPClassifier

# データ取得
mnist = datasets.fetch_mldata('MNIST original', data_home='data/mnist/')

# データとラベルを取り出す
mnist_data = mnist.data / 255 # 0.0～1.0に正規化
#mnist_data = preprocessing.minmax_scale(mnist.data)
mnist_label = mnist.target

# 学習用(全データの30%)とテスト用(全データの5%)に分割
data_train, data_test, label_train, label_test = model_selection.train_test_split(mnist_data, mnist_label, test_size=0.05, train_size=0.3)
print('train_size:', data_train.shape[0]) # 学習データ数
print('test_size:', data_test.shape[0]) # テストデータ数

# ハイパーパラメータのリスト
tuned_parameters = [
    {
        # 最適化手法
        "solver":["lbfgs", "sgd", "adam"], 
        # 隠れ層の層の数と、各層のニューロンの数
        "hidden_layer_sizes":[(100,), (100, 10), (100, 100, 10), (100, 100, 100, 10)], 
    }
]

# ニューラルネットワークの分類器とハイパーパラメータのリストを定義
licv=model_selection.GridSearchCV(MLPClassifier(early_stopping=True), param_grid=tuned_parameters, scoring="accuracy", cv=5)
# 最適なハイパーパラメータを探索する
print('Learning Time(s):', timeit.timeit(lambda: licv.fit(data_train, label_train), number=1))

# 最適なパラメータを表示(リストで指定している項目のみ)
print('licv.best_params_ : ', licv.best_params_)

# 最適なパラメータを表示(リストで指定していない項目も含め全項目表示)
print('licv.best_estimator_ : ', licv.best_estimator_)

print('licv.best_score_ : ', licv.best_score_)

# 求められた最適なパラメータで予測、評価
pre = licv.predict(data_test)
# 正解率取得
ac_score = metrics.accuracy_score(label_test, pre)
print('AC Score:', ac_score)
# 混同行列の出力
co_mat = metrics.confusion_matrix(label_test, pre)
print('confusion matrix:')
print(co_mat)
# precision, recall, f1-score の評価
cl_repo = metrics.classification_report(label_test, pre)
print('classification report:')
print(cl_repo)

```

今回はscikit-learnを使って、ディープラーニングを構築してみました。
また、最適なパラメータを簡単に導き出す方法についても解説しました。