見出し画像

Optuna入門

1. Optuna

Optuna」は、「ハイパーパラメータ」の最適化を自動化するためのフレームワークです。自動的に試行錯誤しながら、優れた性能を発揮する「ハイパーパラメータ」の値を発見します。
「ハイパーパラメータ」は、機械学習アルゴリズムの挙動を制御するパラメータのことで、勾配法によって最適化できないパラメータに相当します。「学習率」「バッチサイズ」「学習イテレーション数」などがこれにあたります。

2. Optunaのインストール

Optunaをインストールするには、仮想環境で次のコマンドを実行します。
Python 2.7とPython 3.5以降をサポートしています。

pip install optuna

3. 2次関数の最適化

はじめに練習として、Optunaで2次関数の最適化を行います。
「目的関数」と試行錯誤する「パラメータ」を定義し、「目的関数」が最も最小になる「パラメータ」の組み合わせを探索します。これを「最適化」と呼びます。

◎目的関数とパラメータの定義
「目的関数」と「パラメータ」を定義するコードは、次の通りです。

import optuna

# 目的関数
def objective(trial):
  # パラメータ
  x = trial.suggest_uniform('x', -10, 10)

  # この戻り値が最小になるパラメータの組み合わせを探索
  return (x - 2) ** 2

「目的関数」には引数「trial」が渡されます。
「パラメータ」は、trial.suggest_XXXで作成します。

'''
# カテゴリパラメータ
optimizer = trial.suggest_categorical('optimizer', ['MomentumSGD', 'Adam'])

# Intパラメータ
num_layers = trial.suggest_int('num_layers', 1, 3)

# Uniformパラメータ
dropout_rate = trial.suggest_uniform('dropout_rate', 0.0, 1.0)

# Loguniformパラメータ
learning_rate = trial.suggest_loguniform('learning_rate', 1e-5, 1e-2)

# Discrete-uniformパラメータ
drop_path_rate = trial.suggest_discrete_uniform('drop_path_rate', 0.0, 1.0, 0.1)
'''

◎最適化の開始
最適化を開始するには、Studyを作成し、目的関数をoptimize()に渡します。n_trialsは試行回数です。

study = optuna.create_study()
study.optimize(objective, n_trials=100)
[I 2019-07-28 00:52:18,414] Finished trial#0 resulted in value: 35.279719166515186. Current best value is 35.279719166515186 with parameters: {'x': -3.9396733215316804}.
   :
[I 2019-07-28 00:52:32,157] Finished trial#99 resulted in value: 10.3190457815786. Current best value is 4.429902612717294e-06 with parameters: {'x': 2.0021047333828106}.

Optuna、最適な「x値」として「2.0021047333828106」を見つけたことがわかります。最適値の「2」に近い値になります。

◎最適化結果の分析
Optunaの用語をまとめると、次のようになります。

・Study:最適化の一連の試行の情報を管理
・Trial:目的関数の実行1回分の試行の情報を管理
・Paramater:最適化されるべき変数

Optunaでは、「Study」を使用して「最適化」を管理します。
「Study」は最適化の結果を分析するのに役立つ便利なプロパティを持っています。

ベストパラメータは次の通りです。

# ベストパラメータ
study.best_params
{'x': 2.0021047333828106}

ベスト値は次の通りです。

# ベスト値
study.best_value
4.429902612717294e-06

ベストTrialの情報は次の通りです。

# ベストTrial
study.best_trial
FrozenTrial(number=65, state=<TrialState.COMPLETE: 1>, value=4.429902612717294e-06, datetime_start=datetime.datetime(2019, 7, 28, 0, 52, 27, 269504), datetime_complete=datetime.datetime(2019, 7, 28, 0, 52, 27, 396915), params={'x': 2.0021047333828106}, distributions={'x': UniformDistribution(low=-10, high=10)}, user_attrs={}, system_attrs={'_number': 65}, intermediate_values={}, params_in_internal_repr={'x': 2.0021047333828106}, trial_id=65)

全Trialの情報は次の通りです。

# 全Trial
study.trials
[FrozenTrial(number=0, state=<TrialState.COMPLETE: 1>, value=35.279719166515186, datetime_start=datetime.datetime(2019, 7, 28, 0, 52, 18, 250356), datetime_complete=datetime.datetime(2019, 7, 28, 0, 52, 18, 414121), params={'x': -3.9396733215316804}, distributions={'x': UniformDistribution(low=-10, high=10)}, user_attrs={}, system_attrs={'_number': 0}, intermediate_values={}, params_in_internal_repr={'x': -3.9396733215316804}, trial_id=0),
   :
FrozenTrial(number=99, state=<TrialState.COMPLETE: 1>, value=10.3190457815786, datetime_start=datetime.datetime(2019, 7, 28, 0, 52, 32, 22529), datetime_complete=datetime.datetime(2019, 7, 28, 0, 52, 32, 146832), params={'x': -1.2123271597984229}, distributions={'x': UniformDistribution(low=-10, high=10)}, user_attrs={}, system_attrs={'_number': 99}, intermediate_values={}, params_in_internal_repr={'x': -1.2123271597984229}, trial_id=99)]

