ログイン新規登録

Qiitaにログインして、便利な機能を使ってみませんか?

あなたにマッチした記事をお届けします

便利な情報をあとから読み返せます

13

この記事は最終更新日から3年以上が経過しています。

Hyperasを使ったKerasハイパーパラメータチューニング

最終更新日 投稿日 2019年06月24日

ディープラーニングをしているとハイパーパラメータが何か最適かを悩むことが多いです。
ハイパーパラメータの探索には、ランダムサーチやグリッドサーチなど様々な方法があります。記事「ハイパーパラメータ自動調整いろいろ」にわかりやすく書かれていて、勉強させていただきました。
今回は、Hyperasを使ってKerasのハイパーパラメータを自動チューニングします。

環境

前提としてpyenvが入っているくらいでしょうか。pyenvインストールと設定については記事「UbuntuにpyenvとvenvでPython開発環境構築」を参照ください。

種類 バージョン 内容
OS Ubuntu18.04.01 LTS 仮想で動かしています
pyenv 1.2.11 複数Python環境を使うことがあるのでpyenv使っています
Python 3.6.8 pyenv上でpython3.6.8を使っています
パッケージはvenvを使って管理しています

インストール

pipでhyperasをインストール。venvの仮想環境を使っています。バージョンは0.41です。

pip install hyperas

プログラム

Jupyter Labで動かしています。プログラムはGitHubに置いています。
今回のモデルの意味に関しては、記事「【Keras入門(1)】単純なディープラーニングモデル定義」を参照ください。

1. ライブラリインポート

Hyperas関連ライブラリをインポートします。

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

2. 訓練データ関数

訓練データを返す関数を作ります。今回はテスト用の乱数です。
通常はファイルを読み込んだり、訓練だけでなくテストデータも分割して渡します。

def data():
    import numpy as np
    x_train = np.random.rand(128, 2)
    y_train = (np.sum(x_train, axis=1) > 1.0) * 1
    y_train = y_train.reshape(128,1)

    return x_train, y_train

3. モデル関数

データを受け取ってモデル定義と訓練実行の関数です。
通常だと評価も実行するかと思います。

def create_model(x_train, y_train):
    # Sequentialモデル使用(Sequentialモデルはレイヤを順に重ねたモデル)
    model = Sequential()

    # 結合層
    model.add(Dense({{choice([4, 8, 16, 32, 64, 128])}}, input_dim=2, activation="tanh"))

    model.add(Dropout({{uniform(0, 1)}}))

    # 結合層:入力次元を省略すると自動的に前の層の出力次元数を引き継ぐ
    model.add(Dense(1, activation="sigmoid"))

    # モデルをコンパイル
    model.compile(loss="binary_crossentropy",
                  optimizer={{choice(['adam', 'sgd'])}},
                  metrics=["accuracy"])

    result = model.fit(x_train, y_train, epochs=3, validation_split=0.2, verbose=0)

    # 普通はここでevaluateしてその結果を使うが、今回は簡易的に訓練時の結果を使用

    validation_acc = np.amax(result.history['val_acc']) 
    print('Best validation acc of epoch:', validation_acc)

    # ここでreturnするlossの値を最小化するように探索する
    return {'loss': -validation_acc, 'status': STATUS_OK, 'model': model}

3.1. choice

上記のchoiceの詳細です。{{choice([4, 8, 16, 32, 64, 128])}}を使ってレイヤ数を選択させています。

model.add(Dense({{choice([4, 8, 16, 32, 64, 128])}}, input_dim=2, activation="tanh"))

3.2. uniform

上記のuniformの詳細です。{{uniform(0, 1)}}とすることでDropoutの数値を0から1までの間の数で探索します。

model.add(Dropout({{uniform(0, 1)}}))

4. チューニング実行

チューニングを実行して、最も結果が良かったパラメータとモデルを受け取ります。

  • max_evals : ハイパーパラメータを探索する回数です。回数が多いほどいい値を探索してくれるかもしれませんが、その半面時間がかかります。
  • eval_space : Trueを渡すことで、1番目のリターンパラメータにわかりやすい値が返ってきます。デフォルトはFalseで、Falseでchoiceを使った場合に対して指定した順の値が返ってきます。今回の例{{choice([4, 8, 16, 32, 64, 128])}}で0の場合は4が最も良かったDenseの値です。
  • notebook_name : 今回は、Jupyterで動かしているのでパラメータ"notebook_name"に自身のノートブック名を渡しています
best_run, best_model = optim.minimize(model=create_model,
                                      data=data,
                                      algo=tpe.suggest,
                                      max_evals=3,
                                      eval_space=True,
                                      notebook_name='Keras07_hyperas', # This is important!
                                      trials=Trials())

実行時出力

実行時の出力です。

>>> Imports:
#coding=utf-8

try:
    from tensorflow.keras.models import Sequential
except:
    pass

try:
    from tensorflow.keras.layers import Dense, Dropout
except:
    pass

try:
    from hyperopt import Trials, STATUS_OK, tpe, rand
except:
    pass

try:
    from hyperas import optim
except:
    pass

