查看全课大纲(第 8 / 40 节)
  1. 1.从规则到学习:深度学习解决什么问题
  2. 2.表示学习与端到端训练
  3. 3.张量、计算图与自动微分
  4. 4.训练三要素:数据、模型、目标函数
  5. 5.从感知机到多层感知机
  6. 6.反向传播:手推一个两层网络
  7. 7.激活函数与初始化
  8. 8.优化器:SGD → Momentum → Adam
  9. 9.正则化:权重衰减、Dropout、BN/LN
  10. 10.卷积的直觉与参数共享
  11. 11.池化、感受野与下采样
  12. 12.经典网络演进:LeNet → AlexNet → VGG → ResNet
  13. 13.残差连接与归一化为什么有效
  14. 14.迁移学习与特征可视化
  15. 15.RNN 与 BPTT
  16. 16.LSTM/GRU:门控如何缓解梯度问题
  17. 17.Seq2Seq 与编码器-解码器
  18. 18.注意力前史:Bahdanau 加性注意力
  19. 19.Self-Attention:QKV 与缩放点积
  20. 20.多头注意力:并行的多种关系
  21. 21.位置编码:正弦与可学习
  22. 22.残差、LayerNorm 与前馈层
  23. 23.从零实现一个最小 Transformer block
  24. 24.训练稳定性、复杂度与常见误区
  25. 25.自监督与语言建模目标
  26. 26.BERT:掩码语言模型与双向编码
  27. 27.GPT:自回归生成与缩放定律
  28. 28.指令微调与对齐:SFT → RLHF/DPO
  29. 29.参数高效微调:LoRA 与适配器
  30. 30.自编码器与 VAE
  31. 31.GAN:对抗训练与模式崩塌
  32. 32.扩散模型:前向加噪与反向去噪
  33. 33.扩散模型的应用与采样加速
  34. 34.对比学习与 CLIP
  35. 35.ViT:把图像当序列
  36. 36.多模态架构:对齐、融合与生成
  37. 37.能力边界、幻觉与评测
  38. 38.PyTorch 2.x 训练循环与混合精度
  39. 39.实验管理、评测与复现
  40. 40.推理部署与成本:量化/蒸馏概览

优化器:SGD → Momentum → Adam

优化器:从 SGD 到 Adam,梯度下降如何“聪明”起来?

小象实战讲义 · 深度学习基础:从神经网络到 Transformer

在上一节,我们掌握了反向传播算法,它能高效地计算出神经网络中每个参数的梯度。然而,拿到梯度只是第一步,如何利用梯度来更新参数,让模型朝着损失最小的方向“走”得更快、更稳,是优化器的核心任务。本节将深入剖析从最基础的随机梯度下降(SGD)到 Momentum,再到现代深度学习标配 Adam 优化器的演进逻辑,回答“为什么有了梯度还要加动量?”以及“Adam 如何自适应地调整每个参数的学习步长?”这两个关键问题。理解这些优化器,是高效训练模型的基础。

💡 核心导读

  • SGD 的困境:基础的随机梯度下降在复杂损失函数(如峡谷地形)中容易震荡、收敛慢。
  • Momentum 的惯性:引入“动量”概念,让参数更新不仅考虑当前梯度,还累积历史梯度方向,从而抑制震荡、加速收敛。
  • Adam 的自适应:结合 Momentum 和 RMSProp 思想,为每个参数维护独立的自适应学习率(一阶矩和二阶矩估计),使其在不同维度上更新步长不同,是目前最鲁棒、最常用的优化器。
  • PyTorch 实现:掌握 torch.optim.SGD 和 torch.optim.Adam 的关键参数(lr, momentum, betas, weight_decay)及其在代码中的真实用法。

🧭 前置知识

  • 梯度下降法的基本思想(参数更新公式:w = w - lr * grad)。
  • 损失函数关于参数的梯度计算(反向传播)。
  • 矩阵/张量的基本操作(不展开,见站内数学基础课)。

