前回AutokerasでMNISTを自動化分類してみましたが、今回は特定のハイパーパラメータを最適化するHyperas(Hyperoptのkerasラッパー)を試してみました。最適化にはいろいろ種類があって、HyperasはTree-structured Parzen Estimators (TPE)というガウス過程の欠点を補った方法のようです。グリッドサーチよりも計算量が少なく、ベイズ最適化のように探索していくようですが、微妙に計算方法が異なるようです。
使い方:
Hyperasの使い方は非常に簡単で、
例えばドロップアウト率を0から1の範囲で最適化したい場合は、
Dropout({{uniform(0, 1)}})
複数の選択肢がある場合は、
optimizer={{choice(['adam', 'rmsprop', 'SGD'])}}
を通常の変数部分に入れて行くだけです。ディクショナリーを用意する必要もないので楽です。
ちなみに、今回最適化したハイパーパラメータは以下。
・Dropout率
・Dense層出力ユニット数
・batch_size数
・validation_splitの比率
ルール:
ただし少しだけルールがあるようです(このあたりがやや使いにくいかも)。
HyperasはHyperoptのラッパーで、一旦コーディングした内容をそのままHyperoptへ翻訳変換してしまうようです。そのためまずdata関数とmodel関数を作り(カプセル化するフォーマット)、その後dataとmodelをoptim.minimize()へ渡してモデル探索します。基本的には以下のようなフォーマット。
def data():
データの読み込み
データの前処理
return X_train, Y_train, X_test, Y_test
def model(X_train, Y_train, X_test, Y_test):
model = Sequential()
...
model.compile()
model.fit()
...
return{'loss':-acc, 'status': STATUS_OK, 'model': model}
best_run, best_model = optim.minimize(model, data, ...)
それから、data関数とmodel関数外で定義したグローバル変数はdata関数とmodel関数のスコープに届かないようです(importしたモジュールなどは問題ない)。よってグローバル変数などは、新たにdata関数とmodel関数内に定義しないといけなさそうです。globalを使えばいいのかも。
それと引数や戻り値の変数名も一致させておかないとエラーがでました。
また、model関数内で{{uniform()}}や{{choice()}}を含んだ行を#でコメントアウトしても、Hyperoptへ変換される際には最適化する変数として読み込まれてしまうので消去する必要があります。
最初なぜエラーがでるのかわかりませんでしたが、Hyperoptへそのまま翻訳変換するためそういうルールのようです。そういうことから、やや複雑なことをするならHyperoptを使ったほうがいいかもしれません。
ということで、このルールに戸惑いましたが何とかコーディングしたものが以下。
最適化されたハイパーパラメータはbest_runによって出力されます。
{'Dense': 64,
'Dense_1': 64,
'Dropout': 0.21099660541107612,
'Dropout_1': 0.29327102196615873,
'Dropout_2': 0.7302305870589935,
'Dropout_3': 0.258985915829989,
'batch_size': 32,
'validation_split': 0.10388179991112252}
今回のハイパーパラメータはこんな感じ。
デフォルトだとchoice型のハイパーパラメータはリストのindex番号として出力されるので(例えば上記の'Dense': 64がデフォルトだと'Dense': 0と表示される)、optim.minimize()内に
eval_space=True
を加えておけば、リスト内の実際の値として出力されます。
また今回は、max_evals=10としました。これは探索回数のことだと思うのですが、最低どのくらいが必要かは試してみないとわかりません(ハイパーパラメータのハイパーパラメータ)。探索回数が多いほど正確に予測できると思いますが、その分時間もかかってしまいます。以前ベイズ最適化を試した時は15回くらいの探索でブラックボックス関数にほぼ近似していましたが、Hyperas(Hyperopt)のTPEアルゴリズムにおいてはどうなのか?
まとめ:
まだ使い方に慣れていないためか、スコアは0.98371(max_evals=10)でいまいちでした。何回かやり直してみたので、それなりに時間がかかってしまいました(エラーや失敗も含めて)。そうなると、まだまだ手動調節(スコア:099528)のほうがMNISTの場合であれば早いという感じです。
ただ、手っ取り早く試すにはHyperasはいいのかもしれません。少し使い勝手が悪いので、流れがわかればHyperoptへ移行してもいいと思います。
このほかKeras用の最適化ライブラリでTalosというものも試してみました。Talosもシンプルで簡単ですが、グリッドサーチやランダムサーチをベースにしているのか、最適化範囲や選択肢が増えると探索にとてつもなく時間がかかってしまったので一旦中止。
次は、Hyperoptで実装しなおすか、GpyOptを試してみようかと思います。
追記その1:
その後、max_evals=20で探索すると0.99200までスコアがあがりました。
追記その2:
さらに、max_evals=100まで試すと0.99271まで向上。
以下が最適化されたハイパーパラメータ:
{'Dense': 256,
'Dense_1': 128,
'Dropout': 0.01772328721174527,
'Dropout_1': 0.4978692970747428,
'Dropout_2': 0.13740853439432676,
'Dropout_3': 0.01305834864014449,
'batch_size': 32,
'validation_split': 0.12455270876039955}
結構まっとうな値です。たまにDropout:0.7以上がでるときもありますが、今回はどれも0.5以下。
Denseの出力ユニット数はそれぞれ選択肢の最大値256と128なので、もう一段階512と256へ上げてもいいかもしれません。
GPU:GTX1060(6GB)で数時間回せばこのような結果が出せるのでそれなりに使えそうです。探索回数であるmax_evalsは最低でも100、もしくは500や1000で1日中回すのがいいかもしれません。