try:
    from hyperas.distributions import choice, uniform
except:
    pass

try:
    import numpy as np
except:
    pass

>>> Hyperas search space:

def get_space():
    return {
        'Dense': hp.choice('Dense', [8, 16, 32, 64, 128]),
        'Dropout': hp.uniform('Dropout', 0, 1),
        'optimizer': hp.choice('optimizer', ['adam', 'sgd']),
    }

>>> Data
  1: 
  2: import numpy as np
  3: x_train = np.random.rand(128, 2)
  4: y_train = (np.sum(x_train, axis=1) > 1.0) * 1
  5: y_train = y_train.reshape(128,1)
  6: 
  7: 
  8: 
  9: 
>>> Resulting replaced keras model:

  1: def keras_fmin_fnct(space):
  2: 
  3:     # Sequentialモデル使用(Sequentialモデルはレイヤを順に重ねたモデル)
  4:     model = Sequential()
  5: 
  6:     # 結合層
  7:     model.add(Dense(space['Dense'], input_dim=2, activation="tanh"))
  8:     
  9:     model.add(Dropout(space['Dropout']))
 10: #    model.add(Dropout(0.2))
 11: 
 12:     # 結合層:入力次元を省略すると自動的に前の層の出力次元数を引き継ぐ
 13:     model.add(Dense(1, activation="sigmoid"))
 14: 
 15:     # モデルをコンパイル
 16:     model.compile(loss="binary_crossentropy",
 17:                   optimizer=space['optimizer'],
 18:                   metrics=["accuracy"])
 19:     
 20:     result = model.fit(x_train, y_train, epochs=30, validation_split=0.2, verbose=0)
 21:     
 22:     
 23:     # 普通はここでevaluateしてその結果を使うが、今回は簡易的に訓練時の結果を使用
 24:     
 25:     validation_acc = np.amax(result.history['val_acc']) 
 26:     print('Best validation acc of epoch:', validation_acc)
 27:     
 28:     return {'loss': -validation_acc, 'status': STATUS_OK, 'model': model}
 29: 
  0%|          | 0/3 [00:00<?, ?it/s, best loss: ?]
WARNING:tensorflow:From Instructions for updating:
Use tf.cast instead.
Best validation acc of epoch:
0.88461536
Best validation acc of epoch:
0.9230769
Best validation acc of epoch:
0.7692308
100%|██████████| 3/3 [00:08<00:00,  3.04s/it, best loss: -0.9230769276618958]

この出力は大事なので消さないようにしましょう。例えばDROPOUTの変数を複数最適化しようとすると、最後に出力する最適値がどちらの変数かわからなくなります。基本的に時間がかかる処理なので、最後に無駄にしないように、コンソールに出力する値はとっておきましょう。

{'Dropout': 0.42522861686845626,
 'Dropout_1': 0.23316134447477344}

5. 評価

チューニングして最も結果が良かったモデルを使って評価もできます。

x_test, y_test = data()
print("Evalutation of best performing model:")
print(best_model.evaluate(x_test, y_test))
Evalutation of best performing model:
128/128 [==============================] - 0s 99us/sample - loss: 0.4846 - acc: 0.9141
[0.4846123978495598, 0.9140625]

6. ハイパーパラメータ表示

best_runに最も結果が良かったハイパーパラメータが入っています。

print("Best performing model chosen hyper-parameters:")
print(best_run)
Best performing model chosen hyper-parameters:
{'Dense': 3, 'Dropout': 0.21280043312755825, 'optimizer': 0}

複雑なパターンのチューニング

試していませんが、「Kerasだってハイパーパラメータチューニングできるもん。【hyperas】」の記事にあるように複雑なチューニングも可能なようです。

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)}}))

※2019/8/15追記
上記の方法だけでは失敗しました。こんなおまじないを書かないとうまくいきませんでした。変数のディクショナリを作る箇所のバグっぽいです。

# (layer = {{choice([['three', 'four', 'five'])}})

Google Colaboratoryの場合

Keras Hyperparameter Tuning in Google Colab using Hyperasに書いてあるとおりにすればできる?試せていないです。

メモリ不足によるエラー対策

GitHubのIssueを見てモデル出力をやめ、以下のコードを追加することで解決するかもしれません。私がやったときは、他に原因があったので、下記の方法でメモリ削減が達成できるのか調べられていません。

import gc
from tensorflow.python.keras import backend as K

K.clear_session()
gc.collect()

新規登録して、もっと便利にQiitaを使ってみよう

  1. あなたにマッチした記事をお届けします
  2. 便利な情報をあとで効率的に読み返せます
  3. ダークテーマを利用できます
ログインすると使える機能について

コメント

この記事にコメントはありません。

いいね以上の気持ちはコメントで

13

新規登録して、Qiitaをもっと便利に使ってみませんか

この機能を利用するにはログインする必要があります。ログインするとさらに下記の機能が使えます。

  1. ユーザーやタグのフォロー機能であなたにマッチした記事をお届け
  2. ストック機能で便利な情報を後から効率的に読み返せる

ソーシャルアカウントでログイン・新規登録

メールアドレスでログイン・新規登録