从随机梯度下降(SGD)说起

随机梯度下降(Stochastic Gradient Descent, SGD)是最基础的优化算法。其核心思想是:在每次迭代(或每个小批量)中,计算损失函数关于模型参数的梯度,然后沿着梯度的反方向(因为梯度指向函数增长最快的方向)以一定的步长(学习率 lr)更新参数,以期望降低损失。

基础 SGD 更新公式: θt=θt−1−γ⋅gt\theta_{t} = \theta_{t-1} - \gamma \cdot g_t 其中:

  • θt\theta_t 是第 tt 步迭代后的参数。
  • γ\gamma 是学习率(learning rate)。
  • gt=∇θft(θt−1)g_t = \nabla_{\theta} f_t(\theta_{t-1}) 是第 tt 步计算得到的梯度。

在 PyTorch 中,基础的 SGD 优化器可以这样使用:

import torch
import torch.nn as nn
import torch.optim as optim

torch.manual_seed(0)

# 构造一个简单的线性模型和虚拟数据
model = nn.Linear(2, 1, bias=False)  # y = w1*x1 + w2*x2
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.1)

# 虚拟一个小批量数据
X = torch.tensor([[1.0, 2.0], [3.0, 4.0]])
y = torch.tensor([[5.0], [11.0]])

# 一次训练迭代
optimizer.zero_grad()  # 清空历史梯度
output = model(X)
loss = criterion(output, y)
loss.backward()        # 反向传播,计算梯度
print(f"梯度: w1_grad={model.weight.grad[0,0]:.4f}, w2_grad={model.weight.grad[0,1]:.4f}")
optimizer.step()       # 执行 SGD 更新: w = w - lr * grad
print(f"更新后权重: w1={model.weight[0,0]:.4f}, w2={model.weight[0,1]:.4f}")
梯度: w1_grad=-32.7424, w2_grad=-46.4877
更新后权重: w1=3.2689, w2=5.0281

可以看到,权重按照 新权重 = 旧权重 - 0.1 * 梯度 的规则进行了更新。然而,当损失函数的等高线呈“峡谷”状(一个方向陡峭,另一个方向平缓)时,基础 SGD 的更新路径会像“之”字形一样剧烈震荡,收敛速度很慢。

引入动量(Momentum):让优化带上“惯性”

为了解决 SGD 的震荡问题,动量法(Momentum)被提出。其核心思想是模拟物理中的动量:参数更新不仅取决于当前的梯度,还累积了历史梯度的指数加权平均(动量项)。这有助于在相关梯度方向加速,并抑制震荡。

带动量的 SGD 更新公式(PyTorch 实现版本): vt=μ⋅vt−1+gtθt=θt−1−γ⋅vt\begin{aligned} v_t &= \mu \cdot v_{t-1} + g_t \\ \theta_t &= \theta_{t-1} - \gamma \cdot v_t \end{aligned} 其中:

  • vtv_t 是第 tt 步的动量(velocity)。
  • μ\mu 是动量因子(momentum),通常设为 0.9,决定了历史梯度的影响衰减速度。
  • 注意:PyTorch 的实现与早期 Sutskever 等人的公式略有不同。早期课程里有时会将学习率 γ\gamma 乘在 gtg_t 上(即 vt=μ⋅vt−1+γ⋅gtv_t = \mu \cdot v_{t-1} + \gamma \cdot g_t),但 PyTorch 2.x 的官方实现是上述公式,学习率只在最后一步与 vtv_t 相乘。
import torch
import torch.nn as nn
import torch.optim as optim

torch.manual_seed(0)

# 使用相同的模型和数据
model_momentum = nn.Linear(2, 1, bias=False)
criterion = nn.MSELoss()
# 关键:设置 momentum 参数
optimizer_momentum = optim.SGD(model_momentum.parameters(), lr=0.1, momentum=0.9)

