注意
转到末尾 下载完整的示例代码。
Ask-and-Tell 界面
Optuna 提供了一个 Ask-and-Tell 界面,该界面为超参数优化提供了更灵活的接口。本教程将介绍 Ask-and-Tell 界面有益的三种用例。
以最小的修改将 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) # the objective
然后,您尝试使用 Optuna 来优化分类器的超参数 C 和 solver。当您天真地引入 Optuna 时,您会定义一个 objective 函数,该函数接受 trial 并调用 trial 的 suggest_* 方法来采样超参数。
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` is a `Trial` and not a `FrozenTrial`.
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) # tell the pair of trial and objective value
主要区别在于使用两个方法:optuna.study.Study.ask() 和 optuna.study.Study.tell()。 optuna.study.Study.ask() 创建一个可以采样超参数的 trial,而 optuna.study.Study.tell() 通过传递 trial 和一个目标值来完成 trial。您可以在不使用 objective 函数的情况下,将 Optuna 的超参数优化应用于您的原始代码。
如果您想通过 pruner 使优化更快,您需要显式地将 trial 的状态传递给 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
# define study with hyperband pruner.
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) # tell the pruned state
else:
score = clf.score(X_valid, y_valid)
study.tell(trial, score) # tell objective value
注意
optuna.study.Study.tell() 方法可以接受 trial 编号而不是 trial 对象。 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 的示例。
在调用 optuna.study.Study.ask() 方法进行 define-and-run API 之前,为超参数定义分布。例如:
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) # pass the pre-defined distributions.
# two hyperparameters are already sampled from the pre-defined distributions
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)
批量优化 (Batch Optimization)
Ask-and-Tell 界面使我们能够优化批量目标以实现更快的优化。例如,并行评估、向量运算等。
下面的目标函数接收批量超参数 xs 和 ys,而不是单个超参数对 x 和 y,并在整个向量上计算目标。
def batched_objective(xs: np.ndarray, ys: np.ndarray):
return xs**2 + ys
在下面的示例中,批量超参数对的数量为 \(10\),并且 batched_objective 被评估了三次。因此,trial 的数量为 \(30\)。请注意,您需要存储 trial_numbers 或 trial 才能在批量评估后调用 optuna.study.Study.tell() 方法。
batch_size = 10
study = optuna.create_study(sampler=optuna.samplers.CmaEsSampler())
for _ in range(3):
# create batch
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))
# evaluate batched objective
x_batch = np.array(x_batch)
y_batch = np.array(y_batch)
objectives = batched_objective(x_batch, y_batch)
# finish all trials in the batch
for trial_number, objective in zip(trial_numbers, objectives):
study.tell(trial_number, objective)
提示
optuna.samplers.TPESampler 类可以接受一个布尔参数 constant_liar。建议在批量优化期间将此值设置为 True,以避免多个工作节点评估相似的参数配置。特别是在每个目标函数评估成本很高且运行状态持续时间较长,以及/或工作节点数量较多的情况下。
提示
optuna.samplers.CmaEsSampler 类可以接受一个 popsize 属性参数,该参数用作 CMA-ES 算法的初始种群大小。在批量优化的上下文中,它可以设置为批量大小的倍数,以便从并行操作中获益。
脚本总运行时间: (0 分钟 0.102 秒)