文章目录
一.梯度下降
梯度下降算法相信大家都不陌生,这里就是展示一下公式,这里的 θ \theta θ是一个向量。 θ ( n ) = θ ( n − 1 ) − η ∇ L ( θ ( n − 1 ) ) \theta^{(n)}=\theta^{(n-1)}-\eta\nabla L(\theta^{(n-1)}) θ(n)=θ(n−1)−η∇L(θ(n−1)) 下面是一张体现普通梯度下降的图,可见走的方向恰好与梯度方向相反,且正比于偏导数。
二.自适应步长
普通的梯度下降的问题在于,我们的步长(这里也被称作学习率)是固定的,有时候太大会造成不收敛,太小会造成收敛速度太慢;因此我们要寻求办法,能否在训练过程中也自动调整步长,保证步长在当前的形势下,是比较良好的步长。
1.与牛顿迭代法的联系
牛顿迭代法是一种求连续可导的函数的零点的数值解的数值分析方法。
对于函数
f
(
x
)
f(x)
f(x),其中
f
(
x
0
)
=
0
f(x_0)=0
f(x0)=0,则在
x
0
x_0
x0的邻域内,我们可以使用泰勒展开得到如下式子:
f
(
x
0
)
=
f
(
x
)
+
(
x
0
−
x
)
f
′
(
x
)
+
o
(
x
0
−
x
)
f(x_0)=f(x)+(x_0-x)f'(x)+o(x_0-x)
f(x0)=f(x)+(x0−x)f′(x)+o(x0−x) 我们忽略高阶无穷小,我们利用上式的点
x
x
x的信息就可以估计出
x
0
x_0
x0的值,即:
x
0
^
=
x
−
f
(
x
)
f
′
(
x
)
\hat{x_0}=x-\frac{f(x)}{f'(x)}
x0^=x−f′(x)f(x) 之后我们不断重复即可。这种方法的收敛速度一般是很快的;但是问题在于我们还要算点
x
x
x处的导数,因此有一种简化的方式,就是将公式转化为:
x
0
^
=
x
−
f
(
x
)
C
\hat{x_0}=x-\frac{f(x)}{C}
x0^=x−Cf(x) 此时我们再看我们的梯度下降,我们其实想求的就是损失函数的极小值点嘛,因此我们可以记
f
(
x
)
f(x)
f(x)就是
L
′
(
x
)
L'(x)
L′(x);我们带入简化后的牛顿求零点公式,恰好就是:
x
(
n
)
=
x
(
n
−
1
)
−
L
′
(
x
(
n
−
1
)
)
C
x^{(n)}=x^{(n-1)}-\frac{L'(x^{(n-1)})}{C}
x(n)=x(n−1)−CL′(x(n−1)) 其中
C
C
C就是学习率的倒数,因此就是我们普通的梯度下降。因此如果我们为了更快收敛,我们完全可以用
L
′
′
(
x
)
L''(x)
L′′(x)来代替
C
C
C,来得到新的梯度下降公式:
x
(
n
)
=
x
(
n
−
1
)
−
L
′
(
x
(
n
−
1
)
)
L
′
′
(
x
(
n
−
1
)
)
x^{(n)}=x^{(n-1)}-\frac{L'(x^{(n-1)})}{L''(x^{(n-1)})}
x(n)=x(n−1)−L′′(x(n−1))L′(x(n−1))
这样的收敛步数明显会减少,相当于按普通的牛顿迭代法进行操作;但问题在于,有的时候求导是十分麻烦的;尤其后面对于一个神经网络,想再求一次二阶导数是极其复杂的,因此我们还要争取利用牛顿迭代法的思想,去用其他东西来模拟二阶导数。
2.Adagrad
其实Adagrad就是从两个角度去对普通的梯度下降做优化。
第一个角度就是上文所说的寻找一个比较好的能够模拟二阶导数的方法。如下图所示,上面两个图为损失函数,而下面两个图是对应的导数;那在同一个范围内,如果取的点比较多的话,那么导数大的那个函数取出的点的函数值也会更大(因为是损失函数,一定为正值)。因此,我们就可以试图用所取过的这些点的值,来代表我们的导数值。
这里我们就用几何平均数来代表对应的导数值了。
第二个角度就是,我们还是希望总趋势保证步长应该越来越小,因此就把原先的步长除以一个与走的步数相关的函数,保证越来越小。
把这两者合并,如下图所示,出现了抵消现象,得到如下的式子
w
t
+
1
=
w
t
−
η
Σ
i
=
0
t
(
g
i
)
2
g
t
w^{t+1}=w^t-\frac{\eta}{\sqrt{\Sigma_{i=0}^t(g^i)^2}}g^t
wt+1=wt−Σi=0t(gi)2ηgt 注:
w
w
w和
g
g
g都是对单个参数而言的。
三.加速收敛的方式
1.小批量梯度下降
方式就是把参与每轮训练更新参数的样本从全部变量减少为一部分。极限情况下可以每轮只有一个样本或者每轮使用全部样本。
显然,若不是每次使用全部的样本,梯度下降的过程就会有震荡;尤其是每次训练的样本越少,震荡的幅度就会越大。思想好理解,主要讨论的就是下面两个问题:一个是收敛速度的大小,一个是稳定性。
收敛速度指的是所有数据使用了多少轮后可以得到我们需要的满意解。每次更新用的样本数量越少,那使用所有数据一轮走的步数就会增多;如果可以保证相对收敛,显然样本越少越快。
稳定性也就是是否收敛,以及最后收敛的那个区间的大小是否让我们满意。显然样本数量越少,受到样本的随机性的影响越大,就越不稳定;而每次如果取全部样本,则一定稳定(损失函数为凸函数)。
因此综上,现实中一般使用小批量梯度下降,一般每次使用32或64个样本。
2.特征缩放
我们可以考虑如下的一个例子:每个成年人的数据有两个维度,分别为身高(m)和体重(kg),那么身高的范围差不多就是1-2.5;而体重则差不多是30-150,可见范围有数量级的差距;我们的梯度下降中,每个方向上的更新大小(正比于梯度大小)和范围大小无关,为了照顾身高范围小,因此我们的学习率不能太大;但这样就会使得体重这个维度更新的速度与身高数量级上相同,从而使得这个维度上降低的十分的慢1…如果两个维度之间差距更大,可能一个维度上差不多了的时候,还要再花费几百倍的时间…因此我们为了训练速度,希望每个维度的范围之间没有过大的差距。
那这就要改变数据,我们如何在改动的同时,尽量保证每个特征的信息呢?显然,要满足同序性——变换函数
f
f
f变换前
a
>
b
a>b
a>b,则必有
f
(
a
)
>
f
(
b
)
f(a)>f(b)
f(a)>f(b)。从而衍生出了很多种方法:最小-最大值归一化,平均值归一化,标准化等等方法,其实效果差不多,毕竟只是为了增加效率。这里我们介绍标准化。
标准化的意思很简单,将任意一组数据转化成一组均值为0,方差为1的数据。设数据为
x
x
x,维度为
n
n
n,变换函数为
f
f
f,则
x
ˉ
=
1
n
Σ
i
=
1
n
x
i
\large \bar x=\frac{1}{n}\Sigma_{i=1}^nx_i
xˉ=n1Σi=1nxi
σ
=
1
n
Σ
i
=
1
n
(
x
i
−
x
ˉ
)
2
\large \sigma=\sqrt{\frac{1}{n}\Sigma_{i=1}^n(x_i-\bar x)^2}
σ=n1Σi=1n(xi−xˉ)2
f
(
x
i
)
=
x
i
−
x
^
σ
\large f(x_i)=\frac{x_i-\hat x}{\sigma}
f(xi)=σxi−x^
我们对每个维度都做这样的处理,得到的
f
(
x
)
f(x)
f(x)就是我们的新数据了,现在每个维度都已经是处理好的了。
四.最新的优化方式
1.SGDM
SGD就是我们介绍的批次梯度下降,这个M是momentum的缩写,也就是说,更新的过程中有个备忘录,每步更新的时候不仅是考虑到这步的梯度,还要考虑到备忘录带来的影响,类似于“惯性”。
公式如下图所示,其中的
λ
\lambda
λ就是备忘录中的衰减效果,例如
λ
=
0.5
\lambda=0.5
λ=0.5就意味着更新第
k
k
k步的时候,备忘录的值先都衰减一半,再算出第
k
+
1
k+1
k+1步,之后再更新备忘录。
这种方法的优点在于有惯性,所以如果到达了红框所在的这个很平坦的地方(据研究,这种情况非常常见,比陷入局部最优常见多了),普通的梯度下降可能就卡住了;而用SGDM就可以凭借着惯性继续前进从而优化;有的时候也能利用惯性跑出最优解,但这个不一定很靠谱,例如这张图最右面的位置,我们不好判断是不是会跑到右面;也不能保证跑到了右面会不会找到更好的解。
2.RMSProp
这种方法是对Adagrad的一个改进,本质思想也是类似,对于这个学习率的计算中,也存在着衰减的过程,即前面的步骤应该影响力越来越小(Adagrad中每步的影响力是一样大的);同时也使得我们的学习率不会每步都越来越小。
公式如下图所示,其中这个
α
\alpha
α也是衰减率。
3.Adam
理解了上面的两个算法,其实大名鼎鼎的,一般不用研究,只是用来被掉包的Adam就好理解了。Adam其实就是SGDM+RMSProp。
但是问题在于,为什么额外加入了红框所说的步骤呢?所谓的消除误差又是消除什么误差呢?蓝色的这个参数悠悠什么意义呢?这些其实才是关键,其他的确确实实就是把之前的两者结合了。
我们以
v
^
t
\hat v_t
v^t为例来推导。有一点很重要,那就是最后我们的值应该是收敛的,因此足够轮之后,我们的值就是完全不变的,所以说,对于
g
t
g_t
gt来说,只要t足够大,最后一定都是趋近于某个极限值;那前面每轮计算的结果,其实期望值也都是这个极限,毕竟是对同一个值的寻找操作,因此有
E
(
g
1
)
=
E
(
g
2
)
=
.
.
.
.
.
.
=
E
(
g
t
)
E(g_1)=E(g_2)=......=E(g_t)
E(g1)=E(g2)=......=E(gt) 从而也有
E
(
g
1
2
)
=
E
(
g
2
2
)
=
.
.
.
.
.
.
=
E
(
g
t
2
)
E(g_1^2)=E(g_2^2)=......=E(g_t^2)
E(g12)=E(g22)=......=E(gt2)
而对于我们的
v
t
v_t
vt而言,我们不管
β
2
\beta_2
β2的值,每步全是由
g
2
g^2
g2的值来固定,因此有
E
(
v
t
)
=
E
(
g
t
2
)
E(v_t)=E(g_t^2)
E(vt)=E(gt2)
但我们按照之前的公式,对左面展开,有
E
(
v
t
)
=
E
[
β
2
g
t
−
1
+
(
1
−
β
2
)
g
t
]
=
E
[
(
1
−
β
2
)
Σ
i
=
1
t
β
2
t
−
i
g
i
2
]
=
E
(
g
t
2
)
[
(
1
−
β
2
)
(
1
+
β
2
+
β
2
2
+
.
.
.
+
β
2
t
−
1
]
=
E
(
g
t
2
)
(
1
−
β
2
)
1
−
β
2
n
1
−
β
2
=
E
(
g
t
2
)
(
1
−
β
2
n
)
\begin{aligned} E(v_t)&=E[\beta_2g_{t-1}+(1-\beta_2)g_t ]\\&=E[(1-\beta_2)\Sigma_{i=1}^t\beta_2^{t-i}g_i^2]\\&=E(g_t^2)[(1-\beta_2)(1+\beta_2+\beta_2^2+...+\beta_2^{t-1}]\\&=E(g_t^2)(1-\beta_2)\frac{1-\beta_2^n}{1-\beta_2}\\&=E(g_t^2)(1-\beta_2^n)\end{aligned}
E(vt)=E[β2gt−1+(1−β2)gt]=E[(1−β2)Σi=1tβ2t−igi2]=E(gt2)[(1−β2)(1+β2+β22+...+β2t−1]=E(gt2)(1−β2)1−β21−β2n=E(gt2)(1−β2n)
上述的推导也可以称作为"满足矩估计的要求",也可以就是所谓的保证期望值相对应,从而要除以这个偏置项;但说这个操作是什么对二阶动量的矩估计,那可能有点故意吓唬人了…
也可以换一个角度去理解,第一轮有
1
−
β
2
1-\beta_2
1−β2的比例是
g
1
2
g_1^2
g12,因此有
β
2
\beta_2
β2不靠谱(为0),因此为了平衡掉这部分,分母就是
1
−
β
2
1-\beta_2
1−β2;然后第二步,有不靠谱的比例就是
β
2
2
\beta_2^2
β22,因此分母就是
1
−
β
2
2
1-\beta_2^2
1−β22,这样以此类推到第
k
k
k步,就是符合公式上所说的了。
而这个
ϵ
\epsilon
ϵ当然应该是0,这样才能保证期望值对应,但是不得不处理分母为0的情况,因此只能额外加一个很小很小的数,来保证分母不能十分趋近于0,这就是
ϵ
\epsilon
ϵ的来源。
4.更好的优化策略?
最近几年有没有更好的优化策略了呢?答案是没有,实际上目前绝大多数有名的项目,都使用的是Adam或者SGDM训练出来的。这些优化器原理都不复杂,但是已经有了十分强大的优势。通过大量的实践证明,Adam训练速度最快,但是泛化能力(这里指的是训练集上的表现)略逊于SGDM;SGDM训练速度略逊于Adam,但是泛化能力是最好的。
因此最近几年也有很多人尝试融合这两者,或者在其基础上改善。但是,没有显著的稳定的提升,因此也不再介绍更复杂的方法了。
五.总结
实际写代码的过程中,若不是特殊需求,一般无脑调包Adam即可…