X = torch.tensor([[1.0, 2.0], [3.0, 4.0]])
y = torch.tensor([[5.0], [11.0]])

# 模拟连续两次更新,观察动量的累积
for step in range(2):
    optimizer_momentum.zero_grad()
    output = model_momentum(X)
    loss = criterion(output, y)
    loss.backward()
    grad = model_momentum.weight.grad.clone()
    optimizer_momentum.step()
    print(f"Step {step+1}: 梯度=({grad[0,0]:.1f}, {grad[0,1]:.1f}), "
          f"更新后权重=({model_momentum.weight[0,0]:.4f}, {model_momentum.weight[0,1]:.4f})")
Step 1: 梯度=(-32.7, -46.5), 更新后权重=(3.2689, 5.0281)
Step 2: 梯度=(65.1, 92.3), 更新后权重=(-0.2925, -0.0207)

发生了什么? 上面这段代码用的是默认随机初始化,所以数字取决于初始权重;为了能逐项核对,下面把初始权重显式设为 0 再跑一遍。

import torch
import torch.nn as nn

torch.manual_seed(0)

model = nn.Linear(2, 1, bias=False)
# 手动设置一个已知的初始权重,便于跟踪
with torch.no_grad():
    model.weight.fill_(0.0)
print(f"初始权重: {model.weight}")

lr = 0.1
momentum = 0.9
# 手动模拟带 momentum 的 SGD
v = None
X = torch.tensor([[1.0, 2.0], [3.0, 4.0]])
y = torch.tensor([[5.0], [11.0]])
criterion = nn.MSELoss()

for step in range(2):
    output = model(X)
    loss = criterion(output, y)
    loss.backward()
    g = model.weight.grad.clone()
    model.zero_grad()
    
    if v is None:
        v = g  # PyTorch: 第一步动量初始化为梯度
    else:
        v = momentum * v + g  # 阻尼 tau=0
    
    # 更新参数
    with torch.no_grad():
        model.weight -= lr * v
    print(f"Step {step+1}:")
    print(f"  梯度 g   = {[[round(x, 4) for x in row] for row in g.tolist()]}")
    print(f"  动量 v   = {[[round(x, 4) for x in row] for row in v.tolist()]}")
    print(f"  更新后 w = {[[round(x, 4) for x in row] for row in model.weight.tolist()]}")
初始权重: Parameter containing:
tensor([[0., 0.]], requires_grad=True)
Step 1:
  梯度 g   = [[-38.0, -54.0]]
  动量 v   = [[-38.0, -54.0]]
  更新后 w = [[3.8, 5.4]]
Step 2:
  梯度 g   = [[75.6, 107.2]]
  动量 v   = [[41.4, 58.6]]
  更新后 w = [[-0.34, -0.46]]

逐项核对:

  • 第 1 步:梯度 g1=(−38,−54)g_1=(-38,-54)。动量缓冲初始为空,PyTorch 直接令 v1=g1v_1 = g_1(不是 μ⋅0+g1\mu \cdot 0 + g_1 之外的任何东西)。参数更新为 (0,0)−0.1×(−38,−54)=(3.8,5.4)(0,0) - 0.1 \times (-38,-54) = (3.8, 5.4)。
  • 第 2 步:参数已经越过最优点——本例数据满足 y=1⋅x1+2⋅x2y = 1 \cdot x_1 + 2 \cdot x_2(1+4=51+4=5、3+8=113+8=11),最优解是 w=(1,2)w=(1,2),而当前在 (3.8,5.4)(3.8,5.4)。梯度因此反号,变成 g2=(75.6,107.2)g_2=(75.6,107.2)。动量为 v2=0.9×(−38,−54)+(75.6,107.2)=(41.4,58.6)v_2 = 0.9 \times (-38,-54) + (75.6,107.2) = (41.4, 58.6),比 g2g_2 小——历史动量与当前梯度方向相反,两者部分抵消。参数更新为 (3.8,5.4)−0.1×(41.4,58.6)=(−0.34,−0.46)(3.8,5.4) - 0.1 \times (41.4,58.6) = (-0.34,-0.46)。

