如何选择神经网络的学习率调度器
- 分钟 阅读
- 作者:Katherine (Yi) Li
- 2021年10月13日更新
研究人员普遍认为,神经网络模型很难训练。最大的问题之一是有大量的超参数需要指定和优化。隐蔽层的数量、激活函数、优化器、学习率、正则化--这个清单不胜枚举。
调整这些超参数可以大大改善神经网络模型。对我们来说,作为数据科学家,建立神经网络模型是为了解决一个优化问题。我们希望通过基于梯度的方法,如梯度下降法,找到目标函数的最小值(全局,有时也是局部)。
在所有的梯度下降超参数中,学习率(时间表)是保证模型良好性能的最关键参数之一。在这篇文章中,我们将探讨学习率,并解释为什么在模型训练期间安排我们的学习率是至关重要的。
接下来,我们将看到如何通过实现和利用Keras中的各种调度器来选择学习率时间表。然后,我们将在Neptune中创建实验来比较这些调度器的表现。
内容。
什么是神经网络中的学习率?
什么是学习率,它对神经网络有什么作用?学习率(或步长)被解释为在反向传播训练过程中对模型权重的变化/更新的幅度。作为一个可配置的超参数,学习率通常被指定为一个小于1.0的正值。
在反向传播中,模型权重被更新以减少我们损失函数的错误估计。我们不是用全额来改变权重,而是将其乘以某个学习率值。例如,将学习率设置为0.5意味着用0.5*估计的权重误差(即梯度或总误差变化与权重的关系)来更新(通常是减去)权重。
学习率的影响
学习率控制着优化器达到损失函数的最小值需要多大的步骤。这对我们的优化算法有什么影响?请看这些图。
- 有了大的学习率(在右边),算法就能快速学习,但也可能导致算法在最小值附近震荡,甚至跳过最小值。更糟糕的是,高的学习率等于大的权重更新,这可能导致权重溢出。
- 相反,小的学习率(在左边),对权重的更新很小,这将引导优化器逐渐走向最小值。然而,优化器可能需要太长的时间来收敛,或者卡在高原或不希望出现的局部最小值。
- 一个好的学习率是在覆盖率和过冲之间的权衡(在中间)。它不能太小,这样我们的算法才能迅速收敛,也不能太大,这样我们的算法就不会在没有达到最小值的情况下来回跳跃。
尽管找到一个合适的学习率的理论原则是直接的(不要太大,也不要太小),但说起来容易做起来难!为了解决这个问题,学习率是一个很重要的因素。为了解决这个问题,引入了学习率时间表。
学习率时间表
学习率计划是一个预定义的框架,随着训练的进行,在历时或迭代之间调整学习率。两种最常见的学习率表技术是。
- 恒定学习率:顾名思义,我们初始化一个学习率,在训练期间不改变它。
- 学习率衰减:我们选择一个初始的学习率,然后按照一个调度表逐渐降低。
知道了什么是学习率调度,你一定想知道为什么我们首先要降低学习率?好吧,在一个神经网络中,我们的模型权重的更新方式是。
其中eta是学习率,而偏导是梯度。
对于训练过程来说,这很好。在训练的早期,学习率被设置得很大,以便达到一组足够好的权重。随着时间的推移,这些权重被微调,以通过利用小的学习率达到更高的精度。
注意:你可能会读到一些文章,其中学习率计划只被定义为(学习率)的衰减。虽然这两个术语(学习率计划和衰减)有时可以互换使用,但在本文中,我们将实现恒定学习率的方案,作为性能基准的基准模型。
分析数据集和Neptune中的实验配置
出于演示的目的,我们将使用Keras自带的流行的Fashion-MINIST数据。这个数据集由70,000张图片组成(训练集和测试集分别为60,000和10,000)。这些图像为28×28像素,与10个类别相关。
为了跟踪和比较我们的模型在不同学习率调度器下的表现,我们将在Neptune中进行实验。Neptune监控一切与模型有关的东西。关于如何用Python设置和配置你的Neptune项目,请参考该文档 的详细步骤说明。
在这个练习中,我们将创建一个Neptune项目,并将其标记为 "LearingRateSchedule"。在得到你的NeptuneAPI令牌后,你可以使用下面的代码将Python连接到我们的项目。
# Connect your script to Neptune
接下来,我们将用Keras中的一些实用函数加载数据集。
为了减少本地机器的运行时间,我们的模型将针对20000张图片进行训练,而不是整个60000张。因此,我们将使用下面的代码随机选择20,000条数据记录。
在此基础上,我们还将定义几个辅助函数,在训练过程中保存和绘制学习率。
#### Random seed
这里有几个注意事项。
- 当前的数据集通过除以255而被归一化;因此,它被重新划分为0-1的范围。
- 我们定义了一个函数get_lr_metric()来保存和打印出学习率,作为Keras verbose的一部分。
此外,我们还创建了一个辅助函数,在整个实验过程中向Neptune记录学习率和模型性能图表。
def plot_Neptune(history, decayTitle, npt_exp):
神经网络模型
有了数据集和辅助函数的准备,我们现在可以建立一个神经网络模型作为图像分类器。为了简单起见,我们目前的模型包含了2个隐藏层和一个输出层,并使用*"softmax "* 激活函数进行多类分类。
#### Define the Neural Network model
下面是模型的结构,这是一个相当简单的网络。
具有恒定学习率的基线模型
如前所述,恒定的调度是所有学习率调度中最简单的方案。为了设定一个性能基线,我们将在所有的历时中持续使用学习率0.01来训练模型。
# Create an experiment and log the model
在这里,我们。
- 在我们的项目下创建了一个Neptune实验来跟踪基础模型的性能。
- 在Keras的标准SGD优化器中使用`learning_rate`参数指定学习率。
- 增加了lr_metric 作为用户定义的监测指标,这使得学习率信息可以显示在训练逐字记录中。
- 在Neptune中记录了学习率和性能图表(损失和准确率曲线)。
观察训练进度,我们可以确认,当前的学习率固定为0.01,没有变化。
在我们的Neptune实验中,我们会发现以下性能图。
恒定学习率时间表性能图|在Neptune中看到
恒定学习率计划性能图|在Neptune中查看
随着学习的展开,训练损失在减少,准确性在增加;尽管如此,当涉及到验证集时,模型的性能并没有太大变化。这将是我们的基线模型,用于以后与衰减调度器进行基准测试。
Keras中内置的衰减 调度的问题
Keras提供了一个内置的标准衰减策略,它可以在优化器的`decay`参数中指定,如下图所示。
initial_learning_rate =
这个衰减策略遵循的是基于时间的衰减,我们将在下一节讨论,但现在,让我们熟悉一下基本公式。
假设我们的初始学习率=0.01,衰减=0.001,我们将期望学习率变成。
- 0.1 * (1/(1+0.01*1)) = 0.099 在第1个历时之后
- 0.1 * (1/(1+0.01*20)) = 0.083,在第20个历时之后
然而,看一下Keras的训练进度,我们注意到不同的数值,在第一个历时之后,学习率已经从0.1降低到0.0286。
令人困惑?
好吧,这是一个误解,即Keras在每个历时结束后都会更新学习率;相反,学习率的更新是分批进行的,这意味着它是在Keras的每个批次后 实施的。该公式为
其中参数Steps也被称为Iterations。
如果我们回到之前的例子,由于我们有总的训练数据=20000张图片,并且验证率=0.2,训练集=20000*0.2=16000。那么设置批处理量为64意味着。
- 16000/64=250步或迭代需要完成一个epoch。
- 学习率在每个历时后更新250次,这相当于。
0.1 * (1/(1+0.01*250)) = 0.0286!
因此,当使用Keras中的标准衰减实现时,请记住,这是一个批次性的 更新,而不是历时性的更新。为了避免这个潜在的问题,Keras还允许数据科学家定义自定义学习率调度器。
在本文的其余部分,我们将遵循这一路线,使用Keras的Callback()功能实现我们自己的调度器。
使用Keras Callback的学习率调度器
学习率衰减的基本机制是随着历时的增加降低学习率。因此,我们基本上想把我们的学习率指定为epochs的一些递减函数。
在所有潜在的候选人中,线性函数是最直接的,所以学习率随着历时的增加而线性下降。由于其简单性,线性衰减通常被认为是第一个尝试的方案。
线性衰减方案
通过这个方案,学习率将在训练历时结束时衰减为零。为了实现线性衰减。
initial_learning_rate =
在这里,我们定义了一个lr_polynomial_decay类,其中的参数。`power'控制衰减的速度;也就是说,较小的power使学习率衰减得更慢,较大的power使衰减得更快。
将 "power "设为1可以得到一个线性衰减,其曲线图如下所示。
线性学习率的衰减
为了用这个自定义的线性衰减来训练我们的模型,我们只需要在LearingRateScheduler函数中指定它。
npt_exp_4 = neptune.init(
api_token=os.getenv(
运行这个模型,我们可以在我们的Neptune项目中看到下面的性能图。
线性学习率计划性能图|在Neptune中查看
线性学习率计划性能图|在Neptune中查看
从验证集上的损失和准确度曲线中,我们观察到。
- 在整个训练过程中,这两个指标都是波动的。
- 在大约40个历时之后,模型出现了过拟合,训练损失继续减少,而验证损失开始增加(准确率几乎持平)。
这种模式表明,我们的模型随着训练的进行而出现分歧,而这很可能是因为学习率太高了。
我们是否应该降低学习率作为epochs的线性函数?也许不应该。最好的办法是制定一个政策,让学习率在训练开始时衰减得更快,然后在训练结束时逐渐变平,变成一个小值。
这就是非线性衰减的基本概念,其中最常用的是基于时间的衰减和指数式衰减。
基于时间的衰变和指数衰变
基于时间的衰减的公式定义为。
def lr_time_based_decay(epoch, lr):
其中`decay`是一个参数,通常被计算为。
decay = initial_learning_rate/epochs
让我们指定以下参数。
initial_learning_rate =
则此图显示了生成的学习率曲线。
基于时间的学习率衰减
与线性函数相比,基于时间的衰减使学习率在训练开始时下降得更快,而在训练结束后下降得更慢。和之前一样,让我们把这个调度器传递给LearningRateScheduler 回调,并把性能图记录到Neptune。
npt_exp_1 = neptune.init(
api_token=os.getenv(
下面是这个模型的性能。
基于时间的学习率调度性能图 |在Neptune中查看
基于时间的学习率计划性能图 |在Neptune中查看
我们可以看到,这个模型比线性衰减模型对验证集的拟合效果更好。有几个观察结果。
- 当我们的学习率降低到接近零的时候,学习几乎停止在38个历时左右。
- 与线性方案类似,训练开始时有一些大的波动。
现在,有什么方法可以平滑这些波动吗?让我们来看看指数衰减,它被定义为epochs数量的指数函数。
def lr_exp_decay(epoch):
同样,指定initial_learning_rate = 0.5和epochs = 100将产生以下衰减曲线(与线性和基于时间的衰减相比)。
学习率衰减比较
指数方案在开始时提供了一个更平滑的衰减路径,这应该导致一个更平滑的训练曲线。让我们运行这个模型,看看情况是否如此。
npt_exp_3 = neptune.init(
api_token=os.getenv(
下面是与验证集的比较。
比较基于时间的衰减和指数衰减|见于海王星
很容易看出,指数衰减的训练曲线(橙色线)比基于时间的衰减(蓝色线)要平滑得多。总的来说,指数衰减的表现略胜一筹。
到目前为止,我们只看了连续的衰减策略,那么离散的呢?接下来,我们将继续讨论流行的离散阶梯式衰减,又称基于阶梯的衰减。
基于步骤的衰减
在这个策略下,我们的学习率被安排在每N个历时中减少一定数量。
def lr_step_based_decay(epoch):
其中`drop_rate`指定了学习率的修改量,`epochs_drop`指定了修改的频率。
和上面一样,设置我们的初始学习率=0.5,epochs=100,产生这个阶梯式的学习曲线。
基于步骤的学习率衰减
把它传递给我们的模型。
npt_exp_2 = neptune.init(
api_token=os.getenv(
我们会有与线性衰减相当类似的性能图,在这里我们的模型过度适应。
基于步骤的学习率计划性能图|在海王星中见
基于步骤的学习率计划性能图|见海王星中的表现
模型性能基准测试
有了各种衰减方案的实现,我们现在可以把事情放在一起,比较模型的表现。
学习率调度器
根据我们的实验,总体看来,学习在大约60个epochs时停止;因此,为了便于可视化,我们将放大以关注前60个epochs。和以前一样,我们将在Neptune中记录出图,以便追踪。
## Create an experiment in Neptune for tracking
不同调度器下验证集的损失曲线 |在Neptune中查看
不同调度器下验证集的准确度曲线|在Neptune中查看
上面的性能图来自于目前的练习,意味着指数衰减表现最好,其次是基于时间的衰减;线性和基于步骤的衰减方案会导致模型过拟合。
自适应优化器
除了带有学习率调度器的SGD,第二个最有影响力的优化技术是自适应优化器,如AdaGrad、RMSprop、Adam等。这些优化器使用模型的内部反馈来逼近梯度;这意味着它们几乎是无参数的,而且与我们前面提到的学习率调度器相比,与SGD不兼容。
在所有的自适应优化器中,亚当一直是机器学习从业者的最爱。虽然关于这个优化器的细节超出了本文的范围,但值得一提的是,Adam为每个模型参数/权重分别更新一个学习率。这意味着,使用Adam,学习率可能首先在早期层增加,从而有助于提高深度神经网络的效率。
现在,为了慎重起见,让我们用Keras默认的`Adam`优化器来训练我们的模型,作为最后一个实验。
npt_exp_5 = neptune.init(
api_token=os.getenv(
现在,毫无疑问,这个`亚当'学习器使我们的模型相当快地发散了。
自适应/亚当优化器性能图|在Neptune中查看
自适应/亚当优化器性能图|在Neptune中查看
尽管 "亚当 "是一个非常有效的学习器,但如果不对超参数进行调整,"亚当 "并不总是最佳选择。另一方面,SGD在调整了学习率或衰减调度器的情况下可以表现得更好。
最后的想法
通过所有的实验,我们应该对学习率调度的重要性有了更好的理解;过度激进的衰减会导致优化器永远无法达到最小值,而缓慢的衰减则会导致混乱的更新,没有明显的改善。
一些提示和关键的收获包括。
- 要选择一个学习率时间表,常见的做法是以一个不太小的值开始,例如0.5,然后以指数方式降低,得到更小的值,如0.01、0.001、0.0001。
- 虽然经常是深度学习应用中的默认优化器,但引擎盖下的 "Adam "并不一定总是表现出色;它可能会导致模型分歧。
- 为了建立一个有效的模型,我们还应该考虑其他超参数,如动量、正则化参数(辍学、提前停止等)。
最后,值得一提的是,目前的结果是基于一个神经网络和数据集的。当涉及到使用其他数据集的其他模型时,最佳学习率的安排可能会有所不同。尽管如此,这篇文章应该为你提供一个指南,告诉你如何系统地选择一个最适合你的特定模型和数据集的学习率调度器。
希望你能发现这篇文章的信息和作用。我们的Neptune项目可以在这里访问,完整的脚本可以在我的Github repo里找到。
Katherine (Yi) Li
数据科学家|数据科学作家
一个专门从事机器学习和数据挖掘的数据爱好者。编程、编码和提供数据驱动的洞察力是她的激情所在。她相信,知识在分享中增长;因此,她写了关于数据科学的文章,希望能激励那些正在开始类似数据科学生涯的人。
- 关注我
阅读下一篇
如何比较机器学习模型和算法
9分钟阅读 | 作者:Samadrita Ghosh | 2021年9月16日更新
在过去的几年里,机器学习已经迅速扩展。如今,数据科学家和开发人员不再是简单的、单向的或线性的ML管道,而是运行多个并行的实验,即使对于大型团队来说也会变得不堪重负。每个实验都被期望以不可改变和可重复的格式记录下来,这就导致了无尽的日志,其中有宝贵的细节。
我们需要通过平行实验彻底比较机器学习模型来缩小技术范围。使用一个精心策划的方法是必要的,以了解如何选择正确的组合算法和手头的数据。
因此,在这篇文章中,我们将探讨如何对待比较ML模型和算法。
模型选择的挑战
每个模型或任何机器学习算法都有几个特征,以不同方式处理数据。通常情况下,根据以前的实验阶段,送入这些算法的数据也是不同的。但是,由于机器学习团队和开发人员通常会记录他们的实验,所以有充足的数据可供比较。
挑战在于如何理解必须考虑哪些参数、数据和元数据来得出最终的选择。这是一个典型的悖论,即有大量的细节而不明确。
更具挑战性的是,我们需要了解一个具有高值的参数,比如说较高的指标得分,是否真的意味着模型比得分较低的模型更好,或者只是由统计偏差或错误的指标设计引起的。