常见问题¶
某某库可以和 Optuna 配合使用吗?(某某是你常用的机器学习库)¶
Optuna 和绝大多数机器学习库兼容,并且很容易同他们配合使用。参见 examples.
如何定义带有额外参数的目标函数?¶
有两种方法可以实现这类函数。
首先,如下例所示,可调用的 objective 类具有这个功能:
import optuna
class Objective(object):
def __init__(self, min_x, max_x):
# Hold this implementation specific arguments as the fields of the class.
self.min_x = min_x
self.max_x = max_x
def __call__(self, trial):
# Calculate an objective value by using the extra arguments.
x = trial.suggest_uniform('x', self.min_x, self.max_x)
return (x - 2) ** 2
# Execute an optimization by using an `Objective` instance.
study = optuna.create_study()
study.optimize(Objective(-100, 100), n_trials=100)
其次,你可以用 lambda
或者 functools.partial
来创建带有额外参数的函数(闭包)。 下面是一个使用了 lambda
的例子:
import optuna
# Objective function that takes three arguments.
def objective(trial, min_x, max_x):
x = trial.suggest_uniform('x', min_x, max_x)
return (x - 2) ** 2
# Extra arguments.
min_x = -100
max_x = 100
# Execute an optimization by using the above objective function wrapped by `lambda`.
study = optuna.create_study()
study.optimize(lambda trial: objective(trial, min_x, max_x), n_trials=100)
其他例子参见 sklearn_addtitional_args.py .
没有远程 RDB 的情况下可以使用 Optuna 吗?¶
可以。
在最简单的情况下,Optuna 使用内存 (in-memory) 存储:
study = optuna.create_study()
study.optimize(objective)
如果想保存和恢复 study 的话,你可以轻松地将 SQLite 用作本地存储。
study = optuna.create_study(study_name='foo_study', storage='sqlite:///example.db')
study.optimize(objective) # The state of `study` will be persisted to the local SQLite file.
更多细节请参考 用 RDB 后端保存/恢复 Study.
如何保存和恢复 study?¶
有两种方法可以将 study 持久化。具体采用哪种取决于你是使用内存存储 (in-memory) 还是远程数据库存储 (RDB).通过 pickle
或者 joblib
, 采用了内存存储的 study 可以和普通的 Python 对象一样被存储和加载。比如用 joblib
的话:
study = optuna.create_study()
joblib.dump(study, 'study.pkl')
恢复 study:
study = joblib.load('study.pkl')
print('Best trial until now:')
print(' Value: ', study.best_trial.value)
print(' Params: ')
for key, value in study.best_trial.params.items():
print(f' {key}: {value}')
如果你用的是 RDB, 具体细节请参考 用 RDB 后端保存/恢复 Study.
如何禁用 Optuna 的日志信息?¶
默认情况下,Optuna 打印处于 optuna.logging.INFO
层级的日志信息。通过设置 optuna.logging.set_verbosity()
, 你可以改变这个层级。
比如,下面的代码可以终止打印每一个trial的结果:
optuna.logging.set_verbosity(optuna.logging.WARNING)
study = optuna.create_study()
study.optimize(objective)
# Logs like '[I 2018-12-05 11:41:42,324] Finished a trial resulted in value:...' are disabled.
更多的细节请参考 optuna.logging
.
如何在目标函数中保存训练好的机器学习模型?¶
Optuna 会保存超参数和对应的目标函数值,但是它不会存储诸如机器学习模型或者网络权重这样的中间数据。要保存模型或者权重的话,请利用你正在使用的机器学习库提供的对应功能。
在保存模型的时候,我们推荐将 optuna.trial.Trial.number
一同存储。这样易于之后确认对应的 trial.比如,你可以用以下方式在目标函数中保存训练好的 SVM 模型:
def objective(trial):
svc_c = trial.suggest_loguniform('svc_c', 1e-10, 1e10)
clf = sklearn.svm.SVC(C=svc_c)
clf.fit(X_train, y_train)
# Save a trained model to a file.
with open('{}.pickle'.format(trial.number), 'wb') as fout:
pickle.dump(clf, fout)
return 1.0 - accuracy_score(y_valid, clf.predict(X_valid))
study = optuna.create_study()
study.optimize(objective, n_trials=100)
# Load the best model.
with open('{}.pickle'.format(study.best_trial.number), 'rb') as fin:
best_clf = pickle.load(fin)
print(accuracy_score(y_valid, best_clf.predict(X_valid)))
如何获得可复现的优化结果?¶
要让 Optuna 生成的参数可复现的话,你可以通过设置 RandomSampler
或者 TPESampler
中的参数 seed
来指定一个固定的随机数种子:
sampler = TPESampler(seed=10) # Make the sampler behave in a deterministic way.
study = optuna.create_study(sampler=sampler)
study.optimize(objective)
但是这么做的需要注意以下两点。
首先,如果一个 study 的优化过程本身是分布式的或者并行的,那么这个过程中存在着固有的不确定性。因此,在这种情况下我们很难复现出同样的结果。如果你想复现结果的话,我们建议用顺序执行的方式来优化你的 study.
其次,如果你的目标函数的行为本身就是不确定的(也就是说,即使送入同样的参数,其返回值也不是唯一的),那么你就无法复现这个优化过程。要解决这个问题的话,请设置一个选项(比如随机数种子)来让你的优化目标的行为变成确定性的,前提是你用的机器学习库支持这一功能。
Trial 是如何处理抛出异常的?¶
那些抛出异常却没有对应的捕获机制的 trial 会被视作失败的 trial, 也就是处于 FAIL
状态的 trial.
在默认情况下,除了目标函数中抛出的 TrialPruned
, 其他所有异常都会被传回给调用函数 optimize()
.换句话说,当此类异常被抛出时,对应的 study 就会被终止。但有时候我们希望能用剩余的 trial 将该 study 继续下去。要这么做的话,你得通过 optimize()
函数中的 catch
参数来指定要捕获的异常类型。这样,此类异常就会在 study 内部被捕获,而不会继续向外层传递。
你可以在日志信息里找到失败的 trial.
[W 2018-12-07 16:38:36,889] Setting status of trial#0 as TrialState.FAIL because of \
the following error: ValueError('A sample error in objective.')
你也可以通过查看 trial 的状态来找到它们:
study.trials_dataframe()
number |
state |
value |
... |
params |
system_attrs |
0 |
TrialState.FAIL |
... |
0 |
Setting status of trial#0 as TrialState.FAIL because of the following error: ValueError('A test error in objective.') |
|
1 |
TrialState.COMPLETE |
1269 |
... |
1 |
参见
The catch
argument in optimize()
.
Trial 返回的 NaN 是如何处理的?¶
返回 NaN
的 trial 被视为失败的 trial, 但是它们并不会导致 study 被终止。
这些返回 NaN
的 trial 在日志里长这样:
[W 2018-12-07 16:41:59,000] Setting status of trial#2 as TrialState.FAIL because the \
objective function returned nan.
动态地改变搜索空间会导致怎样的结果?¶
由于参数空间只在调用 suggestion API (比如 suggest_uniform()
和 suggest_int()
) 的时候才会被确定,因此,即使在同一个 study 中,我们也可以通过在不同 trial 里对不同的参数空间进行采样来实现对搜索空间的改变。参数空间改变之后的行为是由 sampler 来决定的。
注解
关于 TPE sampler 的 讨论:https://github.com/optuna/optuna/issues/822
如何在两块 GPU 上同时跑两个 trial?¶
如果你的优化目标支持 GPU (CUDA) 加速,你又想指定优化所用的 GPU 的话,设置 CUDA_VISIBLE_DEVICES
环境变量可能是实现这一目标最轻松的方式了:
# On a terminal.
#
# Specify to use the first GPU, and run an optimization.
$ export CUDA_VISIBLE_DEVICES=0
$ optuna study optimize foo.py objective --study-name foo --storage sqlite:///example.db
# On another terminal.
#
# Specify to use the second GPU, and run another optimization.
$ export CUDA_VISIBLE_DEVICES=1
$ optuna study optimize bar.py objective --study-name bar --storage sqlite:///example.db
更多细节见 CUDA C Programming Guide.
如何对目标函数进行测试?¶
在对目标函数的测试中,我们总倾向于使用固定的,而不是随机采样的参数。这时,你可以选择用 FixedTrial
作为目标函数的输入参数。它会从一个给定的参数字典中送入固定的参数值。比如,针对函数 \(x + y\), 你可以用如下方式送入两个任意的 \(x\) 和 \(y\):
def objective(trial):
x = trial.suggest_uniform('x', -1.0, 1.0)
y = trial.suggest_int('y', -5, 5)
return x + y
objective(FixedTrial({'x': 1.0, 'y': -1})) # 0.0
objective(FixedTrial({'x': -1.0, 'y': -4})) # -5.0
如果使用 FixedTrial
的话,你也可以用如下方式写单元测试:
# A test function of pytest
def test_objective():
assert 1.0 == objective(FixedTrial({'x': 1.0, 'y': 0}))
assert -1.0 == objective(FixedTrial({'x': 0.0, 'y': -1}))
assert 0.0 == objective(FixedTrial({'x': -1.0, 'y': 1}))