提交新活动

谢谢!您的提交已收到!
糟糕!提交表单时出现问题。

提交新闻报道

谢谢!您的提交已收到!
糟糕!提交表单时出现问题。

订阅新闻通讯

谢谢!您的提交已收到!
糟糕!提交表单时出现问题。
2019年9月30日

使用 Dask 更优化、更快速地进行超参数优化

作者

Scott Sievert 写了这篇博文。原文发布于https://stsievert.com/blog/2019/09/27/dask-hyperparam-opt/,样式更佳。这项工作由 Anaconda, Inc. 提供支持。

Dask 的机器学习包 Dask-ML 现在实现了 Hyperband,这是一种先进的“超参数优化”算法,表现相当出色。本文将:

  • 描述“超参数优化”,这是机器学习中的一个常见问题
  • 描述 Hyperband 的优点及其工作原理
  • 通过示例展示如何使用 Hyperband,并进行性能比较

在这篇文章中,我将通过一个实际示例,并重点介绍论文“使用 Dask 更优化、更快速地进行超参数优化”中的关键部分,该论文也在 SciPy 2019 的 ~25 分钟演讲中进行了总结。

问题

机器学习需要数据、未训练的模型和“超参数”,这些参数是在训练开始前选择的,有助于模型和数据之间的协调。用户需要为这些超参数指定值才能使用模型。一个很好的例子是使用正则化参数调整 Ridge 回归或 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 通过遍历模型停止的频率来平衡这两个极端情况。这种遍历使得 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 = ... # 详细信息在附录中
>>> params.keys()
dict_keys(['hidden_layer_sizes', 'alpha', 'batch_size', 'learning_rate'
'learning_rate_init', 'power_t', 'momentum'])
>>> params["hidden_layer_sizes"] # 总是 24 个神经元
[(24, ), (12, 12), (6, 6, 6, 6), (4, 4, 4, 4, 4, 4), (12, 6, 3, 3)]

我选择这些超参数是为了创建一个复杂的搜索空间,模仿大多数神经网络进行的搜索。这些搜索通常涉及“dropout”、“learning rate”、“momentum”和“weight decay”等超参数。5最终用户并不关心这些超参数;它们不改变模型架构,只改变寻找特定架构的最佳模型的过程。

如何快速找到高性能的超参数值?

寻找最佳参数

首先,让我们看看 Dask-ML 实现的 Hyperband 所需的参数(在 HyperbandSearchCV 类中)。

Hyperband 参数:经验法则

HyperbandSearchCV 有两个输入:

  1. max_iter,决定调用 partial_fit 的次数
  2. Dask 数组的块大小,决定每次 partial_fit 调用接收多少数据。

一旦知道训练最佳模型需要多长时间以及需要采样多少参数(大致数量),这些就会自然而然地得出:

n_examples = 50 * len(X_train) # 训练最佳模型需遍历数据集 50 次
n_params = 299 # 采样约 300 个参数

# 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 向用户隐藏了一些细节(这使得数学保证成为可能),特别是关于训练量和创建模型数量的细节。这些细节在元数据属性中可用:

>>> 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 次。让我们看看最终验证分数的分布:

“被动”比较实际上是将 RandomizedSearchCV 配置为与 HyperbandSearchCV 执行相同数量的工作。让我们看看这随时间表现如何:

此图显示了 200 次运行中的平均分数,用实线表示,阴影区域表示四分位距。虚线绿色线表示将 4 个模型训练到完成所需的数据量。“遍历数据集”是衡量“解决方案时间”的一个很好的指标,因为只有 4 个工作进程。

此图表明 HyperbandSearchCV 找到参数的速度至少比 RandomizedSearchCV 快 3 倍。

Dask 机会

Hyperband 和 Dask 的结合带来了哪些机会?HyperbandSearchCV 具有很大的内部并行性,而 Dask 是一个高级任务调度程序。

最明显的机会涉及作业优先级。Hyperband 并行地拟合许多模型,而 Dask 可能没有那么多可用的工作进程。这意味着一些作业必须等待其他作业完成。当然,Dask 可以对作业进行优先级排序7,并选择先拟合哪些模型。

让我们将拟合某个模型的优先级指定为该模型最近的得分。这种优先级方案如何影响得分?让我们比较上述 200 次运行中的一次运行中的优先级方案:

这两条线在所有方面都相同,除了优先级方案不同。此图比较了“高分”优先级方案和 Dask 的默认优先级方案(“fifo”)。

这张图确实得益于它只用 4 个工作进程运行。如果有无限个工作进程,作业优先级就不重要了;永远不会有等待运行的作业队列。

并行性适宜性

Hyperband 如何随工作进程数量扩展?

我进行了另一个单独的实验来衡量。这个实验在相关论文中有更多描述,但相关的区别在于使用了 PyTorch 神经网络,并通过 skorch 而非 Scikit-learn 的 MLPClassifier。

我用不同数量的 Dask 工作进程运行了相同的实验。8 以下是 HyperbandSearchCV 的扩展情况:

将一个模型训练到完成需要 243 秒(用白线标记)。这是与 patience 的比较,patience 会在模型得分没有足够增加时停止训练。实际上,这非常有用,因为用户可能会不小心将 n_examples 设置得太大。

看来对于这个例子,速度提升在 16 到 24 个工作进程之间开始饱和。当然,对于大量工作进程来说,patience 的效果不太好。9

未来工作

目前有一些正在进行的拉取请求来改进 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,控制优化器用于近似梯度的样本数量
  • learning_rate, learning_rate_init, power_t,控制我将使用的 SGD 优化器的一些基本超参数
  • momentum,一个用于带有 Nesterov 动量的 SGD 的更高级超参数。

  1. 相当于在 Scikit-learn 的 RidgeLASSO 中选择 alpha
  2. 据我所知,这是第一个使用高级任务调度器实现的 Hyperband
  3. 更准确地说,Hyperband 有很高的概率在预期的得分中找到接近最佳的模型,使用 $N$ 次 partial_fit 调用,其中“接近”意味着“在得分上限的对数项范围内”。详细信息请参阅相关论文的 Corollary 1 或Hyperband 论文的 Theorem 5。
  4. 通过 Scikit-learn API 包装器 skorch
  5. AdamAdagrad 这样的自适应步长方法调优较少,但它们在测试数据上的表现可能较差(参见“自适应梯度方法对机器学习的边际价值”)
  6. 但它可能不会这么快:视频速度加快了 3 倍。
  7. 请参阅 Dask 文档中关于作业优先级的内容
  8. 每次运行都相同:采样的超参数、模型的内部随机状态、用于拟合的数据。只有工作进程数量不同。
  9. 如果工作进程无限多,则提前停止作业没有任何时间上的好处;永远没有等待运行的作业队列。
  10. Matthew Rocklin 的“Pickle 不慢,它是一个协议