这揭示了动量的两面性:梯度方向持续一致时,动量越滚越大、加速前进;一旦越过最优点、梯度反号,动量会带来滞后,把参数继续往旧方向推,造成过冲和来回震荡。所以动量常与较小的学习率或学习率衰减配合使用。

顺带说明:PyTorch 的动量实现是 vt=μvt−1+gtv_t = \mu v_{t-1} + g_t(学习率只在最后一步乘),而不是早期文献里的 vt=μvt−1+γgtv_t = \mu v_{t-1} + \gamma g_t。不要拿教科书公式去对照 PyTorch 的中间变量,否则会以为实现有 bug。

Adam:自适应矩估计优化器

虽然 Momentum 解决了 SGD 的震荡问题,但它对所有参数使用相同的学习率。在现实中,不同参数的重要性、梯度量级和更新频率可能不同。Adam(Adaptive Moment Estimation)优化器应运而生,它结合了 Momentum 和 RMSProp 的思想,为每个参数维护两个状态:

  1. 一阶矩估计(First Moment) mtm_t:梯度的指数移动平均(类似 Momentum),估计梯度的一阶矩(均值)。
  2. 二阶矩估计(Second Moment) vtv_t:梯度平方的指数移动平均(来自 RMSProp),估计梯度的二阶矩(未中心化的方差)。

然后,Adam 利用这两个估计值对每个参数进行自适应的学习率缩放。

Adam 算法(根据 PyTorch 2.14.1 官方文档): 初始化: m0←0,  v0←0for t=1 to … do:gt←∇θft(θt−1)mt←β1mt−1+(1−β1)gtvt←β2vt−1+(1−β2)gt2m^t←mt/(1−β1t)(偏差校正)v^t←vt/(1−β2t)(偏差校正)θt←θt−1−γ⋅m^t/(v^t+ϵ)\begin{aligned} & \textbf{初始化: } m_0 \leftarrow 0,\; v_0 \leftarrow 0 \\ & \textbf{for } t=1 \textbf{ to } \ldots \textbf{ do:} \\ & \quad g_t \leftarrow \nabla_{\theta} f_t(\theta_{t-1}) \\ & \quad m_t \leftarrow \beta_1 m_{t-1} + (1 - \beta_1) g_t \\ & \quad v_t \leftarrow \beta_2 v_{t-1} + (1 - \beta_2) g_t^2 \\ & \quad \hat{m}_t \leftarrow m_t / (1 - \beta_1^t) \quad \text{(偏差校正)} \\ & \quad \hat{v}_t \leftarrow v_t / (1 - \beta_2^t) \quad \text{(偏差校正)} \\ & \quad \theta_t \leftarrow \theta_{t-1} - \gamma \cdot \hat{m}_t / (\sqrt{\hat{v}_t} + \epsilon) \\ \end{aligned} 其中:

  • β1,β2∈[0,1)\beta_1, \beta_2 \in [0, 1) 是衰减率,默认值通常为 β1=0.9,β2=0.999\beta_1=0.9, \beta_2=0.999。
  • ϵ\epsilon 是一个极小的数(如 10−810^{-8}),防止除以零。
  • gt2g_t^2 表示逐元素平方。
  • 偏差校正(Bias Correction):由于 m0,v0m_0, v_0 初始化为 0,在训练初期,mt,vtm_t, v_t 会偏向于 0。除以 (1−βt)(1-\beta^t) 可以校正这种偏差,使得估计更准确。

Adam 的核心优势在于:m^t\hat{m}_t 提供了带有动量的梯度方向,而 v^t\sqrt{\hat{v}_t} 近似于梯度各维度的幅度(标准差)。更新步长变为 γ⋅m^t/v^t\gamma \cdot \hat{m}_t / \sqrt{\hat{v}_t},这意味着:

  • 对于梯度幅度大的维度(可能震荡),v^t\sqrt{\hat{v}_t} 大,有效学习率小,更新更谨慎。
  • 对于梯度幅度小的维度(可能进展缓慢),v^t\sqrt{\hat{v}_t} 小,有效学习率大,更新更快。
  • 从而实现了每个参数的自适应学习率。
