上一篇文章2-3 李宏毅2021春季机器学习教程-类神经网络训练不起来怎么办(二)批次与动量(Batch and Momentum)
介绍了机器学习时训练神经网络的第二个策略:批次与动量。下面介绍第三个策略:自动调整学习率。
在训练一个Network时,critical point其实不一定是你会遇到的最大的障碍,今天要告诉大家的是一个叫做Adaptive Learning Rate的技术,给每一个参数不同的learning rate。
目录
Training stuck ≠ Small Gradient
Training can be difficult even without critical points
Different parameters needs different learning rate
Training stuck ≠ Small Gradient
大部分人相信训练受阻是因为参数到达了critical point附近。但是critical point不一定我们训练过程中最大的阻碍。为什么?
如上图所示,横轴代表参数update的次数。往往我们在训练一个network的时候,会把loss记录下来,随着你参数不断的update,这个loss会越来越小,最后就卡住了,你的loss不再下降。那多数时候,大家会说是不是走到了critical point,因为gradient等于零的关系,所以我们没有办法再更新参数,但是真的是这样吗?
当走到critical point时,意味着gradient非常的小,但是当loss不再下降的时候,gradient真的很小吗?其实多数时候我们都没有确认过这件事,而事实上在今天李宏毅老师show的这个例子里面,当loss不再下降的时候,gradient并没有真的变得很小。
上图第二行是gradient的norm,即gradient的长度,随着参数更新的时候的变化,你会发现说虽然loss不再下降,但是gradient的大小并没有真的变得很小。这样子的结果其实也不难猜想,也许你遇到的是这样子的状况。
上图是error surface,然后你现在的gradient,在error surface山谷的两个谷壁间不断来回的震荡。这个时候loss不会再下降,所以你会觉得它到了critical point,卡到了saddle point或者local minima吗?不是的,它的gradient仍然很大,只是loss不见得再减小了。所以你要注意,当你今天训练一个network,发现loss不再下降的时候,不要随便说卡在local minima、saddle point,有时候根本两个都不是,你只是单纯的loss没有办法再下降。
就是为什么在作业2-2,需要算一下gradient的norm,然后算一下是卡在saddle point还是critical point,因为多数的时候,当你说你训练卡住了,很少有人会去分析卡住的原因。
Wait a minute
有的同学就会有一个问题,如果我们在训练的时候,很少卡到saddle point或者是local minima,那这一个图是怎么做出来的呢?
我们上次有画过这个图,是说我们训练一个Network,训练到参数在critical point附近,然后我们再来根据eigen value的正负号,来判断说这个critical point是saddle point还是local minima。实际上训练时,要走到saddle point或者是local minima,是一件困难的事情。
那这边告诉大家一个秘密,这个图你要训练出这样的结果,训练到参数很接近critical point,用一般的gradient descend其实是做不到的,因为在gradient还很大的时候,loss就已经掉了下去,这个需要特别方法的训练。所以做完这个实验以后,李宏毅老师更感觉要走到一个critical point其实是困难的一件事,多数时候training在还没有走到critical point的时候就已经停止了,那这并不代表critical point不是一个问题。目前用gradient descend来optimization时,我们真正应该要怪罪的对象往往不是critical point,而是其他的原因。
Training can be difficult even without critical points
如果今天critical point不是问题的话,为什么我们的training会卡住呢?这边看一个非常简单的error surface。
我们只有两个参数w和b,这两个参数值不一样的时候,Loss的值不一样,我们就画出了一个error surface,这个error surface的最低点在黄色X这个地方,事实上,这个error surface是convex的形状(convex optimization常翻译为“凸优化”)。
它的等高线是椭圆形的,只是它在横轴的地方,它的gradient非常的小,它的坡度的变化非常的小,非常的平滑,所以这个椭圆的长轴非常的长,短轴相对之下比较短,在纵轴的地方gradient的变化很大,error surface的坡度非常的陡峭。
那现在我们要从黑点(初始的点)来做gradient descend。你可能觉得说,这个convex的error surface,做gradient descend,不就是一路滑下来,然后可能再走过去吗,应该是非常容易。你实际上自己试一下,你会发现形状这么简单的error surface用gradient descend,都不见得能把它做好。举例来说这是李宏毅老师自己试了一下的结果。
(1)如上图左侧,learning rate=10⁻²时,参数在山谷山壁的两端不断的震荡,loss掉不下去,但是gradient其实仍然是很大的。那可能因为你learning rate设太大了,learning rate决定了我们update参数的时候步伐有多大,learning rate显然步伐太大,你没有办法慢慢地滑到山谷里面,只要把learning rate设小一点,不就可以解决这个问题了吗?事实不然。
(2)上图右侧,调learning rate,从10⁻²一直调到10⁻⁷,调到10⁻⁷以后,终于不再震荡了。但是你发现这个训练永远走不到终点,因为learning rate太小了,竖直往上这一段这个很斜的地方,因为这个坡度很陡(gradient的值很大),还能够前进一点,左拐以后这个地方坡度已经非常的平滑了,这么小的learning rate根本没有办法再让我们的训练前进。事实上在左拐这个地方,看到这边一大堆黑点,这边有十万个点,但是都没有办法靠近local minima,所以显然就算是一个convex的error surface,用gradient descend也很难train。
这个convex的optimization的问题,确实有别的方法可以解。但是你想想看,如果今天是更复杂的error surface,gradient descend是你唯一可以仰赖的工具,但是gradient descend连这么简单的error surface都做不好,“一室之不治,何以天下国家为”,那如果难的问题,它又怎么有可能做好呢?所以我们需要更好的gradient descend的版本,在之前我们的gradient descend里面所有的参数都是设同样的learning rate,这显然是不够的,learning rate它应该要为每一个参数定制化,所以接下来我们就是要讲,定制化的learning rate怎么做到这件事情?
Different parameters needs different learning rate
那我们要怎么定制化learning rate呢,我们不同的参数到底需要什么样的learning rate呢?
从刚才的例子里面,我们可以看到一个大原则,如果在某一个方向上gradient的值很小,非常的平坦,那我们会希望learning rate调大一点,如果在某一个方向上非常的陡峭,坡度很大,那我们期待learning rate可以设得小一点。
我们要改一下gradient descend原来的式子,只放某一个参数update的式子(之前往往是讲所有参数update的式子),完全可以把这个方法推广到所有参数的状况。
我们只看一个参数,这个参数叫θᵢᵗ(第t个iteration的值),它减掉learning rate η乘以gᵢᵗ(在第t个iteration,即θ等于θᵗ的时候,参数θᵢ对loss的微分),表达式为:
这是我们原来的gradient descend,learning rate是固定的。
现在我们要有一个随着参数客制化的learning rate,我们把原来的η除以σᵢᵗ,表达式为:
我们就有一个parameter dependent的learning rate,接下来我们看看这个parameter dependent的learning rate有什么常见的计算方式。
Root mean square
一个常见的类型是算gradient的Root Mean Square,计算的步骤如下:
现在参数要update的式子,我们从θᵢ⁰初始化参数减掉gᵢ⁰乘上learning rate η除以σᵢ⁰,就得到θᵢ¹,此时σᵢ⁰是第一次update参数,通过(gᵢ⁰)²开根号求解。
这个是第一步的状况。重点是接下来怎么处理,那θᵢ¹也一样,减掉gradient gᵢ¹乘上η除以σᵢ¹,σᵢ¹就是我们之前所有计算出来的gradient的平方的平均再开根号,是(gᵢ⁰)²加上(gᵢ¹)²乘以½再开根号,这个就是Root Mean Square,我们算出σᵢ¹后,我们的learning rate就是η除以σᵢ¹,然后把θᵢ¹减掉η除以σᵢ¹乘以gᵢ¹ 得到θᵢ²求解公式如下:
同样的操作就反覆继续下去,到第t + 1次update参数的时候,σᵢᵗ就是过去所有的gradient,gᵢᵗ从第一步到目前为止所有算出来的gᵢᵗ的平方和,再平均,再开根号,一般的表达式为:
然后把learning rate除以它,得到新的learning rate来update你的参数。
Adagrad
那这一招被用在一个叫做Adagrad的方法里面,为什么这一招可以做到坡度比较大的时候learning rate就减小,坡度比较小的时候learning rate就放大呢?
如上图,假设现在我们有两个参数θᵢ¹ 和θᵢ²,θᵢ¹坡度小 ,而θᵢ²坡度大。θᵢ¹因为它坡度小,所以在θᵢ¹上算出来的gradient值都比较小,然后这个σ是gradient的平方和取平均再开根号,所以算出来的σ就小,learning rate就大。
反过来说θᵢ²是一个比较陡峭的参数,在θᵢ²这个方向上loss的变化比较大,所以算出来的gradient都比较大,σ就比较大,learning rate就小,你在update的时候,你的参数update的量就比较小。
所以有了σ这一项以后,你就可以随着每一个参数的gradient的不同,来自动的调整learning rate的大小,那这个并不是你今天会用的最终极的版本,接下来看RMSProp。
RMSProp
我们刚才的假设好像是同一个参数的gradient的大小就会固定是差不多的值,但事实上并不一定是这个样子的,刚才那个版本,同一个参数的learning rate也会随着时间而改变。
举例来说我们来看,这个新月形的error surface:
如果我们考虑横轴(水平线方向)的话,在绿色箭头这个地方坡度比较陡峭,所以我们需要比较小的learning rate。但是到了红色箭头的时候,坡度又变得平滑了起来,就需要比较大的learning rate,所以就算是同一个参数同一个方向,我们期待learning rate是可以动态调整,于是就有了一个新的招数,叫做RMS Prop。
RMS Prop这个方法有点传奇,它传奇的地方在于它找不到论文,应该是将近十年前,Hinton在Coursera上开过deep learning的课程,那个时候他在里面讲了RMS Prop这个方法,然后这个方法没有论文,所以你要cite的话,你要cite那个影片的链接。让我们看看吧。
如上图所示,①RMS Prop的第一步跟Root Mean Square(那个Apagrad的方法)是一模一样的。
②第二步一样要算出σᵢ¹,只是我们现在算出σᵢ¹的方法跟算Root Mean Square的时候不一样,在算Root Mean Square时,每一个gradient都有同等的重要性,但在RMS Prop里面,你可以自己调整,你觉得它有多重要。
在RMS Prop里,我们这个σᵢ¹是(σᵢ⁰)²乘上α加上(1-α)乘上现在我们刚算出来的gᵢ¹,那这个α就像learning rate一样,你要自己调它,它是一个hyperparameter。如果α设很小趋近于0,就代表我觉得gᵢ¹相较于之前所算出来的gradient而言,比较重要;如果α设很大趋近于1,那就代表我觉得现在算出来的gᵢ¹比较不重要,之前算出来的gradient比较重要。
③同理在第三次update参数的时候,我们要算σᵢ² ,把σᵢ¹拿出来取平方再乘上α,那σᵢ¹里面有gᵢ¹跟σᵢ⁰ ,σᵢ⁰里面又有gᵢ⁰,所以你知道σᵢ¹里面有gᵢ¹和gᵢ⁰, 然后这个gᵢ¹跟gᵢ⁰呢他们会被乘上α,然后再加上1-α乘上这个(gᵢ²)²,所以这个α就会决定说gᵢ²在整个σᵢ²里面占有多大的影响力。
那同样的过程就反覆继续下去,σᵢᵗ等于根号[α乘上(σᵢᵗ⁻¹)²加上(1-α)乘上 (gᵢᵗ)²],用α来决定现在刚算出来的gᵢᵗ有多重要,gᵢᵗ相较于之前存在σᵢᵗ⁻¹里面的gᵢᵗ到gᵢᵗ⁻¹而言,它的重要性有多大。如果用RMS Prop的话,你就可以动态调整σ这一项,我们现在假设从这个地方开始。
上图黑线是我们的error surface,从①开始update参数,这个球就从①走到②,那因为一路上都很平坦,代表说g算出来很小,那我们会走比较大的步伐。接下来继续滚,滚到③以后gradient变大了,如果不是RMS Prop,原来的Adagrad的话它反应比较慢,但如果你用RMS Prop,然后把α设小一点,让新的gradient影响比较大的话,那你就可以很快的让σ的值变大,也可以很快的让你的步伐变小。
你就可以踩一个煞车,本来很平滑走到这个地方,突然变得很陡,那RMS Prop可以很快的踩一个煞车,把learning rate变小,如果你没有踩剎车的话,你走到这里这个地方,learning rate太大了,那gradient又很大,两个很大的东西乘起来,你可能就很快就飞出去了,飞到很远的地方。
如果继续走,又走到平滑的地方④,因为这个σᵢᵗ 你可以调整α,让它比较看重于最近算出来的gradient,所以你gradient一变小,σ可能就反应很快,它的这个值就变小了,然后走的步伐就变大了,这个就是RMS Prop。
Adam: RMSProp + Momentum
那今天你最常用的optimization的策略,也叫做optimizer,今天最常用的optimization的策略,就是Adam。
Adam就是RMS Prop加上Momentum,那Adam的算法跟原始的论文链接为https://arxiv.org/pdf/1412.6980.pdf。
在pytorch里面optimizer这个deep learning的套件,往往都帮你做好了,所以我们不用担心这种optimization的问题,但也有一些参数需要调,有一些hyperparameter需要人工决定,但是往往用预设的参数就好了,自己调有时候会调到比较差的。在pytorch里面,Adam这个optimizer预设的参数不要随便调,使用默认参数可以得到不错的结果了,关于Adam的细节,就留给大家自己研究。
补充:Adam可以参考这篇2-2 Coursera吴恩达《改善深度神经网络》第二周课程笔记-优化算法的2.8节Adam优化算法讲解。
Learning Rate Scheduling
我们刚才讲说这个简单的error surface训练不起来,现在我们加上Adaptive Learning Rate以后,能不能训练得起来?
采用最原始的Adagrad做法,learning rate把过去看过的gradient通通都平方再平均再开根号当作这个σ ,做起来是上图这个样子的。这个走下来没有问题,然后接下来在左转的时候,红色圆圈也是update了十万次,之前update了十万次会卡在左转这个地方。那现在有Adagrad后,可以再继续走下去,走到非常接近终点的位置。当你走到这个地方的时候,因为左右方向的gradient很小,所以learning rate会自动调整左右这个方向的,learning rate会自动变大,所以你这个步伐就可以变大,就可以不断的前进。接下来的问题就是,为什么快走到终点的时候突然爆炸了呢?
我们在做这个σ的时候,是把过去所有看到的gradient都拿来作平均,所以纵轴的方向,在这个初始的这个地方,感觉gradient很大,可是这边走了很长一段路以后,这个纵轴的方向,gradient算出来都很小,所以这个y轴的方向就累积了很小的σ,累积到一个地步以后,这个step就变很大,然后就爆走就喷出去了。
喷出去以后没关系,有办法修正回来,因为喷出去以后,就走到了这个gradient比较大的地方,σ又慢慢的变大,参数update的步伐就慢慢的变小。你就发现说走着走着,突然往左右喷了一下,但是这个喷了一下不会永远就是震荡,不会做简谐运动停不下来,这个力道慢慢变小,有摩擦力让它慢慢地又回到中间这个峡谷来,但是累计一段时间以后又会喷,然后又慢慢地回来。怎么办呢?有一个方法也许可以解决这个问题,这个叫做learning rate的scheduling。
Learning Rate Scheduling
我们刚才的η是一个固定的值,learning rate scheduling指不要把η当一个常数,而是把它跟时间练联系起来。最常见的策略叫做Learning Rate Decay,随着时间的不断地进行,参数不断update,我们让η越来越小,那这个也就合理了。因为一开始我们距离终点很远,随着参数不断update,距离终点越来越近,learning rate减小让我们参数的更新踩了一个刹车,更新能够慢慢地慢下来,所以刚才那个状况,如果加上Learning Rate Decay有办法解决。我们就可以很平顺的走到终点,因为后期越靠近终点,η越小,虽然说它本来想要左右乱喷,但是因为乘上这个非常小的η,就可以慢慢地走到终点。
Warm Up
除了Learning Rate Decay以外,还有另外一个经典的常用Learning Rate Scheduling方式,叫做Warm Up。
Warm Up这个方法听起来有点匪夷所思,它让learning rate要先变大后变小,那变大要变到多大呢,变大速度要多快呢 ,小速度要多快呢?这个也是hyperparameter,要自己用手调的。那这个方法听起来很神奇,就是一个黑科技,这个黑科技出现在很多远古时代的论文里面。
最近因为在训练BERT的时候,往往需要用到Warm Up,所以又被大家常常拿出来讲,但它并不是有BERT以后才有Warm Up的,这东西远古时代就有了,举例来说,Residual Network里面是有Warm Up的。
如上图,Residual network在arXiv上面的链接点这里,今天这种有关machine learning 的文章往往在投conference之前,投国际会议之前,就先放到一个叫做arXiv的网站上,把它公开来让全世界的人都可以看。residual network这篇文章是2015年年底放在arXiv上面的。在deep learning变化这么快速的领域里面,五六年前就是上古时代,Residual Network里面就已经记载了Warm Up,它说先用learning rate 0.01,再把learning rate改成0.1。
用过去我们通常最常见的训练Learning Rate Scheduling的方法,就是让learning rate越来越小,但是Residual Network反其道而行,一开始要设0.01 ,接下来设0.1,还特别加一个注解(一开始就用0.1反而train不好),不知道为什么也没解释,反正就是train不好,需要Warm Up这个黑科技。
而在这个黑科技,在知名的Transformer里面(这门课也会讲到),论文也用一个式子提了它。式子说它的learning rate遵守这一个神奇的function来设定它的learning rate。实际上,把这个function画出来的话,会发现它就是Warm Up,learning rate会先增加,然后接下来再递减。
其实你发现说Warm Up这个技术,在很多知名的network里面都有,被当作一个黑科技,论文里面不解释说为什么要用这个,但就在一个你没有注意到的小地方说这个network要用这种黑科技,才能够把它训练起来。那为什么需要warm Up呢,这个仍然是今天可以研究的问题。
这边有一个可能的解释是说,你想想看当我们在用Adam RMS Prop或Adagrad的时候,我们会需要计算σ,它是一个统计的结果,σ告诉我们,某一个方向它到底有多陡或者是多平滑,那要看得够多笔数据以后这个统计才精准。一开始我们的统计是不精准的,所以我们一开始learning rate比较小,一开始不要让我们的参数,走离初始的地方太远,先让它在初始的地方探索,收集一些有关error surface的情报,先收集有关σ的统计数据,等σ统计得比较精准以后,再让learning rate爬升。这是一个解释,为什么我们需要warm up的可能性。
那如果你想要学更多warm up的话,你其实可以看一篇paper,它是Adam的进阶版叫做RAdam,其中对warm up有更多的理解。那有关optimization的部分,我们就讲到这边啦。
Summary of Optimization
我们从最原始的gradient descent,进化到这一个版本。
(1)这个版本里面我们有Momentum,现在不是完全顺着gradient的方向来update参数,而是把过去所有算出来gradient的方向,做一个加总当作update的方向。(2)接下来应该要update多大的步伐呢,我们要除以gradient的Root Mean Square。那讲到这边可能有同学会觉得很困惑,momentum和σ都考虑过去所有的gradient,一个放在分子一个放在分母,不就是正好抵销了吗?但其实它们使用过去所有gradient的方式是不一样的,Momentum是直接把所有的gradient通通都加起来,所以它有考虑方向,它有考虑gradient的正负号,它有考虑gradient是往左走还是往右走;但是这个Root Mean Square,它就不考虑gradient的方向了,它只考虑gradient的大小,我们在算σ的时候都要取平方项,只考虑gradient的大小,不考虑它的方向,所以Momentum跟这个σ,算出来的结果并不会互相抵销掉。(3)那最后我们还会加上一个learning rate scheduling。
那这个是今天optimization的完整的版本了,这种Optimizer,Adam可能是今天最常用的,但除了Adam以外,还有各式各样的变形,但其实各式各样的变形都不错,就是不同的方法算M,不同的方法算σ,不同的Learning Rate Scheduling的方式。
那如果你想要知道更多跟optimization有关的事情的话,那有之前助教的录像,给大家参考到这里(上图所示)。
到目前为止我们讲的是error surface非常崎岖的情况,就像下面这个例子。
我们需要一些比较好的方法来做optimization,前面有一座山挡着,我们希望可以绕过那座山,山不转路转的意思这样,你知道这个gradient,这奇怪的error surface,会让人觉得很痛苦。那就要用神罗天征,把这个炸平成右边的样子,所以接下来我们会讲的技巧,就是有没有可能直接把这个error surface移平,通过改Network里面的什么东西,改Network的架构activation function,或者是其他的东西,直接移平error surface,让它变得比较好train,也就是山挡在前面,就把山直接铲平的意思。这些技巧之后会讲解。
说明:记录学习笔记,如果错误欢迎指正!写文章不易,转载请联系我。