はじめに
OptunaはPFN社が開発しているハイパーパラメータを最適化してくれるPythonライブラリで,その手軽さから幅広く使われています.ベイズ最適化を手軽に実装できて,目的関数を最小化するように探索してくれます.ベイズ最適化以外にもいくつか実装できるアルゴリズムはあるみたいですが,使ったことはありません.ベイズ最適化用ライブラリとしてGPyOptなども有名です.何も知らないところから調べて執筆しているので,間違えている部分もあるかもしれません.ご容赦ください.
準備
著者はmacを使っているので,macベースでの環境構築を掲載させていただきます.
まずanaconda上で仮想環境を構築します.webからanaconda navigatorをインストールします.anaconda navigatorを起動し,EnvironmentからCreateを選択し,適当な名前の環境を構築してください.その後,ターミナルで
conda activate 環境名
と入力し,仮想環境をactiveにします.そうした後,次のコマンドを入力すればOptunaのインストールは完了です.
pip install Optuna
実装
まず,適当なサンプルコードを載せてみます.僕が実際にLSTMのパラメータチューニングを行った時のものです.
import optuna
import numpy as np
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense
from tensorflow.keras.optimizers import Adam
from sklearn.metrics import mean_absolute_error
x_train = input_data_force['input_Data1_force']
y_train = output_data_force['outputData1_force']
def create_sequences(data, timesteps):
sequences = []
for i in range(len(data) - timesteps):
sequences.append(data[i:i + timesteps])
return np.array(sequences)
def objective(trial):
#ハイパーパラメータの選定
n_lstm_layers = trial.suggest_int('n_lstm_layers', 1, 3) #LSTM層の数
n_lstm_units = trial.suggest_int('n_lstm_units', 32, 128) #各LSTM層のユニット数
epochs = trial.suggest_int('epochs', 10, 100) #エポック数
batch_size = trial.suggest_categorical('batch_size', [16, 32, 64]) #バッチサイズ
learning_rate = trial.suggest_loguniform('learning_rate', 1e-4, 1e-2) #学習率
timesteps = trial.suggest_int('timesteps', 10, 60) # タイムステップの範囲を指定
#タイムステップに基づいてデータをシーケンスに変換
x_train_sequences = create_sequences(x_train, timesteps)
y_train_sequences = y_train[timesteps:] #出力もタイムステップ分ずらす
#モデルの定義
model = Sequential()
model.add(LSTM(n_lstm_units, activation='tanh', return_sequences=(n_lstm_layers > 1), input_shape=(x_train_sequences.shape[1], x_train_sequences.shape[2])))
#LSTM層の追加
for i in range(n_lstm_layers - 1):
model.add(LSTM(n_lstm_units, activation='tanh', return_sequences=False if i == (n_lstm_layers - 2) else True))
#出力層
model.add(Dense(1, activation='linear'))
#コンパイル
optimizer = Adam(learning_rate=learning_rate)
model.compile(optimizer=optimizer, loss='mae', metrics=['mae'])
#モデルの学習
history = model.fit(x_train_sequences, y_train_sequences, epochs=epochs, batch_size=batch_size, verbose=0)
#予測と評価
y_pred = model.predict(x_train_sequences)
mae = mean_absolute_error(y_train_sequences, y_pred)
return mae
#Optunaの最適化
study = optuna.create_study(direction='minimize') #MAEを最小化する方向
study.optimize(objective, n_trials=50) #トライアル数を設定
#最適なパラメータを表示
print("Best trial:")
trial = study.best_trial
print(f" Value (MAE): {trial.value}")
print(f" Params: {trial.params}")
一応コメントは書いていますが,細かく書いていきます.
import optuna
import numpy as np
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense
from tensorflow.keras.optimizers import Adam
from sklearn.metrics import mean_absolute_error
各モジュールのインポートです.ここはいつも通りです.
x_train = input_data_force['input_Data1_force']
y_train = output_data_force['outputData1_force']
ここでデータを入力シーケンスとして保存しています.
def create_sequences(data, timesteps):
sequences = []
for i in range(len(data) - timesteps):
sequences.append(data[i:i + timesteps])
return np.array(sequences)
ここは,LSTM用の入力にするために,タイムステップ分ずらした行列を作る関数の定義をしています.今回のoptunaの話とは関係ありませんが念の為.
def objective(trial):
# ハイパーパラメータの選定
n_lstm_layers = trial.suggest_int('n_lstm_layers', 1, 3) # LSTM層の数
n_lstm_units = trial.suggest_int('n_lstm_units', 32, 128) # 各LSTM層のユニット数
epochs = trial.suggest_int('epochs', 10, 100) # エポック数
batch_size = trial.suggest_categorical('batch_size', [16, 32, 64]) # バッチサイズ
learning_rate = trial.suggest_loguniform('learning_rate', 1e-4, 1e-2) # 学習率
timesteps = trial.suggest_int('timesteps', 10, 60) # タイムステップの範囲を指定
# タイムステップに基づいてデータをシーケンスに変換
x_train_sequences = create_sequences(x_train, timesteps)
y_train_sequences = y_train[timesteps:] # 出力もタイムステップ分ずらす
# モデルの定義
model = Sequential()
model.add(LSTM(n_lstm_units, activation='tanh', return_sequences=(n_lstm_layers > 1), input_shape=(x_train_sequences.shape[1], x_train_sequences.shape[2])))
# LSTM層の追加
for i in range(n_lstm_layers - 1):
model.add(LSTM(n_lstm_units, activation='tanh', return_sequences=False if i == (n_lstm_layers - 2) else True))
# 出力層
model.add(Dense(1, activation='linear'))
# コンパイル
optimizer = Adam(learning_rate=learning_rate)
model.compile(optimizer=optimizer, loss='mae', metrics=['mae'])
# モデルの学習
history = model.fit(x_train_sequences, y_train_sequences, epochs=epochs, batch_size=batch_size, verbose=0)
# 予測と評価
y_pred = model.predict(x_train_sequences)
mae = mean_absolute_error(y_train_sequences, y_pred)
return mae
ここがメインの関数定義になります.詳しくみていきましょう.
def objective(trial):
として関数を定義し,この中にチューニングしたいパラメータについて書いていきます.
# ハイパーパラメータの選定
n_lstm_layers = trial.suggest_int('n_lstm_layers', 1, 3) # LSTM層の数
n_lstm_units = trial.suggest_int('n_lstm_units', 32, 128) # 各LSTM層のユニット数
epochs = trial.suggest_int('epochs', 10, 100) # エポック数
batch_size = trial.suggest_categorical('batch_size', [16, 32, 64]) # バッチサイズ
learning_rate = trial.suggest_loguniform('learning_rate', 1e-4, 1e-2) # 学習率
timesteps = trial.suggest_int('timesteps', 10, 60) # タイムステップの範囲を指定
これがチューニングしたいパラメータです.
n_lstm_layers = trial.suggest_int('n_lstm_layers',1, 3)
ここではLSTMの層の数を探索します.trial.suggest_intは整数の離散値の中から探索します.この場合1,2,3の中から探索します.
batch_size = trial.suggest_categorical('batch_size', [16, 32, 64])
ここではバッチサイズの探索をします.trial.suggest_categoricalも離散値から選びますが,後ろで設定している[16, 32, 64]の中から探索します.
learning_rate = trial.suggest_loguniform('learning_rate', 1e-4, 1e-2)
ここでは学習率の探索をします.trial.suggest_longuniformは対数スケールで10^-4から10^-2までの範囲で探索をしています.この書き方は少し古いらしく,次に示すコードを使う方がいいみたいです.
trial.suggest_float('learning_rate', 1e-4, 1e-2, log = True)
# タイムステップに基づいてデータをシーケンスに変換
x_train_sequences = create_sequences(x_train, timesteps)
y_train_sequences = y_train[timesteps:] # 出力もタイムステップ分ずらす
ここでLSTM用にデータを整形します.
# モデルの定義
model = Sequential()
model.add(LSTM(n_lstm_units, activation='tanh', return_sequences=(n_lstm_layers > 1), input_shape=(x_train_sequences.shape[1], x_train_sequences.shape[2])))
# LSTM層の追加
for i in range(n_lstm_layers - 1):
model.add(LSTM(n_lstm_units, activation='tanh', return_sequences=False if i == (n_lstm_layers - 2) else True))
# 出力層
model.add(Dense(1, activation='linear'))
ここでモデル定義をしています.return_sequencesは出力層の一つ前まではTrueにして,出力層の一つ前の層ではFalseにします.
# コンパイル
optimizer = Adam(learning_rate=learning_rate)
model.compile(optimizer=optimizer, loss='mae', metrics=['mae'])
# モデルの学習
history = model.fit(x_train_sequences, y_train_sequences, epochs=epochs, batch_size=batch_size, verbose=0)
# 予測と評価
y_pred = model.predict(x_train_sequences)
mae = mean_absolute_error(y_train_sequences, y_pred)
return mae
ここでモデルのコンパイル,学習,予測と評価を行います.今回は損失関数,評価関数ともにMAEにしています.最後に,評価関数に戻します.
# Optunaの最適化
study = optuna.create_study(direction='minimize') # MAEを最小化する方向
study.optimize(objective, n_trials=50) # トライアル数を設定
ここでMAEを最小化する方向へ50回探索するようにしています.
# 最適なパラメータを表示
print("Best trial:")
trial = study.best_trial
print(f" Value (MAE): {trial.value}")
print(f" Params: {trial.params}")
そして最後に最適化結果を示します.
これが一連の流れです.
まとめ
基本的な流れを示しました.具体的にかくと
import optuna
def objective(trial):
評価関数 = ----
return 評価関数
study = optuna.create_study(direction = '最小化,最大化')
study.optimize(objective, n_trials = 指定回数)
trial = study.best_trial
こんな感じです.
簡単に実装できましたよね?皆さんもぜひ使ってみてください!