梯度下降与优化器几何:Momentum、Adam 和 loss surface 轨迹
梯度下降与优化器几何:Momentum、Adam 和 loss surface 轨迹
站内搜索
直接问 AI

梯度下降与优化器几何:Momentum、Adam 和 loss surface 轨迹

同一个模型、同一份数据,换个优化器结果可能差很多。这件事光看公式不容易理解,但把它们在损失曲面上走过的路径画出来就很直观——梯度下降在陡峭的方向来回震荡、在平缓的方向几乎不动;动量把震荡抵消掉;Adam 给每个参数各自的步长。

下面用一个二维二次函数做例子,因为它的等高线是椭圆,”某个方向陡、另一个方向平”这个导致麻烦的性质可以直接看到。起点和目标都固定,三种优化器的差别就完全来自它们各自怎么处理曲率、历史梯度和尺度。第一步会手算一遍,之后的轨迹用 NumPy 跑出来。

一、损失曲面的几何与病态曲率

在深度学习中,损失曲面极少是各向同性的(即完美的球形)。相反,它通常是高度病态的,充满了峡谷和狭长的山谷。让我们来看一个典型的用来演示这个问题的函数:

L(x, y) = 1/2 * (8x^2 + y^2) + 0.8xy
grad L = [8x + 0.8y, y + 0.8x]

这个函数的 Hessian 矩阵具有差异极大的特征值。该曲面在 x 方向非常陡峭(高曲率),而在 y 方向则较为平缓(低曲率)。这就造成了所谓的“病态曲率(Pathological Curvature)”问题。普通的梯度下降算法会在陡峭的峡谷两侧来回震荡,而在通向极小值的平缓谷底方向上进展极其缓慢。

梯度下降、Momentum 和 Adam 在二维二次函数上的轨迹
等高线越密集,loss 变化越快。由于对病态曲率的处理方式不同,不同优化器在同一曲面上的路径截然不同。

二、手算第一步梯度下降

(2.2, -2.0) 出发,梯度为:

grad = [8*2.2 + 0.8*(-2.0), -2.0 + 0.8*2.2]
     = [16.0, -0.24]

如果学习率是 0.08

x_new = 2.2  - 0.08 * 16.0  = 0.92
y_new = -2.0 - 0.08 * -0.24 = -1.9808

y 方向相比,参数在 x 方向上迈出了一大步,这纯粹是因为 x 方向的梯度绝对值大得多。实验输出也印证了这一点:梯度下降第 1 步的坐标是 x=0.920000y=-1.980800,loss 从 17.840000 降到了 3.889516。然而,如果学习率稍微再大一点,x 方向的步长就会跨过山谷的另一端,导致发散。

三、Momentum 和 Adam 在改什么

普通梯度下降是没有记忆的;它只看当前的梯度。这正是导致上述震荡的原因。

动量(Momentum) 引入了速度变量,把过去的梯度方向累积起来。想象一个沉重的球滚下山坡。在陡峭的 x 方向上,交替正负的梯度会相互抵消;而在平缓的 y 方向上,方向一致的梯度不断累加,从而加速优化器冲向极小值。

v_t = beta * v_{t-1} + grad_t
theta_t = theta_{t-1} - lr * v_t

Adam(Adaptive Moment Estimation) 则更进一步,它同时维护梯度的一阶矩(均值)和二阶矩(未中心化的方差)。它能够为每一个参数单独动态缩放学习率。通过除以累积梯度平方的平方根,Adam 归一化了步长。它本质上是强迫优化器在平缓的方向迈大步,在陡峭的方向迈小步。

m_t = beta1 * m_{t-1} + (1-beta1) * grad_t
v_t = beta2 * v_{t-1} + (1-beta2) * grad_t^2
theta_t = theta_{t-1} - lr * m_hat / (sqrt(v_hat) + eps)

四、优化器决策流程图解

我们该如何决定使用哪种优化器?下面的图表直观地展示了这些优化算法的架构流程。


graph TD
    A[计算当前梯度] --> B{是否需要历史信息?}
    B -->|否| C[普通 SGD]
    B -->|是| D{是否需要自适应缩放?}
    D -->|否| E[带 Momentum 的 SGD]
    D -->|是| F[计算一阶和二阶矩]
    F --> G[偏差修正]
    G --> H[Adam / AdamW]
    C --> I[应用参数更新]
    E --> I
    H --> I

五、核心代码实践与实现

为了真正掌握这些算法,我们应该从零开始构建它们。以下是使用 NumPy 在我们的二次曲面上对比 SGD、Momentum 和 Adam 的核心实现。

import numpy as np

