正如我们所见,标准梯度下降仅根据当前位置计算的梯度来更新权重。尽管这种方法可行,但它可能导致训练效率低下,特别是在某些类型的损失函数形状中。想象损失曲面中一个狭窄的沟壑或山谷。梯度下降倾向于在沟壑的陡峭壁上反复振荡,沿着底部向最小值缓慢移动。同样,它可能停滞在局部最小值,或者在鞍点处显著减速。思想:加入惯性为了解决这些问题,我们可以引入动量的理念,借鉴物理学的思想。想象一个球从山上滚下来。球不是仅仅计算当前位置最陡峭的坡度并稍微沿该方向移动(像标准梯度下降那样),而是具有动量。它当前的速率会影响它在下一步的移动。如果它已经以某个方向快速移动,即使当前的坡度略有改变,它也倾向于保持该方向。这种动量有助于它:在一致的坡度上加速。通过随时间平均抵消的梯度方向来抑制振荡。由于其积累的速度,越过小的局部最小值或平台。带有动量的梯度下降将物理类比应用于优化过程。它引入了一个“速率”向量 $v$,该向量累积了过去梯度的指数衰减移动平均值。权重更新随后会同时考虑当前梯度和这个速率。动量如何运作带有动量的梯度下降的更新规则在每个迭代 $t$ 中包含两个步骤:更新速率向量 $v_t$: $$ v_t = \beta v_{t-1} + \eta \nabla L(w_{t-1}) $$ 这里:$v_t$ 是时间步 $t$ 的速率向量。$v_{t-1}$ 是前一时间步的速率向量(初始化为零)。$\beta$ 是动量系数,这是一个超参数,通常在0.5到0.99之间(常用0.9)。它决定了保留多少过去的速率。更高的 $\beta$ 值意味着过去的梯度影响更大。$\eta$ 是学习率。$\nabla L(w_{t-1})$ 是损失函数 $L$ 对前一时间步 $t-1$ 的权重 $w$ 的梯度。这一步主要通过取旧速率的一部分($\beta$)并加上缩放的当前梯度($\eta \nabla L(w_{t-1})$)来计算新的速率。如果当前梯度方向与之前的速率方向一致,则速率的大小会增加。如果梯度方向振荡,速率倾向于被抑制,因为相反的梯度项会随时间部分抵消。更新权重 $w_t$: $$ w_t = w_{t-1} - v_t $$ 权重通过沿着新计算的速率向量 $v_t$ 的方向移动来更新。通过加入速率 $v_t$(其中包含近期梯度历史的信息),更新变得更平滑、更快,尤其是在梯度方向一致的路径上。动量效果的可视化考虑优化一个带有狭窄山谷的函数。标准梯度下降可能会在两侧壁之间来回跳动,而动量则会走一条更直接的路径。{ "layout": { "xaxis": { "range": [-1, 10.5], "title": "w1", "zeroline": false}, "yaxis": { "range": [-1.5, 1.5], "title": "w2", "zeroline": false}, "title": "梯度下降与动量路径对比", "showlegend": true, "width": 600, "height": 400 }, "data": [ { "type": "contour", "x": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], "y": [-1, -0.5, 0, 0.5, 1], "z": [[5, 5.1, 5.4, 5.9, 6.6, 7.5, 8.6, 9.9, 11.4, 13.1, 15], [1.25, 1.35, 1.65, 2.15, 2.85, 3.75, 4.85, 6.15, 7.65, 9.35, 11.25], [0, 0.1, 0.4, 0.9, 1.6, 2.5, 3.6, 4.9, 6.4, 8.1, 10], [1.25, 1.35, 1.65, 2.15, 2.85, 3.75, 4.85, 6.15, 7.65, 9.35, 11.25], [5, 5.1, 5.4, 5.9, 6.6, 7.5, 8.6, 9.9, 11.4, 13.1, 15]], "colorscale": [[0, "#e9ecef"], [0.5, "#adb5bd"], [1, "#495057"]], "contours": {"coloring": "lines", "start": 0, "end": 20, "size": 2}, "showscale": false, "name": "损失等高线" }, { "type": "scatter", "x": [10, 9.8, 9.8, 9.604, 9.604, 9.4119, 9.4119, 9.2237], "y": [1, -1, 0.8, -0.8, 0.64, -0.64, 0.512, -0.512], "mode": "lines+markers", "line": {"color": "#f06595", "width": 1}, "marker": {"color": "#f06595", "size": 5}, "name": "标准梯度下降" }, { "type": "scatter", "x": [10, 9.8, 9.42, 8.89, 8.25, 7.54, 6.79, 6.03, 5.28], "y": [1, 0.82, 0.66, 0.53, 0.42, 0.33, 0.26, 0.21, 0.16], "mode": "lines+markers", "line": {"color": "#228be6", "width": 2}, "marker": {"color": "#228be6", "size": 6}, "name": "动量" } ] }样本损失曲面上的优化路径。标准梯度下降(粉色)在狭窄山谷中振荡,向最小值(接近原点)缓慢移动。动量(蓝色)抑制了这些振荡,并沿着谷底加速,走了一条更直接的路径。动量系数 $\beta$动量系数 $\beta$ 控制着过去梯度的影响。如果 $\beta = 0$,更新规则就会简化为标准梯度下降($v_t = \eta \nabla L(w_{t-1})$,$w_t = w_{t-1} - \eta \nabla L(w_{t-1})$)。当 $\beta$ 接近1时,过去的梯度被赋予更大的权重,导致更新更平滑,但如果学习率也较高,优化器可能会越过最小值。$\beta$ 的常用值是0.9或更高,例如0.99。通常从0.9开始使用效果不错,并在训练期间可能增加它。这个参数,就像学习率一样,可能需要针对特定问题进行调整以达到最佳效果。在PyTorch中的实现在PyTorch等框架中使用动量很简单。在定义优化器时,只需指定 momentum 参数即可。import torch import torch.nn as nn import torch.optim as optim # 假设 'model' 是你定义的神经网络 (nn.Module) # 假设 'learning_rate' 是你选择的学习率(例如 0.01) # 使用带有动量的SGD定义优化器 momentum_coefficient = 0.9 optimizer = optim.SGD(model.parameters(), lr=learning_rate, momentum=momentum_coefficient) # --- 在你的训练循环中 --- # loss.backward() # 计算梯度 # optimizer.step() # 使用带有动量的SGD更新权重 # optimizer.zero_grad() # 重置梯度以进行下一次迭代通过简单地将 momentum 参数添加到 optim.SGD 构造函数中,优化器会自动实现前面描述的速率计算和权重更新步骤。带有动量的梯度下降是标准梯度下降的显著改进,通常能带来更快的收敛速度和对复杂损失曲面的更好应对。尽管它有助于应对许多问题,但进一步的改进促成了RMSprop和Adam等自适应方法的出现,我们将在下一节中进行考察。