Python のハイパーパラメータ自動最適化ライブラリー Optuna その2
– Optunaを使うとき最低限覚えておきたい探索範囲の指定方法 –

Python のハイパーパラメータ自動最適化ライブラリー Optuna その2 – Optunaを使うとき最低限覚えておきたい探索範囲の指定方法 –

機械学習などの数理モデルには、通常幾つかのハイパーパラメータがあり、そのハイパーパラメータの設定次第で大きく精度が変わります

このハイパーパラメータを調整し最適な設定を探すタスクを、ハイパーパラメータチューニングと言います。

前回は、「Optuna のちょっとした使い方」についてお話ししました。

Python の ハイパーパラメータ自動最適化ライブラリー Optuna その1 Optuna のちょっとした使い方

今回は、「Optunaを使うとき最低限覚えておきたい探索範囲の指定方法」についてお話しします。

主なハイパーパラメータの探索範囲などの指定方法

以下、主なハイパーパラメータの探索範囲などの指定方法です。

  • 指定範囲の実数から選ぶ(suggest_float)
  • 指定範囲の整数から選ぶ(suggest_int)
  • 指定範囲の離散値から選ぶ(suggest_discrete_uniform)
  • 指定範囲のカテゴリーから選ぶ(suggest_categorical)

基本、ハイパーパラメータカテゴリーなのか、実数なのか、整数ないのかで選択すればいいでしょう。

実数整数などのハイパーパラメータでも、離散値として指定することもできます。

指定範囲のカテゴリーから選ぶ」方法で一番出番がありそうなのが、複数の数理モデルを検討するときです。この場合のカテゴリーとは、数理モデル名になります。

前回利用した簡単な例を使って説明していきます。

先ずは手順の復習です。

 

Optuna の手順の復習

Optunaは、目的関数(objective function)を設定し最適化を目指すことでより良いハイパーパラメータの組み合わせを探索します。

以下、ざっくり手順です。

  • ステップ1:目的関数を設定するハコを作る
  • ステップ2:目的関数の中で、構築するモデルを定義する
  • ステップ3:目的関数の中で、各モデルのハイパーパラメータの集合を定義する
  • ステップ4:目的関数の中で、良し悪しを判断するメトリクスを定義する
  • ステップ5:目的関数の最適化を実行する

ステップ4で試行回数(n_trials)を設定し実行します。試行回数が多いほど時間が掛かりますが、より良い解になる可能性が高くなります

 

簡単な例

前回登場した2次関数の最小値を求める例で説明します。

先ず、ライブラリーを読み込みます。

以下、コードです。

# ライブラリーの読み込み
import optuna

 

簡単な例|suggest_float 利用

前回登場した2次関数の最小値を求める例では、実数から選ぶsuggest_float関数を使って、ハイパーパラメータの探索方法を指定しています。

以下、コードです。数理モデルが無いので、ステップ2を飛ばしています。

# 2次関数
def f(x):
    return ((x - 3) ** 2)

# 目的関数の設定(ステップ1)
def objective(trial):
    x = trial.suggest_float('x', -7, 13)  #ステップ3
    return f(x)                           #ステップ4

# 目的関数の最適化を実行する(ステップ5)
study = optuna.create_study(direction="minimize")
study.optimize(objective, n_trials=100)

# 最適解の出力
print(f"The best value is : \n {study.best_value}")
print(f"The best parameters are : \n {study.best_params}")

 

以下、実行結果です。

 

suggest_float('x', -7, 13)について、簡単に説明します。

suggest_float('x', -7, 13)は……

ハイパーパラメータxを
-7から13の範囲の
実数から探索する

……ということです。

 

簡単な例|suggest_int 利用

実数から選ぶsuggest_float関数の代わりに、整数から選ぶsuggest_int関数を使ってみます。

以下、コードです。

# 2次関数
def f(x):
    return ((x - 3) ** 2)

# 目的関数の設定(ステップ1)
def objective(trial):
    x = trial.suggest_int('x', -7, 13)  #ステップ3
    return f(x)                         #ステップ4

# 目的関数の最適化を実行する(ステップ5)
study = optuna.create_study(direction="minimize")
study.optimize(objective, n_trials=100)

# 最適解の出力
print(f"The best value is : \n {study.best_value}")
print(f"The best parameters are : \n {study.best_params}")

 

