【備忘録】ハイパーパラメータチューニングテンプレート【Optuna + Keras】

### はじめに
Kerasでニューラルネットワークモデルを構築する際の、叩き台としてOptunaを使用してある程度の性能が出せるネットワークの設定を発見するコード。毎回作り直すのがめんどくさいため、ここに備忘録として残しておく。

### コード
- `create_model`関数の中の`num_hidden`で隠れ層のユニット数の範囲を、`dropout`で不活性化させるノードの割合の範囲を、コンパイルの部分でその他最適化アルゴリズムや損失関数を設定  
- 他にも以下のようにOptunaの`suggest`を使って、探索したいパラメータを追加することができる
    - https://optuna.readthedocs.io/en/stable/reference/generated/optuna.trial.Trial.html#optuna.trial.Trial

```py
# 最適化アルゴリズム
optimizer = trial.suggest_categorical('optimizer', ['MomentumSGD', 'Adam'])

# optimizerを代入
model.compile(optimizer=optimizer,loss="categorical_crossentropy",metrics=["accuracy"])
```

- 目的関数の`return`の部分でどんな値を返して、それを最大化(maximize)したいのか、最小化(minimize)したいのか、決定する。
- train_test_splitなどであらかじめデータセットを分けておいて、study.optimizeに代入する 
- 入力層のインプットの形状を指定する必要はないが、出力層は指定する
- `study.optimize(lambda trial: objective(trial, 変数1、変数2...), n_trials=100)`という書式で目的関数にtrial以外の引数を入れることができる  
- `optuna.logging.enable_default_handler()`でOptunaのログを出力できる  
- 逆にログを吐き出したくない場合は`optuna.logging.disable_default_handler()`を書く

```py
# ライブラリのインポート
import optuna
import tensorflow.keras
from  tensorflow.keras.models import Sequential
from  tensorflow.keras.layers import Dense, Dropout, Flatten
from  tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import EarlyStopping
from sklearn.model_selection import train_test_split

# モデルの作成
def create_model(trial):
 
    # 層の数を選択する
    n_layers = trial.suggest_int("n_layers", 3, 7)
    model = Sequential()
    
    for i in range(n_layers):
        # Dense
        num_hidden = int(trial.suggest_loguniform(f"n_units_l{i}",4,512))
        model.add(Dense(num_hidden, activation="relu"))

        # ドロップアウト層
        dropout = trial.suggest_uniform(f"dropout_l{i}", 0.2, 0.5)
        model.add(Dropout(rate=dropout))
    
    # 出力層
    model.add(Dense(1))

    # モデルのコンパイル
    model.compile(loss='mse',
              optimizer=Adam(),
              metrics=['mse'])

    return model

# 目的関数
def objective(trial,X_train, X_test, y_train, y_test):
    # 前のセッションをクリアする
    keras.backend.clear_session()

    # モデル作成
    model = create_model(trial)

    # モデルの学習
    history = model.ﬁt(X_train,y_train,epochs=10,batch_size=1024,validation_data=(X_test, y_test))

    return 1 - history.history["val_mse"][0]

# 最適化
study = optuna.create_study(direction='maximize')
study.optimize(lambda trial: objective(trial, X_train, X_test, y_train, y_test), n_trials=100)
```

### 実際のログ(例)
- 非常にわかりやすい

