使用 Dask 进行更好更快的超参数优化
Scott Sievert 写了这篇文章。原文在 https://stsievert.com/blog/2019/09/27/dask-hyperparam-opt/,样式更好。这项工作由 Anaconda, Inc. 支持。
Dask 的机器学习包 Dask-ML 现在实现了 Hyperband,这是一种先进的“超参数优化”算法,性能相当不错。本文将
- 描述“超参数优化”,这是机器学习中的一个常见问题
- 描述 Hyperband 的优势以及它为什么有效
- 通过示例展示如何使用 Hyperband,并进行性能比较
在本文中,我将介绍一个实际示例,并重点介绍论文“使用 Dask 进行更好更快的超参数优化”的关键部分,该论文也在 一段约 25 分钟的 SciPy 2019 演讲中进行了总结。
问题
机器学习需要数据、未训练的模型以及“超参数”,超参数是在训练开始前选择的参数,有助于模型和数据之间的协调。用户需要为这些超参数指定值才能使用模型。一个很好的例子是使用正则化参数使岭回归或 LASSO 适应数据中的噪声量。1
模型性能很大程度上取决于提供的超参数。一个相当复杂的例子是使用特定的可视化工具 t-SNE。该工具需要(至少)三个超参数,并且性能完全取决于超参数。事实上,“如何有效地使用 t-SNE”的第一部分标题就是“那些超参数真的至关重要”。
找到这些超参数的良好值至关重要,并且在 Scikit-learn 的文档中有一个专门的页面:“调整估算器的超参数”。简而言之,找到合适的超参数值很困难,需要猜测或搜索。
如何使用像 Dask 这样的高级任务调度器快速高效地找到这些超参数? 并行性会带来一些挑战,但 Dask 架构支持一些高级算法。
注意:本文假定您了解 Dask 的基础知识。Dask 的文档中涵盖了这部分内容,例如 为什么使用 Dask?、一个约 15 分钟的Dask 视频介绍、一个Dask-ML 视频介绍以及我写的一篇博客文章,记录了我首次使用 Dask 的经历。
贡献
Dask-ML 可以快速找到高性能的超参数。我将用直觉和实验证据来支持这一说法。
具体来说,这是因为 Dask-ML 现在实现了 Li 等人在“Hyperband: 一种新颖的基于 bandit 的超参数优化方法”中提出的算法。将 Dask 与 Hyperband 配对,带来了令人兴奋的新性能机会,特别是因为 Hyperband 实现简单,而 Dask 是一个高级任务调度器。2
让我们了解 Hyperband 的基础知识,然后通过示例说明其用法和性能。这将突出相应论文的一些关键点。
Hyperband 基础知识
Hyperband 的动机是以最小的训练成本找到高性能的超参数。考虑到这个目标,将更多时间花在训练高性能模型上是合理的——如果一个模型过去表现不佳,为什么要浪费更多训练时间呢?
一种将更多时间花在高性能模型上的方法是初始化许多模型,开始训练所有模型,然后在训练完成前停止训练低性能的模型。这就是 Hyperband 所做的。在最基本的层面,Hyperband 是一种(有原则的)RandomizedSearchCV 的早期停止方案。
决定何时停止训练模型取决于训练数据对得分的影响有多大。有两个极端情况:
- 只有训练数据重要时
- 即,当超参数对得分完全没有影响时
- 只有超参数重要时
- 即,当训练数据对得分完全没有影响时
Hyperband 通过扫频(sweeping)模型停止的频率来平衡这两个极端。这种扫频允许数学证明 Hyperband 将以最少的 partial_fit
调用找到最佳模型3。
Hyperband 具有显著的并行性,因为它有两个“易并行”的 for 循环——Dask 可以利用这一点。Hyperband 已在 Dask 中实现,具体来说是在 Dask 的机器学习库 Dask-ML 中。
它的表现如何?让我们通过一个例子来说明。在进行性能比较之前,需要一些设置。
示例
注意:想自己尝试 HyperbandSearchCV
吗?Dask 有一个使用示例。甚至可以在浏览器中运行!
我将用一个合成示例来说明。让我们构建一个包含 4 个类别的数据集。
>>> from experiment import make_circles
>>> X, y = make_circles(n_classes=4, n_features=6, n_informative=2)
>>> scatter(X[:, :2], color=y)
注意:此内容摘自 stsievert/dask-hyperband-comparison,或进行了少量修改。
让我们构建一个包含 24 个神经元的全连接神经网络用于分类。
>>> from sklearn.neural_network import MLPClassifier
>>> model = MLPClassifier()
使用 PyTorch 构建神经网络也是可能的4(也是我在开发中使用的)。
这个神经网络的行为由 6 个超参数决定。只有一个控制最优架构的模型(hidden_layer_sizes
,即每层的神经元数量)。其余的控制如何找到该架构的最佳模型。有关超参数的详细信息在附录中。
>>> params = ... # details in appendix
>>> params.keys()
dict_keys(['hidden_layer_sizes', 'alpha', 'batch_size', 'learning_rate'
'learning_rate_init', 'power_t', 'momentum'])
>>> params["hidden_layer_sizes"] # always 24 neurons
[(24, ), (12, 12), (6, 6, 6, 6), (4, 4, 4, 4, 4, 4), (12, 6, 3, 3)]
我选择这些超参数是为了创建一个复杂的搜索空间,模仿大多数神经网络的搜索过程。这些搜索通常涉及“dropout”、“学习率”、“动量”和“权重衰减”等超参数。5 最终用户不关心这些超参数;它们不会改变模型架构,只会影响找到特定架构的最佳模型。
如何快速找到高性能的超参数值?
寻找最佳参数
首先,让我们看看 Dask-ML 实现 Hyperband 所需的参数(位于 HyperbandSearchCV
类中)。
Hyperband 参数:经验法则
HyperbandSearchCV
有两个输入:
max_iter
,决定调用partial_fit
的次数- Dask 数组的块大小,决定每个
partial_fit
调用接收多少数据。
一旦知道了训练最佳模型需要多长时间以及需要采样多少参数,这些参数就很容易确定了。
n_examples = 50 * len(X_train) # 50 passes through dataset for best model
n_params = 299 # sample about 300 parameters
# inputs to hyperband
max_iter = n_params
chunk_size = n_examples // n_params
这个经验法则的输入正是用户关心的:
- 衡量搜索空间复杂度的指标(通过
n_params
) - 训练最佳模型需要多长时间(通过
n_examples
)
值得注意的是,与 Scikit-learn 的 RandomizedSearchCV
不同,n_examples
和 n_params
之间没有权衡,因为 n_examples
仅适用于部分模型,而不是所有模型。关于这个经验法则的更多详细信息,请参见 HyperbandSearchCV
文档的“Notes”部分。
有了这些输入,就可以轻松创建一个 HyperbandSearchCV
对象。
寻找表现最佳的超参数
这个模型选择算法 Hyperband 在 HyperbandSearchCV
类中实现。让我们创建一个该类的实例。
>>> from dask_ml.model_selection import HyperbandSearchCV
>>>
>>> search = HyperbandSearchCV(
... est, params, max_iter=max_iter, aggressiveness=4
... )
aggressiveness
默认为 3。aggressiveness=4
被选择是因为这是一个初始搜索;我对这个搜索空间一无所知。因此,这个搜索应该更积极地淘汰表现不佳的模型。
Hyperband 向用户隐藏了一些细节(这使得数学保证成为可能),特别是关于训练量和创建的模型数量的细节。这些细节可在 metadata
属性中获取。
>>> search.metadata["n_models"]
378
>>> search.metadata["partial_fit_calls"]
5721
现在我们对计算需要多长时间有了一些概念,让我们要求它找到最佳的超参数集合。
>>> from dask_ml.model_selection import train_test_split
>>> X_train, y_train, X_test, y_test = train_test_split(X, y)
>>>
>>> X_train = X_train.rechunk(chunk_size)
>>> y_train = y_train.rechunk(chunk_size)
>>>
>>> search.fit(X_train, y_train)
在此期间,仪表盘将保持活跃6。
这些超参数表现如何?
>>> search.best_score_
0.9019221418447483
HyperbandSearchCV
模仿了 Scikit-learn RandomizedSearchCV 的 API,因此可以访问所有预期的属性和方法。
>>> search.best_params_
{"batch_size": 64, "hidden_layer_sizes": [6, 6, 6, 6], ...}
>>> search.score(X_test, y_test)
0.8989070100111217
>>> search.best_model_
MLPClassifier(...)
关于属性和方法的详细信息,请参见HyperbandSearchCV 文档。
性能
我在我的个人笔记本电脑上,使用 4 个核心,运行了 200 次。让我们看看最终验证得分的分布。
“passive”(被动)比较实际上是配置为与 HyperbandSearchCV
工作量相等的 RandomizedSearchCV
。让我们看看它随时间的变化。
该图显示了 200 次运行的平均得分(实线),阴影区域表示四分位距。虚线绿色线表示训练 4 个模型到完成所需的数据量。“遍历数据集次数”是衡量“解决时间”的一个很好的代理指标,因为只有 4 个 worker。
该图显示,HyperbandSearchCV
寻找参数的速度至少比 RandomizedSearchCV
快 3 倍。
Dask 的机会
将 Hyperband 和 Dask 结合会创造什么机会?HyperbandSearchCV
内部具有大量并行性,而 Dask 是一个高级任务调度器。
最明显的机会涉及任务优先级。Hyperband 并行地训练许多模型,而 Dask 可能没有足够的 worker。这意味着有些任务必须等待其他任务完成。当然,Dask 可以优先处理任务7,并选择先训练哪些模型。
让我们将训练某个模型的优先级设置为该模型最近的得分。这种优先级方案如何影响得分?让我们比较上述 200 次运行中单次运行的优先级方案。
除了优先级方案,这两条线在其他方面完全相同。该图比较了“高分”优先级方案和 Dask 的默认优先级方案(“fifo”)。
这个图当然得益于只使用 4 个 worker 运行的事实。如果每个任务都可以立即运行(没有什么需要分配优先级的),任务优先级就不重要了!
对并行性的适应性
Hyperband 随着 worker 数量的增加如何扩展?
我运行了另一个单独的实验来测量。这个实验在相应的论文中有更多描述,但相关的区别是使用通过 skorch 连接的 PyTorch 神经网络,而不是 Scikit-learn 的 MLPClassifier。
我使用不同数量的 Dask worker 运行了相同的实验。8 以下是 HyperbandSearchCV
的扩展情况。
训练一个模型到完成需要 243 秒(白线标记)。这是与 patience
的比较,patience
在模型得分增长不足时停止训练。从功能上讲,这非常有用,因为用户可能不小心将 n_examples
设置得过大。
看起来速度提升在 16 到 24 个 worker 之间开始趋于饱和,至少对于本例是这样。当然,对于大量 worker,patience
的效果不如理想。9
未来工作
当前有一些正在进行的 Pull Request 用于改进 HyperbandSearchCV
。其中最重要的是调整 Hyperband 的一些内部机制,以便 HyperbandSearchCV
在初始或非常探索性的搜索中表现更好(dask/dask-ml #532)。
我认为最大的改进是将数据集大小视为需要保留的稀缺资源,而不是训练时间。这将允许 Hyperband 与任何模型一起工作,而不仅仅是实现 partial_fit
的模型。
序列化是 HyperbandSearchCV
中分布式 Hyperband 实现的一个重要部分。Scikit-learn 和 PyTorch 可以轻松处理这个问题,因为它们支持 Pickle 协议10,但 Keras/Tensorflow/MXNet 带来了挑战。解决这个问题可以增加 HyperbandSearchCV
的使用。
附录
我选择调优 7 个超参数,它们是:
hidden_layer_sizes
,控制每个神经元使用的激活函数alpha
,控制正则化量
更多超参数控制寻找最佳神经网络:
batch_size
,控制optimizer
用于近似梯度的示例数量learning_rate
,learning_rate_init
,power_t
,控制我将使用的 SGD 优化器的一些基本超参数momentum
,一个用于带有 Nesterov 动量的 SGD 的更高级超参数。
-
据我所知,这是 Hyperband 首次使用高级任务调度器实现 ↩
-
更准确地说,Hyperband 有很高的概率找到与使用 $N$ 次
partial_fit
调用所能达到的最佳模型接近的模型,其中“接近”表示“在得分上限的对数项范围内”。详情请参见相应论文的推论 1 或Hyperband 论文的定理 5。 ↩ -
对于像 Adam 或 Adagrad 这样的自适应步长方法,需要调整的参数较少,但它们在测试数据上可能表现不佳(参见“自适应梯度方法在机器学习中的边际价值”) ↩
-
但它可能不会这么快:视频是加速了 3 倍的。 ↩
-
不同运行之间的一切都是相同的:采样的超参数、模型的内部随机状态、用于拟合的数据。只有 worker 的数量不同。 ↩
-
如果有无限多的 worker,提前停止任务就没有时间上的好处;永远不会有排队等待运行的任务。 ↩
-
Matthew Rocklin 的文章:“Pickle 不慢,它是一个协议” ↩
博客评论由 Disqus 提供支持