以下、実行結果です。

 

suggest_int('x', -7, 13)について、簡単に説明します。

suggest_int('x', -7, 13)は……

ハイパーパラメータxを
-7から13の範囲の
整数から探索する

……ということです。

 

簡単な例|suggest_discrete_uniform 利用

離散値から選ぶsuggest_discrete_uniform関数を使ってみます。

以下、コードです。

# 2次関数
def f(x):
    return ((x - 3) ** 2)

# 目的関数の設定(ステップ1)
def objective(trial):
    x = trial.suggest_discrete_uniform('x',
                                       -5,5,0.1) #ステップ3
    return f(x)                                  #ステップ4

# 目的関数の最適化を実行する(ステップ5)
study = optuna.create_study(direction="minimize")
study.optimize(objective, n_trials=100)

# 最適解の出力
print(f"The best value is : \n {study.best_value}")
print(f"The best parameters are : \n {study.best_params}")

 

以下、実行結果です。

suggest_discrete_uniform(‘x’,-5,5,0.1)について、簡単に説明します。

suggest_discrete_uniform(‘x’,-5,5,0.1)は……

ハイパーパラメータxを
-5から5の範囲の
0.1刻みの離散値の中から探索する

……ということです。

-5から5の範囲の0.1刻みの離散値とは……

-5, -4.9, -4.8, …, 4.9, 5

……です。

 

簡単な例|suggest_categorical 利用

カテゴリーから選ぶsuggest_categorical関数を使ってみます。

以下、コードです。

# 2次関数
def f(x):
    return ((x - 3) ** 2)

# 目的関数の設定(ステップ1)
def objective(trial):
    x = trial.suggest_categorical('x',
                                  [-5,-3,-1,1,3,5]) #ステップ3
    return f(x)                                     #ステップ4

# 目的関数の最適化を実行する(ステップ5)
study = optuna.create_study(direction="minimize")
study.optimize(objective, n_trials=100)

# 最適解の出力
print(f"The best value is : \n {study.best_value}")
print(f"The best parameters are : \n {study.best_params}")

 

以下、実行結果です。

suggest_categorical(‘x’,[-5,-3,-1,1,3,5])について、簡単に説明します。

suggest_categorical(‘x’,[-5,-3,-1,1,3,5])は……

ハイパーパラメータxを
指定した[-5,-3,-1,1,3,5]
の中から探索する

……ということです。

 

分類問題(乳がんデータ)

前回利用した分類問題(乳がんデータ)の例で、どのように設定したのかを説明します。

以下、前回のコードです。

# ライブラリーの読み込み
import optuna
import pandas as pd
import sklearn.svm
from sklearn import linear_model
from sklearn import ensemble
from sklearn import datasets
from sklearn import model_selection

# サンプルデータの読み込み(乳がんデータ)
X,y = datasets.load_breast_cancer(return_X_y=True, as_frame=True)

# 目的関数の設定(ステップ1)
def objective(trial):

    #ステップ2
    classifier_name = trial.suggest_categorical("classifier", 
                                                ["LogReg",
                                                 "SVC",
                                                 "RandomForest"
                                                ]
                                               )
    
    #ステップ3
    ##ロジ回(LogReg)
    if classifier_name == 'LogReg':
        logreg_c = trial.suggest_float("logreg_c",
                                       1e-10, 1e10, 
                                       log=True
                                      )
        classifier_obj = linear_model.LogisticRegression(C=logreg_c)
    ##サポートベクターマシン(SVC)
    elif classifier_name == "SVC":
        svc_c = trial.suggest_float("svc_c",
                                    1e-10, 1e10,
                                    log=True
                                   )
        classifier_obj = sklearn.svm.SVC(C=svc_c,
                                         gamma="auto"
                                        )
    ##ランダムフォレスト(RandomForest)
    else:
        rf_n_estimators = trial.suggest_int("rf_n_estimators",
                                            10, 1000
                                           )
        rf_max_depth = trial.suggest_int("rf_max_depth", 
                                         2, 50,
                                         log=True
                                        )
        classifier_obj = ensemble.RandomForestClassifier(
            max_depth=rf_max_depth,
            n_estimators=rf_n_estimators
        )

    #ステップ4
    score = model_selection.cross_val_score(classifier_obj,
                                            X,
                                            y,
                                            n_jobs=-1,
                                            cv=10
                                           )
    accuracy = score.mean()
    return accuracy

