SGD/Momentum/Adam/AdamW/RAdam深度解析|优化器原理、PyTorch实战、调参踩坑与选型决策
摘要
深度学习训练loss震荡、验证集上不去,很多问题根源来自优化器选择与超参配置。本文基于经典论文,拆解SGD、Momentum、Adam、AdamW、RAdam底层数学逻辑,重点讲清Adam与AdamW最容易踩坑的解耦权重衰减;附带可复现PyTorch对比实验;给出参数分组过滤bias/Norm层的生产代码;梳理训练故障排查清单;提供CV、Transformer、LoRA微调多场景选型决策,适合CV、NLP、大模型微调研发人员。
关键词:优化器;AdamW;RAdam;SGD;权重衰减weight_decay;PyTorch训练调参;深度学习
目录
1、训练工程痛点:很多人对优化器的几大误解
2、五大优化器原理、数学与通俗直觉
2.1 SGD & SGD‑Momentum
2.2 Adam:一阶二阶矩自适应学习率
2.3 AdamW:核心——解耦Weight Decay与L2正则
2.4 RAdam:整流修正Adam早期高方差问题
3、横向对比总表 + 工业常用变体简要介绍(Lion、Lookahead)
4、完整可运行PyTorch对比实验(验证Adam/AdamW权重衰减差异)
5、生产必看:weight_decay参数分组代码(过滤bias、Norm层)
6、训练故障排查清单:loss震荡、精度不涨、NaN
7、分场景选型与超参推荐(CV / Transformer / LoRA微调)
8、落地总结:拿到新项目如何挑选优化器
一、训练工程痛点:很多人对优化器的几大误解
做项目过程中见过非常多误区:
误区1:AdamW万能,什么模型无脑AdamW;
误区2:PyTorch中Adam(weight_decay=xxx)等价L2正则,和AdamW效果一样;
误区3:RAdam出来就可以彻底抛弃warmup,直接开训;
误区4:weight_decay越大泛化越好,把bias、LayerNorm也加入衰减。
我之前做NLP微调踩过一个真实坑:直接使用Adam设置weight_decay,训练loss下降很漂亮,但验证集指标死活不动;换成AdamW,再做参数分组过滤Norm与bias,验证集直接上涨2个点。
优化器不是背公式,核心要理解:动量干什么、自适应学习率能干什么、权重衰减到底作用在哪。
二、五大优化器原理、数学与通俗直觉
把训练损失地形想象成盲人下山:
- SGD:盲人只看脚下坡度,一步一步往下挪,稳但是慢,容易困在局部极小;
- Momentum:增加惯性,记住历史方向,更容易冲出小坑;
- Adam:每个参数配一把尺子,一阶矩记录方向,二阶矩记录坡度波动,每个参数自适应步长;
- AdamW:修复Adam致命缺陷,把weight_decay从梯度计算解耦;
- RAdam:修复Adam训练前期二阶矩统计不准,训练初期退化为Momentum,后期切自适应。
2.1 SGD / SGD‑Momentum
SGD:
θt+1=θt−η⋅gt\theta_{t+1}=\theta_t-\eta \cdot g_tθt+1=θt−η⋅gt
带动量SGD:
vt=βvt−1+gtv_t=\beta v_{t-1}+g_tvt=βvt−1+gt
θt+1=θt−ηvt\theta_{t+1}=\theta_t-\eta v_tθt+1=θt−ηvt
✅优点:泛化能力强;
❌缺点:学习率极其敏感,深层模型收敛慢,容易卡在平台区。
2.2 Adam
mt=β1mt−1+(1−β1)gtvt=β2vt−1+(1−β2)gt2m^t=mt1−β1t,v^t=vt1−β2tθt+1=θt−ηv^t+ϵm^t \begin{align*} m_t &= \beta_1 m_{t-1}+(1-\beta_1)g_t \\ v_t &= \beta_2 v_{t-1}+(1-\beta_2)g_t^2 \\ \hat m_t &= \frac{m_t}{1-\beta_1^t},\quad \hat v_t=\frac{v_t}{1-\beta_2^t}\\ \theta_{t+1} &= \theta_t-\frac{\eta}{\sqrt{\hat v_t+\epsilon}}\hat m_t \end{align*} mtvtm^tθt+1=β1mt−1+(1−β1)gt=β2vt−1+(1−β2)gt2=1−β1tmt,v^t=1−β2tvt=θt−v^t+ϵηm^t
重点坑:Adam里面
weight_decay等价于L2正则,是加到梯度中,会被1v^t\frac{1}{\sqrt{\hat v_t}}v^t1缩放。不同参数权重衰减强度被自适应改变,这不是我们想要的L2正则效果。
2.3 AdamW(解耦权重衰减,Transformer事实标准)
AdamW不在梯度里面混入L2,更新步骤直接对参数做衰减:
θt+1=θt−η(m^tv^t+ϵ+λθt)\theta_{t+1} = \theta_t - \eta\left(\frac{\hat m_t}{\sqrt{\hat v_t+\epsilon}}+\lambda \theta_t\right)θt+1=θt−η(v^t+ϵm^t+λθt)
权重衰减独立执行,不受二阶矩缩放影响。
关键点:CV、NLP大模型,优先AdamW,不要用Adam开启weight_decay冒充L2正则。
2.4 RAdam(Rectified Adam)
Adam训练前期ttt很小,v^t\hat v_tv^t统计方差极大,步长不稳定。
RAdam引入整流项ρt\rho_tρt:当ρt\rho_tρt低于阈值,走Momentum更新;条件满足后,再切换Adam自适应更新。
⚠️工程现实:论文宣传“免warmup”,实际项目依然建议搭配warmup;beta2改动会直接改变整流开关时机,不要随便乱改。
三、横向对比总表 + 工业变体简介
| 优化器 | 核心特点 | 优点 | 缺点 | 典型适用场景 |
|---|---|---|---|---|
| SGD+Momentum | 梯度+惯性 | 泛化强 | 学习率敏感、收敛慢 | 传统CV竞赛ResNet |
| Adam | 一阶+二阶矩自适应 | 收敛快,对lr不敏感 | weight_decay被缩放,泛化差 | 快速原型调试 |
| AdamW | Adam+解耦weight_decay | 自适应+正则生效 | 需要调wd | Transformer、LLM微调、ViT |
| RAdam | 整流修正Adam前期方差 | 前期更稳定 | 整流条件受beta2影响,非万能免warmup | 不想精细调warmup的原型 |
补充两个工业经常见到变体:
- Lion:只用符号更新,显存占用更低,适合大规模预训练;
- Lookahead:包装器,维护快慢两套权重,平滑震荡,可套AdamW/RAdam。
四、完整可运行PyTorch对比实验
实验目的:复现Adam weight_decay被缩放,对比AdamW真实正则效果;CPU可运行。
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
torch.manual_seed(42)
# 构造高维稀疏模拟数据集
N, dim = 500, 100
X = torch.randn(N, dim) * 2.0
true_w = torch.zeros(dim)
true_w[:5] = 3.0
y = X @ true_w + 1.0 + torch.randn(N) * 0.3
X_train, y_train = X[:400], y[:400]
X_val, y_val = X[400:], y[400:]
def make_model():
return nn.Sequential(
nn.Linear(dim, 32),
nn.ReLU(),
nn.Linear(32, 1)
)
def make_optimizer(model, name, lr=0.01, wd=0.001):
if name == "SGD":
return optim.SGD(model.parameters(), lr=lr, weight_decay=wd)
if name == "Momentum":
return optim.SGD(model.parameters(), lr=lr, momentum=0.9, weight_decay=wd)
if name == "Adam":
return optim.Adam(model.parameters(), lr=lr, weight_decay=wd)
if name == "AdamW":
return optim.AdamW(model.parameters(), lr=lr, weight_decay=wd)
if name == "RAdam":
return optim.RAdam(model.parameters(), lr=lr, weight_decay=wd)
raise ValueError("opt name error")
def train(opt_name, epochs=300, lr=0.01, wd=0.001):
model = make_model()
opt = make_optimizer(model, opt_name, lr, wd)
loss_fn = nn.MSELoss()
tr_loss, val_loss = [], []
for ep in range(epochs):
model.train()
opt.zero_grad()
pred = model(X_train).squeeze()
loss = loss_fn(pred, y_train)
loss.backward()
opt.step()
tr_loss.append(loss.item())
model.eval()
with torch.no_grad():
v_pred = model(X_val).squeeze()
v_loss = loss_fn(v_pred, y_val).item()
val_loss.append(v_loss)
return tr_loss, val_loss
if __name__ == "__main__":
plt.figure(figsize=(12, 5))
plt.subplot(1,2,1)
opts = ["SGD","Momentum","Adam","AdamW","RAdam"]
for n in opts:
_, v = train(n, epochs=250, lr=0.01, wd=0.001)
plt.plot(v, label=n)
plt.yscale("log")
plt.legend()
plt.title("Val Loss Comparison wd=0.001")
plt.subplot(1,2,2)
# 对比Adam与AdamW权重范数,放大wd看差异
for name in ["Adam","AdamW"]:
m = make_model()
opt = make_optimizer(m, name, lr=0.01, wd=0.1)
loss_fn = nn.MSELoss()
for _ in range(300):
opt.zero_grad()
loss = loss_fn(m(X_train).squeeze(), y_train)
loss.backward()
opt.step()
norm_val = m[0].weight.norm().item()
print(f"{name} | first linear weight L2 norm: {norm_val:.4f}")
plt.tight_layout()
plt.show()
现象解读:同样wd参数,Adam权重L2范数明显更大,代表L2正则被二阶矩缩放,衰减效果变弱;AdamW权重被有效压缩。
五、生产必看:weight_decay参数分组代码
工程铁则:bias、LayerNorm、BatchNorm层不要参与weight_decay,很多新手直接全部参数一起wd,导致性能掉点。
def get_adamw_optimizer(model, lr=3e-4, weight_decay=1e-2):
decay_params = []
no_decay_params = []
for name, param in model.named_parameters():
if not param.requires_grad:
continue
# bias、一维参数、norm层不做weight_decay
if len(param.shape) == 1 or "bn" in name or "ln" in name or "bias" in name:
no_decay_params.append(param)
else:
decay_params.append(param)
groups = [
{"params": decay_params, "weight_decay": weight_decay},
{"params": no_decay_params, "weight_decay": 0.0}
]
return torch.optim.AdamW(groups, lr=lr, betas=(0.9,0.999))
六、训练故障排查清单(优化器角度定位)
| 现象 | 可能根因 | 修复方案 |
|---|---|---|
| 训练loss降得快,验证集精度上不去 | Adam开启weight_decay;没有做参数分组,Norm/bias被衰减 | 替换AdamW,使用上面参数分组代码 |
| 训练前期loss震荡大 | Adam无warmup,二阶矩统计不稳定 | 增加linear warmup,RAdam也建议配warmup |
| 出现NaN、inf | 学习率过大,或beta2设置不合理 | 降低lr,开启梯度裁剪clip_grad_norm_ |
| 收敛极慢,loss几乎不动 | weight_decay设置过大,参数被过度压缩 | 下调wd,检查参数分组是否写反 |
| LoRA微调效果差 | 把lora‑A/B以外参数错误开启weight_decay | 只对LoRA矩阵做decay,embedding/norm关闭 |
七、分场景选型与超参推荐
1、传统CNN图像分类(ResNet系列竞赛)
优先:SGD‑Momentum;lr=1e‑2,wd=1e‑4,配合余弦退火;
2、ViT / Transformer / 大模型全量微调
优先:AdamW;lr=1e‑4~3e‑4,wd=1e‑2,必须做参数分组,warmup;
3、LoRA / QLoRA微调大模型
AdamW;lr=1e‑4 ~ 5e‑4,仅对lora权重做weight_decay;
4、快速原型、小规模实验
AdamW / RAdam,快速验证想法;RAdam依然建议搭配少量warmup。
八、落地总结
1、不要用Adam(weight_decay=xx)假装做L2正则,Transformer/NLP项目优先AdamW;
2、bias、BN、LN层不要参与weight_decay,务必做参数分组;
3、RAdam不是彻底抛弃warmup,现实项目依旧建议保留少量预热;
4、优化器只是训练的一环,出现验证集问题,优先排查:优化器选型 → weight_decay分组 → learning rate + warmup。
你在训练的时候踩过哪些优化器的坑?欢迎评论区交流。
#优化器 #AdamW #RAdam #SGD #weight_decay #PyTorch训练调参 #深度学习
更多推荐

所有评论(0)