Gradient Descent Optimizer —— 梯度下降优化器
一、前言:优化器是AI模型的“引擎”
在深度学习的训练过程中,优化器(Optimizer) 承担着最为核心的职责——根据损失函数的梯度更新模型参数,使模型逐步逼近最优状态。
我们可以把损失函数想象成一个极其复杂、崎岖不平的“地貌景观”(Loss Landscape),而优化器的任务,就是从一个随机的初始位置出发,一步步走到这个地貌的最低点。
所有优化器都围绕着两个核心问题来构建策略:
- 方向(Direction) :朝哪个方向走?
- 步长(Step Size) :走多远?
不同优化器正是对这两个问题给出了不同的答案。本文将遵循“问题提出 → 方案演进”的逻辑脉络,系统梳理从经典的批量梯度下降到现代深度学习模型中默认的AdamW的完整进化历程。
二、梯度下降三兄弟:BGD、SGD与MBGD
2.1 批量梯度下降(BGD):最稳健的“老实人”
批量梯度下降(Batch Gradient Descent, BGD) 每次迭代使用全部训练数据来计算损失函数的梯度,然后更新模型参数。
gt=n1∑i=1n∇θℓ(fθt(xi),yi)
θt+1=θt−η⋅gt
其中 n 是样本总数,η 是学习率。
优点:由于使用的是全量数据的真实梯度方向,BGD的收敛曲线相对平滑,对于凸函数能够保证收敛到全局最优解。
缺点:当数据集规模庞大时,每次迭代的计算量巨大,对内存要求高。在动辄百万、千万甚至亿级样本的数据集上,计算一次真实梯度的成本是无法接受的。
2.2 随机梯度下降(SGD):轻装上阵的“冒险家”
随机梯度下降(Stochastic Gradient Descent, SGD) 每次迭代只随机选取一个样本,根据该样本计算梯度并更新参数。
gt=∇θℓ(fθt(xi),yi) θt+1=θt−η⋅gt
优点:每次只计算一个样本的梯度,计算量小、更新速度快,对内存需求低,适合处理大规模数据集和在线学习场景。
缺点:梯度方向波动大,收敛路径曲折。SGD每次只依据一个样本更新,导致收敛过程具有较大的随机性和波动性,可能在最优解附近震荡。
不过,这种随机性也使得SGD有更大机会跳出局部最优解,尤其是在处理非凸函数时——这反而成了它的一个优势。
2.3 小批量梯度下降(MBGD):集大成者
小批量梯度下降(Mini-Batch Gradient Descent, MBGD) 是BGD和SGD的折中方案——每次迭代使用一小部分样本(一个小批量)来计算梯度和更新参数。
gt=b1∑i=1b∇θℓ(fθt(xi),yi) θt+1=θt−η⋅gt
其中 b 是批量大小(batch size),通常取 16 到 256 之间。
优点:结合了BGD的稳定性和SGD的随机性,通常能更稳定地收敛,且收敛速度比BGD快。同时,小批量的随机性也带来了一定的跳出局部最优的能力。此外,小批量计算可以充分利用GPU的向量化并行计算能力。
实际应用:MBGD在实际的深度学习应用中最为广泛,如图像分类、自然语言处理等领域。
1import numpy as np2
3def gradient_descent(X, y, theta, learning_rate, batch_size, n_epochs, method='mbgd'):4 """梯度下降的三种变体实现"""5 n_samples = X.shape[0]6
7 for epoch in range(n_epochs):8 if method == 'bgd':9 # 批量梯度下降:使用全部数据10 gradients = compute_gradients(X, y, theta)11 theta -= learning_rate * gradients12
13 elif method == 'sgd':14 # 随机梯度下降:每次一个样本15 for i in range(n_samples):16 idx = np.random.randint(n_samples)17 gradients = compute_gradients(X[idx:idx+1], y[idx:idx+1], theta)18 theta -= learning_rate * gradients19
20 elif method == 'mbgd':21 # 小批量梯度下降:打乱数据后分批22 indices = np.random.permutation(n_samples)23 for start in range(0, n_samples, batch_size):24 end = min(start + batch_size, n_samples)25 batch_indices = indices[start:end]26 gradients = compute_gradients(X[batch_indices], y[batch_indices], theta)27 theta -= learning_rate * gradients28
29 return theta2.4 三种方法的收敛特性对比
| 特性 | BGD | SGD | MBGD |
|---|---|---|---|
| 每次使用的样本数 | 全部 n | 1个 | b 个(16∼256) |
| 梯度准确性 | 最准确 | 噪声最大 | 适中 |
| 收敛速度(每次迭代) | 慢 | 快 | 适中 |
| 收敛稳定性 | 最稳定 | 波动大 | 较稳定 |
| 跳出局部最优能力 | 弱 | 强 | 中等 |
| 计算效率(大规模数据) | 低 | 高 | 高 |
从SGD到MBGD,我们解决了“效率与噪声的权衡”问题。但SGD/MBGD仍然面临一个根本性的挑战:在“峡谷”地形中震荡严重。
三、动量(Momentum)与Nesterov加速:给梯度加上“惯性”
3.1 问题的根源:峡谷地形的震荡
标准梯度下降法在参数空间存在 “峡谷”地形(即某一维度梯度远大于另一维度)时,会沿着陡峭方向来回震荡,导致有效前进速度大幅降低。
这种“近视”的更新方式存在两个本质缺陷:
- 仅考虑当前点的瞬时梯度信息,无法利用历史梯度轨迹
- 在病态曲面上,梯度方向可能与最优方向存在较大偏差
3.2 动量法:模拟物理世界的惯性
动量法(Momentum) 由Rumelhart等人在1986年提出,其核心思想是引入 “速度”变量来累积历史梯度信息。
我们可以将动量算法视为模拟连续时间下牛顿动力学下的粒子。想象一个在冰面上滑行的冰球——每当它沿着表面最陡的部分下降时,它会积累继续在该方向上滑行的速度。负梯度是推动粒子的力,速度是粒子的动量。
动量法的更新规则分为两步:
vt+1=γvt+η∇θJ(θt)
θt+1=θt−vt+1
其中 γ∈(0,1) 是动量系数(通常取 0.9),控制历史信息的衰减速率。
从展开式可以更清楚地看到动量的“记忆”本质:
vt=η∑i=1tγt−i∇θJ(θi)
速度 vt 是所有历史梯度的指数加权移动平均——近期梯度权重更大,但长期趋势也被保留。
动量系数的物理意义:如果将动量系数 γ=0.9,则最大速度可以达到纯梯度下降的 1/(1−γ)=10 倍。这意味着在梯度方向一致的维度上,动量可以积累动能实现加速;而在梯度方向频繁变化的维度上,动量效应会抵消震荡分量。
1def sgd_with_momentum(theta, grad, v, lr=0.01, momentum=0.9):2 """带动量的SGD更新"""3 v = momentum * v + lr * grad4 theta = theta - v5 return theta, v6
7# 模拟在峡谷地形中的对比8# 纯SGD会在陡峭方向来回震荡,而动量法能平滑震荡、加速前进3.3 Nesterov加速梯度(NAG): “看得更远”的动量
Nesterov加速梯度(Nesterov Accelerated Gradient, NAG) 是动量的一个改进版本。
两者的关键区别在于计算梯度的位置:
- 标准动量:在当前参数位置 θt 计算梯度
- Nesterov动量:在“预期位置” θt+γvt 计算梯度
NAG的更新规则:
vt+1=γvt+η∇θJ(θt+γvt)
θt+1=θt−vt+1
物理直觉:标准动量像一个滑雪者,每次到弯道才根据当前坡度调整方向;而Nesterov动量像一个有前瞻性的滑雪者——他先“看向”前方(预期位置),根据前方的坡度预先调整方向。
这种“前瞻”机制使得NAG在理论上具有更快的收敛速度,其收敛速率可以达到 O(1/t2),而标准梯度下降仅为 O(1/t)。
四、自适应学习率:为每个参数定制步长
4.1 AdaGrad:让“稀有特征”获得更大的更新
尽管动量和NAG解决了方向的问题,但所有参数仍然共享同一个全局学习率。这带来了新的问题:
- 对于频繁出现的特征,其梯度已经非常稳定,不需要大步更新
- 对于稀疏特征(如推荐系统中的低频用户ID),每次出现都应该给予较大的更新幅度
AdaGrad(Adaptive Gradient) 由Duchi等人在2011年提出,首次引入了参数级学习率调整的机制。
AdaGrad的核心思想是:为每个参数维护一个累积梯度平方和,用其开方来缩放当前梯度。
Gt=Gt−1+gt2(逐元素平方和)
θt+1=θt−Gt+ϵη⊙gt
其中 ⊙ 表示逐元素乘法,ϵ 是一个防止除零的小常数。
直观理解:
- 如果某个参数的历史梯度很大,Gt 就大,学习率就被缩小
- 如果某个参数的历史梯度很小(稀疏特征),Gt 就小,学习率就被放大
致命缺陷:Gt 是所有历史梯度平方的无衰减累加。随着训练进行,Gt 不断增大,学习率单调递减,最终趋近于零——模型将停止学习。
4.2 RMSProp:引入“遗忘”机制
RMSProp由Geoff Hinton在2012年的课程笔记中提出,其核心改进是用指数加权移动平均替代累积平方和。
E[g2]t=βE[g2]t−1+(1−β)gt2
θt+1=θt−E[g2]t+ϵη⊙gt
其中 β 通常取 0.9 或 0.99。
关键改进:指数加权平均使得远距离的历史梯度信息被逐渐“遗忘” 。学习率不再单调递减到零,而是能够根据近期梯度的变化动态调整。
1def rmsprop(theta, grad, cache, lr=0.001, beta=0.9, epsilon=1e-8):2 """RMSProp更新"""3 cache = beta * cache + (1 - beta) * grad**24 theta = theta - lr * grad / (np.sqrt(cache) + epsilon)5 return theta, cache五、Adam:集大成者
5.1 Adam的融合设计
Adam(Adaptive Moment Estimation) 由Kingma和Ba在2015年提出,它融合了动量法(一阶矩)和RMSProp(二阶矩)的思想。
Adam同时维护两个变量:
-
一阶矩估计 mt (动量的角色):梯度的指数加权平均 mt=β1mt−1+(1−β1)gt
-
二阶矩估计 vt (自适应学习率的角色):梯度平方的指数加权平均 vt=β2vt−1+(1−β2)gt2
5.2 偏差修正:解决初始化问题
在训练初期,mt 和 vt 被初始化为 0,导致估计值严重偏小。Adam通过偏差修正来解决这个问题:
m^t=1−β1tmt,v^t=1−β2tvt
最终的参数更新为:
θt+1=θt−v^t+ϵη⊙m^t
Adam的默认超参数:β1=0.9,β2=0.999,ϵ=10−8。这些默认值在大多数任务中表现稳定,这也是Adam广受欢迎的原因之一。
1def adam(theta, grad, m, v, t, lr=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8):2 """Adam优化器更新"""3 # 更新一阶矩和二阶矩4 m = beta1 * m + (1 - beta1) * grad5 v = beta2 * v + (1 - beta2) * grad**26
7 # 偏差修正8 m_hat = m / (1 - beta1**t)9 v_hat = v / (1 - beta2**t)10
11 # 参数更新12 theta = theta - lr * m_hat / (np.sqrt(v_hat) + epsilon)13 return theta, m, v六、AdamW:解耦权重衰减
6.1 Adam的隐藏问题:权重衰减的“耦合”
Adam虽然强大,但在泛化能力上常常不如带动量的SGD。研究发现,问题出在Adam对权重衰减(Weight Decay) 的处理上。
在标准Adam中,L2正则化(权重衰减)是通过在梯度中直接加上 λθ 项来实现的:
gt=∇θJ(θt)+λθt
问题在于:这个 λθt 项也会被 vt(梯度平方的指数加权平均)缩放。
具体来说,Adam的参数更新为:
θt+1=θt−vt+ϵη⋅(mt+λθt)
权重衰减项 λθt 除以了 vt ——这意味着:
- 对于历史梯度较大的参数(vt 大),权重衰减的效果被削弱
- 对于历史梯度较小的参数(vt 小),权重衰减的效果被放大
这种耦合导致L2正则化在Adam中无法起到预期的效果。
6.2 AdamW的解决方案:解耦
AdamW由Loshchilov和Hutter在2017年提出,其核心思想非常直接:将权重衰减从自适应更新中解耦出来。
在AdamW中,权重衰减不经过 vt 的缩放,而是在参数更新之后独立施加:
Step 1:正常的Adam更新(不包含权重衰减) θt+1′=θt−v^t+ϵη⊙m^t
Step 2:独立施加权重衰减 θt+1=θt+1′−ηλθt+1′
关键差异:
- Adam:θt+1=θt−η⋅(v^t+ϵm^t+λθt) ——权重衰减被自适应学习率缩放
- AdamW:θt+1=(1−ηλ)θt−η⋅v^t+ϵm^t ——权重衰减独立于自适应机制
1def adamw(theta, grad, m, v, t, lr=0.001, beta1=0.9, beta2=0.999,2 epsilon=1e-8, weight_decay=0.01):3 """AdamW优化器更新"""4 # 更新一阶矩和二阶矩(与Adam相同)5 m = beta1 * m + (1 - beta1) * grad6 v = beta2 * v + (1 - beta2) * grad**27
8 # 偏差修正9 m_hat = m / (1 - beta1**t)10 v_hat = v / (1 - beta2**t)11
12 # 参数更新(Adam风格,不含权重衰减)13 theta = theta - lr * m_hat / (np.sqrt(v_hat) + epsilon)14
15 # 解耦的权重衰减(关键区别!)16 theta = theta - lr * weight_decay * theta17
18 return theta, m, v6.3 为什么AdamW更好?
更有效的正则化:由于权重衰减不再被自适应学习率缩放,它对所有参数施加一致且可预测的惩罚力度。
更好的泛化性能:实验表明,AdamW在大型Transformer架构(如BERT、GPT)上取得了显著的性能提升。
更稳定的训练动态:解耦使得训练过程更加可预测和稳定,这对大规模模型尤为重要。
实际应用:目前在自然语言处理(NLP)领域,AdamW已成为事实上的标准优化器。在计算机视觉(CV)领域,带动量的SGD仍然广泛使用。
七、总结:优化器的进化图谱
回顾整条进化路径,每一个优化器都是对前代特定痛点的精准回应:
| 优化器 | 核心贡献 | 解决的问题 |
|---|---|---|
| BGD | 全量数据梯度 | 基础框架 |
| SGD | 单样本梯度 | 计算效率 |
| MBGD | 小批量梯度 | 效率与稳定性的平衡 |
| Momentum | 速度变量 + 指数移动平均 | 峡谷地形的震荡 |
| NAG | 前瞻梯度计算 | 动量方向的“预见性” |
| AdaGrad | 参数级学习率 | 稀疏特征的处理 |
| RMSProp | 指数加权平均(二阶矩) | AdaGrad的学习率消亡 |
| Adam | 一阶矩 + 二阶矩 + 偏差修正 | 集动量与自适应于一身 |
| AdamW | 解耦权重衰减 | Adam中正则化失效 |
这条进化路线清晰地展示了深度学习中“问题驱动”的创新模式:每一个新算法的诞生,都是为了解决前一代在特定场景下的不足。
在实际选择优化器时,可以参考以下经验:
- NLP任务(Transformer/BERT/GPT) :优先选择 AdamW
- CV任务(ResNet等) :带动量的SGD 往往表现更好
- 强化学习:Adam 使用较为普遍
- 推荐系统:根据具体场景灵活选择
理解每种优化器背后的数学原理与设计动机,远比记住调参公式更重要——它能帮助你在面对新问题时,做出更明智的选择。
Some information may be outdated