keras tunerでtf.kerasのハイパーパラメータを探索する
keras tuner
2019年10月末にメジャーリリースされたkeras tunerを試してみたいと思います。 github.com
できること
機械学習モデルのハイパーパラメータの探索
対応フレームワーク・ライブラリ
- tensorflow
- sckit-learn
使用可能な探索アルゴリズム
- ランダムサーチ
- Bayesian optimization
- hyperband: A Novel Bandit-Based Approach to Hyperparameter Optimization. (2018)
その他
- 探索履歴の保存・再読み込み
- 分散処理
基本的な手続き
- 探索するパラメーターの範囲指定 (モデル内に直接書き込む)
- チューナーインスタンス生成 (探索手法の決定)
- 探索実行
目次
- 一連の手続きを紹介
- 探索パラメータの範囲をyamlで指定できるようにする
今回使用したコードはこちらのcolabから確認できます。
1. 一連の手続きを紹介
1.1 パラメーターの範囲指定
後述するtuner instanceの生成時にモデルを作成する関数を渡す必要があります。
なお、その関数はhp
という引数をもっていなければいけません。
そして、モデルを定義する際にhp
を使って、明示的にパラメーターの範囲を指定することでパラメータの探索が可能になります。
指定にはhp.Int
やhp.Choice
などを用います。
from tensorflow import keras from tensorflow.keras.layers import Dense def build_model(hp): model = keras.Sequential() model.add(Dense(units=hp.Int('units', min_value=32, max_value=512, step=32), activation='relu')) model.add(Dense(3, activation='softmax')) model.compile( optimizer=keras.optimizers.Adam( hp.Choice('learning_rate', values=[1e-2, 1e-3, 1e-4])), loss='sparse_categorical_crossentropy', metrics=['acc']) return model
1.2. tuner instanceの生成
最適化するモデルと探索手法などを指定します。今回は、hyperbandを使ってみます。
from kerastuner.tuners import Hyperband tuner = Hyperband( build_model, objective='val_acc', max_epochs=5, directory='my_dir', project_name='tf', overwrite=False )
ここで、結果を保存するためにmy_dir/tf
というディレクトリが作られます。
すでに結果が保存済みの場合に上記のコードを動かすと探索履歴がリロードされます。
また、範囲の確認もできます。
tuner.search_space_summary() # >> Search space summary # |-Default search space size: 2 # units (Int) # |-default: None # |-max_value: 512 # |-min_value: 32 # |-sampling: None # |-step: 32 # learning_rate (Choice) # |-default: 0.01 # |-ordered: True # |-values: [0.01, 0.001, 0.0001]
1.3. 探索
探索を実行します。.fit()
への引数 (callbacksなど) はここでわたします。
# datasetの取得 from sklearn import datasets from sklearn.model_selection import train_test_split iris = datasets.load_iris() x, val_x, y, val_y = train_test_split(iris.data, iris.target) # 探索の実行 tuner.search(x, y, epochs=5, validation_data=(val_x, val_y))
すると、kerasのhistory()
が表示されて探索の経過が確認できます。
1.4. 結果の確認
改めてtunerインスタンスを生成して、保存されている結果をリロードします。 ただし、build_model関数とdirectoryとproject_nameは探索時に指定したものと同じものを入れて下さい。他は違っていてもリロードできます。
注) overwriteをTrueにするとリロードはされません。
tuner = Hyperband( build_model, objective='val_acc', max_epochs=5, directory='my_dir', project_name='tf', overwrite=False )
リロードしたtunerインスタンスから探索の結果を確認します。 以下で、スコアの上位10件の学習結果とそのときのハイパーパラメータがprintされます。
tuner.results_summary() # >>> Results summary # |-Results in my_dir/tf # |-Showing 10 best trials # |-Objective(name='val_loss', direction='min') # ... # >>> Trial summary # |-Trial ID: 51036f17dcb4f6b1aa4cf11a720f1c30 # |-Score: 0.9210526347160339 # |-Best step: 0 # Hyperparameters: # |-learning_rate: 0.001 # |-tuner/bracket: 0 # |-tuner/epochs: 5 # |-tuner/initial_epoch: 0 # |-tuner/round: 0 # |-units: 448
1.a. sklearnのモデルのパラメータ探索
sklearn用のtunerインスタンスを使うことで可能になります。 なお、Optimizationは以下の様にkerasで使ったものとは別クラスなので注意が必要です。
- kerasの場合:
kerastuner
からインポート - sklearnの場合:
kerastuner.oracles
からインポート
from sklearn.ensemble import ExtraTreesClassifier def build_model_sk(hp): model = ExtraTreesClassifier( n_estimators=hp.Int('n_estimators', 10, 50, step=10), max_depth=hp.Int('max_depth', 3, 10), criterion=hp.Choice('criterion', values=['gini', 'entropy']), random_state=222, ) return model import kerastuner as kt from kerastuner.tuners import Sklearn from kerastuner.oracles import BayesianOptimization as OracleBayesianOptim from sklearn import metrics from sklearn import model_selection tuner = Sklearn( hypermodel=build_model_sk, oracle=OracleBayesianOptim( objective=kt.Objective('score', 'max'), max_trials=50), scoring=metrics.make_scorer(metrics.accuracy_score), cv=model_selection.StratifiedKFold(5), directory='my_dir', project_name='sk', overwrite=True) tuner.search(x, y) # >>> Results summary # |-Results in my_dir/sk # |-Showing 10 best trials # |-Objective(name='score', direction='max') # ... # Trial summary # |-Trial ID: 94ea01baee4b398ca1c5cb35e6308856 # |-Score: 0.9644268774703557 # |-Best step: 0 # Hyperparameters: # |-criterion: gini # |-max_depth: 3 # |-n_estimators: 50
2. yamlでパラメータ範囲を指定
パラメータの範囲をかえるたびにコードを変更したり、別ファイルを作っていると、再現性を損なったり管理が面倒になるので、yamlで範囲やデフォルト値を指定して、モデル定義時に読み込む形をとりたいと思います。
まず、yamlで記述されたパラメータの探索範囲をdictに変換し、pythonで扱えるようにします。今回の例ではコンフィグをpython スクリプトに直接書き込みますが、実際は.yml
形式の別ファイルから読み込んで下さい。
import yaml configs_yaml = """ tuner_configs: # tunerインスタンス生成時に指定する値 directory: my_dir project_name: tf_yaml2 epochs: 5 hyperparams: # make_model内で指定する値 fixed: # 固定値 num_dense: 2 units: 64 search: # 探索パラメータ (hp.xxxにあわせて必要な値を記述する) units: # hp.Intなので以下3つが必要 min_value: 32 max_value: 512 step: 32 learning_rate: # hp.Choiceなので以下の1が必須 values: - 1.0e-2 - 1.0e-3 - 1.0e-4 """ configs = yaml.safe_load(configs_yaml) # >>> {'hyperparams': {'fixed': {'num_dense': 2, 'units': 64}, # 'search': {'learning_rate': {'values': [0.01, 0.001, 0.0001]}, # 'units': {'max_value': 512, 'min_value': 32, 'step': 32}}}, # 'tuner_configs': {'directory': 'my_dir', # 'epochs': 5, # 'project_name': 'tf_yaml2'}}
次に、hp.xxx
形式の探索範囲指定をdictから行えるようにbuild_model
を変更します。
モデルの定義は# definition of model
以下で行います。ただし、hp.xxx
に対して_set_params
関数を使って引数に必要な値を代入しています。
また、モデル定義の直前でhp.Fixedに固定値を入力しています。ここで固定値が入力されていれば、モデル定義内でhp.xxx
を使ってパラメータの範囲を指定しても固定値が優先されて、探索は行われなくなります。ただし、探索範囲がないとモデルの定義ができないので、yamlには範囲を書いておく必要があります。
def build_model(configs, num_classes): def _set_fixed_params(hp): # set fixed params in hp fixed_params = configs.get('fixed', {}) for key, val in fixed_params.items(): try: hp.Fixed(key, val) except AttributeError: pass return hp def _set_params(name): # shortcut for input params range in hp.xxx params = configs.get('search') return dict(name=name, **params.get(name, {})) def _build_model(hp): hp = _set_fixed_params(hp) fixed_params = configs.get('fixed', {}) # definition of model model = keras.Sequential() for i in range(fixed_params.get('num_dense')): model.add(Dense(units=hp.Int(**_set_params('units')), activation='relu')) model.add(Dense(num_classes, activation='softmax')) model.compile( optimizer=keras.optimizers.Adam( hp.Choice(**_set_params('learning_rate'))), loss='sparse_categorical_crossentropy', metrics=['acc']) return model return _build_model
ついでに、tunerインスタンスの生成も関数にしておきます。
from kerastuner.tuners import Hyperband def train(x, y, val_x, val_y, configs, overwrite=False): tuner_configs = configs.get('tuner_configs') tuner = Hyperband( build_model(configs.get('hyperparams'), 3), objective='val_acc', max_epochs=tuner_configs.get('epochs'), directory=tuner_configs.get('directory'), project_name=tuner_configs.get('project_name'), overwrite=True ) return tuner.search(x, y, epochs=tuner_configs.get('epochs'), validation_data=(val_x, val_y))
あとは、以下の要領で、yamlのloadだけで様々なパラメータ探索が可能です。
configs = yaml.safe_load(configs_yaml)
train(x, y, val_x, val_y, configs, overwrite=False)
まとめ
パラメータ探索だとOptunaなど有名なものがいくつかありますが、少なくともtf.kerasに関してはkeras tunerは使いやすさの点で優位性があるのではないかと感じました!