Note
Go to the end to download the full example code.
Ask-and-Tell インターフェース
Optuna には Ask-and-Tell インターフェースが用意されており、ハイパーパラメータ最適化においてより柔軟な操作が可能です。 本チュートリアルでは、Ask-and-Tell インターフェースが特に有効な3つのユースケースを解説します:
最小限の変更で既存の最適化問題に Optuna を適用
従来の教師あり分類問題を考えます。検証精度を最大化することが目的です。 この目的を達成するため、単純なモデルとして LogisticRegression を使用します。
import numpy as np
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
import optuna
X, y = make_classification(n_features=10)
X_train, X_test, y_train, y_test = train_test_split(X, y)
C = 0.01
clf = LogisticRegression(C=C)
clf.fit(X_train, y_train)
val_accuracy = clf.score(X_test, y_test) # 目的関数
次に、Optuna を使用して分類器のハイパーパラメータ C
と solver
を最適化します。
Optuna を単純に導入する場合、trial
を受け取り、ハイパーパラメータをサンプリングするために
trial
の suggest_*
メソッドを呼び出す objective
関数を定義します:
def objective(trial):
X, y = make_classification(n_features=10)
X_train, X_test, y_train, y_test = train_test_split(X, y)
C = trial.suggest_float("C", 1e-7, 10.0, log=True)
solver = trial.suggest_categorical("solver", ("lbfgs", "saga"))
clf = LogisticRegression(C=C, solver=solver)
clf.fit(X_train, y_train)
val_accuracy = clf.score(X_test, y_test)
return val_accuracy
study = optuna.create_study(direction="maximize")
study.optimize(objective, n_trials=10)
このインターフェースは柔軟性に欠けます。
例えば、objective
が trial
以外の追加引数を必要とする場合、
独自の引数を持つ目的関数の定義方法
で説明されているように、クラスを定義する必要があります。
Ask-and-Tell インターフェースは、ハイパーパラメータ最適化のためのより柔軟な構文を提供します。
以下の例は、前のコードブロックと同等の動作をします。
study = optuna.create_study(direction="maximize")
n_trials = 10
for _ in range(n_trials):
trial = study.ask() # `trial` は `FrozenTrial` ではなく `Trial` です。
C = trial.suggest_float("C", 1e-7, 10.0, log=True)
solver = trial.suggest_categorical("solver", ("lbfgs", "saga"))
clf = LogisticRegression(C=C, solver=solver)
clf.fit(X_train, y_train)
val_accuracy = clf.score(X_test, y_test)
study.tell(trial, val_accuracy) # トライアルと目的関数値のペアを送信
主な違いは、optuna.study.Study.ask()
と optuna.study.Study.tell()
の2つのメソッドを使用することです。
optuna.study.Study.ask()
はハイパーパラメータをサンプリング可能なトライアルを作成し、
optuna.study.Study.tell()
は trial
と目的関数値を渡してトライアルを終了させます。
objective
関数を使用せずに、元のコードに Optuna のハイパーパラメータ最適化を適用できます。
プルーナーを使用して最適化を高速化する場合、optuna.study.Study.tell()
メソッドの引数に
トライアルの状態を明確に渡す必要があります。
import numpy as np
from sklearn.datasets import load_iris
from sklearn.linear_model import SGDClassifier
from sklearn.model_selection import train_test_split
import optuna
X, y = load_iris(return_X_y=True)
X_train, X_valid, y_train, y_valid = train_test_split(X, y)
classes = np.unique(y)
n_train_iter = 100
# ハイパーバンドプルーナーを使用したスタディの定義
study = optuna.create_study(
direction="maximize",
pruner=optuna.pruners.HyperbandPruner(
min_resource=1, max_resource=n_train_iter, reduction_factor=3
),
)
for _ in range(20):
trial = study.ask()
alpha = trial.suggest_float("alpha", 0.0, 1.0)
clf = SGDClassifier(alpha=alpha)
pruned_trial = False
for step in range(n_train_iter):
clf.partial_fit(X_train, y_train, classes=classes)
intermediate_value = clf.score(X_valid, y_valid)
trial.report(intermediate_value, step)
if trial.should_prune():
pruned_trial = True
break
if pruned_trial:
study.tell(trial, state=optuna.trial.TrialState.PRUNED) # プルーニング状態を送信
else:
score = clf.score(X_valid, y_valid)
study.tell(trial, score) # 目的関数値を送信
Note
optuna.study.Study.tell()
メソッドは、トライアルオブジェクトの代わりにトライアル番号も受け付けます。
study.tell(trial.number, y)
は study.tell(trial, y)
と同等です。
Define-and-Run
Ask-and-Tell インターフェースは、define-by-run と define-and-run の両方の API をサポートしています。 このセクションでは、上記の define-by-run の例に加えて、define-and-run API の例を示します。
define-and-run API を使用する前に、
optuna.study.Study.ask()
メソッドを呼び出す前にハイパーパラメータの分布を定義します。
例えば
distributions = {
"C": optuna.distributions.FloatDistribution(1e-7, 10.0, log=True),
"solver": optuna.distributions.CategoricalDistribution(("lbfgs", "saga")),
}
各呼び出し時に distributions
を optuna.study.Study.ask()
メソッドに渡します。
返された trial
には提案されたハイパーパラメータが含まれます。
study = optuna.create_study(direction="maximize")
n_trials = 10
for _ in range(n_trials):
trial = study.ask(distributions) # 事前に定義した分布を渡します。
# ハイパーパラメータの 2 つは既に事前定義された分布からサンプリングされています
C = trial.params["C"]
solver = trial.params["solver"]
clf = LogisticRegression(C=C, solver=solver)
clf.fit(X_train, y_train)
val_accuracy = clf.score(X_test, y_test)
study.tell(trial, val_accuracy)
バッチ最適化
Ask-and-Tell インターフェースを使用すると、バッチ化された目的関数を最適化して最適化を高速化できます。 例えば、並列化可能な評価、ベクトル演算などです。
以下の目的関数は、単一のハイパーパラメータ x
と y
のペアの代わりに、
バッチ化されたハイパーパラメータ xs
と ys
を受け取り、完全なベクトル上で目的関数を計算します。
def batched_objective(xs: np.ndarray, ys: np.ndarray):
return xs**2 + ys
以下の例では、バッチ内のハイパーパラメータのペア数は \(10\) で、
batched_objective
は 3 回評価されます。
したがって、トライアル数は \(30\) です。
バッチ評価後に
optuna.study.Study.tell()
メソッドを呼び出すには、
trial_numbers
または trial
のいずれかを保存する必要があることに注意してください。
batch_size = 10
study = optuna.create_study(sampler=optuna.samplers.CmaEsSampler())
for _ in range(3):
# バッチを作成
trial_numbers = []
x_batch = []
y_batch = []
for _ in range(batch_size):
trial = study.ask()
trial_numbers.append(trial.number)
x_batch.append(trial.suggest_float("x", -10, 10))
y_batch.append(trial.suggest_float("y", -10, 10))
# バッチ化された目的関数を評価
x_batch = np.array(x_batch)
y_batch = np.array(y_batch)
objectives = batched_objective(x_batch, y_batch)
# バッチ内のすべてのトライアルを終了
for trial_number, objective in zip(trial_numbers, objectives):
study.tell(trial_number, objective)
Tip
optuna.samplers.TPESampler
クラスはブール型パラメータ constant_liar
を
取ります。この値を True
に設定すると、バッチ最適化中に
複数のワーカーが類似したパラメータ設定を評価するのを防ぐことができます。
特に、各目的関数評価が高価で、実行状態の持続時間が
長い場合や、ワーカー数が多い場合に有効です。
Tip
optuna.samplers.CmaEsSampler
クラスは popsize
属性パラメータを
取り、これは CMA-ES アルゴリズムの初期集団サイズとして使用されます。
バッチ最適化の文脈では、並列化可能な操作の恩恵を受けるために、
バッチサイズの倍数に設定することができます。
Total running time of the script: (0 minutes 0.159 seconds)