
【作者主页】Francek Chen
【专栏介绍】⌈ ⌈ ⌈PyTorch深度学习 ⌋ ⌋ ⌋ 深度学习 (DL, Deep Learning) 特指基于深层神经网络模型和方法的机器学习。它是在统计机器学习、人工神经网络等算法模型基础上,结合当代大数据和大算力的发展而发展出来的。深度学习最重要的技术特征是具有自动提取特征的能力。神经网络算法、算力和数据是开展深度学习的三要素。深度学习在计算机视觉、自然语言处理、多模态数据分析、科学探索等领域都取得了很多成果。本专栏介绍基于PyTorch的深度学习算法实现。
【GitCode】专栏资源保存在我的GitCode仓库:https://gitcode.com/Morse_Chen/PyTorch_deep_learning。
文章目录
在随机梯度下降一节中,我们详述了如何执行随机梯度下降,即在只有嘈杂的梯度可用的情况下执行优化时会发生什么。对于嘈杂的梯度,我们在选择学习率需要格外谨慎。如果衰减速度太快,收敛就会停滞。相反,如果太宽松,我们可能无法收敛到最优解。
一、基础
本节将探讨更有效的优化算法,尤其是针对实验中常见的某些类型的优化问题。
(一)泄漏平均值
上一节中我们讨论了小批量随机梯度下降作为加速计算的手段。它也有很好的副作用,即平均梯度减小了方差。小批量随机梯度下降可以通过以下方式计算:
g t , t − 1 = ∂ w 1 ∣ B t ∣ ∑ i ∈ B t f ( x i , w t − 1 ) = 1 ∣ B t ∣ ∑ i ∈ B t h i , t − 1 (1) \mathbf{g}{t, t-1} = \partial{\mathbf{w}} \frac{1}{|\mathcal{B}t|} \sum{i \in \mathcal{B}t} f(\mathbf{x}{i}, \mathbf{w}_{t-1}) = \frac{1}{|\mathcal{B}t|} \sum{i \in \mathcal{B}t} \mathbf{h}{i, t-1} \tag{1} gt,t−1=∂w∣Bt∣1i∈Bt∑f(xi,wt−1)=∣Bt∣1i∈Bt∑hi,t−1(1)
为了保持记法简单,在这里我们使用 h i , t − 1 = ∂ w f ( x i , w t − 1 ) \mathbf{h}{i, t-1} = \partial{\mathbf{w}} f(\mathbf{x}i, \mathbf{w}{t-1}) hi,t−1=∂wf(xi,wt−1)作为样本 i i i的随机梯度下降,使用时间 t − 1 t-1 t−1时更新的权重 t − 1 t-1 t−1。如果我们能够从方差减少的影响中受益,甚至超过小批量上的梯度平均值,那很不错。完成这项任务的一种选择是用泄漏平均值 (leaky average)取代梯度计算:
v t = β v t − 1 + g t , t − 1 (2) \mathbf{v}t = \beta \mathbf{v}{t-1} + \mathbf{g}{t, t-1}\tag{2} vt=βvt−1+gt,t−1(2) 其中 β ∈ ( 0 , 1 ) \beta \in (0, 1) β∈(0,1)。这有效地将瞬时梯度替换为多个"过去"梯度的平均值。 v \mathbf{v} v被称为动量 (momentum),它累加了过去的梯度。为了更详细地解释,让我们递归地将 v t \mathbf{v}t vt扩展到
v t = β 2 v t − 2 + β g t − 1 , t − 2 + g t , t − 1 = ... = ∑ τ = 0 t − 1 β τ g t − τ , t − τ − 1 (3) \begin{aligned} \mathbf{v}t = \beta^2 \mathbf{v}{t-2} + \beta \mathbf{g}{t-1, t-2} + \mathbf{g}{t, t-1} = \ldots = \sum_{\tau = 0}^{t-1} \beta^{\tau} \mathbf{g}_{t-\tau, t-\tau-1}\tag{3} \end{aligned} vt=β2vt−2+βgt−1,t−2+gt,t−1=...=τ=0∑t−1βτgt−τ,t−τ−1(3) 其中,较大的 β \beta β相当于长期平均值,而较小的 β \beta β相对于梯度法只是略有修正。新的梯度替换不再指向特定实例下降最陡的方向,而是指向过去梯度的加权平均值的方向。这使我们能够实现对单批量计算平均值的大部分好处,而不产生实际计算其梯度的代价。
上述推理构成了"加速"梯度方法的基础,例如具有动量的梯度。在优化问题条件不佳的情况下(例如,有些方向的进展比其他方向慢得多,类似狭窄的峡谷),"加速"梯度还额外享受更有效的好处。此外,它们允许我们对随后的梯度计算平均值,以获得更稳定的下降方向。诚然,即使是对于无噪声凸问题,加速度这方面也是动量如此起效的关键原因之一。
正如人们所期望的,由于其功效,动量是深度学习及其后优化中一个深入研究的主题。例如,请参阅文章"Why Momentum Really Works",观看深入分析和互动动画。长期以来,深度学习的动量一直被认为是有益的。
(二)条件不佳的问题
为了更好地了解动量法的几何属性,我们复习一下梯度下降,尽管它的目标函数明显不那么令人愉快。回想我们在梯度下降中使用了 f ( x ) = x 1 2 + 2 x 2 2 f(\mathbf{x}) = x_1^2 + 2 x_2^2 f(x)=x12+2x22,即中度扭曲的椭球目标。我们通过向 x 1 x_1 x1方向伸展它来进一步扭曲这个函数
f ( x ) = 0.1 x 1 2 + 2 x 2 2 (4) f(\mathbf{x}) = 0.1 x_1^2 + 2 x_2^2 \tag{4} f(x)=0.1x12+2x22(4)
与之前一样, f f f在 ( 0 , 0 ) (0, 0) (0,0)有最小值,该函数在 x 1 x_1 x1的方向上非常平坦。让我们看看在这个新函数上执行梯度下降时会发生什么。
python
%matplotlib inline
import torch
from d2l import torch as d2l
eta = 0.4
def f_2d(x1, x2):
return 0.1 * x1 ** 2 + 2 * x2 ** 2
def gd_2d(x1, x2, s1, s2):
return (x1 - eta * 0.2 * x1, x2 - eta * 4 * x2, 0, 0)
d2l.show_trace_2d(f_2d, d2l.train_2d(gd_2d))
从构造来看, x 2 x_2 x2方向的梯度比水平 x 1 x_1 x1方向的梯度大得多,变化也快得多。因此,我们陷入两难:如果选择较小的学习率,我们会确保解不会在 x 2 x_2 x2方向发散,但要承受在 x 1 x_1 x1方向的缓慢收敛。相反,如果学习率较高,我们在 x 1 x_1 x1方向上进展很快,但在 x 2 x_2 x2方向将会发散。下面的例子说明了即使学习率从 0.4 0.4 0.4略微提高到 0.6 0.6 0.6,也会发生变化。 x 1 x_1 x1方向上的收敛有所改善,但整体来看解的质量更差了。
python
eta = 0.6
d2l.show_trace_2d(f_2d, d2l.train_2d(gd_2d))
(三)动量法
动量法 (momentum)使我们能够解决上面描述的梯度下降问题。观察上面的优化轨迹,我们可能会直觉到计算过去的平均梯度效果会很好。毕竟,在 x 1 x_1 x1方向上,这将聚合非常对齐的梯度,从而增加我们在每一步中覆盖的距离。相反,在梯度振荡的 x 2 x_2 x2方向,由于相互抵消了对方的振荡,聚合梯度将减小步长大小。使用 v t \mathbf{v}_t vt而不是梯度 g t \mathbf{g}t gt可以生成以下更新等式:
v t ← β v t − 1 + g t , t − 1 x t ← x t − 1 − η t v t (5) \begin{aligned} \mathbf{v}t &\leftarrow \beta \mathbf{v}{t-1} + \mathbf{g}{t, t-1} \\ \mathbf{x}t &\leftarrow \mathbf{x}{t-1} - \eta_t \mathbf{v}_t \end{aligned} \tag{5} vtxt←βvt−1+gt,t−1←xt−1−ηtvt(5)
请注意,对于 β = 0 \beta = 0 β=0,我们恢复常规的梯度下降。在深入研究它的数学属性之前,让我们快速看一下算法在实验中的表现如何。
python
def momentum_2d(x1, x2, v1, v2):
v1 = beta * v1 + 0.2 * x1
v2 = beta * v2 + 4 * x2
return x1 - eta * v1, x2 - eta * v2, v1, v2
eta, beta = 0.6, 0.5
d2l.show_trace_2d(f_2d, d2l.train_2d(momentum_2d))
正如所见,尽管学习率与我们以前使用的相同,动量法仍然很好地收敛了。让我们看看当降低动量参数时会发生什么。将其减半至 β = 0.25 \beta = 0.25 β=0.25会导致一条几乎没有收敛的轨迹。尽管如此,它比没有动量时解将会发散要好得多。
python
eta, beta = 0.6, 0.25
d2l.show_trace_2d(f_2d, d2l.train_2d(momentum_2d))
请注意,我们可以将动量法与随机梯度下降,特别是小批量随机梯度下降结合起来。唯一的变化是,在这种情况下,我们将梯度 g t , t − 1 \mathbf{g}_{t, t-1} gt,t−1替换为 g t \mathbf{g}_t gt。为了方便起见,我们在时间 t = 0 t=0 t=0初始化 v 0 = 0 \mathbf{v}_0 = 0 v0=0。
(四)有效样本权重
回想一下 v t = ∑ τ = 0 t − 1 β τ g t − τ , t − τ − 1 \mathbf{v}t = \sum{\tau = 0}^{t-1} \beta^{\tau} \mathbf{g}{t-\tau, t-\tau-1} vt=∑τ=0t−1βτgt−τ,t−τ−1。极限条件下, ∑ τ = 0 ∞ β τ = 1 1 − β \sum{\tau=0}^\infty \beta^\tau = \frac{1}{1-\beta} ∑τ=0∞βτ=1−β1。换句话说,不同于在梯度下降或者随机梯度下降中取步长 η \eta η,我们选取步长 η 1 − β \frac{\eta}{1-\beta} 1−βη,同时处理潜在表现可能会更好的下降方向。这是集两种好处于一身的做法。为了说明 β \beta β的不同选择的权重效果如何,请参考下面的图表。
python
d2l.set_figsize()
betas = [0.95, 0.9, 0.6, 0]
for beta in betas:
x = torch.arange(40).detach().numpy()
d2l.plt.plot(x, beta ** x, label=f'beta = {beta:.2f}')
d2l.plt.xlabel('time')
d2l.plt.legend();

