今年夏天参加了一个我很喜爱的超参数框架 Optuna(Optuna – A hyperparameter optimization framework)的文档翻译工作。当初翻译曾经根本实现(https://zh-cn.optuna.org),而 Optuna 更成熟的 2.0 版本最近也要公布了。于是咱们决定写一个介绍,心愿让更多的中文用户理解和应用这个框架,并且能参加到社区两头来。
Tensorflow 和 Pytorch 曾经将实现深度学习模型变成一个 10 分钟不到的过程:申明模型,定义参数、优化器,载入训练数据,启动梯度降落。而后一个接着一个 epoch, 模型在测试集上的精度稳步晋升 … 然而等等,在事实中,训练一个模型从来不会这么顺利,你总会碰到各种各样的问题,比方:
- 后果和 paper 上写的不一样(优化)
- 有些参数组合基本训练不进去,还把训练脚本搞解体了,你却不晓得起因所在
- 训进去也不晓得这个参数范畴是否稳固(参数关系)
下面这些状况阐明了超参数抉择对于模型性能的重要性。于是为了调参,你开始手动往脚本里加内容:超参数优化不就是 for 循环里套 for 循环嘛,很简略。可很快,你又会其余碰到问题:
- 有一台好机器,for 循环一次却只能执行一个模型,节约性能
- 优化完的参数输入到了一个 txt,还得本人写解析来剖析
- for 循环里有些参数只是运气好,理论部署下来并不一样
总之你会碰到十分多问题,它们会节约你的工夫。而 Optuna 则是帮忙你解决下面所有这些问题的一个工具,解放你的双手和工夫,让你能更加专一于模型实现。它将定义一个超参数优化过程变得 非常简单 ,而且 易于保留 , 不便剖析 ,还反对 无缝扩大。
Talk is cheap, show me the code. 咱们将通过一个例子来展现 Optuna 的上述长处。
定义简略
一个极简的 optuna 的优化程序中只有三个最外围的概念,指标函数(objective),单次试验(trial),和钻研(study). 其中 objective 负责定义待优化函数并指定参 / 超参数数范畴,trial 对应着 objective 的单次执行,而 study 则负责管理优化,决定优化的形式,总试验的次数、试验后果的记录等性能。
如果要在 $x,y \in(-10,10)$ 的范畴内找到 $f(x,y)=(x+y)^2$ 这个函数的最大值对应的 $x,y$,那咱们只须要上面的代码:
import optuna
def objective(trial):
x = trial.suggest_uniform('x', -10, 10)
y = trial.suggest_uniform('y', -10, 10)
return (x + y) ** 2
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100)
print(study.best_params)
print(study.best_value)
在上例中,咱们首先定义了一个 objective. 在它外部,$f(x,y)=(x+y)^2$ 的后果被作为返回值,其参数空间的 x 和 y 从两个均匀分布中采样。
而后,Optuna 创立了一个“钻研”(study), 指定了优化的方向为最大化并且最大试验次数为 100,而后将指标函数传入其中,开始优化 (optimize) 过程。最初脚本输入在 100 次试验中找到的最佳参数组合。
命令行工具
因为 Optuna 还提供了命令行工具,下面的优化过程也能够通过在 shell 中间接执行语句来进行。应用命令行工具的话,咱们能够省略掉全副其余代码,只需在脚本中申明指标函数的定义即可。
而后,在终端中执行上面的 `optuna` 命令即可开始优化过程:
$ STUDY_NAME=`optuna create-study --storage sqlite:///example.db`
$ optuna study optimize foo.py objective --n-trials=100 --storage sqlite:///example.db --study-name $STUDY_NAME
这一过程和下面的脚本是齐全等价的(其中 storage 的局部下文会介绍)。该命令的输入如下:
...
[I 2020-07-01 02:41:34,311] Trial 96 finished with value: 3.1406709262042694 and parameters: {'x': -7.882810516401339, 'y': 9.65500433373743}. Best is trial 92 with value: 383.4423553199605.
[I 2020-07-01 02:41:34,314] Trial 97 finished with value: 353.54920261705433 and parameters: {'x': -8.876138448320777, 'y': -9.926765652297679}. Best is trial 92 with value: 383.4423553199605.
[I 2020-07-01 02:41:34,316] Trial 98 finished with value: 319.81596197762224 and parameters: {'x': -9.502045119809319, 'y': -8.381353941264676}. Best is trial 92 with value: 383.4423553199605.
[I 2020-07-01 02:41:34,319] Trial 99 finished with value: 295.705727918292 and parameters: {'x': -8.387075346077019, 'y': -8.809020952743014}. Best is trial 92 with value: 383.4423553199605.
{'x': -9.984261379947354, 'y': -9.59742279991727}
383.4423553199605
你可能感觉下面的代码和命令,尤其是指标函数的定义平平无奇,因为它结构优化过程的形式十分天然,十分 pythonic. 作为比照,让咱们看看另一个超参数优化框架 Hyperopt 实现同样性能须要怎么写:
from hyperopt import hp
from hyperopt import fmin, tpe, space_eval, Trials
# define an objective function
def objective(args):
return -(args['x'] + args['y'])**2
space = {"x": hp.uniform('x', -10, 10),
"y": hp.uniform('y', -10, 10),
}
trials = Trials()
best = fmin(objective, space, algo=tpe.suggest, max_evals=100, trials=trials)
print(best)
print(space_eval(space, best))
首先,参数空间必须在指标函数 内部 定义。其次,要存储试验记录的话,须要在内部独自实例化一个 `Trials` 对象。最初,如果须要最大化指标函数的话,你只能在返回值上增加一个负号,因为
>>> from hyperopt import fmax
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: cannot import name 'fmax' from 'hyperopt'
总的来说,Hyperopt 还算不错,然而从易用性上来说,显然 Optuna 还是更胜一筹。
但你可能问,就这?不就是多写两行代码的事件吗?当然不是了,下面只是一个 toy model, 实际上 Optuna 有更多的个性让它在实在的超参数优化环境中十分好用。
易于保留
出于各种目标,咱们常常有保留优化过程的需要。比方你可能须要追踪或者 debug 一个指标函数的优化过程,比方指标函数的参数空间太大,而一旦机器解体,你的优化过程必须从头再来。又或者,你想实现多台机器并行优化一个指标函数,这时候一个能保留优化试验历史并且能从中复原 / 持续优化的个性就显得尤其重要。而 Optuna 反对这种个性。
初始化记录
默认状况下,Optuna 应用内存存储来记录试验过程。然而如果在创立 study 时增加一个 storage 参数,Optuna 能够依据你的参数类型应用 SQLite, MySQL 或者 Redis 等风行的数据库来记录你的试验历史。上面是一个应用 SQLite 的例子
study_name = 'example-study' # 不同的 study 不能应用雷同的名字。因为当存储在同一个数据库中时,这是辨别不同 study 的标识符.
study = optuna.create_study(study_name=study_name, storage='sqlite:///example.db')
study.optimize(objective, n_trials=300)
如果 ‘sqlite:///example.db’ 这一 URL 对应的数据库文件不存在,Optuna 将创立一个对应的数据库文件并开始新的优化过程。
当初,假如优化过程被打断了,你齐全能够从新运行下面的脚本来从上次中断的地位持续优化。只有 optuna 监测到 `’sqlite:///example.db’` 在门路上存在且该数据库中有 study_name 为 ‘example-study’ 的记录,它就会持续未实现的优化过程。
存储有了,如何提取呢?无需手动进数据库写 query 语句,你只需用一行命令即可提取所有的试验记录
df = study.trials_dataframe(attrs=('number', 'value', 'params', 'state'))
这个语句会返回一个 pandas dataframe
# 来自第一个例子
number value params_x params_y state
0 0 97.081081 -4.012360 -5.840613 COMPLETE
1 1 110.359399 8.495576 2.009632 COMPLETE
2 2 270.740964 -9.909762 -6.544446 COMPLETE
3 3 41.726647 -6.698396 0.238780 COMPLETE
4 4 12.735788 4.018620 -7.587344 COMPLETE
.. ... ... ... ... ...
在下面介绍的根底上,Optuna 还提供了一系列可视化接口,让查看不同参数之间的关系变得非常容易。因为集成到了库办法中,过来须要专门写一个脚本来做的事件当初只须要一两行命令即可。
等高线图
在 study.optimize 执行完结当前,通过调用 optuna.visualization.plot_contour,并将 study 和须要可视化的参数传入该办法,Optuna 将返回一张等高线图。例如,当在下面的例子中,咱们想要查看参数 x 和 y 的关系以及它们对于函数值奉献的话,只须要执行上面的语句即可:
optuna.visualization.plot_contour(study, params=['x', 'y'])
# 如果不指定 params 也是能够的,optuna 将画出所有的参数之间的关系,在这里两者等价。optuna.visualization.plot_contour(study)
它输入:
除了等⾼线图之外,Optuna 还提供了⼀系列其余⾮常实⽤的可视化选项,⽐如每个 trial 训练的两头值折线图。这对了解优化过程细节⾮常有用。上面的折线图来自于 Optuna 的官网文档。
相似地,Optuna 还提供了诸如 优化过程历史记录 和多维度参数 - 指标函数关系 等绘图接口。
优化过程历史记录
多维度参数 - 指标函数关系
多维度参数 - 指标函数关系图十分有用,和后面的等高线图不同,多维度参数 - 指标函数关系图一次性能够展现任意多个参数和指标函数值之间的关系。
其实在上述接口之外,Optuna 还提供了一系列其余绘图接口,比方超参数重要性评估图,参数关系切片图等。具体细节参见其中文文档的 API reference-Visualization 局部。
不难看出,作为一个超参数优化框架,Optuna 应用起来很简略,其提供的存储接口、可视化套件也让用户对优化过程进行记录和剖析变得非常容易。
其实,这些特色中的每一个都只是解决了超参数优化过程中一个或者数个很小的“痒点”,然而至多在我看来,它们的组合却让 Optuna 变成一个十分适于疾速上手的全功能框架。
而且,本文中展现的个性仅仅是 Optuna 全副个性的一小部分。介绍它们只是为了不便你疾速上手 Optuna。因为在这些根底个性之外,它还提供了以下性能:
- 极其不便的分布式优化(同一机器上不同线程或者不同机器节点通过关系型数据库共享优化过程)
- 粗疏的剪枝过程(Optuna 提供了一系列的各种剪枝算法,能提前革除 trial 中有望的局部,节俭计算资源)
- 插件机制(Optuna 提供了一系列插件,能和目前市面上风行的很多机器学习框架,比方 Tensorflow 和 Pytorch 等深度整合,让用户无需扭转太多现存代码,便可将 Optuna 整合进老模型的优化中)
这些性能咱们将在下一篇文章中一一介绍。如果你曾经急不可待了,也能够间接参考 Optuna 的官网文档和对应的中文翻译。
哪里能够找到 OPtuna?
Optuna 官网:https://optuna.org
Optuna 中文文档:https://zh-cn.optuna.org