2019-08-15

実装とともに学ぶハイパーパラメータチューニングのお話

はじめに

前回は交差検証について紹介をしました。今回は、ゼロからKerasシリーズの総まとめとして、ハイパーパラメータチューニングについて紹介します。実装例としては、Keras Tuner と呼ばれる、Keras用のハイパーパラメータ自動最適化ツールを活用した実装を紹介します。

1. ハイパーパラメータとは

ハイパーパラメータとは、最適化アルゴリズムによって最適化できないパラメータのことを指します。例えば、学習エポック数やバッチサイズ・隠れ層の次元数、学習率などがあたります。

最適化アルゴリズムでは最適化できないハイパーパラメータを最適化するには、人手ではなく、専用のツールを使うのが便利です。専用のツールはたくさん種類があるので、フレームワーク等の状況に応じたものを取捨選択すると良いです。

2. 実装

例によって、画像データセット: CIFAR10 の分類実験を題材にハイパーパラメータチューニングを実施してみます。基にするソースコードは前回の交差検証で用いた、cnn.py とします。冒頭で述べたように、Keras Tuner を活用して実装を行います。

参照: (GitHub: cnn.py)

2.1 Keras Tunerについて

Keras Tuner は、その名の通り Keras 用に開発中のハイパーパラメータ最適化ツールです。現時点で対応している最適化手法は、「ランダムサーチ」と Hyperband になります。今回はランダムサーチを適用してチューニングを行います。ランダムサーチは決められた範囲内からパラメータをランダムに選択し、試していく手法です。

なお、Keras Tuner は、TensorFlow 2.0 以降のTensorFlowに統合されたKeras (tf.keras) に対応していることが明記されているので、これまで本ブログで扱ってきたいわゆる無印Keras とは少し異なります。そのため、本記事では、無印Kerasユーザの方でも、tf.keras を使って最低限動かせるような構成にしてあります。

参照: (GitHub: Keras Tuner)

2.2 Keras Tunerのインストール

まずは、Keras Tunerをインストールします。Python 3.6〜 と TensorFlow 2.0 が Requirementsとして指定されています。

git clone https://github.com/keras-team/keras-tuner.git
cd keras-tuner
pip install .

2.3 CNNモデルの実装

これまで活用してきた、cnn.pytf.keras に互換性がないので、tf.keras 向けに書き換えます。

2.3.1 ライブラリ読み出し

ライブラリ読み出し部分は至って簡単に移植できます。今までのソースコードに、tensorflow. を追加するだけです。

from tensorflow.keras import Model
from tensorflow.keras.layers import Input, Dense, Flatten
from tensorflow.keras.layers import Conv2D, MaxPooling2D
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.datasets import cifar10
from tensorflow.keras.utils import to_categorical
import numpy as np

Keras Tuner のライブラリも読み込ませます。

from kerastuner.tuners import RandomSearch

2.3.2 モデルの定義

モデルの定義部分は、Keras Tuner 向けに書き換える必要が出てきます。具体的には、次の2点について改造を施します。

  • 引数に hp : ハイパーパラメータ を取らせる
  • チューニングしたいハイパーパラメータの部分を、hp.Range に置き換える。

hp.Range の使い方は次の表の通りです。

引数 説明
min_value チューニングしたいパラメータの最小値を指定します。
max_value チューニングしたいパラメータの最大値を指定します。
step インクリメントしていく値(幅)を指定します。

以下が、Keras Tunerに対応させたソースコードとなります。

def build_model(hp) -> Model:
    # モデル定義
    _input = Input(shape=(32, 32, 3))
    _hidden = Conv2D(filters=hp.Range('filters', min_value=10, 
                    max_value=40, step=10), 
                    kernel_size=hp.Range('kernel_size', min_value=2,
                    max_value=5, step=1), 
                    strides=(1, 1), padding='valid', activation='relu')(_input)
    _hidden = MaxPooling2D(pool_size=(2, 2))(_hidden)
    _hidden = Flatten()(_hidden)
    _hidden = Dense(units=hp.Range('units', min_value=50,
                    max_value=200, step=50), 
                    activation='relu')(_hidden)
    _output = Dense(10, activation='softmax')(_hidden)
    model = Model(_input, _output)
    model.compile(optimizer=Adam(hp.Choice('learning_rate', values=[1e-2, 1e-3, 1e-4])),
        loss='categorical_crossentropy', metrics=['accuracy'])

    return model