```
Epoch 1/10
1734/1734 [==============================] - 5s 3ms/step - loss: 0.0765 - mse: 0.0765 - val_loss: 0.0439 - val_mse: 0.0439
Epoch 2/10
1734/1734 [==============================] - 4s 2ms/step - loss: 0.0206 - mse: 0.0206 - val_loss: 0.0411 - val_mse: 0.0411
Epoch 3/10
1734/1734 [==============================] - 4s 2ms/step - loss: 0.0173 - mse: 0.0173 - val_loss: 0.0374 - val_mse: 0.0374
Epoch 4/10
1734/1734 [==============================] - 4s 2ms/step - loss: 0.0166 - mse: 0.0166 - val_loss: 0.0387 - val_mse: 0.0387
Epoch 5/10
1734/1734 [==============================] - 4s 2ms/step - loss: 0.0165 - mse: 0.0165 - val_loss: 0.0360 - val_mse: 0.0360
Epoch 6/10
1734/1734 [==============================] - 4s 2ms/step - loss: 0.0164 - mse: 0.0164 - val_loss: 0.0385 - val_mse: 0.0385
Epoch 7/10
1734/1734 [==============================] - 4s 2ms/step - loss: 0.0163 - mse: 0.0163 - val_loss: 0.0393 - val_mse: 0.0393
Epoch 8/10
1734/1734 [==============================] - 4s 2ms/step - loss: 0.0163 - mse: 0.0163 - val_loss: 0.0415 - val_mse: 0.0415
Epoch 9/10
1734/1734 [==============================] - 4s 2ms/step - loss: 0.0161 - mse: 0.0161 - val_loss: 0.0407 - val_mse: 0.0407
Epoch 10/10
1734/1734 [==============================] - 4s 2ms/step - loss: 0.0162 - mse: 0.0162 - val_loss: 0.0421 - val_mse: 0.0421
[I 2021-12-08 17:32:15,216] Trial 0 finished with value: 0.956090159714222 and parameters: {'n_layers': 4, 'n_units_l0': 136.4133153963306, 'dropout_l0': 0.2762079388284835, 'n_units_l1': 6.517867697876512, 'dropout_l1': 0.3674686044843562, 'n_units_l2': 89.82269872356912, 'dropout_l2': 0.4010645579365826, 'n_units_l3': 48.85781763881269, 'dropout_l3': 0.4415658912950136}. Best is trial 0 with value: 0.956090159714222.
Epoch 1/10
1734/1734 [==============================] - 17s 9ms/step - loss: 0.0341 - mse: 0.0341 - val_loss: 0.0972 - val_mse: 0.0972
Epoch 2/10
1734/1734 [==============================] - 16s 9ms/step - loss: 0.0076 - mse: 0.0076 - val_loss: 0.0972 - val_mse: 0.0972
Epoch 3/10
1734/1734 [==============================] - 16s 9ms/step - loss: 0.0053 - mse: 0.0053 - val_loss: 0.0830 - val_mse: 0.0830
Epoch 4/10
1734/1734 [==============================] - 16s 9ms/step - loss: 0.0048 - mse: 0.0048 - val_loss: 0.0842 - val_mse: 0.0842
Epoch 5/10
1734/1734 [==============================] - 16s 9ms/step - loss: 0.0044 - mse: 0.0044 - val_loss: 0.0686 - val_mse: 0.0686
Epoch 6/10
1734/1734 [==============================] - 16s 9ms/step - loss: 0.0042 - mse: 0.0042 - val_loss: 0.0788 - val_mse: 0.0788
Epoch 7/10
1734/1734 [==============================] - 16s 9ms/step - loss: 0.0040 - mse: 0.0040 - val_loss: 0.0641 - val_mse: 0.0641
Epoch 8/10
1734/1734 [==============================] - 17s 10ms/step - loss: 0.0039 - mse: 0.0039 - val_loss: 0.0769 - val_mse: 0.0769
Epoch 9/10
1734/1734 [==============================] - 17s 10ms/step - loss: 0.0038 - mse: 0.0038 - val_loss: 0.0817 - val_mse: 0.0817
Epoch 10/10
1734/1734 [==============================] - 16s 9ms/step - loss: 0.0036 - mse: 0.0036 - val_loss: 0.0796 - val_mse: 0.0796
[I 2021-12-08 17:34:59,020] Trial 1 finished with value: 0.9027610197663307 and parameters: {'n_layers': 6, 'n_units_l0': 82.35173067277402, 'dropout_l0': 0.3951302536642787, 'n_units_l1': 152.68594208166238, 'dropout_l1': 0.4686668540109759, 'n_units_l2': 51.59443308144836, 'dropout_l2': 0.24115393032237953, 'n_units_l3': 254.7841497780783, 'dropout_l3': 0.3725568565724888, 'n_units_l4': 47.12550679065958, 'dropout_l4': 0.46566366782678575, 'n_units_l5': 262.22459761792135, 'dropout_l5': 0.3287760226636271}. Best is trial 0 with value: 0.956090159714222.
............続く
```