機械学習
記事内に商品プロモーションを含む場合があります

Optunaをサクッと動かす方法|ハイパーパラメータの自動最適化を実践

Aru

Optunaは、ハイパーパラメータの自動最適化を簡単に行える便利なフレームワークです。本記事では、Optunaでハイパーパラメータを調整するためのサンプルコード(テンプレート)を紹介し、私がよく使っている応用例について解説します。

Optunaとは

Optunaは、日本のPrefferdNetworkが開発したハイパーパラメータの最適値を探索するためのフレームワークです。機械学習やディープラーニングでは、性能を引き出すためにハイパーパラメータのチューニングが必要ですが、このパラメータを自動チューニングするツールがoptunaになります。

この記事では、私がOptunaを使う場合の使い方について解説します。

もしかしたら、制作者側が意図しない使い方かもしれませんが、「私はこんな使い方をしています」というものになります。

使い方(基本)

インストール

インストールはpipで行うことができます

pip install optuna

テンプレート的なコード

以下のコードは私がOptunaを使う場合のテンプレート的なコードになります。このコードでは、ta * X + tbにノイズの入ったデータからtatbを推定するものです。

import numpy as np
import optuna

# 1. データ準備
n = 1000
ta = 2
tb = 3
X = np.random.uniform(0, 10, n)
Y = ta * X + tb + np.random.randn(n)
print(X)
print(Y)

# 2. パラメータを受け取り処理する関数
def f(a, b) :
    y = a * X + b
    res = np.sum((Y-y)**2)
    return res

# 3. Optunaが最適化で呼び出す関数
def objective(trial):
    a = trial.suggest_float('a', -10, 10)
    b = trial.suggest_float('b', -10, 10)

    loss = f(a, b)
    return loss

# 4. Optunaの実行部分
study = optuna.create_study(direction='minimize')
study.optimize(objective, n_trials=100)

# 5. 結果の表示
print('best params:', study.best_params)

大まかな処理の流れは以下になります。

  1. データ準備
    f(a,b)で使うデータを準備する関数です。サンプルではY = ta*X + tb + εという直線上の点にノイズεを加えたデータを作成しています。
  2. パラメータを受け取り処理する関数
    a,bを受け取り、Y = ta*X + tb + εとの差の絶対値の二乗を計算しています(MSE)。戻り値が小さいほどYとa,bから計算したyとの差が小さくなります。
  3. Optunaが最適化で呼び出す関数
    Optunaの引数で与えるObjective関数です。パラメータa,bをOptunaが決定するためにsuggest_floatを呼び出しています。また、f(a,b)を呼び出し、その結果をlossとして返しています(結果を数値として返すことが重要)。
  4. Optunaの実行部分
    create_studyでオブジェクトを生成してoptimizeで最適化を行なっています。direction='minimize'としているので「小さいほど良い結果」となり、n_trail=100と設定しているので、100回繰り返しを行います。
  5. 結果の表示
    最後に、ベストのパラメータを表示しています。

上記のプログラムを実行すると、おおよそa=2, b=3が結果として戻ります。線形回帰の代わりにOptunaで最適化をかけている例になりますが、基本はこの形で利用できるのでコードパターンとして覚えておくと良いです。

f()の部分は、実際は、ディープラーニングのモデルの学習・推論や、lightGBMによる学習・予測などになります。

なお、suggest_xxxxには以下のようなものが準備されています(代表的なもの)

関数名内容
suggest_categoricalカテゴリ変数
suggest_float浮動小数点
suggest_int整数

応用編

1. 初期に探索するパラメータを設定する

Optunaでは初期の探索パターンを与えることが可能です。

# 3. Optunaの実行部分
study = optuna.create_study(direction='minimize')
study.enqueue_trial({'a': 1.0, 'b': 2.0})
study.enqueue_trial({'a': 2.0, 'b': 3.0})
study.enqueue_trial({'a': 3.0, 'b': 4.0})
study.optimize(objective, n_trials=10)

2. 続きから探索させる

Optunaを使う場合、f()の処理がかなり重かったり、また、別の環境で実行して結果だけ移すということがあります(少なくとも、私は何度か経験しました)。例えば、学習などでは、数時間かけて学習・評価を行うことなんかもあるわけで、Optunaから呼び出して実行というのが結構難しかったりします。

私は、enqueue_trialとペアになる結果を与えて、次のパラメータを予測させるという方法をとっていました。コードのイメージは以下のようになります。

ab = (({'a': -2.50919762305275, 'b': 9.014286128198323}, 435299.07726309635),
      ({'a': -8.466331566873603, 'b': -8.386458410932443},4875667.5918871))
cnt = 0

def objective(trial):
    global cnt, ab
    a = trial.suggest_float('a', -10, 10)
    b = trial.suggest_float('b', -10, 10)

    if cnt < len(ab):
        loss = ab[cnt][1]
        cnt += 1
        return loss

    print(f"a : {a}, b : {b}")
    input()

    loss = f(a, b)
    return loss

study = optuna.create_study(direction='minimize')
for e in ab:
    study.enqueue_trial(e[0])

study.optimize(objective, n_trials=10)

objective関数では、保存されている事前の結果に対しては対応する結果をそのまま返し、新しいものについては、input()で停止させています。表示されたa, bをコピーして新しいパラメータで実行し、abに追加するというのを繰り返します。

面倒ですが、このやり方は意外と便利です。

乱数を初期化しても同じ結果にならないのがちょっと面倒ですが、とりあえずこの方法を使っていました。

なお、n_trailはあらかじめ決めた試行回数をいれておいてください(3回目だから3とかしないように)。

まとめ

Optunaの簡単な使い方と、私が使っているTIPS的な部分いついて解説しました。Optunaの記事は多いのですでにどこかに書かれている情報かもしれませんが、参考になれば幸いです。

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

ABOUT ME
ある/Aru
ある/Aru
IT&機械学習エンジニア/ファイナンシャルプランナー(CFP®)
専門分野は並列処理・画像処理・機械学習・ディープラーニング。プログラミング言語はC, C++, Go, Pythonを中心として色々利用。現在は、Kaggle, 競プロなどをしながら悠々自適に活動中
記事URLをコピーしました