[最適化問題]Optuna vs Hyperopt


はじめに

最適化フレームワークとして、OptunaHyperoptがあります。一体どちらが優れているのか気になったので、関数最適化問題を使って比較してみようと思います。
2つのフレームワークに関しては別記事で紹介しているので、そちらを参考にしてください。
Optunaを使って関数最適化をしてみる
Hyperoptを使って関数最適化をしてみる

比較実験

今回は

x^2+y^2+z^2

の最小化問題を最適化していきます。
試行ごとに結果が異なるため, 3回試行してみようと思います。

コード

今回、実験するために使用したコードは以下になります。

# -*- coding: utf-8 -*-
import optuna
import hyperopt
from hyperopt import hp
from hyperopt import fmin
from hyperopt import tpe
from hyperopt import Trials
import matplotlib.pyplot as plt

# optuna用の目的関数を設定(今回はx^2+y^2+z^2)
def objective_optuna(trial):
    # 最適化するパラメータを設定
    param = {
        'x': trial.suggest_uniform('x', -100, 100),
        'y': trial.suggest_uniform('y', -100, 100),
        'z': trial.suggest_uniform('z', -100, 100)
    }
    # 評価値を返す(デフォルトで最小化するようになっている)
    return param['x'] ** 2 + param['y'] ** 2 + param['z'] ** 2

# optunaで最適化実行
def optuna_exe():
    # studyオブジェクト生成
    study = optuna.create_study()
    # 最適化実行
    study.optimize(objective_optuna, n_trials=500)
    # ベストパラメータ表示
    print(study.best_params)
    # ベスト目的関数値を表示
    print(study.best_value)

    epoches = []    # 試行回数格納用
    values = []    # ベストvalue格納用
    best = 100000
    # best更新
    for i in study.trials:
        if best > i.value:
            best = i.value
        epoches.append(i.number + 1)
        values.append(best)
    return epoches, values

# hyperopt用の目的関数を設定
def objective_hyperopt(args):
    x, y, z = args
    return x ** 2 + y ** 2 + z ** 2

# hyperoptで最適化実行
def hyperopt_exe():
    # 探索空間の設定
    space = [
        hp.uniform('x', -100, 100),
        hp.uniform('y', -100, 100),
        hp.uniform('z', -100, 100)
    ]
    # 探索の様子を記録するためのオブジェクト
    trials = Trials()
    # 探索開始
    best = fmin(objective_hyperopt, space, algo=tpe.suggest, max_evals=500, trials=trials)
    # 結果を出力する
    print(best)
    epoches = [] # 試行回数格納用
    values = [] # ベストvalue格納用
    best = 100000
    # best更新
    for i, n in zip(trials.trials, range(500)):
        if best > i['result']['loss']:
            best = i['result']['loss']
        epoches.append(n+1)
        values.append(best)

    return epoches, values

def plot_graph():
    result_optuna = optuna_exe()
    result_hyperopt = hyperopt_exe()
    epoch_optuna = result_optuna[0]
    value_optuna = result_optuna[1]
    epoch_hyperopt = result_hyperopt[0]
    value_hyperopt = result_hyperopt[1]

    # グラフの描画
    fig, ax = plt.subplots()
    ax.set_xlabel("trial")
    ax.set_ylabel("value")
    ax.set_title("Optuna vs Hyperopt")
    ax.grid() # グリッド線を入れる
    ax.plot(epoch_optuna, value_optuna, color="red", label="Optuna")
    ax.plot(epoch_hyperopt, value_hyperopt, color="blue", label="Hyperopt")
    ax.legend(loc=0) # 凡例
    plt.show() # グラフ表示

if __name__ == '__main__':
    plot_graph()

実験結果1回目

Optuna:
'x': 0.2690396239515218,
'y': -1.75236444646743,
'z': 0.3724308175904496,
best_value:3.2818681863901693

Hyperopt:
'x': -2.9497423868903834,
'y': 0.13662455602710644,
'z': -3.844496541052724,
best_value:23.499800072493738

最終的なbest_valueはOptunaが優れていますね。
グラフより収束速度もOptunaが優れているかなといった感じですかね。

実験結果2回目

Optuna:
'x': 0.7811129871251672,
'y': 0.4130867942356189,
'z': 0.6953642534092288,
best_value:1.2643096431468364

Hyperopt:
'x': -3.7838067947126675,
'y': -2.595648793357423,
'z': -2.683504623035553,
best_value:28.255783580024783

2回目も1回目と同様、最終的なbest_valueと収束速度の面でOptunaが優れていますかね。

実験結果3回目

Optuna:
'x': -0.19339325990518663,
'y': -0.0030977352573082623,
'z': 0.4961595538587318,
best_value:0.2835848518257752

Hyperopt:
'x': 2.810074634010315,
'y': -1.2603362587820195,
'z': -0.7356174272489406,
best_value:10.026099933181214

3回目も最終的なbest_valueの値はOptunaが優れていました。収束速度はあまり変わらない気がしますね。

結論

最終的な最良目的関数値、収束速度の面でOptunaが優れている、との結論に至りました。
最適化問題をもう少し難しくしたら、差も大きく出るのだろうか...