神经网络训练不起来怎么办
# 1. General Guidance
训练模型的过程中,以下就是如何让你做得更好的攻略:
当对训练结果不满意时(testing data 的 loss 太大),首先应检查你的 training data,看看你的 model 有没有在 training data 上学起来,再去看 testing 的结果。如果你发现你的 training data 的 loss 很大,显然它在训练集上面也没有训练好,接下来你要分析一下在训练集上面没有学好是什么原因。一种原因是 model bias,一种是 optimization 的问题。
# 1.1 model bias
所谓 model bias 的意思是说,你的 model 太过简单。
举例来说,我们现在写了一个有未知 parameter 的 function,这个未知的 parameter,我们可以代各种不同的数字,你代
如果 model 太简单,那么这个 function set 太小了,使得它没有包含任何一个 function 可以让我们的 loss 变得够低。这时即便找到这里面最好的那个 function
这个状况就像你想要在大海里面捞针,这个针指的是一个 loss 低的 function,结果针根本就不在海里面。
Solution:重新设计一个 model,给你的 model 更大的弹性。比如增加输入的 features、设计一个更大的 model …
# 1.2 optimization issue
但是并不是 training 的时候,loss 大就代表一定是 model bias,你可能会遇到另外一个问题:optimization 做得不好。
我们可能卡在一个 local minima 的地方,这时你没有办法找到一个真的可以让 loss 很低的参数,如图:
这就好像是说我们想大海捞针,针确实在海里,但是我们却没有办法把针捞起来。
这就产生了一个问题:training data 的 loss 不够低的时候,到底是 model bias,还是 optimization 的问题呢?一个建议判断的方法,就是你可以透过比较不同的模型,来得知你的 model 现在到底够不够大。
举一个例子,如上图,有两个 network,一个有 20 层,一个有 56 层,现在我们把它们测试在测试集上,这个横轴指的是 training 的过程。随着参数的 update,当然你的 loss 会越来越低,但是结果 20 层的 loss 比较 56 层的 loss还高,这说明 56 层的 network 的 optimization 没有做好,因为 20 层 network 能做到的事,56 层可以轻而易举地做到。
所以如果 56 层的 optimization 成功的话,它的 loss 应当是比 20 层的 network 低的。
那么,我们怎样知道我们的 optimization 有没有做好?这边给的建议是:看到一个你从来没有做过的问题,也许你可以先跑一些比较小的、比较浅的network,这些 model 会竭尽全力地找出一组最好的参数,不太会有失败的问题。所以我们可以先 train 一些比较简单的 model,先可以知道它们可以得道什么样的 loss。
If deeper networks do not obtain smaller loss on training data, then there is optimization issue.
- 这个 5 layer 的 model 就是 optimization 没有做好
如果 optimization 没有做好该怎么办?我们会在之后讲。
# 1.3 overfitting
假设你现在经过一番的努力,你已经可以让 training data 的 loss 变小了,那接下来你就可以来看 testing data loss,如果它仍很大,那可能真的遇到 overfitting 的问题了。
注意,training 的 loss 小,testing 的 loss 大,才有可能是 overfitting,而不是一看到 testing 上结果不好就说是 overfitting 了。
什么是 overfitting 不再介绍了。
# 2. local minima 与 saddle point
# 2.1 Critical Point
我们只讨论 Optimization 的时候,怎么把 gradient descent 做得更好,为什么 Optimization 会失败呢?
常常在做 Optimization 时,你会发现,随着你的参数不断的 update,你的 training 的 loss 不会再下降,但是你对这个 loss 仍然不满意。比如你把 deep 的 network 与 shallow network 比较,发现 deep 的并没有做得更好,所以你会觉得 deep network 没有发挥它完整的力量,所以 Optimization 显然是有问题的。有时候甚至会发现,一开始你的 model 就 train 不起来,不管你怎样 update 你的参数,你的 loss 通通掉不下去,这时候到底发生了什么事呢?
过去常见的一个猜想是我们走到了一个地方,这个地方参数对 loss 的微分为零,这样 gradient descent 就没有办法再 update 参数了,loss 当然就不会再下降了。
local minima 和 saddle point 的 gradient 都是 0,所以当你的 loss 没有办法再下降时,也许就是因为卡在了这样的地方,它们统称为 critical point。
但是今天如果你发现你的 gradient 真的很靠近 0,卡在了某个 critical point,我们有没有办法知道,到底是 local minima 还是 saddle point?其实是有办法的。
为什么我们想知道到底是卡在 local minima 还是卡在 saddle point 呢?
- 如果卡在 local minima,那可能就没有路可以走了。因为四周都比较高,你所在的位置就是最低点了
- 如果卡在 saddle point 的话,它的旁边是还有路可以让 loss 变低的,只要你逃离 saddle point,你就有可能让你的 loss 更低
如何鉴别今天的一个 critical point 是属于 local minima 还是 saddle point 呢?
# 2.2 判断是 local minima 还是 saddle point
虽然我们没有办法完整知道整个 loss function 的样子,但如果给定某一组参数,比如说蓝色的这个
- 第一项是
,当 与 很近时, 与 也是很靠近的 - 第二项是
,这个绿色的 是一个向量,它就是我们的 gradient,它会弥补 与 之间的差距,这个向量的第 i 个元素就是 的第 i 个元素对 L 的微分,如下图:
- 第三项会再补足加上 gradient 之后与真正的
之间的差距。其中有一个 Hessian 矩阵 ,计算方式为: 。
比如参数有
和 ,那分别求出 、 、 、 即可得到 。
当我们走到一个 critical point 时,意味着 gradient 为 0,也就是绿色的这一项完全不见了,只剩下红色的这一项。所以在 critical point 处,它的 loss function 可以被近似为
怎样根据 Hessian,即红色这一项,来判断附近的地貌呢?这可以通过将附近的
但是我们怎么可能把所有的
判断是 local minima 还是 saddle point
求出
- 所有 eigen value 都是正的,那代表
,这是一个 local minima; - 所有 eigen value 都是负的,那代表
,这是一个 local maxima; - 如果 eigen valule 有正有负,那代表这是一个 saddle point。
以上我们借助 Hessian 矩阵来判断出了一个 critical point 是属于 local minima 还是 saddle point,但在实际的 implementation 里面,你几乎不会真的把 Hessian 算出来,这个要是二次微分,需要的运算量非常大,更遑论还要求它的eigen value,所以你几乎看不到有人用这一个方法来逃离 saddle point。之后我们会讲其他的方法来逃离 saddle point,我们这里讲这个方法,是想说,如果是卡在 saddle point,也许没有那么可怕,最糟的状况下你还有这一招可以告诉你要往哪一个方向走。
# 2.3 Saddle Point v.s. Local Minima
一个问题是,到底 saddle point 跟 local minima 谁比较常见呢?
我们先讲一个可能不太相关的故事。1543 年东罗马帝国的国王不知道要怎么对抗土耳其人,这时有人找来一个魔法师,叫做狄奥伦娜。他有一个能力跟张飞一样,可以“万军从中取上将首级如探囊取物”,这个狄奥伦娜也一样,他可以直接取得那个苏丹的头。大家想让狄奥伦娜展示一下他的能力,于是他一下拿出了一个圣杯,这个圣杯本来是放在圣索菲亚大教堂的地下室,而且它是被放在一个石棺里面,这个石棺是密封的,没有人可以打开它,但狄奥伦娜却取了出来。为什么他可以做到呢?因为这个石棺你觉得它是封闭的,那是因为你是从三维的空间来看,但狄奥伦娜可以进入四维的空间,从高维的空间中这个石棺是有路可以进去的,它并不是封闭的。
总之这个从三维的空间来看,是没有路可以走的东西,在高维的空间中是有路可以走的,那 error surface 会不会也一样呢?
当你在一维的空间中,一维的一个参数的 error surface,你会觉得好像到处都是 local minima,但是会不会在二维空间来看,它就只是一个 saddle point 呢?如下图所示:
略过实验过程,从经验上看起来,其实 local minima 并没有那么常见,多数的时候,你觉得你 train 到一个地方,你的 gradient 真的很小,然后你的参数不再 update 了,往往是因为你卡在了一个 saddle point。
# 3. Batch
实际上在算微分的时候,并不是真的对所有 data 算出来的 L 作微分,而是把所有的 data 分成一个一个的 mini-batch,每次拿一个 batch 来算 loss、算 gradient,从而 update 参数。所有的 batch 看过一遍,叫做一个 epoch。
Small Batch v.s. Large Batch
考虑如上的两个极端情况,假设我们有 20 笔训练资料:
- 左边的 case 就是没有用 batch,batch size 设的跟训练资料一样多,这种情况叫做 Full Batch,就是没有 batch 的意思
- 右边的 case 就是 batch size = 1
比较两者,会发现左边没有用 Batch 的方式,它蓄力的时间比较长,还有它技能冷却的时间比较长,你要把所有的资料都看过一遍才能够 update 一次参数。而右边的方法 batch size = 1 的时候,蓄力的时间比较短,每次看到一笔参数,你就会更新一次你的参数。
# 3.1 larger batch 可能花费时间更少
但实际上考虑并行运算的话,左边这个并不一定时间比较长。从真正的实验结果来看,比较大的 batch size,你要算 loss,再进而算 gradient,所需要的时间不一定比小的 batch size 要花的时间长。
Larger batch size does not require longer time to compute gradient.
一个实验结果如下图:
- 纵轴是花费的时间
- 因为 GPU 有平行运算的能力,因此实际上当你的 batch size 小的时候,你要跑完一个 epoch 花的时间是比大的 batch size 还要多的
- 但它平行运算能力终究是有个极限,所以你 batch size 真的很大的时候,时间还是会增加的
# 3.2 small batch 训练得到的精确度可能更好
可以看到 Large Batch 在时间上是有优势的。那在训练结果上呢?小的 batch 在训练过程中每一步会受到 noisy 的影响,而 noisy 的 gradient 反而可以帮助 training。我们来看一个实验结果,来比较不同 batch size 在精确度方面的不同:
- 横轴代表的是 Batch Size,从左到右越来越大
- 纵轴代表的是正确率,越上面正确率越高,当然正确率越高越好
可以看到 batch size 越大,它在 training 和 validation 中的 acc 都在降低,这是一个 optimization issue。当你用大的 Batch Size 的时候,你的 optimization 可能会有问题。
为什么 small batch 的 noisy update 会在 training 中更好呢?一个可能的解释是:
- 假如你是 Full Batch,那你今天在 update 你的参数的时候,你就是沿著一个 loss function 来 update 参数,如果走到一个 critical point,就会停下来了从而不再更新参数
- 假如你是 Small Batch,因为我们是每次挑出一个 batch 来算 loss,这样 update 参数的时候 loss function 是有差异的,比如第一个 batch 中用 L1 来算 gradient,到第二个 batch 时用 L2 来算 gradient,这样假设 L1 算 gradient 是 0,卡住了,但 L2 的 function 与 L1 不同,这样 L2 不会卡住从而 update。
# 3.3 small batch 更容易泛化
- 会发现,小的 batch 居然在 testing 的时候会比较好
一个解释是,大的 Batch Size,会让我们倾向于走到峡谷里面,而小的 Batch Size,倾向于让我们走到盆地里面:
- local minima 也有好坏之分,好的 minima 更容易有好的 generalization。
Large Batch 比较 Small Batch
它们各有擅长的地方,所以 batch size 变成另外一个你需要去调整的 hyperparameter。
那我们能不能够鱼与熊掌兼得呢,我们能不能够截取大的 Batch 的优点,跟小的 Batch 的优点,我们用大的 Batch Size 来做训练,用平行运算的能力来增加训练的效率,但是训练出来的结果同时又得到好的结果呢,又得到好的训练结果呢?这时有可能的,有多篇论文给出了一些思路,这里不再介绍。
# 4. Momentum
Momentum 是另外一个可以对抗 critical point 的技术。
- 考虑物理世界,当一个球滚到 local minima 时,由于惯性,他可能继续向前走,从而翻过小坡逃离 local minima。
,一般的 Gradient Descent 长什么样子呢:
- 有一个初始参数
,然后计算一下 gradient,再往 gradient 的反方向去 update 参数: 。
而 Gradient Descent + Momentum 是不只往 Gradient 的反方向来移动参数,而是 Gradient 的反方向,加上前一步移动的方向,用两者加起来的结果去调整去到我们的参数。
- 先初始化一个参数
,在 的地方计算 gradient 的方向 ,之后决定下一步怎么走:
之后一直进行这个过程。
来看一个简单的例子:
当走到一个 local minima 的点时,已经没有 gradient 的方向了 ,但如果有 momentum 的话,就有办法继续走下去,因为下一步的方向不只看 gradient,这样翻过这个小丘的话,也许就走到了一个更好的 local minima。这就是 Momentum 有可能带来的好处。
# 5. Adaptive Learning Rate
critical point 可能并不是训练过程的最大障碍,本节介绍一个叫做 Adaptive Learning Rate 的技术,可以给每一个参数不同的 learning rate。
# 5.1 training stuck ≠ small gradient
为什么我们说 critical point 不一定是我们训练过程中最大的阻碍呢?大家训练 network 时会记录 loss,随着参数的 update,loss 会越来越小,最后卡住了,即 loss 不再下降,多数这时候大家会猜想是不是走到了 critical point 导致没有办法再更新参数。但实际真的这样吗?
当走到 critical point 时 gradient 会很小,但如果在你 loss 不再下降时确认一下 gradient,它并不一定很小,比如下面这个例子,当 loss不再下降时 gradient 并没有真的很小:
gradient 是一个向量,下面是 gradient 的 norm,即 gradient 这个向量的长度,随着参数更新,你会发现说虽然 loss 不再下降,但是这个 gradient 的 norm 并没有真的变得很小。这样子的结果也许是遇到了这样的情况:
- 它的 gradient 仍然很大,只是 loss 不再减小了
所以你在 train 一个 network 的时候,发现 loss 不再下降,这时不要随便说卡在了 critical point,有时候可能就是单纯地 loss 没有办法再下降了。
在实际中,用一般的 gradient descend 其实很难让参数走到 critical point,多数时候还没有走到 critical point 就已经停止了。这不代表说 critical point 不是一个问题,而是说,当你用gradient descend 来做 optimization 的时候,你真正应该要怪罪的对象往往不是 critical point,而是其他的原因。
如果不是 critical point,那我们的 training 为什么会卡住呢?举下面一个简单的 error surface 的例子,我们有两个参数,两个参数值不一样时 loss 值也不一样,这样就画出了一个 convex 形状的 error surface,它的最低点在黄色的 X 处:
- 它在横轴的地方 gradient 非常小,也就是坡度变化非常平滑
- 相比于横轴,其纵轴的 gradient 变化较大,也就是坡度变化非常陡峭
我们从黑色的点开始走,来做 gradient descend,会发现它做不好:
- 参数在峡谷的两端来回震荡,使得 loss 掉不下去。
这也许你会归因于 learning rate 太大导致步伐太大了,如果将 learning rate 调小之后呢?将
- 在左拐的这个小地方有十万个点,所以说很难前进。
显然就算是一个 convex 的 error surface,你用 gradient descend 也很难 train。所以我们需要更好的 gradient descend 版本,之前的所有参数设同样的 learning rate 是不太行的,而应该为每一个参数定制化其 learning rate。
# 5.2 Different parameters needs different learning rate
我们要怎样定制化 learning rate 呢?不同的参数需要什么样的 learning rate 呢?
从刚才的例子中可以看到一个大原则:在某一个方向上,如果 gradient 很小,非常平坦,那我们会希望 learning rate 调大一点;如果非常陡峭,则希望 learning rate 调小一点。
那这个 learning rate 要如何自动调整呢?我们改一下 gradient descend 原来的式子,这里我们只看某一个参数
现在要为每一个参数定制化其 learning rate,我们就把
- 这个
有一个上标 t 和一个下标 i,这说明它是 depend on 的,不同的参数我们要给它不同的 ;同时也是 iteration dependent 的,不同的 iteration 有不同的 。
所以现在的 learning rate
# 5.3 Root mean square 与 Adagrad
有什么方式可以计算
这一招经常用在 Adagrad 中。
为什么这一招可以做到坡度比较大的时候 learning rate 就减小,而坡度比较小的时候 learning rate 就放大呢?
- 在上面蓝色曲线的图中,坡度较小,gradient 值较小,这个
是 gradient 的平方和取平均再开根号,因此算出来的 也较小,这样 learning rate 就较大。 - 下面绿色曲线则与蓝色的相反,其 learning rate 就相对较大。
所以有了
# 5.4 RMSProp
在刚刚的版本里,同一个参数的 learning rate 也会随着时间改变,但在刚刚假设中,好像同一个参数的 gradient 的大小是固定差不多的值,但事实上并不一定是这个样子的。
我们举下面这个新月形的 error surface:
- 考虑横轴方向,会发现绿色箭头这个地方坡度比较陡峭,所以我们需要比较小的 learning rate;而在红色箭头的时候坡度又变得平滑了起来,需要比较大的 learning rate。
所以,就算是同一个参数同一个方向,我们也期待说 learning rate 是可以动态调整的,于是就有了一个叫做 RMSProp 的新招数:
- 它的第一步跟刚刚讲的 Adagrad 方法一样
- 在第二步中,刚刚算 Root Mean Square 时每一个 gradient 都有同等重要性,但在 RMSProp 里面,你可以自己调整现在这个 gradient 的重要程度:
这里面的
- 如果把
设很小,就代表说觉得新鲜热腾的 相较于之前算出来的 gradient 而言比较重要 - 如果把
设很大,就代表我觉得新算出来的 gradient 不如之前的重要
我们形象化地展示一个例子,下面这个黑线代表一个 error surface:
- 在陡峭的地方,你可以借助
来让 的计算更看重当前的 gradient,使得 step 小一些。
# 5.5 Adam
今天最常用的 optimization 策略就是 Adam:
Adam 就是 RMSProp + Momentum,Adam 原始论文 (opens new window)。
在 pytorch 里面该 optimizer 已经帮我们写好了,里面也有一些参数需要调,但往往用预设的就够好了。
# 5.6 Learning Rate Scheduling
普通的 gradient descend 训练不起来 convex 形的 error surface,那加上 Adaptive Learning Rate 以后呢?
a 图是普通的 gradient descend 训练的效果,b 图是加上 Adaptive Learning Rate 之后的效果。
之前是卡在了左转的地方,现在有了 Adagrad 之后,就可以继续走下去了,因为在左转处,左右方向的 gradient 很小,因此 learning rate 会自动调整,使得步伐变大从而不断前进。
接下来的问题是为什么快到终点时突然爆炸了呢?想想看,在计算
- 在纵轴方向上,初始地方的 gradient 很大
- 在左转走了一段路以后,这个纵轴方向的 gradient 算出来都很小,因此这个纵轴方向就累积了很小的
,累积到一定地步后,这个 step 就变很大,然后就暴走喷出去了 - 喷出去以后也没关系,他就走到了一个 gradient 比较大的地方,以后这个
慢慢变大,这个参数 update 的步伐就慢慢变小 - 所以你会发现,突然左右喷了一下,但这个喷了一下不会永远震荡,摩擦力会让他慢慢地又回到中间这个峡谷中。
但是累计一段时间后又会喷一下,怎么办呢?有一个办法也许可以解决这个问题,叫做 learning rate 的 scheduling:
我们之前的
这是合理的,因为一开始我们距离终点很远,随著参数不断 update,我们距离终点越来越近,所以我们把 learning rate 减小,让我们参数的更新踩了一个煞车从而能够慢慢地慢下来。采取了 Learning Rate Decay 之后,可以看到消除了之前的“喷一下”(如下图 b 图):
还有另外一个经典常用的 Learning Rate Scheduling 方式叫做 Warm Up:
- Warm Up 的方法是让 learning rate 先变大后变小。但这个变化的速度也是一个 hyperparameter。
在很多 network 的训练中都使用了 Warm Up:
Warm Up 这个黑科技用在了很多知名 network 中,但这些论文里面不解释说为什么用它,却在你不注意的地方告诉你说这个 network 要用这个黑科技才能训练起来。
一个可能的解释是说,
关于更多,有一个 Adam 的进阶版叫做 RAdam,细节可参考论文 RAdam (opens new window)
小结
我们对 optimization 的方法进行了改进,将原始的 gradient descend 进化到了这样一个版本:
其实关于 optimizer 还有很多东西,这里不再展开了。
# 6. Batch Normalization
# 6.1 Introduction
之前我们说 error surface 如果很崎岖的话,会比较难 train,而 Batch Normalization 就是其中一个把山铲平的方法,从而变得容易去 train。
再来看 optimization 的问题,有时会发现 error surface 是 convex 的,即一个碗的形状,会很难去 train,即两个参数的 loss 斜率相差很大,一个很陡峭,一个很平坦,这时使用 adaptive learning rate 会有好的结果。但从另一个角度想,如果能把难做的 error surface 改掉,不就可以更好做了吗?
假设我们有如下 model,输入是
计算 loss
那什么样的情况会产生比较不好 train 的 error surface 呢?在上面的计算 loss 的过程中,如果
反之,如果
由此便产生了之前所说的很难 train 的情况。可以发现,当 input 的 feature 中每一个 dimension 的值的 scala 差距很大时就可能产生不同方向上斜率非常不同的 error surface 导致难以 train。
一个想法是给 feature 里不同 dimension 有同样的数值范围。其实有很多种不同的方法,合起来统称为 Feature Normalization。
# 6.2 Feature Normalization
以下所讲的方法只是Feature Normalization 的一种可能性。
假设
表示第二个样本的 feature 1
然后我们把不同笔资料里的同一个 dimension 里面的数值取出来,去计算某一个 dimension 的 mean
以后我们用带 tilde 符号的表示被 normalize 后的数值。 经过 normalize 后所有 feature 不同 dimension 的数值都符合标准正态分布。这样的 gradient descent 会让训练更顺利。
# 6.3 Considering Deep Learning
# 6.3.1 对中间层的 feature 也做 normalize
刚刚我们对
这里有一个问题是:对 activation function 的输入做 normalize 还是对输出做?其实差异不大。一个经验是,如果 activate function 是 sigmoid 的话建议在输入前做。但其实也没差啦。
怎样对
之后再往下就是:
- 注意计算
时做的是 element wise 操作。
# 6.3.2 考虑 “batch”
本来如果我们没有做 normalize,那改变
由于 GPU 的 memoey 不可能把整个 data set 都 load 进去,因此实际中,我们只会对一个 batch 里的 data 做 normalization,所以这招叫做 Batch Normalization。
这里有一个问题,你一定要有一个够大的 batch,才可以算得出近似于整个 corpus 的分布(即足够大的 batch 才能算出合理的
# 6.3.3 normalize 后的额外操作
在做 Batch Normalization 的时候,往往还会在算出
这里的
为什么要这个设计呢?因为 normalize 后均值就是 0 了,这给 network 加了限制,也许这个限制会产生一些负面影响,因此把
和 加回去,让 network 自己学习两个参数。
又有人会问加了这个操作,这不会又让不同的 dimension 有不同的 range 了嘛?有可能会吧,但设置初始值时
# 6.3.4 inference 过程中的 moving average
以上说的是 training 过程,在 testing 过程中(也称为 inference),会产生一个问题:training 时是一个一个 batch 的,但当服务上线成一个 application 后,inference 过程不可能等一个 batch size 的数据来了再运算一次。那么 inference 时没有了 batch,该怎么计算这个均值
实际上,pytorch 已经帮我们做好了,Batch Normalization 在 testing 的时候,你不需要做什么特别处理,pytorch 就帮我们处理好了。如果有用 batch normalization,在 training 的时候,你每一个 batch 计算出来的
- 在 training 时,每取一个 batch 时计算出来的
都会用来计算更新一个 ,过程如上图。
等到 inference 的时候,他会这样做:
- 这样在 testing 的时候就不用算
和 了。
# 6.4 Comparison
- 对比黑线和红线,可以看到使用了 batch normalization 后可以更快地得跑到最后收敛的 accuracy。尽管随着数据量的增多,最终收敛结果差不多。
关于 Batch Normalization 为什么会起作用,可以参考原论文,但目前也没有特别肯定的说法。但理论上至少支持了 Batch Normalization 可以改变 error surface,让其比较不崎岖的这个观点。
其实除了 Batch Normalization 还有很多其他的方法,更多可参考如下: