Python 多目的最適化「Optuna」

'21/03/03更新:浮動小数点以外の型の場合の制約条件「カテゴリ変数、整数、対数、離散値」の設定例を追記しました。
 本記事では、Optunaを使った多目的最適化の雛形コードを載せました。 Optunaは、オープンソース機械学習モデルのハイパーパラメータを自動最適化するフレームワークです。しかし、現在の最新ver2.3.0では多目的最適化にも対応しています。本雛形コードでは、パレート解の可視化のためにグラフ化、パレート解の目的変数と説明変数をcsvファイルに保存する仕様にしました。
 インストール方法は2通りあります。pipの場合は次のようにします。

pip install optuna

Anaconda環境ではcondaで出来ます。

conda install -c conda-forge optuna

本コードを実行すると、下図のようにパレート解を図示するグラフを作成して画像ファイルで保存します。

f:id:HK29:20201123133126p:plain

更に、下図のようにパレート解の結果をcsvファイルに保存します。

f:id:HK29:20201123133335p:plain

ここで、変数の制約条件は変数の型によって次のようにして設定します。上図は浮動小数型の場合です。

# 浮動小数点型の場合
lstat = trial.suggest_float('LSTAT', 1.7, 38)
# 整数の場合 rm = trial.suggest_int('RM', 3, 8) # カテゴリの場合
chas = trial.suggest_categorical('CHAS', ['0', '1'])
# 対数の場合 density = trial.suggest_loguniform('Density', 1e-15, 1e-7) # 離散値の場合 rad = trial.suggest_discrete_uniform('RAD', 1,2,3,4,5,6,7,8,24)

■本プログラム

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# https://optuna.readthedocs.io/_/downloads/en/latest/pdf/
import optuna
import matplotlib.pyplot as plt

# 目的関数の定義。複数の目的変数を戻り値とする
def objective(trial):
    x = trial.suggest_float("x", 0, 5) # 変数xを上下限0~5の範囲で浮動小数点
    y = trial.suggest_float("y", 0, 3)
    v0 = 4 * x ** 2 + 4 * y ** 2
    v1 = (x - 5) ** 2 + (y - 5) ** 2
    return v0, v1

# 最適化の条件設定
study = optuna.multi_objective.create_study(
    directions=["minimize", "minimize"], # "minimize" "maximize"
    sampler=optuna.multi_objective.samplers.NSGAIIMultiObjectiveSampler(seed = 1)
    #sampler=optuna.multi_objective.samplers.RandomMultiObjectiveSampler(seed = 1)
)
# 最適化の実行
study.optimize(objective, n_trials=200)

# 最適化過程で得た履歴データの取得。get_trials()メソッドを使用
trials = {str(trial.values): trial for trial in study.get_trials()}
trials = list(trials.values())
# グラフにプロットするため、目的変数をリストに格納する
y1_all_list = []
y2_all_list = []
for i, trial in enumerate(trials, start=1):
    y1_all_list.append(trial.values[0])
    y2_all_list.append(trial.values[1])

# パレート解の取得。get_pareto_front_trials()メソッドを使用
trials = {str(trial.values): trial for trial in study.get_pareto_front_trials()}
trials = list(trials.values())
trials.sort(key=lambda t: t.values)
# グラフプロット用にリストで取得。またパレート解の目的変数と説明変数をcsvに保存する
y1_list = []
y2_list = []
with open('pareto_data.csv', 'w') as f:
    for i, trial in enumerate(trials, start=1):
        if i == 1:
            columns_name_str = 'trial_no,y1,y2'
        data_list = []
        data_list.append(trial.number)
        y1_value = trial.values[0]
        y2_value = trial.values[1]
        y1_list.append(y1_value)
        y2_list.append(y2_value)
        data_list.append(y1_value)
        data_list.append(y2_value)    
        for key, value in trial.params.items():
            data_list.append(value)
            if i == 1:
                columns_name_str += ',' + key 
        if i == 1:
            f.write(columns_name_str + '\n')
        data_list = list(map(str, data_list))
        data_list_str = ','.join(data_list)
        f.write(data_list_str + '\n')

# パレート解を図示
plt.rcParams["font.size"] = 16
plt.figure(dpi=120)
plt.title("multiobjective optimization")
plt.xlabel("Y1")
plt.ylabel("Y2")
plt.grid()
plt.scatter(y1_all_list, y2_all_list, c='blue', label='all trials')
plt.scatter(y1_list, y2_list, c='red', label='pareto front')
plt.legend()
plt.tight_layout()
plt.savefig("pareto_graph.png")
plt.close()

(参考資料)マニュアルは次のリンク先pdfです。https://optuna.readthedocs.io/_/downloads/en/latest/pdf/

以上

<広告>