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