Optuna作为主要面向深度学习超参数调优开发的框架,在实现之初就考虑到了大型模型参数调优的各种实际情况,并逐一针对它们设计了解决方案。
分布式优化
一提到分布式算法,我们想到的可能是麻烦的 debug 过程和分布式实现过程中各种线程锁之类的问题。你可能会好奇,带有 GIL的 python 超参数优化库是怎么实现分布式优化的?实际上,通过选择不同的共享参数的方式,Optuna 规避了这一问题(optuna 在内部使用了 joblib, 然而仍然受到 GIL 的限制)。在 Optuna 中,用户不是在单个脚本内派生出不同的线程,而是通过针对同一个 study 启动不同的优化过程来实现分布式优化的。
在一个分布式的 study 优化过程中,用户首先创建一个空 study:
$ optuna create-study --study-name "distributed-example" --storage "sqlite:///example.db" \[I 2018\-10-31 18:21:57,885\] A new study created with name: distributed-example
这个命令会在当前目录下生成一个数据库文件
$ ls example.db foo.py (doc-optuna)
该 study 将会用于后期的参数和历史记录存储。一个中心化的参数存储是有必要的,因为在超参数优化过程中,一个后续的试验(trial)的参数采样范围会受到前面的参数采样历史记录影响。而由于Optuna 透过数据库接口来读写这些数据,因此多个 client 之间不会造成干扰,用户只需在不同的进程中运行这个 study 即可:比如在不同的终端窗口中运行优化脚本。
假如优化脚本如下:
import optuna def objective(trial): x = trial.suggest_uniform('x', -10, 10) return (x - 2) ** 2 if __name__ == '__main__': study = optuna.load_study(study_name='distributed-example', storage='sqlite:///example.db') study.optimize(objective, n_trials=100)
(注意,这里的 n_trials 不是指 对于该 study 总共会运行100次试验,而是每一个单独的优化进程都会跑100次试验,n个进程下的总试验次数是 n x 100)
考虑两个进程的优化,我们可以在同一个目录下的两个终端窗口中运行:
$ python foo.py [I 2018-10-31 18:46:44,308] Finished a trial resulted in value: 1.1097007755908204. Current best value is 0.00020881104123229936 with parameters: {'x': 2.014450295541348}. [I 2018-10-31 18:46:44,361] Finished a trial resulted in value: 0.5186699439824186. Current best value is 0.00020881104123229936 with parameters: {'x': 2.014450295541348}. ...
等优化完成以后开启第三个脚本检查总的trial数,可以看到总试验次数是200:
>>> len(study.trials) 200
此时,如果打开数据库,我们会发现,试验和试验所用到的参数是分开存储的:红色是试验记录,