参数更新算法
动量更新(Momentum)
复习一下SGD公式
W
i
=
W
i
−
l
r
∗
g
r
a
d
W_i=W_i-lr*grad
Wi=Wi−lr∗grad
我们在使用SGD时比其他参数更新策略更慢,一般使用中也不会仅仅使用SGD,如下我们使用SGD,等高线表示损失值相同的区域
可以这样理解,水平方向表示参数
x
x
x,垂直方向表示参数
y
y
y,损失值是
x
x
x和
y
y
y的函数,中间的损失值最小,这是我们的目标,红色线的长度表示梯度的大小,很容易想到当我们的学习率是给定了,那么参数
y
y
y对于损失函数来说很“陡峭”,所以梯度大,而参数
x
x
x梯度则很小,所以
y
y
y比起
x
x
x来说更新幅度大,而且
y
y
y的小变化引起损失值的较大变化,这样越陡峭那么更新幅度大,进而损失值变换剧烈。这样的结果就很容易想到了,如下图所示,我们的trajectory(轨迹)会来回震荡,原因就是水平方向移动缓慢。
从上图得知,我们会在flat direction上变化缓慢,在steep direction上jitter(抖动)。
补救这种情况的方式就是动量更新,动量更新的的公式如下:
V
i
=
m
u
∗
V
i
−
1
−
l
r
∗
g
r
a
d
(
W
i
−
1
)
V_i=mu*V_{i-1}-lr*grad(W_{i-1})
Vi=mu∗Vi−1−lr∗grad(Wi−1)
W
i
+
=
V
i
W_i+=V_i
Wi+=Vi
我们加入了一个
V
V
V变量,记录上一次迭代更新的方向。
m
u
mu
mu指的是动量系数,一般取0.5、0.9、0.99。其实Momentum更新的思想就是将本次的更新方向和上一次的更新方向进行了整合,如下图所示
这个策略在计算机图形学有个算法,忘记了,思路一样。这个方法可以看做是一个小球的滚动,
m
u
∗
V
mu*V
mu∗V表示摩擦力,在小球前进过程中(不断靠近损失值的最小值)时,这个摩擦力不断损耗速度,进而使得小球能够不断快速的逼近损失值的最小值。也可以这样比较形象的理解,就是
V
V
V是我们每次迭代的初始速度,单独使用SGD没有考虑初始速度,所以下降很慢,而Momentum考虑了初始速度,所以下降很快,更快于SGD地取收敛。我们上面提到的SGD抖动问题,当我们使用Momentum方法后,因为
m
u
∗
V
mu*V
mu∗V保留了水平的速度,上一次水平速度与这一次水平速度一致,那么水平速度会越来越快,而
m
u
∗
v
mu*v
mu∗v保留了上一次的垂直速度,而当前的垂直速度与上一次迭代的垂直速度反向,所以垂直速度被减慢,不会变得很抖动,这两个方向的速度不就是刚才提到的球因为有摩擦而变慢,因为有初速度而变快。这样球在震荡过程中有了阻尼和震动中心(收敛点),那么最后慢慢收敛到最优值。SGD的收敛速度与Momentum的收敛速度结果比较如下图所示
上面的图就没有了震荡,但是从结果来说Momentum收敛更快,因为如果速度小了就增大,如果速度大了(出现了震荡)就减小。所以一般是会加快收敛速度的。对于flat direction增大速度,对于steep direction减小速度。
Question
关于我们是否可以减小学习率来解决SGD的抖动问题,其实这没什么用,学习率是对于所有的参数而言的,水平方向的速度也会减小,所以并没有什么用。
我们一般将
V
V
V初始化为0,从Momentum公式可以看到之前的梯度会随着训练的进行对当前更新方向的影响呈指数下降,因为每一次都衰减
m
u
mu
mu,越在前面迭代的梯度影响越小。
Nesterov Momentum Update
在上面Momentum方法结果中我们看到Momentum方法的轨迹是先远离了最优值然后再折回来,但是整体上是更快地收敛,那么有没有什么方法可以使得“曲线轨迹”能够变直一点呢?就是Nesterov Momentum方法,Momentum Update与Nesterov Momentum Update的区别如下图
与原始的Momentum不一样的地方在于,我们不使用当前点的梯度,而考虑按照动量再走一步即
μ
\mu
μ
V
t
−
1
V_{t-1}
Vt−1,这一步称为预测,我们使用这个新位置的梯度代替当前位置的梯度,然后与动量部分相减,这个梯度我们叫做展望梯度,因为这样的展望使得Nesterov效果更好。这个收敛速度比起Momentum Update更快。那么是否我们可以直接从
θ
t
−
2
\theta_{t-2}
θt−2更新到新点结果
θ
t
\theta_{t}
θt是不是一样的呢?当然不是,因为当前点
θ
t
\theta_{t}
θt位置变了,即两种方法的actual step是不一样。对于这个形式来做前向和后向传播不太方便,因为使用了另一个位置的梯度,可能实现时需要调用其他的函数。怎么去解决呢,使得我们能够像以前一样,我们可以使用如下的方法
变量替换,上图中我们把
ϕ
\phi
ϕ看做是新的参数变量,求这个参数变量的梯度,然后更新。我们现在就像原来一样,可以求得当前点的新参数的梯度,然后根据这个梯度就可以计算新参数的下一个位置了,这样就能够依赖当前点的参数梯度计算下一个位置,与Momentum形式统一起来,更方便,也许求梯度API就只需要一个就可以了。(这里不用掌握)Nesterov Momentum与Momentum的结果比较如下图所示,因为Nesterov Momentum有look ahead的步骤所以它的梯度更优,这样不断累计,使得Nesterov Momentum更快的靠近最优值(其实可以认为是前瞻的梯度能够使得它更快的收敛)。从图中我们得到了NAG是比Momentum优异的。
Question
对于小网络来说我们才考虑局部最小值,不断取优化网络,而伴随着更大的网络来说,局部最小值已经不是问题了,这些局部最小值的损失值都是差不多的,经过研究发现,当你不断扩大神经网络的规模的时候,你最坏的局部最小值和最好的局部最小值之间的差别随着时间的推移会变得越来越小,经过研究结果得出基本就不存在什么坏的局部最小值了,当然这也只在很小的网络中出现。所以一般我们随机初始的权值最后会得到相同的结果和损失值,我们不必去考虑坏的局部最小值,尤其是很大的网络的时候。
AdaGrad Update
它是由凸优化理论发展而来,然后移植到了神经网络中。AdaGrad方法的计算公式如下图所示
cache是一个参数梯度的平方和向量,每个参数对应于cache中的一个元素,下面就是计算公式,这就是参数自适应学习速率的原因了,不同的参数拥有不同的学习率原因,而且学习率随着cache中的值变化而变化。问题:AdaGrad在更新中会发生什么?因为垂直方向的梯度很大,所以cache通过每次的累加,会使得cache不断变大,这样学习率会不断减小(相对于水平方向),那么随着训练的进行垂直方向的更新程度会下降,而水平方向则相反,因为梯度很小,那么学习率可能会增大,我们不断提高水平方向的更新速度,这样就达到了自适应学习率,对不同梯度方向进行补偿措施。感觉很像Momentum减小垂直速度增大水平速度。这样,利用cache使得水平方向的学习速率一定比垂直方向的学习速度要大,而且可能会在开始阶段水平方向的学习率与垂直方向的学习率差别越来越大。当我们长时间训练时,你会发现分母越来越大,直到最后学习率逼近0,最后就停止了学习,在凸优化问题中可能是可以接受的,但是神经网络就不能接受,我们希望能够持续学习,不断改变参数。
RMSProp算法
因为上面AdaGrad算法的缺点,所以有牛人进行了改进,如下图所示
乘了一个衰减率,通常我们把他设置为0.99,这是一个超参数。这样就不会出现学习率变0的情况能够持续训练,且保证了补偿。(它是来自Geoff Hinton的Coursera课程的幻灯片)有些时候AdaGrad更快,毕竟RMSProp是从AdaGrad得来的。衰减的意思是对于前面的梯度我们使用衰减率不断衰减,起作用的就是最近的梯度(可以把公式写成指数和形式就可以看出来了),从这一点来说也能够保证补偿的获得(梯度大则cache大),同时衰减以前的梯度,保证了cache不会变得很大,因为主要是最近的梯度起作用。
Question
这些方法都是针对普通的模型,一般不会存在模型希望往陡峭的方向更新更快,一般是希望陡峭的地方更新慢一点。
Adam
Adam是RMSProp和Momentum的结合,公式如下:
我们可以认为Momentum主要为了稳定梯度,而RMSProp则为了自适应学习率。beta1和beta2为超参数,一般分别取0.9和0.995,可以自己取检验哪个值更好,一般设定上面两个固定值就行。完整的Adam是如下图所示
就是增加了bias correction(偏移矫正)对m、v的处理,这个处理在训练前期(训练步数t很小)有效,当训练步数增大(其中的t)那么1-beta1t和1-beta2t基本等于1,它主要是对m、v进行初始化为0进行补偿,这样m、v在前期不至于很小,训练到后面bias correction就没什么用了。
学习率衰减
上面的算法都是用了学习率,我们使用固定的学习率都是不好的学习率,我们应该在任何时候都使用学习率衰减的方法。常见的衰减学习率的方法如下图所示
第一种:我们在每进行一次epoch(所有数据训练一遍叫一个epoch)以0.9衰减学习率
第二种是指数型衰减,常用的。
模型训练时Adam是目前一个非常不错的选择,助教说他一值用得Adam。它的效果要好于其他的优化算法。
二阶优化算法*
我们前面讨论的都是一阶优化算法,还有二阶优化算法,他需要求Hessian矩阵,然后可以使用牛顿法直接到达最低点,不需要学习率,收敛更快。对于大型网络来说参数很多,Hessian矩阵就会很大,所以并不适用于大型网络。(PS:自己没用过,后面有时间看一看)二阶优化算法有BGFS(并不适合大型网络)、L-BGFS(有限存储BGFS,有些实际项目在用,我们一般不用它,它与一阶方法相比不是很好,太复杂,不去噪声)。我们理想的默认选择是Adam优化算法,一般我们也没有用L-BGFS算法。
Question
使用AdaGrad最后学习率会自动下降至0,所以学习率衰减可能并没有什么意义,随着训练的进行AdaGrad学习率自动在逐渐变小。而Adam我们使用了leaky gradient所以学习率不会下降至0,可以使用学习率衰减。
集成模型(Model Ensembles)
集成模型在这里是指将多个相同的模型结构不同的参数的模型集成在一起。训练多个独立相同模型,而不是单一的模型,然后对参数取平均,最后的效果更好(实际中得到的结果是2%的提升)。缺点是你必须处理所有这些模型,每个模型独立训练,独立进行前向后向传播,这样训练时间就变长了或者同时训练消耗的资源变多了,在使用集成模型时有一种技巧是,就像我们能够在每一个epoch设置检查点,在检查点中使用模型参数对验证集进行测试一样,我们不需要对多个独立的相同模型进行训练,而是通过在程序中设置一些检查点,只使用一个模型进行训练就可以了,如下图所示
从上图可以看出我们使用了指数衰减的方式记录下参数,我们最后使用x_test作为参数在测试集上进行测试,而x只是用作训练。可以这样理解,我们将损失函数看做一个“碗”,得到的x_test是对不同阶段模型的参数进行了平均,这样到了最后模型的参数在碗的最底部左右两边来回震荡,平均过后能够使得模型的参数更靠近最优值即碗的底部。
随机失活(Dropout)
Dropout
这是很常用的技术,在前向传播过程中随机选择一些神经元的输出设为0,即将这些神经元失活,代码实例如下图所示
numpy.random.rand(d1,d2…)返回一个服从uniform distribution over [0,1)的特定维度的数值或数组,因为参数是正整数需要解包,所以前面有一个解包运行符*。这样通过设置p=0.5,意味着有一半的输出值会被置为0,U1和U2称为随机失活过滤器。在后向传播过程中我们也要进行Dropout,同样需要使用上面两个过滤器与激活函数输出值相乘,这样在相应位置将梯度设为0。
Dropout的好处就是我们每次训练只用了半个神经网络,这样参数减少了,模型的表达能力下降了,可以防止过拟合。对于简单模型来说,数据一样复杂,那么这样的模型也难以过拟合。
除了用过拟合解释Dropout,还有一种解释Dropout的方式,如下图所示
我们并不知道哪一个特征会被失活,这样训练的网络会使得,不论哪一种特征被失活我们都能正确分类。随机失活会“强迫”神经网络使你的特征对于图片的表示是冗余的,即拥有少的特征就可以表示图片,我们需要这个冗余,因为我们并不清楚哪个特征失效。这样就形成了如下图所示的结果
另外一种解释是,如下图所示
从上图描述,Dropout在训练一个大的集成的模型,这些子网络间是共享参数的,每一次的Dropout得到的都是一个子模型,这个模型仅仅在一个数据上进行了训练。每一次迭代都会产生新的子网络,我们对这个网络只用一个数据点进行训练,他们的参数是共享的,也就是说如果两个子模型激活了相同神经元,那么我们会对不同的子模型训练共同的参数,如果是失活的神经元,那么它后面的一层的与它相关的参数是不会更新的,一般设为50%为一个子网络。在不同层中我们可以失活不同比例的神经元,我们如果需要更强的正则化,不一定就非要为50%,可以更高,有一些层的参数少,我们可能减少失活的神经元,而有一些层的参数多,我们可能增加失活的神经元。还有一种失活算法就是Drop Connect,它是对每一个神经元按照一定的概率进行失活。
Dropout在测试时处理
我们使用Monte Carlo近似方法,这个算法的原理就是我们设置不同的失活过滤器,然后用测试数据进行测试,然后求概率分布的平均值即求平均值。这个过程效率低,我们需要做一定程度的简化。简化的方法就是我们不使用过滤器,而是将神经网络中的所有神经元变成激活状态,不使用随机失活,这个时候我们需要关注一些事情,如下图所示
我们测试时不设置Dropout所以结果就是如上图所示,这里只考虑线性函数。而因为在训练时我们设置了P=0.5,我们计算训练时这个神经元的输出期望为测试时输出的一半,所以在测试时需要补偿这一半,这一半的原因就是因为我们使用了p=0.5而导致的。如果我们不在测试时前向传播过程对激活值进行缩放,那么我们的神经元输出将变得很大,输出分布也会改变,神经网络中的东西可能会出问题,因为神经网络没有见过这么大的输出,所以我们需要将输出期望降下来,所以我们缩放激活值,这是一个很巧妙的方法。实现如下:
通过上面的操作,我们就能使得测试时神经元的期望输出等于训练时神经元的期望输出,这样两者的期望输出就一样了。我们会乘上失活率到测试时激活函数的输出上,这样效果更佳,也是正确的。还有一种方法是Inverted Dropout,即我们在训练时增大神经元的输出,从而提高期望输出,我们对训练时神经元输出除以失活率,反向传播相应增大,但是测试时就不需要乘上失活率,如下图所示
Question
上面说的p=0.5时,可能有一半的神经元会失活,这只是一种期望,可能会随机多一点少一点。如刚才除上p,也不一定就保证神经元的失活的数量刚好一般,但是从整体上,从期望上来说应该是没有多大的问题,只是随机的偏多偏少,这只是近似。
这个简单的方法就可以将神经网络的效果提高5%左右,是非常棒、少见的想法。
梯度检验(Gradient Checking)
(待补充)
卷积神经网络(Convolutional Neural Networks)
1980由Fukushima实现的神经元感知机,它主要是模拟了人的神经元;1998年LeCun在上面的基础上做了改进,保留了神经网络的基本框架和层级结构,他们使用了反向传播算法进行训练,但是网络简单;2012年神经网络的规模增大显著,这一年提出了AlexNet网络,使用了ReLU激活函数,这个网络与LeNet(即1998年LeCun提出的)很相似,但AlexNet使用了Batch-Normalization、ReLU等方法,滤波器更小。
卷积神经网络可以做很多的事情,包括图像分类、检索相似图片、物体检测、图像分割(将图像中的所有场景、人物、像素进行标识)、人脸识别、视频分类、识别人的姿势、能玩电脑游戏、检测癌细胞、识别交通标识、认识中国汉字、给图片加注释(描述图片的内容,不只是分类)、deep dream(生成艺术图片)。
有人用猴子做了实验发现卷积神经网络的工作机理和大脑相似,论文是《Deep Neural Networks Rival the Representation of Primate IT Cortex for Core Visual Object Recognition》。