Kerasだってハイパーパラメータチューニングできるもん。【hyperas】

# 最近流行りのハイパーパラメータチューニング
　先日(2018年12月3日)、僕のTwitterのタイムラインに下のツイートが流れてきました。ハイパーパラメータを自動で最適化してくれるフレームワークOptuna。

**なんだこれ！**




<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr"><img width="631" alt="スクリーンショット 2018-12-04 21.47.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/221435/7c641f93-0ca1-b99b-8a31-9d0539dcd540.png"><a href="https://t.co/3qhuwMVxAc">https://t.co/3qhuwMVxAc</a></p>&mdash; Takuya Akiba (@iwiwi) <a href="https://twitter.com/iwiwi/status/1069489203874947072?ref_src=twsrc%5Etfw">2018年12月3日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

普段地道にハイパーパラメータを調節している僕はすぐにOptunaについて調べました。

**これは使わねばならん！！！**

そう確信したのですが、残念ながら現在行なっている大学の研究ではkerasでかなり実装を進めてしまっていました。そこで、**hyperas**というkerasユーザーに優しいハイパーパラメータチューニングのライブラリを使ってみることにしました。

# インストール

```command
pip install hyperas
```

以上。
(僕はこれでいけた)
(他の方法は自分で調べてくれい)

# チュートリアル
[公式GitのREADME](https://github.com/maxpumperla/hyperas)に英語ですがチュートリアルがありました。これを基に僕なりにチュートリアルを作り直してみました。多分僕の方がわかりやすいので、是非これに従ってhyperasに入門してみて下さい。

## Tutorial.1 ~choice~

### ■ インポート
これらをインポートします。
一旦読み飛ばしましょう。

```python
import numpy as np

from hyperopt import Trials, STATUS_OK, tpe
from hyperas import optim
from hyperas.distributions import choice

from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Activation, Dropout
from keras.utils.np_utils import to_categorical
```

### ■ データの準備

mnistデータセットを準備します。
returnは必ず`x_train, y_train, x_test, y_test`に合わせて下さい。

```python
def prepare_data():
    (x_train, y_train), (x_test, y_test) = mnist.load_data()
    x_train = x_train.reshape(60000, 784)
    x_test = x_test.reshape(10000, 784)
    x_train = x_train.astype('float32') / 255
    x_test = x_test.astype('float32') / 255
    y_train = to_categorical(y_train, 10)
    y_test = to_categorical(y_test, 10)
    return x_train, y_train, x_test, y_test
```

### ■ モデルの設計
読み飛ばしていた方、***ストップ！！！***
ここが肝です。

関数内の2行目、3行目。
`{{}}`の中に`choice`と書かれています。`choice`は先ほどインポートしたものです。 このように書くことで、2行目ですと、全結合層のノードの数を256個と512個を試してくれます。3行目も同様に16個と32個と64個を試してくれます。内部実装を軽く説明しますと、この`create_model`というテンプレート関数をこの後hyperasに投げることで、具体的なpythonコードを生成してくれるそうです。(間違っていたらご指摘ください。)

関数内の中盤。
普段の`create_model`とは少し感覚が違うでしょうが、関数内でコンパイル(compile)もトレーニング(fit)も行なってしまいます。

関数内の終盤。
返り値はディクショナリとなっております。気をつけるべきことは、`loss`というキーには最小化したいバリューを与えることです。今回は`validation accuracy`を最小化したいので`-val_acc`という風にマイナスをつけてあげればOKです。

```python
def create_model(x_train, y_train, x_test, y_test):
    model = Sequential()
    model.add(Dense({{choice([256, 512])}}, input_shape=(784,)))
    model.add(Dense({{choice([16, 32, 64])}}))
    model.add(Dense(10))
    model.add(Activation('softmax'))

    model.compile(optimizer='rmsprop',
                  loss='categorical_crossentropy',
                  metrics=['accuracy'])

    model.fit(x_train, y_train,
              batch_size=100,
              epochs=10,
              verbose=1,
              validation_data=(x_test, y_test))

    val_loss, val_acc = model.evaluate(x_test, y_test, verbose=0)
    return {'loss': -val_acc, 'status': STATUS_OK, 'model': model}

```

### ■ メインルーチン

先ほどインポートした`optim.minimize`を使用します。この関数に先ほど定義した`create_model`関数と`prepare_data`関数を与えます。あとは`hyperas`君が頑張って出来るだけ最適なハイパーパラメータを探してきてくれます。`max_evals`は探す回数です。

```python
if __name__ == "__main__":

    best_run, best_model = optim.minimize(model=create_model,
                                          data=prepare_data,
                                          algo=tpe.suggest,
                                          max_evals=6,
                                          trials=Trials())

    print(best_model.summary())
    print(best_run)

    _, _, x_test, y_test = prepare_data()
    val_loss, val_acc = best_model.evaluate(x_test, y_test)
    print("val_loss: ", val_loss)
    print("val_acc: ", val_acc)

```

`optim.minimize`は二つの値を返してくれます。`best_run`と`best_model`です。

`best_run`は最も良かった組み合わせのディクショナリです。例えば、

```python
{'Dense': 2, 'Dense_1': 0}
```

こんな感じです。
0番目のDenseは2番目のハイパーパラメータが良かったよ。
1番目のDenseは0番目のハイパーパラメータが良かったよ。
と教えてくれています。

`best_model`はその名の通り最も良かったモデルオブジェクトです。こいつを使って上のように`validation accrary`を確認するのも良し、モデルをしっかり保存するのも良しです。


### ■ 実行結果

```command
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_16 (Dense)             (None, 512)               401920    
_________________________________________________________________
dense_17 (Dense)             (None, 64)                32832     
_________________________________________________________________
dense_18 (Dense)             (None, 10)                650       
_________________________________________________________________
activation_6 (Activation)    (None, 10)                0         
=================================================================
Total params: 435,402
Trainable params: 435,402
Non-trainable params: 0
_________________________________________________________________

{'Dense': 1, 'Dense_1': 2}

10000/10000 [==============================] - 0s 36us/step
val_loss:  0.2902372345119715
val_acc:  0.9204
```

ということで、`hyperas`が叩き出した最適なハイパーパラメータは以下です。

|層の名前|値|
|:--|:--|
|Dense|512|
|Dense_1|64|

## Tutorial.2 ~uniform~


### ■ インポート

```python
import numpy as np

from hyperopt import Trials, STATUS_OK, tpe
from hyperas import optim
from hyperas.distributions import choice, uniform

from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Activation, Dropout
from keras.utils.np_utils import to_categorical
```

### ■ データの準備
Tutorial.1と同じ。

### ■ モデルの設計


さて、`Dropout`が追加され、`Dropout`の引数には`{{uniform(0, 1)}}`と書かれています。`uniform`は一様分布のことです。これで0~1までの数字でどんな値が最適であるかを`hyperas`君が探してきてくれます。これで連続値でも安心ですね。

```python

def create_model(x_train, y_train, x_test, y_test):
    model = Sequential()
    model.add(Dense({{choice([256, 512])}}, input_shape=(784,)))
    model.add(Dropout({{uniform(0, 1)}}))
    model.add(Dense({{choice([16, 32, 64])}}))
    model.add(Dropout({{uniform(0, 1)}}))
    model.add(Dense(10))
    model.add(Activation('softmax'))

    model.compile(optimizer='rmsprop',
                  loss='categorical_crossentropy',
                  metrics=['accuracy'])

    model.fit(x_train, y_train,
              batch_size=100,
              epochs=10,
              verbose=1,
              validation_data=(x_test, y_test))

    val_loss, val_acc = model.evaluate(x_test, y_test, verbose=0)
    return {'loss': -val_acc, 'status': STATUS_OK, 'model': model}

```

### ■ メインルーチン
Tutorial.1と同じ。

### ■ 実行結果

```command
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_10 (Dense)             (None, 256)               200960    
_________________________________________________________________
dropout_7 (Dropout)          (None, 256)               0         
_________________________________________________________________
dense_11 (Dense)             (None, 32)                8224      
_________________________________________________________________
dropout_8 (Dropout)          (None, 32)                0         
_________________________________________________________________
dense_12 (Dense)             (None, 10)                330       
_________________________________________________________________
activation_4 (Activation)    (None, 10)                0         
=================================================================
Total params: 209,514
Trainable params: 209,514
Non-trainable params: 0
_________________________________________________________________

{'Dense': 0, 'Dense_1': 1, 'Dropout': 0.4844455237320119, 'Dropout_1': 0.026079803111884514}
10000/10000 [==============================] - 0s 30us/step
val_loss:  0.2799887662306428
val_acc:  0.9247
```

ということで、`hyperas`が叩き出した最適なハイパーパラメータは以下です。

|層の名前|値|
|:--|:--|
|Dense|256|
|Dense_1|32|
|Dropout|0.4844455237320119|
|Dropout_1|0.026079803111884514|

自分では決して出ない結論ですね...。



## Tutorial.3 ~複雑なパターン~

### ■ インポート

```python
import numpy as np
from sklearn.metrics import accuracy_score

from hyperopt import Trials, STATUS_OK, tpe
from hyperas import optim
from hyperas.distributions import choice, uniform

from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Activation, Dropout
from keras.utils.np_utils import to_categorical
```

### ■ データの準備
Tutorial.1と同じ。

### ■ モデルの設計

たっぷりとパラメータチューニングしました。

組合せ爆発でパターンの数は凄そうですね。

if文の分岐に注目してください。このように、適当な言葉(threeとか)をchoiceさせることで、ニューラルネットの層数自体を変えることもできます。

```python
def create_model(x_train, y_train, x_test, y_test):
    model = Sequential()
    model.add(Dense(512, input_shape=(784,)))
    model.add(Activation({{choice(['linear', 'relu', 'sigmoid'])}}))
    model.add(Dropout({{uniform(0, 1)}}))
    model.add(Dense({{choice([400, 512, 600])}}))
    model.add(Activation({{choice(['linear', 'relu', 'sigmoid'])}}))
    model.add(Dropout({{uniform(0, 1)}}))

    if {{choice(['three', 'four', 'five'])}} == 'three':
        pass
    elif {{choice(['three', 'four', 'five'])}} == 'four':
        model.add(Dense(100))
        model.add(Activation({{choice(['linear', 'relu', 'sigmoid'])}}))
        model.add(Dropout({{uniform(0, 1)}}))
    elif {{choice(['three', 'four', 'five'])}} == 'five':
        model.add(Dense(200))
        model.add(Activation({{choice(['linear', 'relu', 'sigmoid'])}}))
        model.add(Dropout({{uniform(0, 1)}}))
        model.add(Dense(100))
        model.add(Activation({{choice(['linear', 'relu', 'sigmoid'])}}))
        model.add(Dropout({{uniform(0, 1)}}))

    model.add(Dense(10))
    model.add(Activation('softmax'))

    model.compile(optimizer={{choice(['rmsprop', 'adam', 'sgd'])}},
                  loss='categorical_crossentropy',
                  metrics=['accuracy'])

    model.fit(x_train, y_train,
              batch_size=100,
              epochs=12,
              verbose=1,
              validation_data=(x_test, y_test))

    val_loss, val_acc = model.evaluate(x_test, y_test, verbose=0)
    return {'loss': -val_acc, 'status': STATUS_OK, 'model': model}
```

### ■ メインルーチン

`max_evals`を変えました。

組み合わせも増えてきましたので、100回検証させてみました。

そりゃもうすごい成績なんでしょう...。


```python
if __name__ == "__main__":

    best_run, best_model = optim.minimize(model=create_model,
                                          data=prepare_data,
                                          algo=tpe.suggest,
                                          max_evals=100,
                                          trials=Trials())

    print(best_model.summary())
    print(best_run)

    _, _, x_test, y_test = prepare_data()
    val_loss, val_acc = best_model.evaluate(x_test, y_test)
    print("val_loss: ", val_loss)
    print("val_acc: ", val_acc)
```


### ■ 実行結果

```command
Layer (type)                 Output Shape              Param #   
=================================================================
dense_76 (Dense)             (None, 512)               401920    
_________________________________________________________________
activation_76 (Activation)   (None, 512)               0         
_________________________________________________________________
dropout_54 (Dropout)         (None, 512)               0         
_________________________________________________________________
dense_77 (Dense)             (None, 512)               262656    
_________________________________________________________________
activation_77 (Activation)   (None, 512)               0         
_________________________________________________________________
dropout_55 (Dropout)         (None, 512)               0         
_________________________________________________________________
dense_78 (Dense)             (None, 10)                5130      
_________________________________________________________________
activation_78 (Activation)   (None, 10)                0         
=================================================================
Total params: 669,706
Trainable params: 669,706
Non-trainable params: 0
_________________________________________________________________
None
{'Activation': 1, 'Activation_1': 1, 'Activation_2': 1, 'Activation_3': 1, 'Activation_4': 0, 'Dense': 1, 'Dropout': 0.34508543889575766, 'Dropout_1': 0.3554330239504615, 'Dropout_2': 0, 'Dropout_3': 1, 'Dropout_4': 0.1408466259445935, 'Dropout_5': 0, 'Dropout_6': 0.9822922940890546, 'Dropout_7': 0.6033247984045469, 'optimizer': 1}
10000/10000 [==============================] - 1s 82us/step
val_loss:  0.0508109834289553
val_acc:  0.986
```

結果：validation accuracy 0.986
CNNを用いずにここまでの精度は素晴らしいんじゃないでしょうか！


# 公式Example
他にも公式GitHubにいくつかのExampleがありますので、ぜひ試してみて下さい。

[hyperas公式のExamples](https://github.com/maxpumperla/hyperas/tree/master/examples)

# 参考
- ハイパーパラメータチューニング全般
  - [その機械学習プロセス、自動化できませんか？](https://qiita.com/Hironsan/items/30fe09c85da8a28ebd63#%E3%83%8F%E3%82%A4%E3%83%91%E3%83%BC%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF%E3%83%81%E3%83%A5%E3%83%BC%E3%83%8B%E3%83%B3%E3%82%B0)
  - [MNISTでハイパーパラメータをいじってloss/accuracyグラフを見てみる](https://qiita.com/hiroyuki827/items/213146d551a6e2227810)
  - [【機械学習】パラメータチューニングについて](https://qiita.com/nanairoGlasses/items/93d764f549943d42d5e6)
  - [scikit-learnのGridSearchCVでハイパーパラメータ探索](https://qiita.com/saiaron/items/bb96c0f898cd0cbcd788)
  - [Scikit-learnでハイパーパラメータのグリッドサーチ](https://qiita.com/sotetsuk/items/16ffd76978085bfd7628)
  - [ハイパーパラメータチューニングの仕方](https://qiita.com/R1ck29/items/50ba7fa5afa49e334a8f)
  - [Using Machine Learning to Explore Neural Network Architecture](https://qiita.com/miyamotok0105/items/c9cff78937888572dfb4)
- Optuna
  - [OptunaでCNNのハイパーパラメータを最適化](https://qiita.com/ryota717/items/28e2167ea69bee7e250d) 
- hyperas
  - [hyperas公式](https://github.com/maxpumperla/hyperas)
  - [hyperasでkeras-rlのハイパーパラメータチューニングをやってみる](http://takemikami.com/2017/07/31/hyperaskerasrl.html)
- keras
  - [脳死で覚えるkeras入門](https://qiita.com/wataoka/items/5c6766d3e1c674d61425#1%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E7%94%A8%E6%84%8F%E3%81%99%E3%82%8B)


# 自己紹介

冒頭に書くと邪魔になるので最後にひっそりと自己紹介させてください。

<img src="https://qiita-image-store.s3.amazonaws.com/0/221435/16727490-2b41-4318-cc25-e2601479e77a.jpeg" width=30%>

|名前|綿岡晃輝|
|:--|:--|
|職業|大学4年生|
|分野|機械学習, 深層学習, 音声処理|
|Twitter|[@Wataoka_Koki](https://twitter.com/Wataoka_Koki)|

Twitterフォローしてね！