二、实际实验
让我们来看看动量法在实验中是如何运作的。为此,我们需要一个更加可扩展的实现。
(一)从零开始实现
相比于小批量随机梯度下降,动量方法需要维护一组辅助变量,即速度。它与梯度以及优化问题的变量具有相同的形状。在下面的实现中,我们称这些变量为states
。
python
def init_momentum_states(feature_dim):
v_w = torch.zeros((feature_dim, 1))
v_b = torch.zeros(1)
return (v_w, v_b)
python
def sgd_momentum(params, states, hyperparams):
for p, v in zip(params, states):
with torch.no_grad():
v[:] = hyperparams['momentum'] * v + p.grad
p[:] -= hyperparams['lr'] * v
p.grad.data.zero_()
让我们看看它在实验中是如何操作的。
python
def train_momentum(lr, momentum, num_epochs=2):
d2l.train_ch11(sgd_momentum, init_momentum_states(feature_dim),
{'lr': lr, 'momentum': momentum}, data_iter, feature_dim, num_epochs)
data_iter, feature_dim = d2l.get_data_ch11(batch_size=10)
train_momentum(0.02, 0.5)
当我们将动量超参数momentum
增加到0.9时,它相当于有效样本数量增加到 1 1 − 0.9 = 10 \frac{1}{1 - 0.9} = 10 1−0.91=10。我们将学习率略微降至 0.01 0.01 0.01,以确保可控。
python
train_momentum(0.01, 0.9)
降低学习率进一步解决了任何非平滑优化问题的困难,将其设置为 0.005 0.005 0.005会产生良好的收敛性能。
python
train_momentum(0.005, 0.9)
(二)简洁实现
由于深度学习框架中的优化求解器早已构建了动量法,设置匹配参数会产生非常类似的轨迹。
python
trainer = torch.optim.SGD
d2l.train_concise_ch11(trainer, {'lr': 0.005, 'momentum': 0.9}, data_iter)
三、理论分析
f ( x ) = 0.1 x 1 2 + 2 x 2 2 f(x) = 0.1 x_1^2 + 2 x_2^2 f(x)=0.1x12+2x22的2D示例似乎相当牵强。下面我们将看到,它在实际生活中非常具有代表性,至少最小化凸二次目标函数的情况下是如此。
(一)凸二次函数
考虑这个函数
h ( x ) = 1 2 x ⊤ Q x + x ⊤ c + b (6) h(\mathbf{x}) = \frac{1}{2} \mathbf{x}^\top \mathbf{Q} \mathbf{x} + \mathbf{x}^\top \mathbf{c} + b \tag{6} h(x)=21x⊤Qx+x⊤c+b(6) 这是一个普通的二次函数。对于正定矩阵 Q ≻ 0 \mathbf{Q} \succ 0 Q≻0,即对于具有正特征值的矩阵,有最小化器为 x ∗ = − Q − 1 c \mathbf{x}^* = -\mathbf{Q}^{-1} \mathbf{c} x∗=−Q−1c,最小值为 b − 1 2 c ⊤ Q − 1 c b - \frac{1}{2} \mathbf{c}^\top \mathbf{Q}^{-1} \mathbf{c} b−21c⊤Q−1c。因此我们可以将 h h h重写为
h ( x ) = 1 2 ( x − Q − 1 c ) ⊤ Q ( x − Q − 1 c ) + b − 1 2 c ⊤ Q − 1 c (7) h(\mathbf{x}) = \frac{1}{2} (\mathbf{x} - \mathbf{Q}^{-1} \mathbf{c})^\top \mathbf{Q} (\mathbf{x} - \mathbf{Q}^{-1} \mathbf{c}) + b - \frac{1}{2} \mathbf{c}^\top \mathbf{Q}^{-1} \mathbf{c} \tag{7} h(x)=21(x−Q−1c)⊤Q(x−Q−1c)+b−21c⊤Q−1c(7) 梯度由 ∂ x f ( x ) = Q ( x − Q − 1 c ) \partial_{\mathbf{x}} f(\mathbf{x}) = \mathbf{Q} (\mathbf{x} - \mathbf{Q}^{-1} \mathbf{c}) ∂xf(x)=Q(x−Q−1c)给出。也就是说,它是由 x \mathbf{x} x和最小化器之间的距离乘以 Q \mathbf{Q} Q所得出的。因此,动量法还是 Q ( x t − Q − 1 c ) \mathbf{Q} (\mathbf{x}_t - \mathbf{Q}^{-1} \mathbf{c}) Q(xt−Q−1c)的线性组合。
由于 Q \mathbf{Q} Q是正定的,因此可以通过 Q = O ⊤ Λ O \mathbf{Q} = \mathbf{O}^\top \boldsymbol{\Lambda} \mathbf{O} Q=O⊤ΛO分解为正交(旋转)矩阵 O \mathbf{O} O和正特征值的对角矩阵 Λ \boldsymbol{\Lambda} Λ。这使我们能够将变量从 x \mathbf{x} x更改为 z : = O ( x − Q − 1 c ) \mathbf{z} := \mathbf{O} (\mathbf{x} - \mathbf{Q}^{-1} \mathbf{c}) z:=O(x−Q−1c),以获得一个非常简化的表达式:
h ( z ) = 1 2 z ⊤ Λ z + b ′ (8) h(\mathbf{z}) = \frac{1}{2} \mathbf{z}^\top \boldsymbol{\Lambda} \mathbf{z} + b' \tag{8} h(z)=21z⊤Λz+b′(8) 这里 b ′ = b − 1 2 c ⊤ Q − 1 c b' = b - \frac{1}{2} \mathbf{c}^\top \mathbf{Q}^{-1} \mathbf{c} b′=b−21c⊤Q−1c。由于 O \mathbf{O} O只是一个正交矩阵,因此不会真正意义上扰动梯度。以 z \mathbf{z} z表示的梯度下降变成
z t = z t − 1 − Λ z t − 1 = ( I − Λ ) z t − 1 (9) \mathbf{z}t = \mathbf{z}{t-1} - \boldsymbol{\Lambda} \mathbf{z}{t-1} = (\mathbf{I} - \boldsymbol{\Lambda}) \mathbf{z}{t-1} \tag{9} zt=zt−1−Λzt−1=(I−Λ)zt−1(9)这个表达式中的重要事实是梯度下降在不同的 特征空间之间不会混合。也就是说,如果用 Q \mathbf{Q} Q的特征系统来表示,优化问题是以逐坐标顺序的方式进行的。这在动量法中也适用。
v t = β v t − 1 + Λ z t − 1 z t = z t − 1 − η ( β v t − 1 + Λ z t − 1 ) = ( I − η Λ ) z t − 1 − η β v t − 1 (10) \begin{aligned} \mathbf{v}t & = \beta \mathbf{v}{t-1} + \boldsymbol{\Lambda} \mathbf{z}{t-1} \\ \mathbf{z}t & = \mathbf{z}{t-1} - \eta \left(\beta \mathbf{v}{t-1} + \boldsymbol{\Lambda} \mathbf{z}{t-1}\right) \\ & = (\mathbf{I} - \eta \boldsymbol{\Lambda}) \mathbf{z}{t-1} - \eta \beta \mathbf{v}_{t-1} \end{aligned} \tag{10} vtzt=βvt−1+Λzt−1=zt−1−η(βvt−1+Λzt−1)=(I−ηΛ)zt−1−ηβvt−1(10)
在这样做的过程中,我们只是证明了以下定理:带有和带有不凸二次函数动量的梯度下降,可以分解为朝二次矩阵特征向量方向坐标顺序的优化。
(二)标量函数
鉴于上述结果,让我们看看当我们最小化函数 f ( x ) = λ 2 x 2 f(x) = \frac{\lambda}{2} x^2 f(x)=2λx2时会发生什么。对于梯度下降我们有
x t + 1 = x t − η λ x t = ( 1 − η λ ) x t (11) x_{t+1} = x_t - \eta \lambda x_t = (1 - \eta \lambda) x_t \tag{11} xt+1=xt−ηλxt=(1−ηλ)xt(11)
每 ∣ 1 − η λ ∣ < 1 |1 - \eta \lambda| < 1 ∣1−ηλ∣<1时,这种优化以指数速度收敛,因为在 t t t步之后我们可以得到 x t = ( 1 − η λ ) t x 0 x_t = (1 - \eta \lambda)^t x_0 xt=(1−ηλ)tx0。这显示了在我们将学习率 η \eta η提高到 η λ = 1 \eta \lambda = 1 ηλ=1之前,收敛率最初是如何提高的。超过该数值之后,梯度开始发散,对于 η λ > 2 \eta \lambda > 2 ηλ>2而言,优化问题将会发散。
python
lambdas = [0.1, 1, 10, 19]
eta = 0.1
d2l.set_figsize((6, 4))
for lam in lambdas:
t = torch.arange(20).detach().numpy()
d2l.plt.plot(t, (1 - eta * lam) ** t, label=f'lambda = {lam:.2f}')
d2l.plt.xlabel('time')
d2l.plt.legend();

