optunaとkerasによるCNNのハイパーパラメータの最適化

簡単な自己紹介
2019/09現在 画像分類の課題に取り組んでいるエンジニア

会社の方針で画像認識に取り組んでいる。
畳み込みを用いたモデルで認識させようとしているが、ハーパーパラメータの探索がめんどくさい。
optunaが便利そうという記事をいくつか読んだので、コピペで組んだものを有識者に見てほしい。（まだ本番用のモデルの性能が確認できていない）

今回のプログラムは、
①訓練データフォルダとテストデータフォルダを用意
②それぞれの中に同数のクラスフォルダを同順で配置
となっていれば動くと思います。

探索したいパラメータが、
①畳み込みの層数
②畳み込みのフィルター枚数
③畳み込みのストライド数
④ドロップアウト率
となっています。

元のプログラムはhttps://www.snova301.work/entry/2018/12/14/191025
の記事を参照しました（というかほぼ丸コピー、ご指摘ただければすぐに記事について対応いたします。）


```sample.py
import numpy as np
import optuna
import os
from PIL import Image
    
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPooling2D, BatchNormalization
from keras.utils import np_utils


def machine_learning_data(make_folder):
    train_data_folder_list = os.listdir("./" + make_folder)
    a = [] # 画像の絶対パス格納用配列
    b =[] # 画像のクラス番号格納用配列
    c=0
    
    for i in train_data_folder_list:
        train_data_file_list = os.listdir("./"+ make_folder + "/" + i)
        for l in train_data_file_list:
            a.append(os.path.abspath("./"+ make_folder + "/" + i + "/" + l))
            b.append(c)
        c = c + 1
    d = np.array(Image.open(a[0]))
    z = np.zeros((len(a),d.shape[0],d.shape[1]))
    b = np_utils.to_categorical(b,7) # クラス番号をOne Hot 表現に変換
    for i,l in enumerate(a):
        z[i] = np.array(Image.open(l))
    z = z.reshape(len(a),d.shape[0],d.shape[1],1)
    z = z.astype('float32')/255
    return z,b
    
x_train,y_train = machine_learning_data("notMNIST_small")
x_test,y_test = machine_learning_data("sample")


def create_model(n_layer,n_filter,activation, filter_step, mid_units, dropout_rate):
    model = Sequential()

    for i in range(n_layer):
        print("n_layer",n_layer,"n_filter",n_filter,'stride',filter_step,"activation", activation, "mid_units", mid_units, "dropout_rate",dropout_rate)
        model.add(Conv2D(filters=n_filter, kernel_size=(3, 3), strides=(filter_step,filter_step), activation=activation, input_shape=(200, 200, 1)))
        model.add(MaxPooling2D(pool_size=(1, 1), strides=2))
        model.add(BatchNormalization())

    model.add(Flatten())
    model.add(Dropout(dropout_rate))

    model.add(Dense(7, activation=activation))

    return model

def objective(trial):
    x_train,y_train = machine_learning_data("notMNIST_small")
    x_test,y_test = machine_learning_data("sample")

    # 調整したいハイパーパラメータの設定
    n_layer = trial.suggest_int('n_layer', 1, 6) # 追加する層を1-3から選ぶ
    n_filter = trial.suggest_int('n_filter', 5, 25) # 畳み込みフィルターの数
    filter_step = trial.suggest_int('filter_step',1,3) # 畳み込みフィルターのストライド数
    mid_units = trial.suggest_int('mid_units', 10, 100) # ユニット数
    dropout_rate = trial.suggest_uniform('dropout_rate', 0, 1) # ドロップアウト率
    activation = trial.suggest_categorical('activation', ['relu','sigmoid']) # 活性化関数
    optimizer = trial.suggest_categorical('optimizer', ['sgd', 'adam', 'rmsprop']) # 最適化アルゴリズム

    # 学習モデルの構築と学習の開始
    model = create_model(n_layer,n_filter,activation, filter_step, mid_units, dropout_rate)
    model.compile(optimizer=optimizer,
                    loss='categorical_crossentropy',
                    metrics=['accuracy'])
    history = model.fit(x_train, y_train, 
                        verbose=1,
                        epochs=10,
                        validation_data=(x_test, y_test),
                        batch_size=100)

    # 学習モデルの保存
    model_json = model.to_json()
    with open('keras_model.json', 'w') as f_model:
        f_model.write(model_json)

    model.save_weights('keras_model.hdf5')

    # 最小値探索なので
    return -np.amax(history.history['val_acc'])

def main():
    study = optuna.create_study(sampler=optuna.samplers.TPESampler())
    study.optimize(objective, n_trials=100)
    print('best_params')
    print(study.best_params)
    print('-1 x best_value')
    print(-study.best_value)

    sortdict = sorted(study.best_params.items())
    z = np.zeros((len(sortdict),1))
    z = z.astype(np.unicode)
    for i,l in enumerate(sortdict):
        print(i,l)
        z[i,0] = l[0]

    np.save("best_params_item",z)

    sortdict = sorted(study.best_params.items())
    z = np.zeros((len(sortdict),1))
    z = z.astype(np.unicode)
    for i,l in enumerate(sortdict):
        print(i,l)
        z[i,0] = l[1]

    np.save("best_params_value",z)

    print('\n --- sorted --- \n')
    sorted_best_params = sorted(study.best_params.items(), key=lambda x : x[0])
    for i, k in sorted_best_params:
        print(i + ' : ' + str(k))

if __name__ == '__main__':
    main()
```
普段はchainerを使っているのでkerasのモデルの組み方がまだあまり分かっておらず勉強しようかと思ってます。
多分層ごとにフィルターの枚数やストライドは変えてないと思うので、その辺も探索できるようにしたいけど、知ってる方いたらご教授願います。

どなたかの参考になれば幸いです。