优化器进化史:从 SGD 到 AdamW,到底该用哪个?
2026/4/23 18:11:23 网站建设 项目流程

💻完整代码 + 对比实验:GitHub 仓库
📖配套教程:CSDN 专栏
如果觉得有用,欢迎 ⭐ Star 支持!


🎯 为什么优化器这么重要?

大白话:优化器决定模型"怎么学习"

损失函数:告诉模型"你错得多离谱" 优化器:告诉模型"该怎么改参数" 类比: 损失函数 = GPS 导航(告诉你离目的地多远) 优化器 = 司机(决定怎么走、走多快)

选错优化器的后果:

SGD(学习率太大): 在最低点附近震荡,永远找不到最优解 Adam(默认参数): 收敛快,但可能泛化差(训练集好,测试集差) AdamW(推荐): 收敛快 + 泛化好(最佳平衡)

今天带你从SGD 到 AdamW,看清楚每个优化器的优缺点!


📊 优化器总览

优化器年份收敛速度泛化能力超参数适用场景
SGD1951⭐⭐⭐⭐⭐⭐⭐lr, momentum理论分析
SGD + Momentum1964⭐⭐⭐⭐⭐⭐⭐lr, momentum经典选择
RMSProp2012⭐⭐⭐⭐⭐⭐⭐lr, alphaRNN 训练
Adam2014⭐⭐⭐⭐⭐⭐⭐⭐lr, betas默认选择
AdamW2017⭐⭐⭐⭐⭐⭐⭐⭐⭐lr, betas, weight_decay当前最佳
AdamW + Cosine2020⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐lr, T_maxSOTA 模型

关键进化:

  1. 收敛速度:SGD 慢 → Adam 快(10 倍)
  2. 泛化能力:Adam 差 → AdamW 好(+5% 准确率)
  3. 易用性:SGD 调参难 → AdamW 几乎不用调

1️⃣ SGD(随机梯度下降):鼻祖

核心思想

一句话总结:沿着梯度的反方向走

梯度:函数上升最快的方向 负梯度:函数下降最快的方向 优化器: 参数 = 参数 - 学习率 × 梯度

大白话解释

场景:下山

你在山上,想最快到达谷底 方法 1(最速下降): 每步都往最陡的方向走 问题: - 可能走到局部最低点(不是全局最低) - 可能来回震荡 类比 SGD: 参数更新 = 当前位置 - 学习率 × 梯度

代码实现

import torch import torch.nn as nn # PyTorch 内置 optimizer = torch.optim.SGD( model.parameters(), lr=0.01, # 学习率 momentum=0.0 # 动量(先设为 0) ) # 训练循环 for epoch in range(num_epochs): for inputs, targets in dataloader: optimizer.zero_grad() # 清空梯度 outputs = model(inputs) loss = criterion(outputs, targets) loss.backward() # 计算梯度 optimizer.step() # 更新参数

手写实现(理解原理)

class SGDOptimizer: """手写 SGD 优化器""" def __init__(self, params, lr=0.01): self.params = params self.lr = lr def step(self): for param in self.params: if param.grad is not None: # 参数更新公式 param.data = param.data - self.lr * param.grad def zero_grad(self): for param in self.params: if param.grad is not None: param.grad.zero_() # 使用 optimizer = SGDOptimizer(model.parameters(), lr=0.01) for epoch in range(num_epochs): for inputs, targets in dataloader: optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, targets) loss.backward() optimizer.step()

SGD 的问题

问题 1:学习率难调

学习率太大: 在最低点附近震荡,不收敛 学习率太小: 收敛太慢(走 100 步还没到谷底) 类比: 学习率 = 步长 太大:一步迈过最低点 太小:走 100 步还在半山腰

问题 2:容易卡在局部最优

损失函数形状: ╱‾‾╲ ╲ ╱ ╲______ ╱ 局部最优 ╱ 全局最优 ╱ ───────────────── SGD: 从左边开始 → 下到局部最优 → 卡住 原因: 梯度为 0 就不走了 但局部最优的梯度也是 0

2️⃣ SGD + Momentum:加入惯性

核心思想

一句话总结:下山时保留"冲力"

SGD: 每步只看当前梯度(像盲人摸象) SGD + Momentum: 考虑之前的"冲力"(像滚雪球) 公式: v_t = momentum × v_{t-1} + lr × gradient param = param - v_t

大白话解释

场景:下山

