我们将会收获什么
我们将会学习深度学习中的一些基本概念,包括优化失败的原因以及如何解决这些问题。首先,我们需要了解为什么优化会失败,这通常是因为模型陷入了局部极小值或鞍点而导致的。接着,我们可以采用自适应学习率和学习率调度等方法来调整学习率,从而提高优化效果。此外,批量归一化也是一种有效的方法,它可以改变误差表面,进而改善优化效果。
3.1 局部极小值与鞍点
在优化过程中,损失函数可能会停留在一个局部最小值处而不是_全局最小值_处,这会导致模型性能不佳。此外,有些情况下,模型可能_无法收敛_,无论怎样调整参数都无法降低损失函数。这些问题的根本原因是什么?
3.1.1 临界点及其种类
梯度为零的点,也就是临界点,包括**局部极小值(local minimum) ,局部极大值(local maximum) 和鞍点(saddle point) **等。这些点的特点是在该点处,梯度的方向与函数的斜率相等,因此无法通过梯度下降来继续优化模型。
3.1.2 判断临界值种类的方法
如何判断一个临界点到底是局部极小值还是鞍点。
①:首先,我们需要了解损失函数的形状,可以通过泰勒级数近似(Tayler series appoximation)** **来近似损失函数。
L
(
θ
)
≈
L
(
θ
′
)
+
(
θ
−
θ
′
)
T
g
+
1
2
(
θ
−
θ
′
)
T
H
(
θ
−
θ
′
)
L(\boldsymbol{\theta})\approx L\left(\boldsymbol{\theta}^{\prime}\right)+\left(\boldsymbol{\theta}-\boldsymbol{\theta}^{\prime}\right)^{\mathrm{T}}\boldsymbol{g}+\frac{1}{2}\left(\boldsymbol{\theta}-\boldsymbol{\theta}^{\prime}\right)^{\mathrm{T}}\boldsymbol{H}\left(\boldsymbol{\theta}-\boldsymbol{\theta}^{\prime}\right)
L(θ)≈L(θ′)+(θ−θ′)Tg+21(θ−θ′)TH(θ−θ′)
第一项 L(θ)′ 告诉我们,当 **θ **跟 θ′ 很近的时候, L(θ) 应该跟 L(θ′) 还蛮靠近的;第二项 (**θ **- θ′ )T **g **中, **g **代表梯度,它是一个向量,可以弥补 L(θ′) 跟 L(θ) 之间的差距。有时候梯度 **g **会写成 ∇L(θ′)。 _gi _是向量 _**g **_的第 _i _个元素,就是 _L _关于 _**θ **_的第 _i _个元素的微分,即
g
i
=
∂
L
(
θ
′
)
∂
θ
i
.
g_i=\frac{\partial L\left(\boldsymbol{\theta}^{\prime}\right)}{\partial\theta_i}.
gi=∂θi∂L(θ′).
②其次,我们需要知道损失函数在某一点附近的海森矩阵和梯度信息。通过对海森矩阵的特征值进行分析,可以判断临界点是局部极小值、局部极大值还是鞍点。
光看 **g **还是没有办法完整地描述 L(θ),还要看第三项 1/2( **θ **- θ′)T **H (θ **- θ′)。第三项跟海森矩阵(Hessian matrix) **H _有关。 _H _里面放的是 _L _的二次微分,它第 _i _行,第 _j _列的值 Hij 就是把 **θ **的第 i 个元素对 L (θ′)作微分,再把 **θ _的第 j 个元素对
∂
L
(
θ
′
)
∂
θ
i
\frac{\partial L\left(\boldsymbol{\theta}^{\prime}\right)}{\partial\theta_i}
∂θi∂L(θ′)作微分后的结果,即
H
i
j
=
∂
2
∂
θ
i
∂
θ
j
L
(
θ
′
)
H_{ij}=\frac{\partial^2}{\partial\theta_i\partial\theta_j}L\left(\boldsymbol{\theta}^{\prime}\right)
Hij=∂θi∂θj∂2L(θ′)
在临界点,梯度 **g **为零,因此 (**θ **- θ′) T**g **为零。所以在临界点的附近,损失函数可被近似为
L
(
θ
)
≈
L
(
θ
′
)
+
1
2
(
θ
−
θ
′
)
T
H
(
θ
−
θ
′
)
;
L(\boldsymbol{\theta})\approx L\left(\boldsymbol{\theta}^{\prime}\right)+\frac{1}{2}\left(\boldsymbol{\theta}-\boldsymbol{\theta}^{\prime}\right)^{\mathrm{T}}\boldsymbol{H}\left(\boldsymbol{\theta}-\boldsymbol{\theta}^{\prime}\right);
L(θ)≈L(θ′)+21(θ−θ′)TH(θ−θ′);
我们可以根据 1/2 (**θ **- θ′)T **H (θ **- θ′) 来判断在 _θ′ _附近的误差表面(error surface) **到底长什么样子。知道误差表面的“地貌”,我们就可以判断 L(θ′) 是局部极小值、局部极大值,还是鞍点。为了符号简洁,我们用向量 **v **来表示 **θ **- θ′, (**θ **- θ′)T **H (θ **- θ′) 可改写为 vTHv,有如下三种情况。
(1)如果对所有 v, vT**Hv **> 0. 这意味着对任意 θ, L(θ) > L(θ′). 只要 θ _在 θ′ 附近, L(θ) 都大于 L(θ′). 这代表 L(θ′) 是附近的一个最低点,所以它是局部极小值。
(2)如果对所有 v, _vTHv < **0. 这意味着对任意 θ, L(θ) < L(θ′), θ′ 是附近最高的一个点, L(θ′) 是局部极大值。
(3)如果对于 v, vT**Hv **有时候大于零,有时候小于零。这意味着在 θ′ 附近,有时候_L(θ) > L(θ′),有时候 L(θ) < L(θ′_). 因此在 θ′ 附近, L(θ′) 既不是局部极大值,也不是局部极小值,而是鞍点。
算出一个海森矩阵后,不需要把它跟所有的 _**v **_都乘乘看,只要看 _H_的特征值。若 _**H **_的所有特征值都是正的, _**H **_为正定矩阵,则 _vT**Hv **> _0,临界点是局部极小值。若 _**H **_的所有特征值都是负的, _**H **_为负定矩阵,则 _vT**Hv **< _0,临界点是局部极大值。若 _**H **_的特征值有正有负,临界点是鞍点。
举个例子,我们有一个简单的神经网络,它只有两个神经元,而且这个神经元还没有激活函数和偏置。输入 x, _x _乘上 _w_1 以后输出,然后再乘上 _w_2,接着再输出,最终得到的数据就是 y。
_y = w_1_w_2_x.
如图所示,可以取 [-_2,2] 之间的 _w_1 跟 _w_2的数值,算出这个范围内 _w_1, _w_2 数值所带来的损失,四个角落的损失是高的。我们用黑色的点来表示临界点,原点 (0,0) 是临界点,另外两排点是临界点。我们可以进一步地判断这些临界点是鞍点还是局部极小值。
除了尝试取所有可能的损失,我们还有其他的方法,比如把损失的函数写出来。损失函数 _L _是正确答案 _y _减掉模型的输出 _y_ˆ = _w_1_w_2_x _后取平方误差(square error),这里只有一组数据,因此不会对所有的训练数据进行加和。令 _x _= 1, _y _= 1,损失函数为
L
=
(
y
−
w
1
w
2
x
)
2
=
(
1
−
w
1
w
2
)
2
L=(y-w_1w_2x)^2=(1-w_1w_2)^2
L=(y−w1w2x)2=(1−w1w2)2
可以求出损失函数的梯度 :
g
=
[
∂
L
∂
w
1
,
∂
L
∂
w
2
]
\boldsymbol{g}=[\frac{\partial L}{\partial w_1},\frac{\partial L}{\partial w_2}]
g=[∂w1∂L,∂w2∂L]
{
∂
L
∂
w
1
=
2
(
1
−
w
1
w
2
)
(
−
w
2
)
;
∂
L
∂
w
2
=
2
(
1
−
w
1
w
2
)
(
−
w
1
)
\begin{cases}\frac{\partial L}{\partial w_1}&=2\left(1-w_1w_2\right)\left(-w_2\right);\\\frac{\partial L}{\partial w_2}&=2\left(1-w_1w_2\right)\left(-w_1\right)\end{cases}
{∂w1∂L∂w2∂L=2(1−w1w2)(−w2);=2(1−w1w2)(−w1)
什么时候梯度会为零(也就是到一个临界点)呢?比如,在原点时, _w_1 = 0, w_2 = 0,此时的梯度为零,原点就是一个临界点,但通过海森矩阵才能判断它是哪种临界点。刚才我们通过取 [-_2,2] 之间的 _w_1 和 _w_2 来判断出原点是一个鞍点,但是假设我们还没有取所有可能的损失,我们要看看能不能够用海森矩阵来判断原点是什么临界点。海森矩阵 _**H **_收集了 _L _的二次微分:
{
H
1
,
1
=
∂
2
L
∂
w
1
2
=
2
(
−
w
2
)
(
−
w
2
)
;
H
1
,
2
=
∂
2
L
∂
w
1
∂
w
2
=
−
2
+
4
w
1
w
2
;
H
2
,
1
=
∂
2
L
∂
w
2
∂
w
1
=
−
2
+
4
w
1
w
2
;
H
2
,
2
=
∂
2
L
∂
w
2
2
=
2
(
−
w
1
)
(
−
w
1
)
.
\begin{cases}H_{1,1}&=\frac{\partial^2L}{\partial w_1^2}=2\left(-w_2\right)\left(-w_2\right);\\H_{1,2}&=\frac{\partial^2L}{\partial w_1\partial w_2}=-2+4w_1w_2;\\H_{2,1}&=\frac{\partial^2L}{\partial w_2\partial w_1}=-2+4w_1w_2;\\H_{2,2}&=\frac{\partial^2L}{\partial w_2^2}=2\left(-w_1\right)\left(-w_1\right).\end{cases}
⎩
⎨
⎧H1,1H1,2H2,1H2,2=∂w12∂2L=2(−w2)(−w2);=∂w1∂w2∂2L=−2+4w1w2;=∂w2∂w1∂2L=−2+4w1w2;=∂w22∂2L=2(−w1)(−w1).
对于原点,只要把 _w_1 = 0, _w_2 = 0 代进去,有海森矩阵
H
=
[
0
−
2
−
2
0
]
.
\boldsymbol{H}=\left[\begin{array}{rr}0&-2\\-2&0\end{array}\right].
H=[0−2−20].
特征值有正有负,因此原点是鞍点。
如果临界点是一个鞍点,还可以再看 H,怎么再看 _**H **_呢, _**H **_怎么告诉我们怎么更新参数呢?设 _λ _为 _H 的一个特征值 λ, u _为其对应的特征向量。对于我们的优化问题,可令 **u **= **θ **- θ′,则
u
T
H
u
=
u
T
(
λ
u
)
=
λ
∥
u
∥
2
.
\boldsymbol{u}^\mathrm{T}\boldsymbol{H}\boldsymbol{u}=\boldsymbol{u}^\mathrm{T}(\lambda\boldsymbol{u})=\lambda\|\boldsymbol{u}\|^2.
uTHu=uT(λu)=λ∥u∥2. (3.10)
若 _λ < _0,则 _λ∥u∥_2 < 0。所以 1 /2 (**θ **- θ′)T **H (θ **- θ′) < 0。此时, L(θ) < L(θ′),且
θ
=
θ
′
+
u
.
θ = θ^′ + u.
θ=θ′+u. (3.11)
沿着 **u _的方向更新 θ,损失就会变小。因为根据式 (3.10) 和式 (3.11),只要 **θ **= _θ′ _+ u,沿着特征向量 **u **的方向去更新参数,损失就会变小,所以虽然临界点的梯度为零,如果我们是在一个鞍点,**只要找出负的特征值,再找出这个特征值对应的特征向量。将其与 ****θ′ **相加,就可以找到一个损失更低的点。 **
在前面的例子中,原点是一个临界点,此时的海森矩阵如式 (3.9) 所示,该海森矩阵有一个负的特征值: _-_2,特征值 _-_2 对应的特征向量有无穷多个。不妨取 **u **= [1, _1]T,作为 _-_2 对应的特征向量。我们其实只要顺着 **u 的方向去更新参数,就可以找到一个比鞍点的损失还要更低的点。以这个例子来看,原点是鞍点,其梯度为零,所以梯度不会告诉我们要怎么更新参数。但海森矩阵的特征向量告诉我们只要往 [1, **1]T 的方向更新。损失就会变得更小,就可以逃离鞍点。
所以从这个角度来看,鞍点似乎并没有那么可怕。但实际上,我们几乎不会真的把海森矩阵算出来,因为海森矩阵需要算二次微分,计算这个矩阵的运算量非常大,还要把它的特征值跟特征向量找出来,所以几乎没有人用这个方法来逃离鞍点。还有一些其他逃离鞍点的方法的运算量都比要算海森矩阵小很多。
3.1.3 逃离鞍点的方法
讲到这边会有一个问题:鞍点跟局部极小值谁比较常见?鞍点其实并没有很可怕,如果我们经常遇到的是鞍点,比较少遇到局部极小值,那就太好了。科幻小说《三体 III:死神永生》中有这样一个情节:东罗马帝国的国王君士坦丁十一世为对抗土耳其人,找来了具有神秘力量的魔法师狄奥伦娜。狄奥伦娜可以于万军丛中取上将首级,但大家不相信她有这么厉害,想要狄奥伦娜先展示下她的力量。于是狄奥伦娜拿出了一个圣杯,大家看到圣杯大吃一惊,因为这个圣杯本来是放在圣索菲亚大教堂地下室的一个石棺里面,而且石棺是密封的,没有人可以打开。狄奥伦娜不仅取得了圣杯,还自称在石棺中放了一串葡萄。于是君士坦丁十一世带人撬开了石棺,发现圣杯真的被拿走了,而是棺中真的有一串新鲜的葡萄,为什么迪奥伦娜可以做到这些事呢?是因为狄奥伦娜可以进入四维的空间。从三维的空间来看这个石棺是封闭的,没有任何路可以进去,但从高维的空间来看,这个石棺并不是封闭的,是有路可以进去的。误差表面会不会也一样呢。
如图 3.5(a) 所示的一维空间中的误差表面,有一个局部极小值。但是在二维空间(如图 3.5(b) 所示),这个点就可能只是一个鞍点。常常会有人画类似图 3.5© 这样的图来告诉我们深度学习的训练是非常复杂的。如果我们移动某两个参数,误差表面的变化非常的复杂,有非常多局部极小值。低维度空间中的局部极小值点,在更高维的空间中,实际是鞍点。同样地,如果在二维的空间中没有路可以走,会不会在更高维的空间中,其实有路可以走?
**在高维空间中,鞍点比局部极小值更为常见。**多数的时候,我们训练到一个梯度很小的地方,参数不再更新,往往只是遇到了鞍点 。
3.2 批量和动量
机器学习中的批量优化方法:在计算梯度的过程中,通常不会对所有数据的损失函数 L 进行计算,而是将其分成一个个批量(batch),每个批量包含** B 笔数据**。每次更新参数时,都会从批量中选择 B 笔数据来计算损失和梯度,并根据梯度更新参数。这个过程被称为一个回合(epoch)。为了防止模型陷入局部最优解,批量还需要进行随机打乱(shuffle)操作,使得每个回合的批量数据都不同。这样可以增加模型的泛化能力,避免过拟合现象的发生。
3.2.1 批量大小对梯度下降法的影响
假设现在我们有 20 笔训练数据,先看下两个最极端的情况,如图 3.8 所示。
• 图 3.8 (a)的情况是没有用批量,批量大小为训练数据的大小,这种使用全批量(fullbatch)的数据来更新参数的方法即批量梯度下降法(Batch Gradient Descent, BGD)。此时模型必须把 20 笔训练数据都看完,才能够计算损失和梯度,参数才能够更新一次。
• 图 3.8(b)中,批量大小等于 1,此时使用的方法即**随机梯度下降法(Stochastic Gradient Descent, SGD) **,也称为增量梯度下降法。批量大小等于 1 意味着只要取出一笔数据即可计算损失、更新一次参数。如果总共有 20 笔数据,那么在每一个回合里面,参数会更新 20 次。用一笔数据算出来的损失相对带有更多噪声,因此其更新的方向如图 3.8 所示,是曲曲折折的 。实际上,批量梯度下降并没有“划分批量”:要把所有的数据都看过一遍,才能够更新一次参数,因此其每次迭代的计算量大。但相比随机梯度下降,批量梯度下降每次更新更稳定、更准确。
随机梯度下降的梯度上引入了随机噪声,因此在非凸优化问题中,其相比批量梯度下降更容易逃离局部最小值
通过实验结果表明,大的批量更新比较稳定,小的批量的梯度的方向是比较有噪声的,但实际上有噪声的梯度反而可以帮助训练。同时,小批量梯度下降法每次是挑一个批量计算损失,所以每一次更新参数的时候所使用的损失函数是有差异的,这种有噪声的更新方式反而对训练其实是有帮助的。
使用 Tesla V100 GPU 在 MNIST数据集得到的实验结果如图 3.9 所示。图 3.9 中横坐标表示批量大小,纵坐标表示给定批量大小的批量,计算梯度并更新参数所耗费的时间。
MNIST 中的“NIST”是指国家标准和技术研究所(National Institute of Standards and Technology),其最初收集了这些数据。 MNIST 中“M”是指修改的(Modified),数据经过预处理以方便机器学习算法使用。 MNIST 数据集收集了数万张手写数字(09̃)的28_×_28 像素的灰度图像及其标签。一般大家第一个会尝试的机器学习的任务,往往就是用 MNIST 做手写数字识别, 这个简单的分类问题是**深度学习研究中的“Hello World”。 **
图 3.10(a) 和图 3.10(b) 的趋势正好是相反的。因此实际上,在有考虑并行计算的时候,大的批量大小反而是较有效率的,一个回合大的批量花的时间反而是比较少的。
批量大小越大,验证集准确率越差。但这不是过拟合,因为批量大小越大,训练准确率也是越低。因为用的是同一个模型,所以这不是模型偏见的问题。 但大的批量大小往往在训练的时候,结果比较差。这个是优化的问题,大的批量大小优化可能会有问题,小的批量大小优化的结果反而是比较好的。 **
一个可能的解释如图 3.12 所示,批量梯度下降在更新参数的时候,沿着一个损失函数来更新参数,走到一个局部最小值或鞍点显然就停下来了。梯度是零,如果不看海森矩阵,梯度下降就无法再更新参数了 。但小批量梯度下降法(mini-batch gradient descent)每次是挑一个批量计算损失,所以每一次更新参数的时候所使用的损失函数是有差异的。选到第一个批量的时候,用 _L_1 计算梯度;选到第二个批量的时候,用 _L_2 计算梯度。假设用 L1 算梯度的时候,梯度是零,就会卡住。但 L2 的函数跟 L1 又不一样, L2 不一定会卡住,可以换下个批量的损失 L2 计算梯度,模型还是可以训练,还是有办法让损失变小,所以这种有噪声的更新方式反而对训练其实是有帮助的_ _**
论文“On Large-Batch Training for Deep Learning: Generalization Gap and Sharp Minima”中,作者在不同数据集上训练了六个网络(包括全连接网络、不同的卷积神经网络),在很多不同的情况都观察到一样的结果。
这篇论文给出了一个解释,如图 3.13 所示,训练损失上面有多个局部最小值,这些局部最小值的损失都很低,其损失可能都趋近于 0。但是局部最小值有好最小值跟坏最小值之分,如果局部最小值在一个“峡谷”里面,它是坏的最小值;如果局部最小值在一个平原上,它是好的最小值。训练的损失跟测试的损失函数是不一样的,这有两种可能。一种可能是本来训练跟测试的分布就不一样;另一种可能是因为训练跟测试都是从采样的数据算出来的,训练跟测试采样到的数据可能不一样,所以它们计算出的损失是有一点差距。 对在一个“盆地”里面的最小值,其在训练跟测试上面的结果不会差太多,只差了一点点。但对在右边在“峡谷”里面的最小值,一差就可以天差地远 。虽然它在训练集上的损失很低,但训练跟测试之间的损失函数不一样,因此测试时,损失函数一变,计算出的损失就变得很大。
**大的批量大小会让我们倾向于走到“峡谷”里面,而小的批量大小倾向于让我们走到“盆地”里面。**小的批量有很多的损失,其更新方向比较随机,其每次更新的方向都不太一样。即使“峡谷”非常窄,它也可以跳出去,之后如果有一个非常宽的“盆地”,它才会停下来。
批量大小是需要去调整的超参数,大的批量跟小的批量各有优缺点,需要根据具体情况选择合适的批量大小来进行训练。(见下表)
评价标准 | 小批量梯度下降 | 批量梯度下降 |
---|---|---|
一次更新的速度(没有并行计算) | 更快 | 更慢 |
一次更新的速度(有并行计算) | 相同 | 相同(批量大小不是很大) |
一个回合的时间 | 更慢 | 更快 |
梯度 | 有噪声 | 稳定 |
优化 | 更好 | 更坏 |
泛化 | 更好 | 更坏 |
3.2.2 动量法
动量法是一种可以对抗鞍点或局部最小值的方法,在梯度下降的过程中加入动量可以使模型跳出局部最优解,从而找到全局最优解。(作用)通过物理学中的惯性原理,我们可以理解动量法的作用,即在梯度下降过程中加入前一步移动的方向,使得模型能够继续前进而不是被困在局部最优解中。同时,动量法还可以考虑过去所有梯度的总和,从而更加全面地考虑模型的优化情况。动量法的优点是可以帮助模型跳出局部最优解,缺点是在某些情况下可能会导致模型无法收敛。
一般的梯度下降(vanilla gradient descent)如图 3.15 所示。初始参数为 _θ_0,计算一下梯度,计算完梯度后,往梯度的反方向去更新参数 _θ_1 = _θ_0 _- ηg_0。有了新的参数 _θ_1 后,再计算一次梯度,再往梯度的反方向,再更新一次参数,到了新的位置以后再计算一次梯度,再往梯度的反方向去更新参数
引入动量后,每次在移动参数的时候,不是只往梯度的反方向来移动参数,而是根据梯度的反方向加上前一步移动的方向决定移动方向。 图 3.16 中红色虚线方向是梯度的反方向,蓝色虚线方向是前一次更新的方向,蓝色实线的方向是下一步要移动的方向。把前一步指示的方向跟梯度指示的方向相加就是下一步的移动方向。如图 3.16 所示,初始的参数值为 _θ_0 = 0,前一步的参数的更新量为 _m_0 = 0。接下来在 _θ_0 的地方,计算梯度的方向 _g_0。下一步的方向是梯度的方向加上前一步的方向,不过因为前一步正好是 0,所以更新的方向跟原来的梯度下降是相同的。但从第二步开始就不太一样了。从第二步开始,计算 _g1,接下来更新的方向为m_2 = _λm_1 _- ηg_1,参数更新为 _θ_2 ,接下来就反复进行同样的过程。
每一步的移动都用 _**m **来表示。m _其实可以写成之前所有计算的梯度的加权和,如式 (3.13)所示。其中 _η _是学习率, _λ _是前一个方向的权重参数,也是需要调的。引入动量后,可以从两个角度来理解动量法。一个角度是动量是梯度的负反方向加上前一次移动的方向。另外一个角度是当加上动量的时候,更新的方向不是只考虑现在的梯度,而是考虑过去所有梯度的总和。
m
0
=
0
m
1
=
−
η
g
0
m
2
=
−
λ
η
g
0
−
η
g
1
\begin{aligned}\boldsymbol{m}_{0}&=0\\\boldsymbol{m}_{1}&=-\eta\boldsymbol{g}_{0}\\\boldsymbol{m}_{2}&=-\lambda\eta\boldsymbol{g}_{0}-\eta\boldsymbol{g}_{1}\end{aligned}
m0m1m2=0=−ηg0=−ληg0−ηg1 (3.13)
一般梯度下降走到一个局部最小值或鞍点时,就被困住了。但有动量还是有办法继续走下去,因为动量不是只看梯度,还看前一步的方向。即使梯度方向往左走,但如果前一步的影响力比梯度要大,球还是有可能继续往右走,甚至翻过一个小丘,也许可以走到更好的局部最小值,这就是动量有可能带来的好处。
3.3 自适应学习率
通常情况下,在训练神经网络的过程中,损失函数会逐渐降低,但当到达某个点之后,损失函数不再继续降低,而是开始波动甚至增加,这就是所谓的“饱和”。本章节通过图表的方式展示了这种情况下的梯度情况,指出这并不是真正的卡住,而是由于梯度较小导致的。
接着,我将介绍如何解决这个问题。一种常见的方法是调整学习率,使其更小一些,以便更好地逼近局部最优解。然而,如果学习率过小,则会导致训练停滞不前。因此,提出了一种新的方法——自适应学习率,即根据不同参数的情况动态调整学习率大小。这种方法可以根据每个参数的梯度情况来决定学习率的大小,从而更好地逼近全局最优解。
一般在训练一个网络的时候,损失原来很大,随着参数不断的更新,损失会越来越小,最后就卡住了,损失不再下降。(一般情况3.18)当我们走到临界点的时候,意味着梯度非常小,但损失不再下降的时候,梯度并没有真的变得很小,图 3.19 给出了示例。图 3.19 中横轴是迭代次数,竖轴是梯度的范数(norm),即梯度这个向量的长度。随着迭代次数增多,虽然损失不再下降,但是梯度的范数并没有真的变得很小。
图 3.20 是误差表面,梯度在山谷的两个谷壁间,不断地来回“震荡”,这个时候损失不会再下降,它不是真的卡到了临界点,卡到了鞍点或局部最小值。但它的梯度仍然很大,只是损失不一定再减小了。所以训练一个网络,训练到后来发现损失不再下降的时候,有时候不是卡在局部最小值或鞍点,只是单纯的损失无法再下降。
举个例子,我们有两个参数 _w _和 b,这两个参数值不一样的时候,损失值也不一样,得到了图 3.21 所示的误差表面,该误差表面的最低点在叉号处。事实上,该误差表面是凸的形状。凸的误差表面的等高线是椭圆形的,椭圆的长轴非常长,短轴相比之下比较短,其在横轴的方向梯度非常小,坡度的变化非常小,非常平坦;其在纵轴的方向梯度变化非常大,误差表面的坡度非常陡峭。现在我们要从黑点(初始点)来做梯度下降。
学习率 _η = 10-_2 的结果如图 3.22(a) 所示。参数在峡谷的两端,参数在山壁的两端不断第“震荡”,损失降不下去,但是梯度仍然是很大的。我们可以试着把学习率设小一点,学习率决定了更新参数的时候的步伐,学习率设太大,步伐太大就无法慢慢地滑到山谷里面。调学习率从 10_-2 调到 10-_7 的结果如图 3.22(b) 所示,参数不再“震荡”了。参数会滑到山谷底后左转,但是这个训练永远走不到终点,因为学习率已经太小了。 AB 段的坡度很陡,梯度的值很大,还能够前进一点。左拐以后, BC 段的坡度已经非常平坦了,这种小的学习率无法再让训练前进。事实上在 BC 段有 10 万个点(10 万次更新),但都无法靠近局部最小值,所以显然就算是一个凸的误差表面,梯度下降也很难训练。
最原始的梯度下降连简单的误差表面都做不好,因此需要更好的梯度下降的版本。在梯度下降里面,所有的参数都是设同样的学习率,这显然是不够的,**应该要为每一个参数定制化学习率,即引入自适应学习率(adaptive learning rate)的方法,**给每一个参数不同的学习率。如图 3.23 所示,如果在某一个方向上,梯度的值很小,非常平坦,我们会希望学习率调大一点;(平原)如果在某一个方向上非常陡峭,坡度很大,我们会希望学习率可以设得小一点。 (峡谷)
3.3.1 AdaGrad
这是一种自适应学习率的方法。它可以自动调整学习率,当梯度较大时,学习率会减小,而当梯度较小时,学习率会增加。这种算法的优点是可以针对不同的参数设置不同的学习率,从而更好地优化模型。在图表中,可以看到不同参数需要不同的学习率,这正是AdaGrad算法所体现的优势。通过这种方法,可以更加高效地训练神经网络,提高模型的准确性和泛化能力。
AdaGrad(Adaptive Gradient) 是典型的自适应学习率方法,其能够根据梯度大小自动调整学习率。 AdaGrad 可以做到梯度比较大的时候,学习率就减小,梯度比较小的时候,学习率就放大
梯度下降更新某个参数 _θ it _的过程为
θ
t
+
1
i
←
θ
t
i
−
η
g
t
i
\boldsymbol{\theta}_{t+1}^{i}\leftarrow\boldsymbol{\theta}_{t}^{i}-\eta\boldsymbol{g}_{t}^{i}
θt+1i←θti−ηgti
_θit _在第 _t _个迭代的值减掉在第 _t _个迭代参数 _i _算出来的梯度
g
t
i
=
∂
L
∂
θ
i
∣
θ
=
θ
t
\boldsymbol{g}_t^i=\left.\frac{\partial L}{\partial\boldsymbol{\theta}^i}\right|_{\boldsymbol{\theta}=\boldsymbol{\theta}_t}
gti=∂θi∂L
θ=θt
_gi t _代表在第 _t _个迭代,即 **θ **= _θt _时, 参数 _θi _对损失 _L _的微分,学习率是固定的。现在要有一个随着参数定制化的学习率,即把原来学习率 _η _变成 η/σit
θ
t
+
1
i
←
θ
t
i
−
η
σ
t
i
g
t
i
\boldsymbol{\theta}_{t+1}^{i}\leftarrow\boldsymbol{\theta}_{t}^{i}-\frac{\eta}{\sigma_{t}^{i}}\boldsymbol{g}_{t}^{i}
θt+1i←θti−σtiηgti
σit 的上标为 i,这代表参数 σ 与 i 相关,不同的参数的 σ 不同。 σti 的下标为 t,这代表参数 σ 与迭代相关,不同的迭代也会有不同的 σ。学习率从 η 改成 _η/σit_的时候,学习率就变得参数相关(parameter dependent)。
参数相关的一个常见的类型是算梯度的均方根(root mean square)。参数的更新过程为
θ
1
i
←
θ
0
i
−
η
σ
0
i
g
0
i
\boldsymbol{\theta}_{1}^{i}\leftarrow\boldsymbol{\theta}_{0}^{i}-\frac{\eta}{\sigma_{0}^{i}}\boldsymbol{g}_{0}^{i}
θ1i←θ0i−σ0iηg0i
其中 _θi _0 是初始化参数。而 _σi0 _的计算过程为 :
σ
0
i
=
(
g
0
i
)
2
=
∣
g
0
i
∣
\sigma_{0}^{i}=\sqrt{\left(\boldsymbol{g}_{0}^{i}\right)^{2}}=\begin{vmatrix}\boldsymbol{g}_{0}^{i}\end{vmatrix}
σ0i=(g0i)2=
g0i
其中 _gi _0 是梯度。将 _σ_0_i _的值代入更新的公式可知 gi_0/ σi _0的值是 +1 或 _-_1。第一次在更新参数,从 _θi _0 更新到 _θi _1 的时候,要么是加上 η,要么是减掉 η,跟梯度的大小无关,这个是第一步的情况。第二次更新参数过程为
θ
2
i
←
θ
1
i
−
η
σ
1
i
g
1
i
\boldsymbol{\theta}_2^i\leftarrow\boldsymbol{\theta}_1^i-\frac\eta{\sigma_1^i}\boldsymbol{g}_1^i
θ2i←θ1i−σ1iηg1i
其中 _σ_1_i _是过去所有计算出来的梯度的平方的平均再开根号,即均方根,如式 (3.20) 所示。
σ
1
i
=
1
2
[
(
g
0
i
)
2
+
(
g
1
i
)
2
]
\sigma_1^i=\sqrt{\frac12\left[\left(\boldsymbol{g}_0^i\right)^2+\left(\boldsymbol{g}_1^i\right)^2\right]}
σ1i=21[(g0i)2+(g1i)2]
同样的操作反复继续下去,如式 (3.21) 所示
θ
3
i
←
θ
2
i
−
η
σ
2
i
g
2
i
σ
2
i
=
1
3
[
(
g
0
i
)
2
+
(
g
1
i
)
2
+
(
g
2
i
)
2
]
\boldsymbol{\theta}_3^i\leftarrow\boldsymbol{\theta}_2^i-\frac\eta{\sigma_2^i}\boldsymbol{g}_2^i\quad\sigma_2^i=\sqrt{\frac13\left[\left(\boldsymbol{g}_0^i\right)^2+\left(\boldsymbol{g}_1^i\right)^2+\left(\boldsymbol{g}_2^i\right)^2\right]}
θ3i←θ2i−σ2iηg2iσ2i=31[(g0i)2+(g1i)2+(g2i)2]
第 _t _+ 1 次更新参数的时候,即
θ
t
+
1
i
←
θ
t
i
−
η
σ
t
i
g
t
i
σ
t
i
=
1
t
+
1
∑
i
=
0
t
(
g
t
i
)
2
\boldsymbol{\theta}_{t+1}^i\leftarrow\boldsymbol{\theta}_t^i-\frac\eta{\sigma_t^i}\boldsymbol{g}_t^i\quad\sigma_t^i=\sqrt{\frac1{t+1}\sum_{i=0}^t\left(\boldsymbol{g}_t^i\right)^2}
θt+1i←θti−σtiηgtiσti=t+11∑i=0t(gti)2
有两个参数: _θ_1 和 _θ_2。 _θ_1 坡度小, _θ_2 坡度大。因为 _θ_1 坡度小,根据式 (3.22), _θi _1 这个参数上面算出来的梯度值都比较小,因为梯度算出来的值比较小,所以算出来的 _σti _就小, _σti _小学习率就大。反过来, _θ_1 坡度大,所以计算出的梯度都比较大, _σti _就比较大,在更新的时候,步伐(参数更新的量)就比较小。因此有了 _σti _这一项以后,就可以随着梯度的不同,每一个参数的梯度的不同,来自动调整学习率的大小。
3.3.2 RMSProp
RMSprop算法根据误差曲面的不同部分自适应地调整每个参数的学习率,使得在陡峭的部分使用小的学习率,而在平坦的部分使用大的学习率。虽然该算法没有论文支持,但是在深度学习领域被广泛使用。
RMSProp(Root Mean Squared propagation)
①第一步跟 Adagrad 的方法是相同的,
σ
0
i
=
(
g
0
i
)
2
=
∣
g
0
i
∣
\sigma_{0}^{i}=\sqrt{\left(g_{0}^{i}\right)^{2}}=\begin{vmatrix}\boldsymbol{g}_{0}^{i}\end{vmatrix}
σ0i=(g0i)2=
g0i
②第二步更新过程为
θ
2
i
←
θ
1
i
−
η
σ
1
i
g
1
i
σ
1
i
=
α
(
σ
0
i
)
2
+
(
1
−
α
)
(
g
1
i
)
2
\boldsymbol{\theta}_2^i\leftarrow\boldsymbol{\theta}_1^i-\frac\eta{\sigma_1^i}\boldsymbol{g}_1^i\quad\sigma_1^i=\sqrt{\alpha\left(\sigma_0^i\right)^2+\left(1-\alpha\right)\left(\boldsymbol{g}_1^i\right)^2}
θ2i←θ1i−σ1iηg1iσ1i=α(σ0i)2+(1−α)(g1i)2
其中 0 _< α < _1,其是一个可以调整的超参数。计算 _θi _1 的方法跟 AdaGrad 算均方根不一样,在算均方根的时候,每一个梯度都有同等的重要性,但在 RMSprop 里面,可以自己调整现在的这个梯度的重要性。如果 _α _设很小趋近于 0,代表 _gi _1 相较于之前算出来的梯度而言,比较重要;如果 _α _设很大趋近于 1,代表 _gi _1 比较不重要,之前算出来的梯度比较重要。同样的过程就反复继续下去,如式 (3.25) 所示
θ
3
i
←
θ
2
i
−
η
σ
2
i
g
2
i
σ
2
i
=
α
(
σ
1
i
)
2
+
(
1
−
α
)
(
g
2
i
)
2
θ
t
+
1
i
←
θ
t
i
−
η
σ
t
i
g
t
i
σ
t
i
=
α
(
σ
t
−
1
i
)
2
+
(
1
−
α
)
(
g
t
i
)
2
\boldsymbol{\theta}_3^i\leftarrow\boldsymbol{\theta}_2^i-\frac\eta{\sigma_2^i}\boldsymbol{g}_2^i\quad\sigma_2^i=\sqrt{\alpha\left(\sigma_1^i\right)^2+\left(1-\alpha\right)\left(\boldsymbol{g}_2^i\right)^2}\\\boldsymbol{\theta}_{t+\boldsymbol{1}}^i\leftarrow\boldsymbol{\theta}_t^i-\frac\eta{\sigma_t^i}\boldsymbol{g}_t^i\quad\sigma_t^i=\sqrt{\alpha\left(\sigma_{t-1}^i\right)^2+\left(1-\alpha\right)\left(\boldsymbol{g}_t^i\right)^2}
θ3i←θ2i−σ2iηg2iσ2i=α(σ1i)2+(1−α)(g2i)2θt+1i←θti−σtiηgtiσti=α(σt−1i)2+(1−α)(gti)2
RMSProp 通过 _α _可以决定, _gi t _相较于之前存在 _σti-_1 里面的 _gi 1, gi 2, · · · · · · , gi t-_1 的重要性有多大。如果使用 RMSprop,就可以动态调整 _σti _这一项。图 3.26 中黑线是误差表面,球就从 A 走到 B, AB 段的路很平坦, _**g **_很小,更新参数的时候,我们会走比较大的步伐。走动BC 段后梯度变大了, AdaGrad 反应比较慢,而 RMSprop 会把 _α _设小一点,让新的、刚看到的梯度的影响比较大,很快地让 _σti _的值变大,很快地让步伐变小, RMSprop 可以很快地“踩刹车”。如果走到 CD 段, CD 段是平坦的地方,可以调整 α,让其比较看重最近算出来的梯度,梯度一变小, _σti _的值就变小了,走的步伐就变大了。
3.3.3 Adam
最常用的优化的策略或者优化器(optimizer) 是Adam(Adaptive moment estimation)。 Adam 可以看作 RMSprop 加上动量,其使用动量作为参数更新方向,并且能够自适应调整学习率。 PyTorch 里面已经写好了 Adam 优化器,这个优化器里面有一些超参数需要人为决定,但是往往用 PyTorch 预设的参数就足够好了。
3.4 学习率调度
如图 3.22 所示的简单的误差表面,我们都训练不起来,加上自适应学习率以后,使用AdaGrad 方法优化的结果如图 3.27 所示。一开始优化的时候很顺利,在左转的时候,有 AdaGrad 以后,可以再继续走下去,走到非常接近终点的位置。走到 BC 段时,因为横轴方向的梯度很小,所以学习率会自动变大,步伐就可以变大,从而不断前进。接下来的问题走到图 3.27中红圈的地方,快走到终点的时候突然“爆炸”了。 _σti _是把过去所有的梯度拿来作平均。在 AB段梯度很大,但在 BC 段,纵轴的方向梯度很小,因此纵轴方向累积了很小的 σti,累积到一定程度以后,步伐就变很大,但有办法修正回来。因为步伐很大,其会走到梯度比较大的地方。走到梯度比较大的地方后, _σti _会慢慢变大,更新的步伐大小会慢慢变小,从而回到原来的路线。
通过学习率调度(learning rate scheduling) 可以解决这个问题。之前的学习率调整方法中 _η _是一个固定的值,而在学习率调度中 _η _跟时间有关,如式 (3.26) 所示。学习率调度中最常见的策略是学习率衰减(learning rate decay),也称为学习率退火(learning rate annealing)。随着参数的不断更新,让 _η _越来越小,如图 3.28 所示。图 3.22b 的情况,如果加上学习率下降,可以很平顺地走到终点,如图 3.29 所示。在图 3.22b 红圈的地方,虽然步伐很大,但 _η _变得非常小,步伐乘上 _η _就变小了,就可以慢慢地走到终点。
θ
t
+
1
i
←
θ
t
i
−
η
t
σ
t
i
g
t
i
\boldsymbol{\theta}_{t+1}^{i}\leftarrow\boldsymbol{\theta}_{t}^{i}-\frac{\eta_{t}}{\sigma_{t}^{i}}\boldsymbol{g}_{t}^{i}
θt+1i←θti−σtiηtgti (3.26)
除了学习率下降以外,还有另外一个经典的学习率调度的方式———预热。预热的方法是让学习率先变大后变小,至于变到多大、变大的速度、变小的速度是超参数。 残差网络论文 里面是有预热的,在残差网络里面,学习率先设置成 0.01,再设置成 0.1,并且其论文还特别说明,一开始用 0.1 反而训练不好。除了残差网络, BERT 和 Transformer 的训练也都使用了预热
Q:为什么需要预热?
A:当我们使用 Adam、 RMSprop 或 AdaGrad 时,需要计算 σ。而 _σ _是一个统计的结果。从 _σ _可知某一个方向的陡峭程度。统计的结果需要足够多的数据才精准,一开始统计结果 _σ _是不精准的。一开始学习率比较小是用来探索收集一些有关误差表面的情报(类似了解一下地形),先收集有关 _σ _的统计数据,等 _σ _统计得比较精准以后,再让学习率慢慢爬升。如果读者想要学更多有关预热的东西可参考 Adam 的进阶版———RAdam。
3.5 优化总结
所以我们从最原始的梯度下降,进化到这一个版本,如式 (3.27) 所示。
θ
t
+
1
i
←
θ
t
i
−
η
t
σ
t
i
m
t
i
\boldsymbol{\theta}_{t+1}^{i}\leftarrow\boldsymbol{\theta}_{t}^{i}-\frac{\eta_{t}}{\sigma_{t}^{i}}\boldsymbol{m}_{t}^{i}
θt+1i←θti−σtiηtmti, (3.27)
_mi t _是动量。
这个版本里面有动量,其不是顺着某个时刻算出的梯度方向来更新参数,而是把过去所有算出梯度的方向做一个加权总和当作更新的方向。接下来的步伐大小为 mi t/σit。最后通过 ηt 来实现学习率调度。这个是目前优化的完整的版本,这种优化器除了 Adam 以外,还有各种变形。但其实各种变形是使用不同的方式来计算 _mi t _或 σti,或者是使用不同的学习率调度的方式。
Q:动量 _mi t _考虑了过去所有的梯度,均方根 _σi t_考虑了过去所有的梯度,一个放在分子,一个放在分母,并且它们都考虑过去所有的梯度,不就是正好抵消了吗?
A: _mi t _和 _σi t_在使用过去所有梯度的方式是不一样的,动量是直接把所有的梯度都加起来,所以它有考虑方向,它有考虑梯度的正负。但是均方根不考虑梯度的方向,只考虑梯度的大小,计算 _σi t_的时候,都要把梯度取一个平方项,把平方的结果加起来,所以只考虑梯度的大小,不考虑它的方向,所以动量跟 _σi t_计算出来的结果并不会互相抵消
3.6 分类
分类与回归是深度学习最常见的两种问题,第一章的观看次数预测属于回归问题,本节将介绍分类问题。
3.6.1 分类与回归的关系
回归是指输入一个向量x,输出yˆ,希望yˆ跟某个标签y越接近越好。而分类则可以看作是一种特殊的回归问题,输入x后,输出仍是一个标量yˆ,要让其跟正确答案的类别越接近越好。为了更好地表示类别,有时需要用**独热向量(one-hot vector)**来代替数字表示。例如,如果有三个类别,则标签y就是一个三维向量,每个类别对应一个独热向量。这样就不会存在类与类之间距离不一致的问题了。当目标y是一个向量时,网络也需要输出相应数量的数字才能实现分类任务。通过这种方式,我们可以更加准确地判断输入数据所属的类别。
3.6.2 带有 softmax 的分类
按照上述的设定,分类实际过程是:输入 x,乘上 W,加上 b,通过激活函数 σ,乘上_W ′_,再加上 _b′ _得到向量 yˆ。但实际做分类的时候,往往会把 _y_ˆ 通过 softmax 函数得到 y′,才去计算 _y′ _跟 _y_ˆ 之间的距离。
Q:为什么分类过程中要加上 softmax 函数?
A:一个比较简单的解释是, _y _是独热向量,所以其里面的值只有 0 跟 1,但是 _y_ˆ 里面有任何值。既然目标只有 0 跟 1,但 _y_ˆ 有任何值,可以先把它归一化到 0 到 1 之间,这样才能跟标签的计算相似度
😉一句话说明,有点类似归一化。
softmax 的计算如式 (3.28) 所示,先把所有的 _y _取一个指数(负数取指数后也会变成正的),再对其做归一化(除掉所有 _y _的指数值的和)得到 y′。图 3.33 是 softmax 的块(block),输入 _y_1、 _y_2 和 _y_3,产生 y_1′ _、 y_2′ _和 y_3′ _。比如 _y_1 = 3, _y_2 = 1, _y_3 = _-3,取完指数的时候,exp(3) = 20、 exp(1) = 2.7 和 exp(-3) = 0._05,做完归一化后,就变成 0.88、 0.12 跟 0。 _-_3 取完指数,再做归一化以后,会变成趋近于 0 的值。所以 softmax 除了归一化,让 y_1′ _、 y_2′ 和_y_3′ _,变成 0 到 1 之间,和为 1 以外,它还会让大的值跟小的值的差距更大
y
i
′
=
exp
(
y
i
)
∑
j
exp
(
y
i
)
y_i^{\prime}=\frac{\exp(y_i)}{\sum_j\exp(y_i)}
yi′=∑jexp(yi)exp(yi) (3.28)
其中,
1
>
y
i
′
>
0
,
∑
i
y
i
′
=
1
。
1>y_i^{\prime}>0,\quad\sum_iy_i^{\prime}=1\text{。}
1>yi′>0,∑iyi′=1。
图 3.33 考虑了三个类的状况,两个类也可以直接套 softmax 函数。但一般有两个类的时候,我们不套 softmax,而是直接取 sigmoid。当只有两个类的时候, sigmoid 和 softmax 是等价的。
3.6.3 分类损失
当我们把 _x _输入到一个网络里面产生 _y_ˆ 后,通过 softmax 得到 y′,再去计算 _y′ _跟 _y _之间的距离 e,如图 3.34 所示。
计算 _y′ _跟 _y _之间的距离不只一种做法,可以是如式 (3.29) 所示的均方误差,即把 _y _里面每一个元素拿出来,计算它们的平方和当作误差。
e
=
∑
i
(
y
i
−
y
i
′
)
2
e=\sum_i(y_i-y_i^{\prime})^2
e=∑i(yi−yi′)2 (3.29)
但如式 (3.30) 所示的**交叉熵(Cross Entropy )**更常用,当 _y_ˆ 跟 _y′ _相同时,可以最小化交叉熵的值,此时均方误差也是最小的。**最小化交叉熵其实就是最大化似然(maximize likelihood) **
e
=
−
∑
i
y
i
ln
y
i
′
e=-\sum_iy_i\ln y_i^{\prime}
e=−∑iyilnyi′ (3.30)
图 3.36 是分别在 _e _为均方误差和交叉熵时, _y_1、 _y_2 的变化对损失的影响,对误差表面的影响,红色代表损失大,蓝色代表损失小。如果 _y_1 很大, _y_2 很小,代表 y_1′ _会很接近 1, y_2′_会很接近 0。所以不管 _e _取均方误差或交叉熵,如果 _y_1 大、 _y_2 小,损失都是小的;如果 _y_1小, _y_2 大, y_1′ _是 0, y_2′ _是 1,这个时候损失会比较大。
图 3.36 中左上角损失大,右下角损失小,所以期待最后在训练的时候,参数可以“走”到右下角的地方。假设参数优化开始的时候,对应的损失都是左上角。如果选择交叉熵,如图 3.36(a) 所示,左上角圆圈所在的点有斜率的,所以可以通过梯度,一路往右下的地方“走”;如果选均方误差,如图 3.36(b) 所示,左上角圆圈就卡住了,均方误差在这种损失很大的地方,它是非常平坦的,其梯度是非常小趋近于 0 的。如果初始时在圆圈的位置,离目标非常远,其梯度又很小,无法用梯度下降顺利地“走”到右下角。
因此做分类时,选均方误差的时候,如果没有好的优化器,有非常大的可能性会训练不起来。如果用 Adam,虽然图 3.36(b) 中圆圈的梯度很小,但 Adam 会自动调大学习率,还有机会走到右下角,不过训练的过程比较困难。总之,改变损失函数可以改变优化的难度。
3.7 批量归一化
如果误差表面很崎岖,它比较难训练。能不能直接改误差表面的地貌,“把山铲平”,让它变得比较好训练呢? 批量归一化(Batch Normalization, BN) 就是其中一个“把山铲平”的想法。不要小看优化这个问题,有时候就算误差表面是凸(convex)的,它就是一个碗的形状,都不一定很好训练。如图 3.37 所示,假设两个参数对损失的斜率差别非常大,在 _w_1 这个方向上面,斜率变化很小,在 _w_2 这个方向上面斜率变化很大。
在有的线性的的模型里面,当输入的特征,每一个维度的值,它的范围差距很大的时候,我们就可能产生像这样子的误差表面,就可能产生不同方向,斜率非常不同,坡度非常不同的误差表面所以怎么办呢,有没有可能给特征里面不同的维度,让它有同样的数值的范围。如果我们可以给不同的维度,同样的数值范围的话,那我们可能就可以制造比较好的误差表面,让训练变得比较容易一点其实有很多不同的方法,这些不同的方法往往就合起来统称为特征归一化(feature normalization)。
特征归一化的一种可能性,即 Z 值归一化(Z-score normalization),也称为标准化(standardization)。它并不是特征归一化的全部,假设 _x_1 到 xR,是我们所有的训练数据的特征向量。我们把所有训练数据的特征向量,统统都集合起来。向量 _x_1 里面就_x_1 1 代表 _x_1 的第一个元素, x_2 1 代表 x_2 的第一个元素,以此类推。我们把不同笔数据即不同特征向量,同一个维度里面的数值,把它取出来,对于每个维度 i,计算其**平均值(mean) _mi_和标准差(standard deviation) **σi。接下来我们就可以做一种归一化
x
~
i
r
←
x
i
r
−
m
i
σ
i
\tilde{x}_i^r\leftarrow\frac{x_i^r-m_i}{\sigma_i}
x~ir←σixir−mi (3.31)
我们就是把这边的某一个数值 x,减掉这一个维度算出来的平均值,再除掉这个维度,算出来的标准差,得到新的数值 _x_˜。得到新的数值以后,再把新的数值把它塞回去。
归一化有个好处,做完归一化以后,**这个维度上面的数值就会平均是 0,其方差是 1,所以这一排数值的分布就都会在 0 上下;对每一个维度都做一样的归一化,所有特征不同维度的数值都在 0 上下,可能就可以制造一个比较好的误差表面。所以像这样子的特征归一化方式往往对训练有帮助,它可以让在做梯度下降的时候,损失收敛更快一点,**训练更顺利一点。
3.7.1 考虑深度学习
_x_˜ 代表归一化的特征,把它丢到深度网络里面,去做接下来的计算和训练。如图 3.41 所示, _x_˜1 通过第一层得到 _z_1,
如何对 _**z **_做特征归一化? _**z **_可以看成另外一种特征。首先计算下 _z1, z2, z_3 的平均值,
μ
=
1
3
∑
i
=
1
3
z
i
\boldsymbol{\mu}=\frac13\sum_{i=1}^3\boldsymbol{z}^i
μ=31∑i=13zi (3.32)
接下来计算标准差
σ
=
1
3
∑
i
=
1
3
(
z
i
−
μ
)
2
\boldsymbol{\sigma}=\sqrt{\frac13\sum_{i=1}^3\left(\boldsymbol{z}^i-\boldsymbol{\mu}\right)^2}
σ=31∑i=13(zi−μ)2
注意,式 (3.33) 中的平方就是指对每一个元素都去做平方,开根号指的是对向量里面的每一个元素开根号。最后,根据计算出的 _**µ **和 **σ _进行归一化:
z
~
i
=
z
i
−
μ
σ
\tilde{\boldsymbol{z}}^i=\frac{\boldsymbol{z}^i-\boldsymbol{\mu}}\sigma
z~i=σzi−μ (3.34)
其中,除号代表逐元素的除,即分子分母两个向量对应元素相除。归一化的过程如图 3.42 所示。
使用batch的原因;因为训练数据非常多,现在一个数据集可能有上百万笔数据, GPU 的显存无法把它整个数据集的数据都加载进去。因此,在实现的时候,我们不会让这一个网络考虑整个训练数据里面的所有样本,而是只会考虑一个批量里面的样本。比如批量设 64,这个网络就是把 64 笔数据读进去,计算这 64 笔数据的 µ, σ,对这 64 笔数据做归一化。因为实际实现的时候,只对一个批量里面的数据做归一化,所以技巧称为批量归一化。一定要有一个够大的批量,才算得出 **µ, σ。所以批量归一化适用于批量大小比较大的时候,批量大小如果比较大,也许这个批量大小里面的数据就足以表示整个数据集的分布。这个时候就不需要对整个数据集做特征归一化,而改成只在一个批量上做特征归一化作为近似。
在做批量归一化的时候,如图 3.44 所示,往往还会做如下操作:
z
^
i
=
γ
⊙
z
~
i
+
β
\hat{\boldsymbol{z}}^i=\gamma\odot\tilde{\boldsymbol{z}}^i+\beta
z^i=γ⊙z~i+β
其中, ⊙ 代表逐元素的相乘。 β, γ 可以想成是网络的参数,需要另外再被学习出来。
Q:为什么要加上 _**β **_跟 _**γ **_呢?
A:如果做归一化以后, _z_˜ 的平均值一定是 0,如果平均值是 0 的话,这会给网络一些限制,这个限制可能会带来负面的影响,所以需要把 _β, **γ **_加回去,让网络隐藏层的输出平均值不是 0。让网络学习 _β, **γ **_来调整一下输出的分布,从而来调整 _z_ˆ 的分布。
Q:批量归一化是为了要让每一个不同的维度的范围相同,如果把 _**γ **_跟 _**β **_加进去,这样不同维度的分布,其范围不会又都不一样了吗?
A:有可能,但是实际上在训练的时候, _**γ **_的初始值都设为 1,所以 _**γ **_值都为 1 的向量。 _**β **_是值全部都是 0 的向量,即零向量。所以让网络在一开始训练的时候,每一个维度的分布,是比较接近的,也许训练到后来,已经训练够长的一段时间,已经找到一个比较好的误差表面,走到一个比较好的地方以后,再把 _γ, **β **_慢慢地加进去,所以加了 _γ, **β **_的批量归一化,往往对训练是有帮助的。
3.7.2 测试时的批量归一化
测试(testing)有时候又称为推断(inference)。批量归一化在测试的时候,会有什么样的问题呢?在测试的时候,我们一次会得到所有的测试数据,确实也可以在测试的数据上面,制造一个一个批量。但是假设系统上线,做一个真正的线上的应用,比如批量大小设 64,我一定要等 64 笔数据都进来,才做一次做运算,这显然是不行的
但如果在测试的时候,根本就没有批量,如何算 _µ, σ _呢?所以真正的实现上的解法是这个样子的。批量归一化在测试的时候,并不需要做什么特别的处理, PyTorch 已经处理好了。在训练的时候,如果有在做批量归一化,每一个批量计算出来的 µ, σ,都会拿出来算移动平均(moving average)。假设现在有各个批量计算出来的 µ1, µ2, µ3, · · · · · · , µt,则可以计算移动平均
μ
ˉ
←
p
μ
ˉ
+
(
1
−
p
)
μ
t
\bar{\boldsymbol{\mu}}\leftarrow p\bar{\boldsymbol{\mu}}+(1-p)\boldsymbol{\mu}^{t}
μˉ←pμˉ+(1−p)μt (3.36)
其中, _µ_¯ 是 _**µ **_的个平均值, _p _是因子,这也是一个常数,这也是一个超参数,也是需要调的那种。在 PyTorch 里面, _p _设 0.1。计算滑动平均来更新 _**µ **_的平均值。最后在测试的时候,就不用算批量里面的 _**µ **_跟 _**σ **_了。因为测试的时候,在真正应用上也没有批量,就可以就直接拿_µ_¯ 跟 _σ_¯ ,也就是 _µ, **σ **_在训练的时候,得到的移动平均来取代原来的 _**µ **_跟 σ,如图 3.45 所示,这就是批量归一化在测试的时候的运作方式。
3.7.3 内部协变量偏移
接下来的问题就是批量归一化为什么会有帮助呢?原始的批量归一化论文里面提出**内部协变量偏移(internal covariate shift) 概念。如图 3.47 所示,假设网络有很多层, -**x _通过第一层后得到 a, **a **通过第二层以后得到 b;计算出梯度以后,把 **A **更新成 A′,把 **B **这一层的参数更新成 B′。但是作者认为说,**我们在计算 ****B ****更新到 ****B′ ****的梯度的时候,这个时候前一层的参数是 **A,或者是前一层的输出是 **a_。那当前一层从 ****A ****变成 ****A′ ****的时候,其输出就从 ****a ****变成 **a′ **。但是我们计算这个梯度的时候,是根据 ****a ****算出来,所以这个更新的方向也许它适合用在 ****a ****上,但不适合用在 ****a′ ****上面。因为我们每次都有做批量归一化,就会让 ****a **和**a′ ****的分布比较接近,也许这样就会对训练有帮助。**但是论文“How Does Batch Normalization Help Optimization?”[11] 认为内部协变量偏移有问题。这篇论文从不同的角度来说明内部协变量偏移不一定是训练网络的时候的一个问题。批量归一化会比较好,可能不一定是因为它解决了内部协变量偏移。这篇论文里面做了很多实验,比如其比较了训练的时候 _**a **_的分布的变化,发现不管有没有做批量归一化,其变化都不大。就算是变化很大,对训练也没有太大的伤害。不管是根据 _**a **_算出来的梯度,还是根据 _a′ _算出来的梯度,方向居然都差不多。内部协变量偏移可能不是训练网络的时候,最主要的问题,它可能也不是批量归一化会好的一个的关键。
协变量偏移(covariate shift),训练集和预测集样本分布不一致的问题就叫做协变量偏移现象,这个词汇是原来就有的,内部协变量偏移是批量归一化的作者自己发明的 。
为什么批量归一化会比较好呢,那在这篇“How Does Batch Normalization Help Optimization?”这篇论文从实验和理论上,**至少支持批量归一化可以改变误差表面,让误差表面比较不崎岖这个观点。所以这个观点是有理论的支持,也有实验的佐证的。如果要让网络误差表面变得比较不崎岖,其实不一定要做批量归一化,还有很多其他的方法都可以让误差表面变得不崎岖,这篇论文就试了一些其他的方法,发现跟批量归一化表现也差不多,甚至还稍微好一点,这篇论文的作者也觉得批量归一化是一种偶然的(见下图serendipitous)**发现,但无论如何,其是一个有用的方法。其实批量归一化不是唯一的归一化,还有很多归一化方法,比如批量重归一化(batch renormalization) 、层归一化(layer normalization) 、实例归一化(instance normalization) 、组归一化(group normalization) 、权重归一化(weight normalization) 和谱归一化(spectrum normalization)。