用户自定义采样器

借助用户自定义采样器,您可以

  • 试验您自己的采样算法,

  • 实现特定于任务的算法以提升优化性能,或

  • 包装其他优化库以将其集成到 Optuna 管道中(例如,BoTorchSampler)。

本节介绍采样器类的内部行为,并展示了一个实现用户自定义采样器的示例。

采样器概述

采样器负责确定在一次 trial 中要评估的参数值。当在目标函数内部调用 suggest API(例如,suggest_float())时,内部会创建相应的分布对象(例如,FloatDistribution)。采样器从该分布中采样一个参数值。采样到的值会返回给 suggest API 的调用者,并在目标函数中进行评估。

要创建一个新的采样器,您需要定义一个继承自 BaseSampler 的类。该基类有三个抽象方法:infer_relative_search_space()sample_relative()sample_independent()

顾名思义,Optuna 支持两种类型的采样:一种是相对采样,它可以考虑 trial 中参数的相关性;另一种是独立采样,它独立采样每个参数。

在 trial 开始时,会调用 infer_relative_search_space() 来提供 trial 的相对搜索空间。然后调用 sample_relative() 从搜索空间中采样相对参数。在目标函数执行期间,使用 sample_independent() 采样不属于相对搜索空间的参数。

注意

有关更多详细信息,请参阅 BaseSampler 的文档。

示例:实现 SimulatedAnnealingSampler

例如,以下代码定义了一个基于模拟退火 (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  # Current temperature.
        self._current_trial = None  # Current state.

    def sample_relative(self, study, trial, search_space):
        if search_space == {}:
            return {}

        # Simulated Annealing algorithm.
        # 1. Calculate transition probability.
        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  # Decrease temperature.

        # 2. Transit the current state if the previous result is accepted.
        if self._rng.uniform(0, 1) < probability:
            self._current_trial = prev_trial

        # 3. Sample parameters from the neighborhood of the current point.
        # The sampled parameters will be used during the next execution of
        # the objective function passed to the 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 = (
                    "Only suggest_float() with `step` `None` or 1.0 and"
                    " `log` `False` is supported"
                )
                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

    # The rest are unrelated to SA algorithm: boilerplate
    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)

注意

为了代码简洁性,上述实现不支持某些特性(例如,最大化)。如果您对如何支持这些特性感兴趣,请参阅 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("Parameters that achieve the best value: ", best_trial.params)
Best value:  -4.90725931140956
Parameters that achieve the best value:  {'x': -0.013583803468734335, 'y': -4.907443831126237}

在此优化中,参数 xy 的值是通过使用 SimulatedAnnealingSampler.sample_relative 方法采样的。

注意

严格来说,在第一次 trial 中,使用 SimulatedAnnealingSampler.sample_independent 方法来采样参数值。因为 SimulatedAnnealingSampler.infer_relative_search_space 中使用的 intersection_search_space() 在没有完整 trial 的情况下无法推断搜索空间。

脚本总运行时间: (0 分钟 0.222 秒)

由 Sphinx-Gallery 生成的画廊