普通 SGD: Step 1: 往左走(梯度向左) Step 2: 往右走(梯度向右)← 来回震荡 SGD + Momentum: Step 1: 往左走,获得向左的速度 Step 2: 虽然梯度向右,但冲力向左,继续向左走 效果: 1. 减少震荡 2. 加速收敛 3. 更容易跳出局部最优

代码实现

optimizer = torch.optim.SGD( model.parameters(), lr=0.01, momentum=0.9, # 动量系数(默认 0.9) weight_decay=1e-4 # 权重衰减(L2 正则化) )

可视化对比

优化路径: SGD(无动量): ━━╲╱╲╱╲╱╲╱━━━━ ← 来回震荡 SGD + Momentum: ━━━━━━━━━━━━━━━━ ← 平滑前进 效果: - 训练快 2-3 倍 - 更稳定

Momentum 的问题

学习率还是难调:

Momentum 只是"加速",没解决根本问题: - 不同参数需要不同学习率 - 不同阶段需要不同学习率 - 梯度大小变化大时不稳定

3️⃣ RMSProp:自适应学习率

核心思想

一句话总结:每个参数有自己的学习率

SGD:所有参数用同一个学习率 RMSProp: 梯度大的参数 → 学习率自动减小 梯度小的参数 → 学习率自动增大 公式: E[g²]_t = β × E[g²]_{t-1} + (1-β) × g²_t param = param - lr × g_t / √(E[g²]_t + ε)

大白话解释

场景:不同参数更新幅度不同

参数 1(梯度大): 普通 SGD:大步走 → 可能过冲 RMSProp:自动缩小步长 → 走得稳 参数 2(梯度小): 普通 SGD:小步走 → 太慢 RMSProp:自动放大步长 → 走得快

代码实现

optimizer = torch.optim.RMSprop( model.parameters(), lr=0.001, alpha=0.99, # 衰减率(默认 0.99) eps=1e-8 # 数值稳定(防止除 0) )

RMSProp 的问题

累计梯度可能无限大:

RMSProp 只考虑"近期"梯度(指数衰减) → 没有考虑"历史"总梯度 结果: 学习率可能变得很大(不稳定)

4️⃣ Adam:集大成者

核心思想

一句话总结:Momentum + RMSProp 的完美结合

Adam = Momentum(一阶动量)+ RMSProp(二阶动量) 一阶动量(类似 Momentum): 记录梯度的"方向" 二阶动量(类似 RMSProp): 记录梯度的"幅度" 自适应学习率: 学习率 = 一阶动量 / √(二阶动量)

大白话解释

Adam 的 3 个优势:

1. 自适应学习率(每个参数不同) → 不用手动调学习率 2. 动量加速(考虑历史梯度) → 收敛快 3. 偏差修正(初期更准确) → 训练稳定

代码实现

optimizer = torch.optim.Adam( model.parameters(), lr=0.001, # 学习率 betas=(0.9, 0.999), # 一阶和二阶动量衰减率 eps=1e-8, # 数值稳定 weight_decay=0 # 权重衰减(Adam 默认不加) )

Adam 的问题

泛化能力差:

实验对比(CIFAR-100): SGD + Momentum: 训练集准确率:95% 测试集准确率:85% ← 泛化好 Adam: 训练集准确率:99% 测试集准确率:80% ← 过拟合! 原因: Adam 收敛太快,跳到尖锐的最小值 SGD 收敛慢,找到平坦的最小值(泛化好)

5️⃣ AdamW:当前最佳

核心思想

一句话总结:Adam + 正确的权重衰减

Adam 的问题: weight_decay 和 L2 正则化不等价 AdamW 的改进: 分离权重衰减和梯度更新 公式: Adam: 参数 = 参数 - lr × (梯度 + weight_decay × 参数) AdamW: 参数 = 参数 × (1 - lr × weight_decay) - lr × 梯度

大白话解释

权重衰减的作用:

目的:防止参数变得太大(过拟合) Adam 的错误实现: weight_decay 被当成梯度的一部分 → 自适应学习率会把 weight_decay 缩小 → 正则化效果弱 AdamW 的正确实现: weight_decay 独立于梯度 → 不受自适应学习率影响 → 正则化效果好

代码实现

optimizer = torch.optim.AdamW( model.parameters(), lr=0.001, betas=(0.9, 0.999), eps=1e-8, weight_decay=0.01 # 注意:AdamW 必须加 weight_decay! )

对比实验

# Adam vs AdamW # Adam optimizer_adam = torch.optim.Adam(model.parameters(), lr=0.001) train(model, optimizer_adam) print(f"Adam 测试集准确率: {evaluate(model):.2%}") # 80.5% # AdamW optimizer_adamw = torch.optim.AdamW(model.parameters(), lr=0.001, weight_decay=0.01) train(model, optimizer_adamw) print(f"AdamW 测试集准确率: {evaluate(model):.2%}") # 85.2% # 提升:+4.7%

📊 完整对比

收敛速度对比

训练 Loss 曲线: SGD: ╱━━━━━━━━━━━━━━━━━ ↗ ↗ ↗ SGD+Momentum: ━━━━━━━━━━━╱━━━━ ↗ ↗ Adam: ╱━━━━━━━━━━━━━━━━━━━━ ↗ ↗ AdamW: ╱━━━━━━━━━━━━━━━━━━━━ ↗ ↗ 结论: SGD 最慢,Adam/AdamW 最快

泛化能力对比

测试集准确率: SGD: ████████████████████ 85% SGD+Momentum: ██████████████████████ 87% Adam: ███████████████████ 80% AdamW: ██████████████████████ 85% 结论: SGD 泛化好,Adam 泛化差 AdamW 结合了速度和泛化

超参数敏感性

学习率扫描(0.0001 ~ 0.1): SGD: ★☆☆☆☆ ← 非常敏感 Adam: ★★★★☆ ← 不敏感 AdamW: ★★★★★ ← 最不敏感 结论: AdamW 最"傻瓜式",几乎不用调参

💡 实战建议

1. 如何选择?

快速原型(实验阶段): → AdamW(收敛快,不用调参) 追求极致性能(论文/比赛): → SGD + Momentum + Cosine LR → 需要仔细调参,但上限更高 RNN/LSTM 训练: → Adam 或 RMSProp → 梯度变化大,自适应学习率更好 推荐配置: → AdamW(99% 场景够用)

2. AdamW 最佳实践

# Transformer 模型(BERT、GPT) optimizer = torch.optim.AdamW( model.parameters(), lr=3e-5, # 小学习率 betas=(0.9, 0.999), eps=1e-8, weight_decay=0.01 # 必须有! ) # 学习率预热(前 10% 训练) scheduler = get_linear_schedule_with_warmup( optimizer, num_warmup_steps=1000, num_training_steps=10000 )

3. SGD 最佳实践

# CNN 训练(追求极致性能) optimizer = torch.optim.SGD( model.parameters(), lr=0.1, # 大学习率 momentum=0.9, weight_decay=1e-4, nesterov=True # Nesterov 动量(更稳) ) # Cosine 学习率衰减 scheduler = torch.optim.lr_scheduler.CosineAnnealingLR( optimizer, T_max=num_epochs )

4. 优化器调试

# 监控梯度范数(判断是否爆炸) for name, param in model.named_parameters(): if param.grad is not None: grad_norm = param.grad.norm() print(f"{name}: grad_norm={grad_norm:.4f}") # 梯度裁剪(防止爆炸) torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

🎯 总结

优化器进化路线:

SGD (1951) ↓ 加入动量 SGD + Momentum (1964) ↓ 自适应学习率 RMSProp (2012) ↓ 一阶 + 二阶动量 Adam (2014) ↓ 正确的权重衰减 AdamW (2017) ↓ 学习率调度 AdamW + Cosine (2020)

选择原则:

  1. 默认用 AdamW- 99% 场景够用
  2. 追求极致用 SGD- 需要调参,但上限高
  3. 必须加权重衰减- 防止过拟合
  4. 配合学习率调度器- 提升性能

常用组合:

Transformer: AdamW + Cosine LR + Warmup CNN: SGD + Momentum + Cosine LR RNN: Adam + 梯度裁剪 GAN: Adam (lr=0.0002, betas=(0.5, 0.999))

📚 延伸阅读

完整代码和实验:https://github.com/Lee985-cmd/AI-30-Day-Challenge

30 天 AI 挑战教程:https://blog.csdn.net/m0_67081842

评论区留言:你平时用哪个优化器?

  • Adam?
  • AdamW?
  • SGD?
  • 其他?

欢迎分享你的经验!


如果这篇文章帮到你了,请 Star GitHub 项目支持一下!🌟 Star 链接

已有 12 人 Star,你的支持是我持续更新的动力!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询