摘要

深度学习训练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=βvt1+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=β1mt1+(1β1)gt=β2vt1+(1β2)gt2=1β1tmt,v^t=1β2tvt=θtv^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被缩放,泛化差快速原型调试
AdamWAdam+解耦weight_decay自适应+正则生效需要调wdTransformer、LLM微调、ViT
RAdam整流修正Adam前期方差前期更稳定整流条件受beta2影响,非万能免warmup不想精细调warmup的原型

补充两个工业经常见到变体:

  1. Lion:只用符号更新,显存占用更低,适合大规模预训练;
  2. 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训练调参 #深度学习

更多推荐