import torch
import torch.nn as nn
import torch.optim as optim

torch.manual_seed(0)

model_adam = nn.Linear(2, 1, bias=False)
criterion = nn.MSELoss()
# 使用 Adam,默认 betas=(0.9, 0.999)
optimizer_adam = optim.Adam(model_adam.parameters(), lr=0.1)

X = torch.tensor([[1.0, 2.0], [3.0, 4.0]])
y = torch.tensor([[5.0], [11.0]])

for step in range(2):
    optimizer_adam.zero_grad()
    output = model_adam(X)
    loss = criterion(output, y)
    loss.backward()
    grad = model_adam.weight.grad.clone()
    optimizer_adam.step()
    print(f"Step {step+1}: 梯度=({grad[0,0]:.1f}, {grad[0,1]:.1f}), "
          f"更新后权重=({model_adam.weight[0,0]:.4f}, {model_adam.weight[0,1]:.4f})")
Step 1: 梯度=(-32.7, -46.5), 更新后权重=(0.0947, 0.4793)
Step 2: 梯度=(-30.3, -43.1), 更新后权重=(0.1944, 0.5791)

注意第一步发生了什么:偏差校正让 m^1=m1/(1−β1)=g1\hat{m}_1 = m_1/(1-\beta_1) = g_1、v^1=v1/(1−β2)=g12\hat{v}_1 = v_1/(1-\beta_2) = g_1^2,于是更新量 γm^1/(v^1+ϵ)≈γ⋅g1/∣g1∣=γ⋅sign(g1)\gamma \hat{m}_1/(\sqrt{\hat{v}_1}+\epsilon) \approx \gamma \cdot g_1/|g_1| = \gamma \cdot \text{sign}(g_1)。也就是说,Adam 的第一步在每一维上走的距离都约等于 lr,与梯度大小无关。上面代码里 lr=0.1,所以两个维度各自挪动约 0.1(从初始的 -0.0053 附近到 0.0947、从 0.379 附近到 0.4793)。对比 SGD 第一步(梯度 -32.7,一步就走 3.27),Adam 显得非常保守——这正是「自适应」的含义:它把每个维度的步长归一化到同一个量级,代价是需要更多步数。

关键参数与代码实践

权重衰减(Weight Decay / L2 正则化)

权重衰减通过在损失函数中添加参数的 L2 范数惩罚项(λ2∣∣θ∣∣2\frac{\lambda}{2}||\theta||^2)来防止过拟合。在优化器更新中,它等价于在梯度上直接加上 λθ\lambda \theta。

  • SGD with weight decay: optim.SGD(..., weight_decay=1e-4)
  • Adam with weight decay: optim.Adam(..., weight_decay=1e-4)
  • 注意:optim.Adam 的 weight_decay 是耦合式 L2——它把 λ·θ 直接加进梯度,然后一起被自适应学习率缩放,效果与 SGD 的 weight decay 并不等价。想做「解耦」的权重衰减,可以直接用 optim.AdamW,或给 optim.Adam 传 decoupled_weight_decay=True(PyTorch 2.x 提供该开关,开启后等价于 AdamW)。解耦版本在参数更新时单独减去 lr·λ·θ,不经过二阶矩归一化,通常泛化更好。本地 torch 2.14.1 实测两个入口都可用。

学习率调度

虽然本节聚焦优化器,但实践中常配合学习率调度器(如 StepLR、CosineAnnealingLR)在训练中动态调整 lr。用法是构造调度器,如 optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1),再在每个 epoch 末尾调用一次 scheduler.step()。调度器不改变优化器的更新规则,只是按计划修改 param_group['lr'],两者职责分离。

