神经网络引擎:梯度下降算法(SGD → Adam)进化史与调参指南
如果说损失函数是模型的"北极星",那梯度下降就是驱动轮船抵达彼岸的"蒸汽机"。
在深度学习的训练过程中,梯度下降(Gradient Descent) 及其变体是唯一负责更新权重参数的优化器。初代机器学习的教材只会教你"求导下降",但在大模型时代,优化器本身已经演化出了一套极其精密的数学控制系统。
很多新手在训练时疯狂调整学习率,却不知道 Adam 和 SGD+Momentum 的底层逻辑完全不同。今天,我们从数学直觉出发,走一遍梯度下降的进化树。
1. 基石:从"批量全算"到"随机单点"
我们先从最朴素的数学定义开始,假设目标函数为 J(θ)J(\theta)J(θ),其中 θ\thetaθ 为模型参数,η\etaη 为学习率(Learning Rate)。
1.1 批量梯度下降(BGD)------ 教科书标准
每次更新时,使用全部训练样本 来计算梯度。
θ=θ−η⋅∇θJ(θ) \theta = \theta - \eta \cdot \nabla_\theta J(\theta) θ=θ−η⋅∇θJ(θ)
进一步展开为:
θ=θ−η⋅1N∑i=1N∇θJi(θ) \theta = \theta - \eta \cdot \frac{1}{N} \sum_{i=1}^{N} \nabla_\theta J_i(\theta) θ=θ−η⋅N1i=1∑N∇θJi(θ)
- 优点:梯度方向最准确,下降轨迹稳定,易于收敛到全局最优(凸函数)或局部最优(非凸)。
- 致命伤:计算极慢。若数据集有 1 亿张图,每次迭代都要遍历全部数据,根本不可行。
1.2 随机梯度下降(SGD)------ 打破计算的枷锁
不再算全体,每次迭代只随机选取 1 个样本 iii 计算梯度。
θ=θ−η⋅∇θJi(θ) \theta = \theta - \eta \cdot \nabla_\theta J_i(\theta) θ=θ−η⋅∇θJi(θ)
- 优点 :计算极快,且由于单个样本带有随机噪声,反而能帮助模型跳出尖锐的局部极小值,泛化能力往往比 BGD 更好。
- 缺点:梯度方向震荡剧烈,像喝醉了酒的人走路,无法利用 GPU 的矩阵并行能力。
1.3 小批量梯度下降(Mini-batch GD)------ 工业界唯一真神
每次选取 mmm 个样本(通常为 32、64、256 等)组成一个 Batch。
θ=θ−η⋅1m∑i=1m∇θJi(θ) \theta = \theta - \eta \cdot \frac{1}{m} \sum_{i=1}^{m} \nabla_\theta J_i(\theta) θ=θ−η⋅m1i=1∑m∇θJi(θ)
- 为什么最强 :向量化计算充分利用 GPU 算力;引入了一定的随机噪声帮助泛化;震荡幅度可控。你在 PyTorch 中调的
batch_size就是针对这个公式的。
2. 困境:震荡、鞍点与病态曲率
原始的 Mini-batch SGD 面临三个地狱级难题:
- 峡谷震荡:在狭长的损失平面(如某些方向的曲率极大,另一方向极小),SGD 会在陡峭方向剧烈震荡,在平坦方向移动极慢。
- 鞍点陷阱:在高维空间中,局部极小值少,鞍点(梯度为 0 但不是极值点)极多。SGD 在这里梯度消失,无法动弹。
- 学习率死局 :全局统一的学习率 η\etaη 太僵硬------稀疏特征希望更新快一点,稠密特征希望更新慢一点。
数学家们为了攻克这三点,开启了优化算法的"军备竞赛"。
3. 动量时代:给下降过程加上"惯性"
3.1 Momentum(动量法)
借鉴物理学的动量概念,引入衰减系数 γ\gammaγ(通常取 0.9),让梯度的更新方向保留上一时刻的速度。
vt=γvt−1+η∇θJ(θ)θ=θ−vt \begin{aligned} v_t &= \gamma v_{t-1} + \eta \nabla_\theta J(\theta) \\ \theta &= \theta - v_t \end{aligned} vtθ=γvt−1+η∇θJ(θ)=θ−vt
- 物理直觉 :小球滚下山坡,历史动量使其在平坦方向持续加速,在震荡方向由于方向来回抵消而自动减速。收敛速度大幅提升。
3.2 Nesterov 加速梯度(NAG)------ 带有"预测能力"的动量
NAG 比 Momentum 多了一个"前瞻"步骤。它在当前点先按照历史动量走一步,再在该位置求梯度。
vt=γvt−1+η∇θJ(θ−γvt−1)θ=θ−vt \begin{aligned} v_t &= \gamma v_{t-1} + \eta \nabla_\theta J(\theta - \gamma v_{t-1}) \\ \theta &= \theta - v_t \end{aligned} vtθ=γvt−1+η∇θJ(θ−γvt−1)=θ−vt
- 精妙之处:相当于小球提前看到了上坡路,会提前减速,防止在越过极小值点时冲过头。它在 RNN 等序列任务中表现出色。
4. 自适应时代:每个参数都有自己的学习率
既然手动调全局学习率太痛苦,为何不给每个参数 iii 分配独立的动态学习率?
4.1 AdaGrad ------ 惩罚频繁特征
AdaGrad 累计历史梯度平方和 GGG,频繁更新的参数学习率会被缩小,稀疏参数获得较大更新。
θt+1=θt−ηGt+ϵ⊙gt \theta_{t+1} = \theta_t - \frac{\eta}{\sqrt{G_t + \epsilon}} \odot g_t θt+1=θt−Gt+ϵ η⊙gt
其中 Gt=∑τ=1tgτgτTG_t = \sum_{\tau=1}^{t} g_\tau g_\tau^TGt=∑τ=1tgτgτT,⊙\odot⊙ 表示逐元素相乘,ϵ\epsilonϵ 为防止除零的小常数(如 10−810^{-8}10−8)。
- 缺点 :GtG_tGt 是单调递增的,学习率最终会缩到无限小,模型过早丧失学习能力。
4.2 RMSprop ------ 解决 AdaGrad 的"早衰"问题
Geoffrey Hinton 在课上提出的方法,用**指数移动平均(EMA)**代替累加和,只关注最近一段时间的梯度大小。
Eg2t=βEg2t−1+(1−β)gt2θt+1=θt−ηEg2t+ϵgt \begin{aligned} Eg\^2t &= \beta Eg\^2{t-1} + (1-\beta) g_t^2 \\ \theta_{t+1} &= \theta_t - \frac{\eta}{\sqrt{Eg\^2_t + \epsilon}} g_t \end{aligned} Eg2tθt+1=βEg2t−1+(1−β)gt2=θt−Eg2t+ϵ ηgt
- 效果:学习率不再单调递减,能够持续学习。它是处理非平稳目标(如 RNN)的利器。
5. 集大成者:Adam(Adaptive Moment Estimation)
Adam 是目前最主流、最鲁棒的优化器。它结合了 Momentum(一阶矩估计)和 RMSprop(二阶矩估计),并加入了偏置校正机制。
设 gtg_tgt 为当前 mini-batch 梯度,β1,β2\beta_1, \beta_2β1,β2 为衰减率(通常取 0.9 和 0.999)。
第一步:计算带偏置的矩估计
mt=β1mt−1+(1−β1)gt(梯度的指数移动平均,即动量)vt=β2vt−1+(1−β2)gt2(梯度平方的指数移动平均,即自适应学习率) \begin{aligned} m_t &= \beta_1 m_{t-1} + (1-\beta_1) g_t \quad \text{(梯度的指数移动平均,即动量)} \\ v_t &= \beta_2 v_{t-1} + (1-\beta_2) g_t^2 \quad \text{(梯度平方的指数移动平均,即自适应学习率)} \end{aligned} mtvt=β1mt−1+(1−β1)gt(梯度的指数移动平均,即动量)=β2vt−1+(1−β2)gt2(梯度平方的指数移动平均,即自适应学习率)
第二步:偏差校正(防止初期初始化为 0 导致步长极小)
m^t=mt1−β1tv^t=vt1−β2t \begin{aligned} \hat{m}_t &= \frac{m_t}{1 - \beta_1^t} \\ \hat{v}_t &= \frac{v_t}{1 - \beta_2^t} \end{aligned} m^tv^t=1−β1tmt=1−β2tvt
第三步:参数更新
θt+1=θt−η⋅m^tv^t+ϵ \theta_{t+1} = \theta_t - \eta \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} θt+1=θt−η⋅v^t +ϵm^t
- Adam 的本质 :分子是带惯性的"方向",分母是自适应缩放的"步长"。当梯度剧烈震荡时 v^t\hat{v}_tv^t 变大,步长自动缩小;当梯度方向一致时,分子动量积累,加速前进。
6. 终极对决:Adam vs. SGD+Momentum,到底选谁?
这是深度学习社区的"千古之争"。我给出明确的工程决策树:
| 场景 | 推荐优化器 | 核心理由 |
|---|---|---|
| CV 视觉任务(分类/检测) | SGD + Momentum (Nesterov) | 虽收敛稍慢,但最终收敛的极小值更"平坦",测试集泛化能力普遍优于 Adam。 |
| NLP / Transformer / 大模型 | AdamW (Adam with decoupled weight decay) | 参数极度稀疏且模型极深,Adam 的自适应特性是稳定训练的基石。 |
| GAN 训练 | Adam | 不稳定对抗中需要自适应学习率来稳住两个网络的震荡。 |
| 强化学习 / 噪声极大 | RMSprop 或 SGD | Adam 对噪声中的异常梯度过于敏感,可能崩塌。 |
重点提示 :如果你用 Adam,一定不要用 L2 正则化(weight decay)直接在 loss 上加,而是要用 AdamW,它将权重衰减与梯度更新解耦,效果更好。
7. 实战必知:学习率(Learning Rate)的调度策略
优化器是"发动机",学习率调度器(Scheduler)是"变速箱"。光有发动机不行,还得会换挡。
- Step Decay:每过 N 个 Epoch,学习率乘以 0.1。
- Cosine Annealing(余弦退火) :学习率按余弦函数周期变化,常用于 Swin Transformer。公式为 ηt=ηmin+12(ηmax−ηmin)(1+cos(TcurTmaxπ))\eta_t = \eta_{min} + \frac{1}{2}(\eta_{max} - \eta_{min})(1 + \cos(\frac{T_{cur}}{T_{max}}\pi))ηt=ηmin+21(ηmax−ηmin)(1+cos(TmaxTcurπ))。
- Warmup(预热) :在训练最初的几千步,学习率从 0 线性上升到目标值。这在 Adam 和 Transformer 训练中是必选项,因为初期梯度方差极大,预热防止梯度过大导致注意力机制崩塌。
8. PyTorch 代码范例(带注释)
python
import torch.optim as optim
# 1. 传统 SGD + Momentum (CV 首选)
optimizer_sgd = optim.SGD(model.parameters(),
lr=0.01,
momentum=0.9,
weight_decay=5e-4,
nesterov=True) # 开启 NAG
# 2. 现代 AdamW (NLP / 大模型首选)
optimizer_adamw = optim.AdamW(model.parameters(),
lr=1e-4,
betas=(0.9, 0.999),
weight_decay=0.01)
# 配合余弦退火 + 预热 (使用 transformers 库的 get_scheduler)
from transformers import get_cosine_schedule_with_warmup
scheduler = get_cosine_schedule_with_warmup(
optimizer_adamw,
num_warmup_steps=1000,
num_training_steps=total_steps
)
结语
从原始的 Vanilla SGD 到集大成的 Adam,优化算法的演进史本质上是人类对高维非凸曲面认知的深化。
- 如果你追求极致的泛化性能,请拥抱 SGD + Momentum。
- 如果你追求开箱即用、训练稳定,请无脑上 AdamW。