为了分析动量的收敛情况,我们首先用两个标量重写更新方程:一个用于 x x x,另一个用于动量 v v v。这产生了:
v t + 1 x t + 1 \] = \[ β λ − η β ( 1 − η λ ) \] \[ v t x t \] = R ( β , η , λ ) \[ v t x t \] (12) \\begin{bmatrix} v_{t+1} \\\\ x_{t+1} \\end{bmatrix} = \\begin{bmatrix} \\beta \& \\lambda \\\\ -\\eta \\beta \& (1 - \\eta \\lambda) \\end{bmatrix} \\begin{bmatrix} v_{t} \\\\ x_{t} \\end{bmatrix} = \\mathbf{R}(\\beta, \\eta, \\lambda) \\begin{bmatrix} v_{t} \\\\ x_{t} \\end{bmatrix} \\tag{12} \[vt+1xt+1\]=\[β−ηβλ(1−ηλ)\]\[vtxt\]=R(β,η,λ)\[vtxt\](12) 用 R \\mathbf{R} R来表示 2 × 2 2 \\times 2 2×2管理的收敛表现。在 t t t步之后,最初的值 \[ v 0 , x 0 \] \[v_0, x_0\] \[v0,x0\]变为 R ( β , η , λ ) t \[ v 0 , x 0 \] \\mathbf{R}(\\beta, \\eta, \\lambda)\^t \[v_0, x_0\] R(β,η,λ)t\[v0,x0\]。因此,收敛速度是由 R \\mathbf{R} R的特征值决定的。请参阅[文章"Why Momentum Really Works"](https://distill.pub/2017/momentum/)了解精彩动画。简而言之,当 0 \< η λ \< 2 + 2 β 0 \< \\eta \\lambda \< 2 + 2 \\beta 0\<ηλ\<2+2β时动量收敛。与梯度下降的 0 \< η λ \< 2 0 \< \\eta \\lambda \< 2 0\<ηλ\<2相比,这是更大范围的可行参数。另外,一般而言较大值的 β \\beta β是可取的。 ### 小结 * 动量法用过去梯度的平均值来替换梯度,这大大加快了收敛速度。 * 对于无噪声梯度下降和嘈杂随机梯度下降,动量法都是可取的。 * 动量法可以防止在随机梯度下降的优化过程停滞的问题。 * 由于对过去的数据进行了指数降权,有效梯度数为 1 1 − β \\frac{1}{1-\\beta} 1−β1。 * 在凸二次问题中,可以对动量法进行明确而详细的分析。 * 动量法的实现非常简单,但它需要我们存储额外的状态向量(动量 v \\mathbf{v} v)。