📝 动手练一练

题目:分别使用 SGD(无动量)、SGD(动量=0.9)和 Adam 优化器,在以下简单二次函数上优化参数 x:f(x)=0.1∗x2f(x) = 0.1 * x^2,初始值 x0=10.0x_0 = 10.0,学习率均为 0.5,迭代 10 步。观察并记录每一步的 x 值,比较三种方法的收敛速度。

👉 点击查看参考答案

思路:定义参数 x 为一个 torch.Tensor 并设置 requires_grad=True。为每种优化器创建一个副本,在循环中计算梯度并更新。注意每次更新前要清空梯度。

import torch
import torch.optim as optim

torch.manual_seed(0)

# 定义优化目标函数和初始参数
def f(x):
    return 0.1 * x**2

# 为三种优化器创建三个独立的参数副本
x_sgd = torch.tensor([10.0], requires_grad=True)
x_momentum = torch.tensor([10.0], requires_grad=True)
x_adam = torch.tensor([10.0], requires_grad=True)

# 创建优化器
opt_sgd = optim.SGD([x_sgd], lr=0.5)
opt_momentum = optim.SGD([x_momentum], lr=0.5, momentum=0.9)
opt_adam = optim.Adam([x_adam], lr=0.5)

results = {'SGD': [], 'Momentum': [], 'Adam': []}

for step in range(10):
    # 清空梯度
    opt_sgd.zero_grad()
    opt_momentum.zero_grad()
    opt_adam.zero_grad()
    
    # 计算损失和梯度 (三个参数独立计算)
    loss_sgd = f(x_sgd)
    loss_momentum = f(x_momentum)
    loss_adam = f(x_adam)
    
    loss_sgd.backward()
    loss_momentum.backward()
    loss_adam.backward()
    
    # 执行一步更新
    opt_sgd.step()
    opt_momentum.step()
    opt_adam.step()
    
    # 记录结果
    results['SGD'].append(round(x_sgd.item(), 4))
    results['Momentum'].append(round(x_momentum.item(), 4))
    results['Adam'].append(round(x_adam.item(), 4))

print("步数 |       SGD |  Momentum |      Adam")
print("-" * 45)
for i in range(10):
    print(f"{i:4d} | {results['SGD'][i]:9.4f} | {results['Momentum'][i]:9.4f} | "
          f"{results['Adam'][i]:9.4f}")
步数 |       SGD |  Momentum |      Adam
---------------------------------------------
   0 |    9.0000 |    9.0000 |    9.5000
   1 |    8.1000 |    7.2000 |    9.0008
   2 |    7.2900 |    4.8600 |    8.5031
   3 |    6.5610 |    2.2680 |    8.0075
   4 |    5.9049 |   -0.2916 |    7.5148
   5 |    5.3144 |   -2.5661 |    7.0257
   6 |    4.7830 |   -4.3565 |    6.5410
   7 |    4.3047 |   -5.5322 |    6.0617
   8 |    3.8742 |   -6.0372 |    5.5887
   9 |    3.4868 |   -5.8879 |    5.1229

观察:三条路径的差别正好对应前面讲的机制。

  • SGD:f′(x)=0.2xf'(x)=0.2x,更新为 x←x−0.5×0.2x=0.9xx \leftarrow x - 0.5 \times 0.2x = 0.9x,每步固定缩小 10%。10 步从 10 降到 3.49,稳但慢。
  • Momentum:前四步明显更快(9.0 → 2.27),因为动量累积同向梯度、步长越滚越大;但它在第 4 步就冲过最优点 0 变成负值,之后在 0 附近来回震荡(-0.29 → -6.04 → -5.89),既没收敛也没发散。
  • Adam:每步只走约 lr=0.5(第 1 步 10 → 9.5),因为更新量被归一化成 lr × sign(梯度),与梯度大小无关。对这个单参数问题,0.5 的固定步长相对 x 的尺度偏小,10 步才到 5.12,是三组里最慢的。