# 目的関数の最適化を実行する(ステップ5)
study = optuna.create_study(direction="maximize")
study.optimize(objective, n_trials=100)

 

以下、実行結果です。

 

構築するモデルを複数設定するために、カテゴリーから選ぶsuggest_categorical関数を使っています。

suggest_categorical("classifier",["LogReg","SVC","RandomForest"])について、簡単に説明します。

suggest_categorical("classifier",["LogReg","SVC","RandomForest"])は……

ハイパーパラメータclassifierを
指定した[“LogReg”,”SVC”,”RandomForest”]
の中から探索する

……ということです。

以下が、ここで登場している数理モデルです。

  • LogReg:ロジスティック回帰
  • SVC:サポートベクターマシン
  • RandomForest:ランダムフォレスト

それぞれのモデルで実施した、ハイパーパラメータの探索範囲の指定を簡単に説明します。

 

分類問題(乳がんデータ)|LogReg:ロジスティック回帰

探索指定したハイパーパラメータは以下です。

  • logreg_c:正則化の強さの逆数(デフォルトは1)

構築するモデルの正則化の強さを探索するために、実数から選ぶsuggest_float関数を使っています。

suggest_float("logreg_c",1e-10, 1e10,log=True)について、簡単に説明します。

suggest_float("logreg_c",1e-10, 1e10,log=True)は……

ハイパーパラメータlogreg_cを
1e-10から1e10の対数変換された範囲の
実数から探索する

……ということです。

logはデフォルトではlog=Falseです。デフォルト(log=False)では、指定した範囲の中で一様分布に従って探索されます。log=Trueとすると、対数変換された空間で一様分布に従って探索されます。

 

分類問題(乳がんデータ)|SVC:サポートベクターマシン

探索指定したハイパーパラメータは以下です。

  • svc_c:正則化の強さの逆数(デフォルトは1)

先程説明したlogreg_cと、svc_cは基本的に同じです。

suggest_float("svc_c ",1e-10, 1e10,log=True)は……

ハイパーパラメータsvc_cを
1e-10から1e10の対数変換された範囲の
実数から探索する

……ということです。

 

分類問題(乳がんデータ)|RandomForest:ランダムフォレスト

探索指定したハイパーパラメータは以下です。

  • rf_n_estimators:決定木の個数(デフォルトは100)
  • rf_max_depth:決定木の深さの最大値(デフォルトはNone)

先ず、rf_n_estimatorsです。

構築するモデルの決定木の個数を選ぶために、整数から選ぶsuggest_int関数を使っています。

suggest_int("rf_n_estimators",10,1000)について、簡単に説明します。

suggest_int("rf_n_estimators",10,1000)は……

ハイパーパラメータrf_n_estimatorsを
10から1000の範囲の
整数から探索する

……ということです。

 

次に、rf_max_depthです。

構築するモデルの決定木の深さの最大値を探索するために、整数から選ぶsuggest_int関数を使っています。

suggest_int("rf_max_depth",2,50,log=True)について、簡単に説明します。

suggest_int("rf_max_depth",2,50,log=True)は……

ハイパーパラメータrf_max_depthを
2から50の対数変換された範囲の
整数から探索する

……ということです。

 

まとめ

今回は、「Optunaを使うとき最低限覚えておきたい探索範囲の指定方法」についてお話ししました。

以下、主なハイパーパラメータの探索範囲などの指定方法です。

  • 指定範囲の実数から選ぶ(suggest_float)
  • 指定範囲の整数から選ぶ(suggest_int)
  • 指定範囲の離散値から選ぶ(suggest_discrete_uniform)
  • 指定範囲のカテゴリーから選ぶ(suggest_categorical)

基本、ハイパーパラメータカテゴリーなのか、実数なのか、整数ないのかで選択すればいいでしょう。

実数整数などのハイパーパラメータでも、離散値として指定することもできます。

指定範囲のカテゴリーから選ぶ」方法で一番出番がありそうなのが、複数の数理モデルを検討するときです。この場合のカテゴリーとは、数理モデル名になります。

Python のハイパーパラメータ自動最適化ライブラリー Optunaを使いとき、参考にして頂ければと思います。