def grad(theta):
    x, y = theta
    return np.array([8.0 * x + 0.8 * y, y + 0.8 * x])

def run_optimizer(optimizer_name, theta_init, lr=0.08, steps=50):
    theta = np.array(theta_init)
    
    # 初始化优化器状态
    v = np.zeros_like(theta)
    m = np.zeros_like(theta)
    beta1, beta2, eps = 0.9, 0.999, 1e-8
    
    trajectory = [theta.copy()]
    
    for t in range(1, steps + 1):
        g = grad(theta)
        
        if optimizer_name == 'SGD':
            theta -= lr * g
            
        elif optimizer_name == 'Momentum':
            v = 0.9 * v + lr * g
            theta -= v
            
        elif optimizer_name == 'Adam':
            m = beta1 * m + (1 - beta1) * g
            v = beta2 * v + (1 - beta2) * (g ** 2)
            
            # 偏差修正
            m_hat = m / (1 - beta1 ** t)
            v_hat = v / (1 - beta2 ** t)
            
            theta -= lr * m_hat / (np.sqrt(v_hat) + eps)
            
        trajectory.append(theta.copy())
        
    return np.array(trajectory)

# 从起点运行测试
traj_sgd = run_optimizer('SGD', [2.2, -2.0])
traj_adam = run_optimizer('Adam', [2.2, -2.0], lr=0.5)
print(f"Adam 最终位置: {traj_adam[-1]}")

六、动画看什么

动画对比普通梯度下降、Momentum 和 Adam 在同一 loss surface 上的路径。

重点看两点:陡峭方向上是否震荡,平缓方向上是否前进太慢。注意 Momentum 是如何像钟摆一样大幅度摇摆才最终稳定下来的,而 Adam 则是切出了一条更加直接、可控的路径,完美适应了不断变化的曲率。

七、个人经验与工程师视角

在工程实践中,优化器优雅的数学原理往往会撞上残酷的硬件和系统现实。以下是我在训练大模型时积累的一些经验:

  • 显存(Memory)瓶颈: Adam 效果极好,但极其消耗显存。普通的 SGD 只需要为参数和梯度分配显存。而 Adam 还需要存储梯度的滑动平均(一阶矩)和梯度平方的滑动平均(二阶矩)。这使得优化器状态的显存占用直接翻了三倍。在显存受限的 GPU 上训练大型 LLM 时,这往往成为最大瓶颈,迫使工程师转向 Adafactor 或 8-bit Adam 等显存友好型变体。
  • 权重衰减(Weight Decay)的陷阱: Adam 和 AdamW 之间有着臭名昭著的区别。标准的 Adam 在自适应缩放之前对梯度应用 L2 正则化。这无意中削弱了对梯度方差较大的权重的惩罚力度,违背了权重衰减的初衷。对于 Transformer 架构,一定要使用 AdamW,它将权重衰减与梯度更新步骤完全解耦。
  • Adam 必须配合 Warmup 使用: 因为 Adam 使用了滑动平均,方差项(二阶矩)初始为零,在最初的几步中可能会极其不准确,导致出现极其巨大且破坏性的更新。引入学习率预热(Warmup,即让学习率从接近零开始,在几千步内逐渐攀升)可以防止模型在训练初期直接崩溃。

八、实践建议

  • 先画训练 loss 和验证 loss,再判断是否需要换优化器。很多训练问题其实是优化路径不稳定,而不是模型结构错了。
  • 学习率通常比优化器的名称更关键。一个经过精心调参的带 Momentum 的 SGD,其泛化能力通常能媲美甚至超越 Adam,尤其是在计算机视觉领域(如 ResNet)。
  • Adam 并非万能药,过大的学习率仍然会导致它发散。
  • 遇到 loss 曲线抖动时,先尝试降低学习率或加 warmup。

九、优化轨迹审计表

优化器实验不能只报告最终 loss。为了判断路径是否可信,应该同时记录坐标、梯度、状态变量和失败模式。下面的审计表可以帮助读者把“看起来收敛了”拆成可复核的数值证据。

审计项 应记录的值 能回答的问题
初始条件 起点、学习率、步数、优化器超参数。 不同曲线是否在同一条件下比较?
逐步轨迹 xy、loss、梯度向量和步长。 路径是在下降,还是在峡谷两侧震荡?
状态变量 Momentum 的 v,Adam 的 mv 和 bias correction。 历史梯度和自适应缩放是否真的改变了路径?
失败模式 发散步、震荡区间、过大 warmup 前更新和最终距离。 失败来自学习率、曲率、初始化还是优化器状态?

下一篇进入卷积层,看看二维局部连接如何变成矩阵计算。

发表回复

向下探索