所以「Adam 更好」不是无条件成立的:它擅长的是各维尺度差异大、梯度噪声重的非凸问题;在这个单参数、曲率固定的凸函数上,反而被归一化拖慢。选优化器要看问题形态,不要背结论。

常见错误

错误理解或写法现象或报错原因正确做法
混淆 PyTorch 的 Momentum 公式自己实现 Momentum 时更新效果与 torch.optim.SGD 不一致。PyTorch 的实现是 v_t = μ * v_{t-1} + g_t,更新为 θ_t = θ_{t-1} - lr * v_t。早期有些实现会将 lr 乘在 g_t 上。以 PyTorch 官方文档为准,或直接使用 torch.optim.SGD(..., momentum=μ)。
忘记调用 optimizer.zero_grad()梯度值越累越大,更新方向错误,模型无法收敛。PyTorch 会累积梯度(grad 属性会累加)。如果不手动清零,每次 backward() 的梯度会加到历史梯度上。在每个训练迭代(step)开始前,调用 optimizer.zero_grad()。
Adam 学习率设置过大模型训练初期震荡剧烈甚至发散。Adam 的自适应学习率机制使其有效步长与 lr 并非线性关系。初始二阶矩估计小,有效学习率可能很大。对于 Adam,通常使用比 SGD 更小的学习率(如 1e-3 或 1e-4 作为起点)。
权重衰减值 (weight_decay) 设置过大模型权重被过度压缩,导致欠拟合(训练集和验证集损失都高)。过大的 L2 惩罚会迫使所有权重趋近于 0,模型能力严重受限。权重衰减是一个需要调节的超参数,常用范围是 1e-4 到 1e-2,从小值开始尝试。

本章小结

  1. SGD 是优化器的基础,直接使用梯度乘以学习率进行更新,但在复杂地形中容易震荡。
  2. Momentum 通过累积历史梯度的指数移动平均(动量)来加速收敛并抑制震荡,其更新方向是当前梯度与历史动量的加权和。
  3. Adam 结合了 Momentum(一阶矩估计)和 RMSProp(二阶矩估计)的思想,为每个参数提供独立的自适应学习率,使其在深度学习中表现出优异的鲁棒性和收敛性能。
  4. 权重衰减 是内置于优化器的 L2 正则化技术,通过在梯度中添加 λ * θ 项来防止过拟合。
  5. 优化器的选择与超参数(学习率、动量因子、权重衰减)设置高度依赖于具体任务和数据,需要通过实验来确定。

📋 行动清单

  • 在 PyTorch 中,分别用 optim.SGD(无动量)、optim.SGD(带动量)和 optim.Adam 训练一个简单的线性回归模型,观察训练损失曲线的差异。
  • 修改 Adam 优化器的 betas 参数(例如改为 (0.8, 0.888)),观察对同一模型收敛速度的影响。
  • 尝试在优化器中使用 weight_decay(例如 1e-4),并与不使用权重衰减的情况对比,观察验证集性能的变化。

延伸阅读

本段 PyTorch 官方文档/教程条目的框架出处:PyTorch: An Imperative Style, High-Performance Deep Learning Library(Paszke et al., 2019,arXiv:1912.01703);页面内容以 PyTorch 2.14.1 为准。

  1. Adam: A Method for Stochastic Optimization(Kingma & Ba, 2015, ICLR, arXiv:1412.6980)—— Adam 原始论文 - https://arxiv.org/abs/1412.6980
  2. On the importance of initialization and momentum in deep learning(Sutskever et al., 2013, ICML)—— 讨论动量作用,注意其公式与 PyTorch 实现的差异 - https://proceedings.mlr.press/v28/sutskever13.html
  3. PyTorch 官方文档 torch.optim(2.14.1,SGD / Adam / AdamW 的算法与参数) - https://pytorch.org/docs/stable/optim.html

—— 小象教研组