Python
Python3
Keras
hyperas
KerasDay 6

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

最近流行りのハイパーパラメータチューニング

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

なんだこれ!

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

これは使わねばならん!!!

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

インストール

pip install hyperas

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

チュートリアル

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

Tutorial.1 ~choice~

■ インポート

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

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に合わせて下さい。

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です。

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は探す回数です。

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_runbest_modelです。

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

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

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

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

■ 実行結果

_________________________________________________________________
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~

■ インポート

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君が探してきてくれます。これで連続値でも安心ですね。

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と同じ。

■ 実行結果

_________________________________________________________________
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 ~複雑なパターン~

■ インポート

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させることで、ニューラルネットの層数自体を変えることもできます。

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回検証させてみました。

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

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)

■ 実行結果

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

参考

自己紹介

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

名前 綿岡晃輝
職業 大学4年生
分野 機械学習, 深層学習, 音声処理
Twitter @Wataoka_Koki

Twitterフォローしてね!