从动力学角度看优化算法(五):为什么学习率不宜过小?
By 苏剑林 | 2020-10-10 | 55219位读者 |本文的主题是“为什么我们需要有限的学习率”,所谓“有限”,指的是不大也不小,适中即可,太大容易导致算法发散,这不难理解,但为什么太小也不好呢?一个容易理解的答案是,学习率过小需要迭代的步数过多,这是一种没有必要的浪费,因此从“节能”和“加速”的角度来看,我们不用过小的学习率。但如果不考虑算力和时间,那么过小的学习率是否可取呢?Google最近发布在Arxiv上的论文《Implicit Gradient Regularization》试图回答了这个问题,它指出有限的学习率隐式地给优化过程带来了梯度惩罚项,而这个梯度惩罚项对于提高泛化性能是有帮助的,因此哪怕不考虑算力和时间等因素,也不应该用过小的学习率。
对于梯度惩罚,本博客已有过多次讨论,在文章《对抗训练浅谈:意义、方法和思考(附Keras实现)》和《泛化性乱弹:从随机噪声、梯度惩罚到虚拟对抗训练》中,我们就分析了对抗训练一定程度上等价于对输入的梯度惩罚,而文章《我们真的需要把训练集的损失降低到零吗?》介绍的Flooding技巧则相当于对参数的梯度惩罚。总的来说,不管是对输入还是对参数的梯度惩罚,都对提高泛化能力有一定帮助。
降得最快的方向 #
该论文跟这个系列的文章一样,将优化过程看成是求解微分方程。回顾之前的博文《从动力学角度看优化算法(三):一个更整体的视角》,设损失函数为$L(\boldsymbol{\theta})$,我们将$\boldsymbol{\theta}$看成是看成是沿着某种时间参数$t$变化的轨迹$\boldsymbol{\theta}(t)$,现在我们考虑它的变化率:
\begin{equation}\frac{d}{dt}L(\boldsymbol{\theta}(t))=\left\langle\nabla_{\boldsymbol{\theta}}L(\boldsymbol{\theta}(t)),\, \dot{\boldsymbol{\theta}}(t)\right\rangle\end{equation}
我们希望$L(\boldsymbol{\theta}(t))$随着时间的变化是递减的(Loss越小越好),所以希望上式小于0,当模长$\Vert\dot{\boldsymbol{\theta}}(t)\Vert$固定时,上式右端的最小值在梯度的反方向$-\nabla_{\boldsymbol{\theta}}L(\boldsymbol{\theta}(t))$取到,所以我们说梯度的负方向下降得最快方向。简单期间,我们可以直接令
\begin{equation}\dot{\boldsymbol{\theta}}(t) = -\nabla_{\boldsymbol{\theta}}L(\boldsymbol{\theta}(t))\triangleq - \boldsymbol{g}(\boldsymbol{\theta}(t))\label{eq:odes}\end{equation}
那么求解参数$\boldsymbol{\theta}$就转化为求解上述常微分方程组,这也是“从动力学角度看优化算法”这个系列的基本出发点。
藏在学习率中的正则 #
然而,实际的问题是,我们没法真正去求解微分方程组$\eqref{eq:odes}$,我们只能用数值迭代,比如采用最简单的欧拉法,得到
\begin{equation}\boldsymbol{\theta}_{t+\gamma} = \boldsymbol{\theta}_{t} - \gamma \boldsymbol{g}(\boldsymbol{\theta}_t)\label{eq:gd}\end{equation}
这其实就是最朴素的梯度下降法,其中$\gamma$也就是我们常说的学习率。上式本质上就是一个差分方程。
可以想象,从$t=0$出发,得到的点$\boldsymbol{\theta}_{\gamma},\boldsymbol{\theta}_{2\gamma},\boldsymbol{\theta}_{3\gamma},\cdots$与方程组$\eqref{eq:odes}$的精确解$\boldsymbol{\theta}(\gamma),\boldsymbol{\theta}(2\gamma),\boldsymbol{\theta}(3\gamma),\cdots$会有一定的出入。如何衡量出入到什么程度呢?不妨这样想象,$\boldsymbol{\theta}_{\gamma},\boldsymbol{\theta}_{2\gamma},\boldsymbol{\theta}_{3\gamma},\cdots$其实也是某个类似$\eqref{eq:odes}$的微分方程组的精确解,只不过对应的$\boldsymbol{g}(\boldsymbol{\theta}(t))$换成了某个新的$\tilde{\boldsymbol{g}}(\boldsymbol{\theta}_t)$,我们比较$\tilde{\boldsymbol{g}}(\boldsymbol{\theta}_t)$与$\boldsymbol{g}(\boldsymbol{\theta}(t))$的差异就好了。
经推导,如果仅保留到$\gamma$的一阶项,那么有
\begin{equation}\tilde{\boldsymbol{g}}(\boldsymbol{\theta}_t) = \boldsymbol{g}(\boldsymbol{\theta}_t) + \frac{\gamma}{4}\nabla_{\boldsymbol{\theta}}\Vert \boldsymbol{g}(\boldsymbol{\theta}_t)\Vert^2 = \nabla_{\boldsymbol{\theta}}\left(L(\boldsymbol{\theta}_t) + \frac{1}{4}\gamma\Vert \nabla_{\boldsymbol{\theta}} L(\boldsymbol{\theta}_t)\Vert^2\right)\end{equation}
推导过程我们放在下一节。可以看到,其实就相当于往损失函数里边加入了梯度惩罚形式的正则项$\frac{1}{4}\gamma\Vert \nabla_{\boldsymbol{\theta}} L(\boldsymbol{\theta})\Vert^2$,而梯度惩罚项有助于模型到达更加平缓的区域,有利于提高泛化性能。这也就是说,离散化的迭代过程隐式地带来了梯度惩罚项,反而是对模型的泛化是有帮助的,而如果$\gamma\to 0$,这个隐式的惩罚则会变弱甚至消失。
因此,结论就是学习率不宜过小,较大的学习率不仅有加速收敛的好处,还有提高模型泛化能力的好处。当然,可能有些读者会想,我直接把梯度惩罚加入到loss中,是不是就可以用足够小的学习率了?理论上确实是的,原论文将梯度惩罚加入到loss中的做法,称为“显式梯度惩罚”。
差分方程到微分方程 #
对于差分方程到微分方程的转换,我们可以用普通的“摄动法”来求解,本博客也有过简单介绍(可以查看标签“摄动”)。不过更漂亮的解法是直接利用算符的级数运算来做,参考之前的文章《算符的艺术:差分、微分与伯努利数》。
我们用泰勒级数展开$\boldsymbol{\theta}_{t+\gamma}$:
\begin{equation}\boldsymbol{\theta}_{t+\gamma}=\boldsymbol{\theta}_{t}+\gamma \dot{\boldsymbol{\theta}}_{t} + \frac{1}{2}\gamma^2\ddot{\boldsymbol{\theta}}_{t} + \frac{1}{6}\gamma^3\dddot{\boldsymbol{\theta}}_{t} + \cdots\end{equation}
如果将对$t$求导的运算记为$D$,那么上式实际上是
\begin{equation}\boldsymbol{\theta}_{t+\gamma} = \left(1+\gamma D + \frac{1}{2}\gamma^2 D^2 + \frac{1}{6}\gamma^3 D^3 + \cdots\right)\boldsymbol{\theta}_{t} = e^{\gamma D}\boldsymbol{\theta}_{t}\end{equation}
所以差分方程$\eqref{eq:gd}$可以写为
\begin{equation}\left(e^{\gamma D} - 1\right)\boldsymbol{\theta}_{t} = - \gamma \boldsymbol{g}(\boldsymbol{\theta}_t)\end{equation}
跟常规的代数运算一样,我们有
\begin{equation}\begin{aligned}
D\boldsymbol{\theta}_{t} =& - \gamma \left(\frac{D}{e^{\gamma D} - 1}\right)\boldsymbol{g}(\boldsymbol{\theta}_t)\\
=& - \left(1 - \frac{1}{2}\gamma D + \frac{1}{12}\gamma^2 D^2 - \frac{1}{720}\gamma^4 D^4 + \cdots\right)\boldsymbol{g}(\boldsymbol{\theta}_t)
\end{aligned}\end{equation}
等号左端就是$\dot{\boldsymbol{\theta}}_{t}$,因此等号右端就是$-\tilde{\boldsymbol{g}}(\boldsymbol{\theta}_t)$的表达式了,保留到一阶项为
\begin{equation}
- \left(1 - \frac{1}{2}\gamma D\right)\boldsymbol{g}(\boldsymbol{\theta}_t) = - \boldsymbol{g}(\boldsymbol{\theta}_t) + \frac{1}{2}\gamma \frac{d}{dt}\boldsymbol{g}(\boldsymbol{\theta}_t)
= - \boldsymbol{g}(\boldsymbol{\theta}_t) + \frac{1}{2}\gamma \nabla_{\boldsymbol{\theta}}\boldsymbol{g}(\boldsymbol{\theta}_t)\dot{\boldsymbol{\theta}}_t
\end{equation}
也就是
\begin{equation}\begin{aligned}
\dot{\boldsymbol{\theta}}_{t} =& - \boldsymbol{g}(\boldsymbol{\theta}_t) + \frac{1}{2}\gamma \nabla_{\boldsymbol{\theta}}\boldsymbol{g}(\boldsymbol{\theta}_t)\dot{\boldsymbol{\theta}}_t\\
=&- \boldsymbol{g}(\boldsymbol{\theta}_t) + \frac{1}{2}\gamma \nabla_{\boldsymbol{\theta}}\boldsymbol{g}(\boldsymbol{\theta}_t)\left[- \boldsymbol{g}(\boldsymbol{\theta}_t) + \frac{1}{2}\gamma \nabla_{\boldsymbol{\theta}}\boldsymbol{g}(\boldsymbol{\theta}_t)\dot{\boldsymbol{\theta}}_t\right]\\
=&- \boldsymbol{g}(\boldsymbol{\theta}_t) - \frac{1}{2}\gamma \nabla_{\boldsymbol{\theta}}\boldsymbol{g}(\boldsymbol{\theta}_t)\boldsymbol{g}(\boldsymbol{\theta}_t)\quad\text{(略去二阶项)}\\
=&- \boldsymbol{g}(\boldsymbol{\theta}_t) - \frac{1}{4}\gamma \nabla_{\boldsymbol{\theta}}\Vert\boldsymbol{g}(\boldsymbol{\theta}_t)\Vert^2
\end{aligned}\end{equation}
所以一阶的$\tilde{\boldsymbol{g}}(\boldsymbol{\theta}_t)=\boldsymbol{g}(\boldsymbol{\theta}_t) + \frac{1}{4}\gamma \nabla_{\boldsymbol{\theta}}\Vert\boldsymbol{g}(\boldsymbol{\theta}_t)\Vert^2$,推导完毕。
例行公事的小总结 #
深度学习的发展和普及离不开基于梯度下降的优化器的成功应用,而梯度下降为何能如此成功,依然还没得到深刻的解释。众多研究人员在“炼丹”过程中,多多少少也能总结出一些不知道为什么有效的“奇技淫巧”出来,诸如batch_size该取多大、学习率该怎么调,估计每个人也有自己的经验。
对于“学习率不能过小”这个现象,大家应该都有所体会,很多时候可能已经默认作为一个“常识”使用,而懒得思考背后的原理了。Google的这篇论文则为理解这个现象提供了一个可能的解释:适当而不是过小的学习率能为优化过程带来隐式的梯度惩罚项,有助于收敛到更平稳的区域。笔者认为其分析过程还是值得参考学习的。
转载到请包括本文地址:https://spaces.ac.cn/archives/7787
更详细的转载事宜请参考:《科学空间FAQ》
如果您还有什么疑惑或建议,欢迎在下方评论区继续讨论。
如果您觉得本文还不错,欢迎分享/打赏本文。打赏并非要从中获得收益,而是希望知道科学空间获得了多少读者的真心关注。当然,如果你无视它,也不会影响你的阅读。再次表示欢迎和感谢!
如果您需要引用本文,请参考:
苏剑林. (Oct. 10, 2020). 《从动力学角度看优化算法(五):为什么学习率不宜过小? 》[Blog post]. Retrieved from https://spaces.ac.cn/archives/7787
@online{kexuefm-7787,
title={从动力学角度看优化算法(五):为什么学习率不宜过小?},
author={苏剑林},
year={2020},
month={Oct},
url={\url{https://spaces.ac.cn/archives/7787}},
}
October 10th, 2020
同学们苏老师又更新了!!!
你们再也没有不调学习率的理由了!!!
October 12th, 2020
看不懂方程的小白。
October 28th, 2020
苏老师你好,我是一个大四的学生,马上准备读研了,导师是做深度学习优化方面内容的,看到你写了一系列动力学优化的内容,想问一下如果要研究做相关内容需要啥基础呢?导师那边只让好好学数学= =目前tensorflow啥的都不太会,看到https://spaces.ac.cn/archives/6163里面还用了变分,感觉内容蛮多的想赶紧学一波= =
November 18th, 2020
Thanx for sharing this wonderful article
Great stuff
i like second paragraph which you written
November 26th, 2020
梯度反方向不一定是下降的方向,可能是鞍点。学习率过小能会削弱batch的平均作用,进而导致等效batch~1,进而导致容易停在鞍点。
根据$(1)$式,代入梯度的反方向得到$\frac{d}{dt}L(\boldsymbol{\theta}(t))= - \left\Vert\nabla_{\boldsymbol{\theta}}L(\boldsymbol{\theta}(t))\right\Vert^2$,所以只要当前点的梯度不为0,那么梯度的反方向就一定是下降的方向。
“学习率过小能会削弱batch的平均作用”,这种推断最好通过数学来定义并证明,否则很难把握你究竟要表达什么意思(比如,什么叫做“削弱batch的平均作用”?)
December 23rd, 2020
学习率直接影响学习速度状态,Batch-Size直接影响每次调整学习方向。我想,LinkerLin的意思是,过小的学习率,削弱了Batch-Size对方向的影响。随机梯度下降具有更好的泛化能力的条件,其中重要的原因是随机梯度方向的不一致性,否则我们总是可以找到一个最优的凸点,它也是泛化的最优的凸点,但训练数据不能代表未来数据。所以,过小(相对事先已经确定的Bathc-Size而言)的学习率,泛化能力不一定的是最优的(而且常常),LinkerLin的意思是这个。
LinkerLin是哪位?您说的似乎跟本文的论述的内容不相关吧?
当然,还是感谢您提供的丰富的参考资料,谢谢。
LinkerLin是楼上那位,当然我也不认识。我是Adam Lee。这些都是我自己写的,不是网上摘抄的,路过发言而已,仅供互相交流学习。
这样看我倒是明白你们两位所要表达的观点了。你们大概是说batch_size和大学习率都能给优化过程带来一定的波动(噪声),适当的波动有利于提高模型的泛化能力吧。但由于噪声的定量描述太过于艰难了,所以一般在理论分析的时候,都难以精准地分析噪声的影响。而本文所要表达的意思,是哪怕在全量梯度下降的情况下,也不应该用太小的学习率。这并不是为了收敛更快,而是大学习率本身隐含着有益的正则化效果。
是的,我也是看到,他说过小的学习率倾向于将Batch设为1,也就是全批量学习。而LinkerLin的措辞让很多人无法理解,所以我顺便一说。你的文章很好,我主要是接你在上楼的话题。
December 23rd, 2020
理论上,我们通常需要使用神网来解决的问题,最优的泛化凸点不存在,这意味着你需要搜索整个宇宙的所有可能。我们能找到一个可以接受的,能够代表未来的解,这主要取决与我们的训练数据对未来数据的代表性。或者说,训练数据越多越多越好,数据整体的方差与理论全集的方差越小越好。但无论多少数据,在无限可能面前总是过小。所以学习率与每一个随机Batch-Size的配合,可以近似的模拟数据的生成过程。脱离Batch-Size单纯的讲学习率是没有参照点的。我们看到10分钟学习Image-Net,是使用了8K大小Batch-Size,同时学习率扩大到原来512->0.02到8K->0.2。速度惊人,泛化能力在训练数据外的测试集中,并未下降。
December 23rd, 2020
在编程实际操作过程中,Bathc-Size调整起来远没有学习率方便直观,这意味着你得每次重新划分数据集。所以在尝试几次之后就确定下来的,具体的大小取决你的硬件条件与你拥有的全部数据量的大小,直观的判断就是,你希望你的的训练数据在每一次Epoch之后,产生多少个不同大小和方向的梯度。举个栗子,这就像仍硬币一样,你觉得多少次抛物实验后,可以近似的模拟数据的整体分布(二项分布)。这个值(经验值)在有限集在10万到千万级一般是1千到1万次。这意味着Batch-Size在10到1万这么大。你要至少抛上千次。
June 1st, 2022
苏老师,公式推导(8)有误,这里应该是伯努利数,-1/720这一项应该是0才对
感谢指正,下一项应该是4次项才对。