snovaのブログ

主にプログラミングやデジタルコンテンツについて書きます。最近はPython, Flutter, VRに興味があります。

Kerasでハイパーパラメータを自動調整したいならOptuna

イントロダクション

前回はHyperasでニューラルネットのハイパーパラメータを自動調整しました。

snova301.hatenablog.com

しかし、コメントアウトしているのに自動調整されるなど、よくわからない挙動をするため、別のモジュールでハイパーパラメータの自動調整に挑戦しようと思いました。
今回は、Optunaというモジュールを使ってハイパーパラメータの自動調整してみます。

目次

計算環境

- OS : Ubuntu 16.04  
- Python : 3.5.5  
- Keras : 2.2.0  
- TensorFlow : 1.9.0  

インストール

pip install optuna

動作確認は

import optuna

コマンドについて

モジュールのインポート

import numpy as np
import optuna

from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPooling2D, BatchNormalization
from keras.utils import np_utils

選択

trial.suggest_categoricalを使います。

ex. ) 活性化関数にrelusigmoidのどちらかを選択するとき

activation = trial.suggest_categorical('activation', ['relu', 'sigmoid'])

数値の自動調整

trial.suggest_discrete_uniform(a, b, c) : 区間[a, b]内を刻み幅cで区切り、最適値を探します。
trial.suggest_uniform(a, b) : 区間[a, b]内で最適値を探します。

ex. ) Dropout率を0から1の間で調整するとき

dropout_rate = trial.suggest_uniform('dropout_rate', 0, 1)

int型にも出来ます。
int型で調整したいときは、trial.suggest_intというのも用意されています。

ex. ) 隠れ層のユニット数を[100, 101, ... 499, 500]のどれかから選ぶとき

mid_units = int(trial.suggest_discrete_uniform('mid_units', 100, 500, 1))
mid_units = trial.suggest_int('mid_units', 100, 500)

層の数を増やしたいとき

層の数を増やしたいときは、for文を使います。

ex. ) for文を使いユニット数100、活性化関数reluの層を増やすとき

n_layer = trial.suggest_int('n_layer', 1, 3)
for i in range(n_layer):
    model.add(Dense('100', activation='relu'))

TPEとランダムサーチ

公式ドキュメントのstudyのページを見ると、新しくcreate_studyしたときのデフォルトのsamplerNoneとあります。
ただ、何も設定しなくても動いていますし、Preferred Researchの記事では、

最適化エンジン自体は Optuna も Hyperopt も TPE を用いており

とあるので、おそらくTPEがバックで動いていると思うのですが、一応明示しておきます。

# TPE
study = optuna.create_study(sampler=optuna.samplers.TPESampler())

# random
study = optuna.create_study(sampler=optuna.samplers.RandomSampler())

optunaのログを表示しないようにする

デフォルトではログを表示します。
ログを非表示にしたいときは、optuna.logging.disable_default_handler()を使います。
逆に、非表示の状態から、表示したくなったら、optuna.logging.enable_default_handler()を使います。
その他、ログに関しては公式ドキュメントのloggingページを参照してください。

結果の出力

いくつかありますが、代表的なものを2つ。

API 説明
best_params 選ばれたハイパーパラメータ
best_value そのときの評価値

MNISTでテスト

MNISTをCNNで学習するコードを作り、ハイパーパラメータをoptunaで自動調整するコードを作りました。

import numpy as np
import optuna

from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPooling2D, BatchNormalization
from keras.utils import np_utils


def MNIST_data():
    (x_train, y_train), (x_test, y_test) = mnist.load_data()

    x_train = x_train.reshape(x_train.shape[0], 28, 28, 1)
    x_test = x_test.reshape(x_test.shape[0], 28, 28, 1)
    x_train = x_train.astype('float32')/255
    x_test = x_test.astype('float32')/255

    y_train = np_utils.to_categorical(y_train, 10)
    y_test = np_utils.to_categorical(y_test, 10)

    return x_train, y_train, x_test, y_test


def create_model(n_layer, activation, mid_units, dropout_rate):
    model = Sequential()

    for i in range(n_layer):
        model.add(Conv2D(32, kernel_size=(3, 3), activation=activation, input_shape=(28, 28, 1)))
        model.add(MaxPooling2D(pool_size=(1, 1), strides=2))
        model.add(BatchNormalization())

    model.add(Flatten())
    model.add(Dense(mid_units, activation=activation))
    model.add(Dropout(dropout_rate))

    model.add(Dense(10, activation=activation))

    return model


def objective(trial):
    # データをロード
    x_train, y_train, x_test, y_test = MNIST_data()

    # 調整したいハイパーパラメータの設定
    n_layer = trial.suggest_int('n_layer', 1, 3) # 追加する層を1-3から選ぶ
    mid_units = int(trial.suggest_discrete_uniform('mid_units', 100, 500, 1)) # ユニット数
    dropout_rate = trial.suggest_uniform('dropout_rate', 0, 1) # ドロップアウト率
    activation = trial.suggest_categorical('activation', ['relu', 'sigmoid']) # 活性化関数
    optimizer = trial.suggest_categorical('optimizer', ['sgd', 'adam', 'rmsprop']) # 最適化アルゴリズム

    # 学習モデルの構築と学習の開始
    model = create_model(n_layer, activation, mid_units, dropout_rate)
    model.compile(optimizer=optimizer,
                    loss='categorical_crossentropy',
                    metrics=['accuracy'])
    history = model.fit(x_train, y_train, 
                        verbose=1,
                        epochs=5,
                        validation_data=(x_test, y_test),
                        batch_size=128)

    # 学習モデルの保存
    model_json = model.to_json()
    with open('keras_model.json', 'w') as f_model:
        f_model.write(model_json)
    model.save_weights('keras_model.hdf5')

    # 最小値探索なので
    return -np.amax(history.history['val_acc'])


def main():
    study = optuna.create_study(sampler=optuna.samplers.TPESampler())
    study.optimize(objective, n_trials=10)
    print('best_params')
    print(study.best_params)
    print('-1 x best_value')
    print(-study.best_value)

    print('\n --- sorted --- \n')
    sorted_best_params = sorted(study.best_params.items(), key=lambda x : x[0])
    for i, k in sorted_best_params:
        print(i + ' : ' + str(k))


if __name__ == '__main__':
    main()

結果 :

...

best_params
{'dropout_rate': 0.5738498759465457, 'n_layer': 3, 'mid_units': 427.0, 'optimizer': 'rmsprop', 'activation': 'sigmoid'}
-1 x best_value
0.9827

 --- sorted --- 

activation : sigmoid
dropout_rate : 0.5738498759465457
mid_units : 427.0
n_layer : 3
optimizer : rmsprop

この結果をまとめると、

ハイパーパラメータ 最適値
活性化関数 sigmoid
ドロップアウト 0.5738498759465457
ユニット数 427
増やした層数 3
最適化アルゴリズム rmsprop

感想

Hyperasより安定しています。
(コメントアウト行の自動調整されていない点など)
不満点は、Hyperasでは学習モデルが返ってきていたが、Optunaではそれが出来ないことです。
ただ、それはモデルの保存をすればクリアできる問題ですし、それ以外は使いやすいので、今日からOptunaを使うことにします。

参考文献

Google Play and the Google Play logo are trademarks of Google LLC.