Note
Go to the end to download the full example code.
ユーザー定義サンプラー
ユーザー定義サンプラーを使用すると、以下のことが可能になります:
独自のサンプリングアルゴリズムを実験する
最適化性能を向上するためのタスク特化型アルゴリズムを実装する
他の最適化ライブラリをラップして Optuna パイプラインに統合する (例: BoTorchSampler)
本セクションでは、サンプラークラスの内部動作について説明し、ユーザー定義サンプラーの実装例を示します。
サンプラーの概要
サンプラーの役割は、トライアルで評価するパラメータ値を決定することです。
目的関数内で suggest API (例: suggest_float()
) が呼び出されると、対応する分布オブジェクト (例: FloatDistribution
) が内部的に作成されます。サンプラーはこの分布からパラメータ値をサンプリングし、suggest API の呼び出し元に返します。返された値は目的関数で評価されます。
新しいサンプラーを作成するには、BaseSampler
を継承したクラスを定義する必要があります。
基本クラスには3つの抽象メソッドがあります:
infer_relative_search_space()
,
sample_relative()
, および
sample_independent()
。
メソッド名が示す通り、Optuna は2種類のサンプリングをサポートしています: 一つは 相対サンプリング で、トライアル内のパラメータ間の相関を考慮できます。もう一つは 独立サンプリング で、各パラメータを独立してサンプリングします。
トライアルの開始時に infer_relative_search_space()
が呼び出され、トライアルの相対探索空間が提供されます。その後、sample_relative()
が呼び出されて探索空間から相対パラメータをサンプリングします。目的関数の実行中には、sample_independent()
が相対探索空間に属さないパラメータのサンプリングに使用されます。
Note
詳細については BaseSampler
のドキュメントを参照してください。
例: SimulatedAnnealingSampler の実装
例えば、以下のコードは Simulated Annealing (SA) に基づくサンプラーを定義しています:
import numpy as np
import optuna
class SimulatedAnnealingSampler(optuna.samplers.BaseSampler):
def __init__(self, temperature=100):
self._rng = np.random.RandomState()
self._temperature = temperature # 現在の温度
self._current_trial = None # 現在の状態
def sample_relative(self, study, trial, search_space):
if search_space == {}:
return {}
# Simulated Annealing アルゴリズム
# 1. 遷移確率を計算
prev_trial = study.trials[-2]
if self._current_trial is None or prev_trial.value <= self._current_trial.value:
probability = 1.0
else:
probability = np.exp(
(self._current_trial.value - prev_trial.value) / self._temperature
)
self._temperature *= 0.9 # 温度を減少
# 2. 前の結果が受け入れられた場合、現在の状態を遷移
if self._rng.uniform(0, 1) < probability:
self._current_trial = prev_trial
# 3. 現在点の近傍からパラメータをサンプリング
# サンプリングされたパラメータは study に渡した目的関数の次の実行時に使用されます
params = {}
for param_name, param_distribution in search_space.items():
if (
not isinstance(param_distribution, optuna.distributions.FloatDistribution)
or (param_distribution.step is not None and param_distribution.step != 1)
or param_distribution.log
):
msg = (
"`step` が `None` または 1.0 で `log` が `False` の suggest_float() のみがサポートされています"
)
raise NotImplementedError(msg)
current_value = self._current_trial.params[param_name]
width = (param_distribution.high - param_distribution.low) * 0.1
neighbor_low = max(current_value - width, param_distribution.low)
neighbor_high = min(current_value + width, param_distribution.high)
params[param_name] = self._rng.uniform(neighbor_low, neighbor_high)
return params
# 以下は SA アルゴリズムとは無関係なボイラープレート
def infer_relative_search_space(self, study, trial):
return optuna.search_space.intersection_search_space(study.get_trials(deepcopy=False))
def sample_independent(self, study, trial, param_name, param_distribution):
independent_sampler = optuna.samplers.RandomSampler()
return independent_sampler.sample_independent(study, trial, param_name, param_distribution)
Note
コードの簡潔さのため、上記の実装では最大化などの一部の機能をサポートしていません これらの機能のサポート方法については、 examples/samplers/simulated_annealing.py を参照してください
SimulatedAnnealingSampler
は以下のように組み込みサンプラーと同様に使用できます:
def objective(trial):
x = trial.suggest_float("x", -10, 10)
y = trial.suggest_float("y", -5, 5)
return x**2 + y
sampler = SimulatedAnnealingSampler()
study = optuna.create_study(sampler=sampler)
study.optimize(objective, n_trials=100)
best_trial = study.best_trial
print("Best value: ", best_trial.value)
print("Best value を達成したパラメータ: ", best_trial.params)
Best value: -2.1556329014602635
Best value を達成したパラメータ: {'x': 0.967862178574185, 'y': -3.092390098174631}
この最適化では、SimulatedAnnealingSampler.sample_relative
メソッドを使用して
x
と y
パラメータの値をサンプリングしています
Note
厳密には、最初のトライアルでは
SimulatedAnnealingSampler.sample_independent
メソッドがパラメータ値のサンプリングに使用されます
SimulatedAnnealingSampler.infer_relative_search_space
で使用されている
intersection_search_space()
は、
完全なトライアルがない場合、探索空間を推測できないためです
Total running time of the script: (0 minutes 0.191 seconds)