My Memo

私のメモ

Optunaの最適化結果をグラフで可視化して保存する

Pythonの最適化ライブラリーOptunaを使ったあとその結果を可視化してグラフに保存したいと思ったのでそのメモ.

必要なライブラリーのインストール

今回必要になるライブラリは Optuna,それと可視化のためのPlotlyそしてKaleidoの3つ.KaleidoはPlotlyのグラフを保存するのに必要みたいで,直接呼び出したりはしない. ではそれぞれpipでインストールしておく

$ pip install optuna
$ pip install plotly 
$ pip install kaleido

Plotlyの簡単な使い方は過去の記事にある. Plotlyで簡単なグラフを描いてみる(Python) - My Memo

Optunaで最適化

次はOptunaで最適化を行う.今回はz=x2+y2+xyが最小になるように x, yを最適化する(最小/最大化させたい関数zを目的関数という).ちなみに,z=x2+y2+xyはx=0, y=0で最小となるので,0に近いx, yが得られれば最適化できたことになる.ちなみにこの式のグラフはこんな感じ.

z=x2+y2+x*y
この図の描画のPlotlyでの仕方は後述する. まずは,今回必要なライブラリをインポートする.

import optuna
import numpy as np
import plotly.graph_objects as go

次に最小化(あるいは最大化)する関数を書く.今回はz=x2+y2+xy.

def func(x, y):
    return x**2+y**2+x*y

次に,最適化したい変数の範囲を設定して目的関数のラッパーを定義する. 最適化したい変数の範囲に関して,今回はx, yともに 0から1の値を取るようにする.

def objective(trial):
    x = trial.suggest_uniform('x', -1, 1)  # xの範囲指定
    y = trial.suggest_uniform('y', -1, 1)  # yの範囲指定
    return func(x, y)

この関数は必ずtrialを引数にとり,最適化したい目的関数を返すようにする.なお範囲を指定するとき,整数のみをとることや,リストの要素から選ぶようにすることも可能

メソッド    
suggest_categorical(name, choices) choicesというリストの中から選ぶ x=trial.suggest_categorical('x', ['a', 'b', 'c'])
suggest_int(name, low, high[, step, log]) 整数から選ぶ x=trial.suggest_categorical('x', 1, 10, 2) 1から10まで2刻み

のように.

そして,目的関数と変数の範囲を指定したら最適化する.

if __name__ == "__main__":
    # search optimal x and y
    study = optuna.create_study(direction='minimize')
    study.optimize(objective, n_trials=50)

今回は目的関数の値を最小化させたいので,direction='minimize'と書いたが,目的関数を最大化させたい場合はdirection='maximize'とすると良い.なおdirectionを省略した場合自動的にdirection='minimize'になる.study.optimize()では目的関数と試行回数を引数として渡す.これで実行すれば最適化をしてくれる.実行すると標準出力に結果が逐次表示されるが,optunaの標準出力をファイルに保存したい場合は実行時に

$ python optimize.py &> output.txt

とすればファイルに保存できる.&を忘れずに.

結果の可視化

ここまで来るとあとはかんたん.最適化したあとの数値的な結果はstudyが持っているので 数値はそれを出せばよい.以下のコードで標準出力してくれる.

    # print result
    print(study.best_params)  # x, yの最適な値
    print(study.best_value)  #  zの最適値
    print(study.best_trial)  # x, y, zの最適値

次は結果のグラフを出力する. まずは, x,yの値に対するzの値を等高線図で表示する.コードは以下.

    # print result
    # contour plot
    fig = optuna.visualization.plot_contour(study)
    fig.write_html('image/contour.html')  # save as html file
    fig.write_image('image/contour.png')  # save as png file

optuna.visualization.plot_contour(study)メソッドは Plotlyのグラフインスタンスを返すので,保存するには上のようにする. Plotlyのグラフインスタンスの扱い方の詳細は Plotlyで簡単なグラフを描いてみる(Python) - My Memo

Plotlyで線グラフをたくさん描く - My Memo

するとこのようなグラフが保存される.

等高線図 (Contour Plot)
次はどの変数が最も最小化に寄与したか(今回はx,yは対称なので,理想的には同じ数値になる).

    # Hyperparameter importances
    fig = optuna.visualization.plot_param_importances(study)
    fig.write_html('image/param_importance.html')  # save as html file
    fig.write_image('image/param_importance.png')  # save as png file

グラフは

Hyperparameter importances
となる. 他にも色々かける

    # Empirical Distribution Function Plot
    fig = optuna.visualization.plot_edf(study)
    fig.write_html('image/edf.html')  # save as html file
    fig.write_image('image/edf.png')  # save as png file

    # Optimization History Plot
    fig = optuna.visualization.plot_optimization_history(study)
    fig.write_html('image/optimization_history.html')  # save as html file
    fig.write_image('image/optimization_history.png')  # save as png file

    # parallel coordinate plot
    fig = optuna.visualization.plot_parallel_coordinate(study)
    fig.write_html('image/parallel_coordinate.html')  # save as html file
    fig.write_image('image/parallel_coordinate.png')  # save as png file

    # slice
    fig = optuna.visualization.plot_slice(study)
    fig.write_html('image/slice.html')  # save as html file
    fig.write_image('image/slice.png')  # save as png file

それぞれグラフは

目的関数の値の分布,最適化の記録,...
といろいろ描ける.

今回のコード全体

今回のコードの全体を貼っておく.なお,目的関数の三次元グラフ描画する部分も付け足しておいた.

import optuna
import numpy as np
import plotly.graph_objects as go


def func(x, y):
    return x**2+y**2+x*y


def objective(trial):
    x = trial.suggest_uniform('x', -1, 1)
    y = trial.suggest_uniform('y', -1, 1)
    return func(x, y)


if __name__ == "__main__":
    # plot func(x, y) by plotly
    x = np.linspace(-1, 1, 100)
    y = np.linspace(-1, 1, 100)
    xx, yy = np.meshgrid(x, y)
    z = func(xx, yy)
    fig = go.Figure()
    fig.add_trace(go.Surface(z=z, x=x, y=y))
    fig.write_html('objective.html')

    # search optimal x and y
    study = optuna.create_study(direction='minimize')
    study.optimize(objective, n_trials=50)

    # print result
    print(study.best_params)
    print(study.best_value)
    print(study.best_trial)

    # visualize
    # contour plot
    fig = optuna.visualization.plot_contour(study)
    fig.write_html('image/contour.html')  # save as html file
    fig.write_image('image/contour.png')  # save as png file

    # Empirical Distribution Function Plot
    fig = optuna.visualization.plot_edf(study)
    fig.write_html('image/edf.html')  # save as html file
    fig.write_image('image/edf.png')  # save as png file

    # Optimization History Plot
    fig = optuna.visualization.plot_optimization_history(study)
    fig.write_html('image/optimization_history.html')  # save as html file
    fig.write_image('image/optimization_history.png')  # save as png file

    # parallel coordinate plot
    fig = optuna.visualization.plot_parallel_coordinate(study)
    fig.write_html('image/parallel_coordinate.html')  # save as html file
    fig.write_image('image/parallel_coordinate.png')  # save as png file

    # Hyperparameter importances
    fig = optuna.visualization.plot_param_importances(study)
    fig.write_html('image/param_importance.html')  # save as html file
    fig.write_image('image/param_importance.png')  # save as png file

    # slice
    fig = optuna.visualization.plot_slice(study)
    fig.write_html('image/slice.html')  # save as html file
    fig.write_image('image/slice.png')  # save as png file