2.3.3 データの読み込み

データセットの読み込み部は一切変更する必要はありません。無印のKeras用に実装したソースコードをそのまま活用できます。

(x_train, y_train), (x_test, y_test) = cifar10.load_data()
x_train = x_train.astype('float') / 255.
x_test = x_test.astype('float') / 255.
y_train = to_categorical(y_train, num_classes=10)
y_test = to_categorical(y_test, num_classes=10)

2.3.4 RandomSearchクラスのインスタンス生成

ここからが本題になります。まずは、ランダムサーチを行うためのインスタンス tuner を生成します。

tuner = RandomSearch(build_model, objective='val_accuracy',
        max_trials=5, executions_per_trial=1, directory='tuning', project_name='log')

RandomSearch クラスの主な引数は次の表の通りです。

引数 説明
hypermodel HyperModel クラスのインスタンスか、ハイパーパラメータを引数にとる、Model インスタンスを返す関数を与えます。
objective 何を基準に最適化を行うかを指定します。
max_trials 最大何回探索を行うかを指定します。
executions_per_trial 一回の探索で何回学習を繰り返すかを指定できます。
複数回指定すると、結果を安定させる効果があります。
directory ログの保存先ディレクトリを指定します。
project_name ログの保存先ディレクトリ2を指定します。
つまり、directory/project_name下にログファイルが保存されます。

2.3.4 ランダムサーチの実行

RandomSearch クラスのインスタンスを生成したら、ランダムサーチを実行できます。その前に、search_space_summary()メソッドを使って、探索候補を確認することができます。

tuner.search_space_summary()

実行結果:

[Search space summary]
 |-Default search space size: 4
 > filters (Range)
 |-default: None
 |-max_value: 40
 |-min_value: 10
 |-step: 10
 > kernel_size (Range)
 |-default: None
 |-max_value: 5
 |-min_value: 2
 |-step: 1
 > units (Range)
 |-default: None
 |-max_value: 200
 |-min_value: 50
 |-step: 50
 > learning_rate (Choice)
 |-default: 0.01
 |-values: [0.01, 0.001, 0.0001]

探索候補の範囲が定義した通りに表示されていますね。

では、本題の探索に移っていきましょう。探索は、search() メソッドを使います。このメソッドは、model.fit()に対応しています。

tuner.search(x_train, y_train, epochs=10, validation_data=(x_test, y_test))

2.3.5 チューニング結果の確認

チューニングの結果は、results_summary() で確認できます。

tuner.results_summary()

実行結果:

[Results summary]
 |-Results in tuning/log
 |-Ran 5 trials
 |-Ran 5 executions (1 per trial)
 |-Best val_accuracy: 0.6094

実行結果から、最高精度を記録したパラメータの設定は下記の設定のときでした。
今回は5回しか探索をしていないので、最適なパラメータが得られたとは言い難いです。実データでパラメータチューニングを行う際は、探索空間の大きさに応じて探索回数を適度に増やすのが無難です。

"values": {"filters": 10, "kernel_size": 3, "units": 50, "learning_rate": 0.001}

今回は使いませんでしたが、get_best_models() で最良の結果を残したモデルをピックアップできます。(このメソッドを使わなくても、重みファイルは自動で保存されます。)

まとめ

今回は、Keras Tunerを活用したハイパーパラメータチューニングの方法について紹介をしました。簡単に使えるので便利ですね。

他にも便利なチューニングツール(例: Optuna)が数多く公開されているので、確認してみると良いかもしれません。

ソースコード

ソースコードは、GitHubにて公開してあります。