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 的早期停止方案。

决定何时停止训练模型取决于训练数据对得分的影响有多大。有两个极端情况:

  1. 只有训练数据重要时
    • 即,当超参数对得分完全没有影响时
  2. 只有超参数重要时
    • 即,当训练数据对得分完全没有影响时

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 有两个输入:

  1. max_iter,决定调用 partial_fit 的次数
  2. 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_examplesn_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 的更高级超参数。
  1. 这相当于在 Scikit-learn 的 RidgeLASSO 中选择 alpha 

  2. 据我所知,这是 Hyperband 首次使用高级任务调度器实现 

  3. 更准确地说,Hyperband 有很高的概率找到与使用 $N$ 次 partial_fit 调用所能达到的最佳模型接近的模型,其中“接近”表示“在得分上限的对数项范围内”。详情请参见相应论文的推论 1 或Hyperband 论文的定理 5。 

  4. 通过 Scikit-learn API 包装器 skorch 

  5. 对于像 AdamAdagrad 这样的自适应步长方法,需要调整的参数较少,但它们在测试数据上可能表现不佳(参见“自适应梯度方法在机器学习中的边际价值”) 

  6. 但它可能不会这么快:视频是加速了 3 倍的。 

  7. 请参阅 Dask 关于优先级工作的文档。 

  8. 不同运行之间的一切都是相同的:采样的超参数、模型的内部随机状态、用于拟合的数据。只有 worker 的数量不同。 

  9. 如果有无限多的 worker,提前停止任务就没有时间上的好处;永远不会有排队等待运行的任务。 

  10. Matthew Rocklin 的文章:“Pickle 不慢,它是一个协议” 


博客评论由 Disqus 提供支持