◎最適化の続行
optimize()をもう一度実行すると、最適化を続行することができます。

study.optimize(objective, n_trials=100)

    :

len(study.trials)
200

4. MNISTの最適化

次に、Kerasで作ったMNISTを最適化します。

◎Optunaパッケージのインポート
最適化に利用する、optunaパッケージとkeras.backendパッケージをインポートします。

import optuna
import keras.backend as K

◎データセットの取得と前処理
データセットの取得や前処理は、通常のMINISTと同様です。

# パッケージのインポート
from tensorflow.keras.datasets import mnist
from tensorflow.keras.layers import Activation, Dense, Dropout
from tensorflow.keras.models import Sequential
from tensorflow.keras.optimizers import SGD
from tensorflow.keras.utils import to_categorical
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

# データセットの準備
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

# データセットの画像の前処理
train_images = train_images.reshape((train_images.shape[0], 784))
test_images = test_images.reshape((test_images.shape[0], 784))

# データセットのラベルの前処理
train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)

◎目的関数とパラメータの定義
目的関数とパラメータの定義を行います。Kerasのセッションをクリアし、モデルを作成し、コンパイルして、学習するまでが1試行となります。
今回は、検証の精度を最大値を最大にしたいので、objective()の戻り値を「1-検証データの精度」とします。

# モデルの作成
def create_model(num_layer, activation):
   model = Sequential()
   model.add(Dense(256, activation=activation, input_shape=(784,))) # 入力層
   for i in range(num_layer):
       model.add(Dense(128, activation=activation)) # 隠れ層
       model.add(Dropout(rate=0.5)) # ドロップアウト
   model.add(Dense(10, activation='softmax')) # 出力層
   return model

# 目的関数
def objective(trial):
   # セッションのクリア
   K.clear_session()

   # 最適化パラメータ「層の数」
   num_layer = trial.suggest_int("num_layer", 1, 3)

   # 最適化パラメータ「活性化関数」
   activation = trial.suggest_categorical("activation", ["relu", "sigmoid", "tanh"])

   # 最適化パラメータ「オプティマイザ」
   optimizer = trial.suggest_categorical("optimizer", ["sgd", "adam", "rmsprop"])

   # モデルの生成
   model = create_model(num_layer, activation)

   # コンパイル
   model.compile(loss='categorical_crossentropy', optimizer=optimizer, metrics=['acc'])

   # 学習
   history = model.fit(train_images, train_labels, batch_size=500,
       epochs=5, validation_split=0.2)

   # この戻り値が最小になるパラメータの組み合わせを探索
   return 1 - history.history["val_acc"][-1]

◎最適化の開始
最適化を開始します。

study = optuna.create_study()
study.optimize(objective, n_trials=10)

◎最適化結果の分析
ベストパラメータは次の通りです。

# ベストパラメータ
study.best_params​​
{'activation': 'tanh', 'num_layer': 4, 'optimizer': 'rmsprop'}

ベスト値は次の通りです。
「1-検証データの精度」は戻して表示しています。

# ベスト値
1-study.best_value
0.940750002861023

実際に行った10回のTrialの内容は次の通りです。
実際には、まだ試していないパラメータの組み合わせはたくさんなので、さらなる最適化をした方がよさそうなのがわかります。

# 全Trial
for t in study.trials:
   print(t.params, 1-t.value)
{'num_layer': 2, 'activation': 'tanh', 'optimizer': 'sgd'} 0.8883333206176758
{'num_layer': 2, 'activation': 'tanh', 'optimizer': 'adam'} 0.9255833625793457
{'num_layer': 2, 'activation': 'tanh', 'optimizer': 'rmsprop'} 0.9315000176429749
{'num_layer': 2, 'activation': 'tanh', 'optimizer': 'adam'} 0.9206666946411133
{'num_layer': 3, 'activation': 'sigmoid', 'optimizer': 'rmsprop'} 0.9136666655540466
{'num_layer': 3, 'activation': 'sigmoid', 'optimizer': 'sgd'} 0.10599999874830246
{'num_layer': 2, 'activation': 'tanh', 'optimizer': 'rmsprop'} 0.9270833134651184
{'num_layer': 1, 'activation': 'tanh', 'optimizer': 'adam'} 0.9214166402816772
{'num_layer': 2, 'activation': 'sigmoid', 'optimizer': 'adam'} 0.9290000200271606
{'num_layer': 1, 'activation': 'sigmoid', 'optimizer': 'adam'} 0.940750002861023


この記事が気に入ったらサポートをしてみませんか?