Kerasでハイパーパラメータを自動調整したいならHyperas
イントロダクション
ニューラルネットの問題点として、ハイパーパラメータが多く、しかもチューニング次第で大きく精度が変わることが知られています。
自動調整にはいくつか方法がありますが、なんとなく敷居が高そうだなと感じていました。
そんなとき、以下の記事を見つけたので、MNISTで実践してみました。
Kerasだってハイパーパラメータチューニングできるもん。【hyperas】
目次
計算機環境
インストール
公式に従って、pip
でインストールします。
- hyperopt 公式
pip install hyperas
動作チェックは、
import hyperas
コマンドについて
モジュールのインポート
hyperopt
モジュールも使用します。
from hyperopt import Trials, STATUS_OK, tpe, rand from hyperas import optim from hyperas.distributions import choice, uniform
選択
choice
コマンドを使います。
ex. ) 活性化関数にrelu
とsigmoid
のどちらかを選択するとき
model.add(Activation({{choice(['relu', 'sigmoid'])}}))
数値の自動調整
uniform
コマンドを使います。
ex. ) Dropout率を0から1の間で調整するとき
model.add(Dropout({{uniform(0, 1)}}))
層の数を増やすとき
層の数を増やしたいときは、if
文とchoice
を併用します。
ex. ) 2層目を追加するかどうか
if {{choice(['one', 'two'])}} == 'two': model.add(Dense(100)) model.add(Activation('relu'))
自動調整のための関数選び
hyperopt
モジュールを使って、自動調整を行っていますが、以下の2つの方法が用意されています。
tpe
: TPEで自動調整
rand
: ランダムサーチで自動調整
ex. ) TPEで自動調整するとき
条件 :
- 学習モデルは関数create_model
の返り値
- データは関数data
の返り値
- 最大5回調整
best_run, best_model = optim.minimize(model=create_model,
data=data,
algo=tpe.suggest,
max_evals=5,
trials=Trials())
hyperoptやTPEに関しては以下の記事を参照
- hyperoptって何してんの?
- Hyperoptなどのハイパーパラメータチューニングとその関連手法についてのメモ
- 予測モデルを使ったシミュレーションと最適解探索
- 機械学習モデルのハイパパラメータ最適化
テストしてみる
公式のサンプルでは、多層パーセプトロンのモデルを構築して、MNISTのハイパーパラメータを自動調整しています。
参考サイト : hyperas - github
サンプルコードをベースにして作ったコードは以下
import numpy as np from hyperopt import Trials, STATUS_OK, tpe from keras.datasets import mnist from keras.layers.core import Dense, Dropout, Activation from keras.models import Sequential from keras.utils import np_utils from hyperas import optim from hyperas.distributions import choice, uniform def 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') x_test = x_test.astype('float32') x_train /= 255 x_test /= 255 nb_classes = 10 y_train = np_utils.to_categorical(y_train, nb_classes) y_test = np_utils.to_categorical(y_test, nb_classes) return x_train, y_train, x_test, y_test def create_model(x_train, y_train, x_test, y_test): model = Sequential() model.add(Dense(512, input_shape=(784,))) model.add(Activation('relu')) model.add(Dropout({{uniform(0, 1)}})) model.add(Dense({{choice([256, 512, 1024])}})) model.add(Activation({{choice(['relu', 'sigmoid'])}})) model.add(Dropout({{uniform(0, 1)}})) if {{choice(['three', 'four'])}} == 'four': model.add(Dense(100)) model.add({{choice([Dropout(0.5), Activation('linear')])}}) model.add(Activation('relu')) model.add(Dense(10)) model.add(Activation('softmax')) model.compile(loss='categorical_crossentropy', metrics=['accuracy'], optimizer={{choice(['rmsprop', 'adam', 'sgd'])}}) result = model.fit(x_train, y_train, batch_size={{choice([64, 128])}}, epochs=2, verbose=2, validation_split=0.1) validation_acc = np.amax(result.history['val_acc']) print('Best validation acc of epoch:', validation_acc) return {'loss': -validation_acc, 'status': STATUS_OK, 'model': model} if __name__ == '__main__': best_run, best_model = optim.minimize(model=create_model, data=data, algo=tpe.suggest, max_evals=5, trials=Trials()) X_train, Y_train, X_test, Y_test = data() print("Evalutation of best performing model:") print(best_model.evaluate(X_test, Y_test)) print("Best performing model chosen hyper-parameters:") print(best_run) # ソート print('--- sorted ---') sorted_best_run = sorted(best_run.items(), key=lambda x : x[0]) for i, k in sorted_best_run: print(i + ' : ' + str(k))
このとき、関数data
とcreate_model
の返り値は順番に注意
結果 :
... Evalutation of best performing model: 10000/10000 [==============================] - 0s 19us/step [0.10739784885103582, 0.9693] Best performing model chosen hyper-parameters: {'Dropout': 0.03323327852409652, 'Activation': 1, 'Dense': 2, 'Dropout_1': 0.0886198698550964, 'optimizer': 0, 'batch_size': 1, 'add': 0, 'Dropout_2': 1} --- sorted --- Activation : 1 Dense : 2 Dropout : 0.03323327852409652 Dropout_1 : 0.0886198698550964 Dropout_2 : 1 add : 0 batch_size : 1 optimizer : 0
このままだと、読みにくいのでまとめると、
パラメータ | 値 |
---|---|
3層目の活性化関数 | sigmoid |
3層目のノード数 | 1024 |
2層目のDropout率 | 0.03323327852409652 |
3層目のDropout率 | 0.0886198698550964 |
4層目を追加するか? | Yes |
5層目にDropout層 or 活性化関数追加? | ドロップアウト層を追加 |
バッチ数 | 128 |
最適化関数 | rmsprop |
使用時の注意
学習のたびに、出力の順番が入れ替わる
print(best_run)
で出力される結果だけに注目すると、2回目の学習では以下のように出力されました。
... Best performing model chosen hyper-parameters: {'Dropout_2': 1, 'add': 0, 'optimizer': 0, 'Activation': 1, 'batch_size': 1, 'Dropout_1': 0.0886198698550964, 'Dense': 2, 'Dropout': 0.03323327852409652} ...
1回目ではDropout
の項目が一番目に出力されていましたが、2回目ではadd
の項目が1番目に出力されています。
正直、読みにくいので、対処法を考えました。
print(type(best_run))
で型を調べたら、<class 'dict'>
だったので、ソートしてfor
で出力することに。
sorted_best_run = sorted(best_run.items(), key=lambda x : x[0]) for i, k in sorted_best_run: print(i + ' : ' + str(k))
結果 :
Activation : 1 Dense : 2 Dropout : 0.03323327852409652 Dropout_1 : 0.0886198698550964 Dropout_2 : 1 add : 0 batch_size : 1 optimizer : 0
relu
とsigmoid
のどちらを選択したのかを明示的に出力するには、コードを結構変えないといけないので、今回は割愛します。
コメントアウトしている行も自動調整されている?
Dropout層の追加する行をコメントアウトで挿入し、検証しました。
モデルを構築している部分のコードを以下のように変えます。
# before ... model = Sequential() model.add(Dense(512, input_shape=(784,))) model.add(Activation('relu')) model.add(Dropout(0.5)) model.add(Dropout({{uniform(0, 1)}})) model.add(Dense({{choice([256, 512, 1024])}})) model.add(Activation({{choice(['relu', 'sigmoid'])}})) model.add(Dropout({{uniform(0, 1)}})) if {{choice(['three', 'four'])}} == 'four': model.add(Dense(100)) model.add({{choice([Dropout(0.5), Activation('linear')])}}) model.add(Activation('relu')) ...
# after ... model = Sequential() model.add(Dense(512, input_shape=(784,))) model.add(Activation('relu')) model.add(Dropout(0.5)) model.add(Dropout({{uniform(0, 1)}})) model.add(Dense({{choice([256, 512, 1024])}})) model.add(Activation({{choice(['relu', 'sigmoid'])}})) model.add(Dropout({{uniform(0, 1)}})) # --- add --- # # model.add(Dropout({{uniform(0, 1)}})) # model.add(Dropout({{uniform(0, 1)}})) # model.add(Dropout({{uniform(0, 1)}})) # model.add(Dropout({{uniform(0, 1)}})) if {{choice(['three', 'four'])}} == 'four': model.add(Dense(100)) model.add({{choice([Dropout(0.5), Activation('linear')])}}) model.add(Activation('relu')) ...
結果は、
# before ... Best performing model chosen hyper-parameters: {'Dropout': 0.03323327852409652, 'Activation': 1, 'Dense': 2, 'Dropout_1': 0.0886198698550964, 'optimizer': 0, 'batch_size': 1, 'add': 0, 'Dropout_2': 1} --- sorted --- Activation : 1 Dense : 2 Dropout : 0.03323327852409652 Dropout_1 : 0.0886198698550964 Dropout_2 : 1 add : 0 batch_size : 1 optimizer : 0
# after ... Best performing model chosen hyper-parameters: {'Dropout_2': 0.9662681038993752, 'Dropout_5': 0.8366666847115819, 'Dropout_3': 0.011106434718081704, 'add': 1, 'optimizer': 0, 'Dropout_1': 0.06765709934504838, 'Dense': 1, 'batch_size': 0, 'Dropout': 0.4970559482092457, 'Dropout_4': 0.9770005173795487, 'Activation': 1, 'Dropout_6': 0} --- sorted --- Activation : 1 Dense : 1 Dropout : 0.4970559482092457 Dropout_1 : 0.06765709934504838 Dropout_2 : 0.9662681038993752 Dropout_3 : 0.011106434718081704 Dropout_4 : 0.9770005173795487 Dropout_5 : 0.8366666847115819 Dropout_6 : 0 add : 1 batch_size : 0 optimizer : 0
Dropout_[n]
の数は6個になっています。
もともとは2個だったので、4個増加していることがわかりました。
この数字は、コメントアウトで挿入したDropoutの行数と同じなので、コメントアウトされた行のハイパーパラメータの値も自動調整されているのではないかと考えることができます。
ただし、best_model.summary()
で構築されたモデルを可視化してみると、
... Layer (type) Output Shape Param # ================================================================= dense_4 (Dense) (None, 512) 401920 _________________________________________________________________ activation_6 (Activation) (None, 512) 0 _________________________________________________________________ dropout_5 (Dropout) (None, 512) 0 _________________________________________________________________ dense_5 (Dense) (None, 512) 262656 _________________________________________________________________ activation_7 (Activation) (None, 512) 0 _________________________________________________________________ dropout_6 (Dropout) (None, 512) 0 _________________________________________________________________ dense_6 (Dense) (None, 10) 5130 _________________________________________________________________ activation_8 (Activation) (None, 10) 0 ================================================================= Total params: 669,706 Trainable params: 669,706 Non-trainable params: 0 ...
となっており、学習モデルにDropout層が追加されているわけではないようです。
そのため、コメントアウト行のハイパーパラメータは自動調整されているが、使用上問題ないと思います。
(ただ、結果が見づらくなるだけ)
層を追加するか判断する出力結果がDropoutとして表示される
文の意味わからないかもしれないので、補足します。
ソートされた後の出力結果の6行目をよく見てみると、
... --- sorted --- Activation : 1 Dense : 2 Dropout : 0.03323327852409652 Dropout_1 : 0.0886198698550964 Dropout_2 : 1 # <- ココ!! add : 0 batch_size : 1 optimizer : 0
Dropout層の自動調整は2回しかされていないはずなのに、Dropout_2
というハイパーパラメータが現れています。
消去法で層の追加に関係するパラメータだと思ったのですが、確認してみました。
層を追加するか判断する行の有無で検証しました。
モデルを構築している部分のコードを以下のように変えます。
# before ... model = Sequential() model.add(Dense(512, input_shape=(784,))) model.add(Activation('relu')) model.add(Dropout({{uniform(0, 1)}})) model.add(Dense({{choice([256, 512, 1024])}})) model.add(Activation({{choice(['relu', 'sigmoid'])}})) model.add(Dropout({{uniform(0, 1)}})) if {{choice(['three', 'four'])}} == 'four': model.add(Dense(100)) model.add({{choice([Dropout(0.5), Activation('linear')])}}) model.add(Activation('relu')) model.add(Dense(10)) model.add(Activation('softmax')) ...
# after ... model = Sequential() model.add(Dense(512, input_shape=(784,))) model.add(Activation('relu')) model.add(Dropout({{uniform(0, 1)}})) model.add(Dense({{choice([256, 512, 1024])}})) model.add(Activation({{choice(['relu', 'sigmoid'])}})) model.add(Dropout({{uniform(0, 1)}})) # delete if sentence model.add(Dense(100)) model.add({{choice([Dropout(0.5), Activation('linear')])}}) model.add(Activation('relu')) model.add(Dense(10)) model.add(Activation('softmax')) ...
結果 :
# before ... Best performing model chosen hyper-parameters: {'Dropout': 0.03323327852409652, 'Activation': 1, 'Dense': 2, 'Dropout_1': 0.0886198698550964, 'optimizer': 0, 'batch_size': 1, 'add': 0, 'Dropout_2': 1} --- sorted --- Activation : 1 Dense : 2 Dropout : 0.03323327852409652 Dropout_1 : 0.0886198698550964 Dropout_2 : 1 add : 0 batch_size : 1 optimizer : 0
# after ... Best performing model chosen hyper-parameters: {'Activation': 0, 'add': 0, 'batch_size': 0, 'optimizer': 1, 'Dense': 2, 'Dropout': 0.47268542196596874, 'Dropout_1': 0.7275581656084844} --- sorted --- Activation : 0 Dense : 2 Dropout : 0.47268542196596874 Dropout_1 : 0.7275581656084844 add : 0 batch_size : 0 optimizer : 1
if文がある行を削除した結果、Dropout_2
の項目が消えています。
つまり、1つ余分に多いDropout
は層の追加に関係した項目だとわかりました。
ちなみに、なぜなのかは不明です。
まとめ
- Kerasでニューラルネットワークのハイパーパラメータを自動調整するときは、
hyperas
を使う - 数行書き加えるだけで自動調整できる
- コメントアウトされた行の値も自動調整している
ディープになるとコードがグチャグチャになりそうだなと思ったので、工夫する必要がありそうです。