梯度下降与优化算法#
沿着最陡方向下山#
想象你在山上迷路了,想要下到山谷:
当前位置:参数当前值 \(\theta_t\)
最陡下降方向:负梯度方向(你脚下最陡的下坡方向)
步长:学习率 \(\eta\)(你每一步迈多大)
目标:到达山谷最低点(损失 \(L\) 最小)
神经网络训练转化为优化问题后,我们需要找到损失函数的最小值点。梯度下降(Gradient Descent)就是解决这个问题的迭代算法。
核心思想:在当前位置,沿着梯度下降方向(损失减小最快的方向)迈出一小步。
其中 \(\eta\) 是学习率(步长),\(\nabla_\theta J(\theta)\) 是损失函数对参数的梯度。
梯度下降:从初始点逐步逼近最小值
学习率的重要性#
学习率的选择至关重要:
学习率 |
效果 |
类比 |
|---|---|---|
太小 |
收敛极慢 |
婴儿学步 |
合适 |
快速收敛 |
正常行走 |
太大 |
震荡/发散 |
大跳,可能跳过山谷 |
学习率过大导致震荡
梯度下降的变体#
在梯度下降与优化算法中,我们根据反向传播算法计算的梯度更新参数。根据使用多少数据计算梯度,有三种变体:
批量梯度下降(Batch GD)#
使用全部数据计算梯度:
特点:
梯度估计准确,收敛稳定
每次迭代计算量大,不适合大数据集
随机梯度下降(SGD)#
每次使用一个样本计算梯度:
特点:
计算快,适合在线学习
梯度有噪声,收敛不稳定
噪声可能帮助跳出局部最优
小批量梯度下降(Mini-batch GD)#
使用一小批样本(通常32-256个)计算梯度:
特点:
平衡计算效率和稳定性
可以利用GPU并行计算
深度学习中最常用
高级优化算法#
Loss Landscape:损失函数塑造的优化地形#
Loss Landscape(损失景观)描述了损失函数在参数空间中的形状。想象一个高维的山地地形:山谷代表损失低的区域,山峰代表损失高的区域。
在深度学习中,这个景观通常是非凸的,包含:
局部最优:局部山谷(浅坑)
全局最优:最深的山谷(最优解)
鞍点:马鞍形状的点(高维空间中非常常见)
Loss Landscape:局部最优 vs 全局最优
关键观察:
全局最优比局部最优明显更深(红色点远低于橙色点)
两个最优之间由鞍点(紫色)分隔
优化算法需要"翻过"鞍点才能从局部最优到达全局最优
鞍点的详细展示:
鞍点像一个马鞍——沿一个方向是极小值,沿另一个方向是极大值:
鞍点:马鞍形状
深度学习的特殊情况:
高维空间中,鞍点比局部最优更常见
随机梯度下降的噪声有助于跳出局部最优和鞍点
现代网络通常能找到足够好的解,即使不是全局最优
动量法:积累速度翻越障碍#
直觉:想象滚雪球下山,球会积累动量,越滚越快,同时可以滚过小的坑洼。在 loss landscape 中,动量帮助我们冲过鞍点和平坦区域。
原理:引入速度变量,累积历史梯度:
逐步理解动量法:
第一步:理解速度变量 \(v_t\)
在基础 SGD 中,我们直接用梯度更新参数: $\(\theta_{t+1} = \theta_t - \eta \cdot \nabla_\theta J(\theta_t)\)$
这相当于每走一步都完全重新决定方向,没有记忆之前走过的路径。
动量法引入速度变量 \(v_t\),相当于给优化过程添加了"记忆"——记住之前累积的运动趋势。
第二步:解读速度更新公式
这行公式的含义是:新速度 = 保留的旧速度 + 当前梯度
公式详解:
符号 |
含义 |
维度 |
直觉理解 |
|---|---|---|---|
\(v_{t+1}\) |
第 \(t+1\) 步的速度 |
与参数相同 |
下一步要迈的"步伐向量" |
\(\gamma\) |
动量系数 |
标量 (0~1) |
惯性大小,通常 0.9(保留 90% 旧速度) |
\(v_t\) |
第 \(t\) 步的速度 |
与参数相同 |
之前累积的运动趋势 |
\(\nabla_\theta J(\theta_t)\) |
当前梯度 |
与参数相同 |
当前位置最陡的下降方向 |
核心洞察:
\(\gamma = 0\)(无动量):\(v_{t+1} = \nabla_\theta J\),退化成基础 SGD
\(\gamma = 0.9\)(有动量):新速度 = 90% 旧速度 + 10% 新梯度
第三步:解读参数更新公式
与 SGD 的区别:
SGD:\(\theta_{t+1} = \theta_t - \eta \cdot \nabla_\theta J\)(只用当前梯度)
Momentum:\(\theta_{t+1} = \theta_t - \eta \cdot v_{t+1}\)(用累积的速度)
为什么这样能减少震荡?
想象你在峡谷中行走:
SGD:每次只看脚下,向左跨一步,发现右边更低,又向右跨一步,在峡谷两侧来回震荡
Momentum:你有了惯性,向左走时积累了向左的速度,即使右边略低,惯性也会带你继续向左,直到左边明显比右边高,才会慢慢转向
展开公式看本质:
如果我们展开 \(v_t\) 的递归定义:
其中 \(g_i = \nabla_\theta J(\theta_i)\) 是第 \(i\) 步的梯度。
关键发现:当前速度是历史梯度的加权平均,越早的梯度权重越小(按 \(\gamma^k\) 指数衰减)。
类比:
基础 SGD:每走一步都重新决定方向,容易在峡谷两侧来回震荡
Momentum:像滚下山坡的球,有惯性,同方向会加速,反方向会减速,自然减少震荡
效果:
加速收敛(尤其在梯度方向一致时)
减少震荡(梯度方向变化时动量抵消)
帮助跳出局部最优
Adam(自适应矩估计)#
Adam [KB15] 是目前最常用的优化算法之一,结合了 Momentum 和 RMSprop 的优点。
直觉:为每个参数单独调整学习率。频繁更新的参数用较小学习率,稀疏更新的用较大学习率。
原理:结合一阶矩估计(动量)和二阶矩估计(自适应学习率):
逐步理解 Adam:
第一步:一阶矩估计( momentum 部分)
这与动量法类似,计算梯度的指数移动平均:
符号 |
含义 |
维度 |
直觉理解 |
|---|---|---|---|
\(m_t\) |
第 \(t\) 步的一阶矩(均值) |
与参数相同 |
梯度的"平均趋势",平滑后的梯度方向 |
\(\beta_1\) |
一阶矩衰减率 |
标量 (0~1) |
通常是 0.9,保留 90% 历史信息 |
\(m_{t-1}\) |
上一步的一阶矩 |
与参数相同 |
之前累积的梯度趋势 |
\(g_t\) |
当前梯度 |
与参数相同 |
当前计算出的梯度值 |
\((1-\beta_1)\) |
新梯度权重 |
标量 |
新梯度占 10% 的权重 |
与动量法的区别:
动量法:\(v_{t+1} = \gamma v_t + \nabla_\theta J\)(新梯度权重为 1)
Adam:\(m_t = \beta_1 m_{t-1} + (1-\beta_1) g_t\)(新梯度权重为 \(1-\beta_1\))
Adam 的形式使 \(m_t\) 更接近真正的梯度均值(数学期望)。
展开看本质:
这是历史梯度的指数加权平均,越近的梯度权重越大。
第二步:二阶矩估计(自适应学习率部分)
注意这里的 \(g_t^2\) 是逐元素平方(element-wise square):
符号 |
含义 |
维度 |
直觉理解 |
|---|---|---|---|
\(v_t\) |
第 \(t\) 步的二阶矩(未中心化的方差) |
与参数相同 |
梯度"变化幅度"的历史平均 |
\(\beta_2\) |
二阶矩衰减率 |
标量 (0~1) |
通常是 0.999,比 \(\beta_1\) 更大 |
\(g_t^2\) |
梯度逐元素平方 |
与参数相同 |
梯度的幅度信息 |
核心洞察:\(v_t\) 记录了每个参数梯度的"活跃程度":
梯度经常很大 → \(v_t\) 很大
梯度经常很小 → \(v_t\) 很小
为什么用平方?
平方消除了符号(正负),只保留幅度信息。我们想知道的是梯度"有多大",而不是"朝哪个方向"。
第三步:偏差修正(Bias Correction)
为什么需要修正?
问题:初始时 \(m_0 = 0, v_0 = 0\)
前几步的偏差:
第 1 步:\(m_1 = (1-\beta_1) g_1\),比真实均值小了约 \((1-\beta_1)\) 倍
第 2 步:\(m_2 = \beta_1(1-\beta_1)g_1 + (1-\beta_1)g_2\),仍有偏差
修正原理:
如果 \(m_t\) 估计的是 \(E[g]\),那 \(E[m_t] = (1-\beta_1^t) E[g]\)
除以 \((1-\beta_1^t)\) 就得到了无偏估计
符号 |
含义 |
计算方式 |
|---|---|---|
\(\hat{m}_t\) |
修正后的一阶矩 |
\(m_t / (1-\beta_1^t)\) |
\(\hat{v}_t\) |
修正后的二阶矩 |
\(v_t / (1-\beta_2^t)\) |
\(t\) |
当前步数 |
训练迭代次数 |
修正效果:
\(t\) 很小(初期):\(1-\beta_1^t\) 很小,除数很小,放大效果明显
\(t\) 很大(后期):\(\beta_1^t \approx 0\),\(1-\beta_1^t \approx 1\),几乎不修正
举例(\(\beta_1 = 0.9\)):
\(t=1\):修正系数 \(1/(1-0.9) = 10\),放大 10 倍
\(t=10\):修正系数 \(1/(1-0.9^{10}) \approx 1.5\),放大 1.5 倍
\(t=100\):修正系数 \(\approx 1.0\),几乎不修正
第四步:参数更新(自适应学习率的精髓)
逐元素解读:
对于第 \(i\) 个参数 \(\theta^{(i)}\):
符号 |
含义 |
作用 |
|---|---|---|
\(\hat{m}_t^{(i)}\) |
第 \(i\) 个参数的平滑梯度 |
决定更新方向 |
\(\sqrt{\hat{v}_t^{(i)}}\) |
第 \(i\) 个参数的梯度 RMS |
决定步长缩放 |
\(\epsilon\) |
数值稳定性常数 |
防止除以 0,通常 \(10^{-8}\) |
\(\eta\) |
全局学习率 |
整体步长控制 |
核心洞察 - 自适应学习率机制:
假设有两个参数:
参数 A(梯度波动大):
历史梯度:[10, -8, 12, -10, 9](大起大落)
\(\hat{m}_t \approx 2.6\)(平均梯度)
\(\hat{v}_t \approx 100\)(平均平方梯度)
\(\sqrt{\hat{v}_t} \approx 10\)
更新步长:\(\eta \cdot 2.6 / 10 = 0.26\eta\)(步长被缩小)
参数 B(梯度波动小):
历史梯度:[0.01, -0.02, 0.015, -0.01, 0.012](小幅波动)
\(\hat{m}_t \approx 0.009\)(平均梯度)
\(\hat{v}_t \approx 0.0002\)(平均平方梯度)
\(\sqrt{\hat{v}_t} \approx 0.014\)
更新步长:\(\eta \cdot 0.009 / 0.014 = 0.64\eta\)(步长相对较大)
结果:
梯度波动大的参数 → 步长自动变小(谨慎更新)
梯度波动小的参数 → 步长相对较大(大胆更新)
为什么用 \(\sqrt{\hat{v}_t}\) 而不是 \(\hat{v}_t\)?
梯度平方 \(g_t^2\) 的量纲是原梯度的平方
除以 \(\sqrt{\hat{v}_t}\)(RMS,均方根)可以抵消量纲,使更新步长与原梯度同量纲
类比:标准差 \(\sigma = \sqrt{\text{方差}}\),与原始数据同量纲
超参数总结:
参数 |
推荐值 |
作用 |
|---|---|---|
\(\beta_1\) |
0.9 |
一阶矩衰减率,控制 momentum 强度 |
\(\beta_2\) |
0.999 |
二阶矩衰减率,控制自适应灵敏度 |
\(\epsilon\) |
\(10^{-8}\) |
数值稳定性 |
\(\eta\) |
0.001(默认) |
全局学习率 |
特点:
结合动量和自适应学习率
对超参数不敏感,默认首选
适合大多数深度学习任务
算法 |
适用场景 |
特点 |
|---|---|---|
SGD + Momentum |
图像分类、大模型 |
泛化性能好,需调学习率 |
Adam |
默认首选、NLP、推荐系统 |
自适应、收敛快、易用 |
AdamW |
Transformer、大模型 |
改进权重衰减,效果更好 |
学习率调度:为什么需要调整学习率?#
核心问题#
训练初期,参数远离最优,需要大学习率快速接近目标。 训练后期,参数接近最优,需要小学习率精细调整,避免在最优点附近震荡。
固定学习率 vs 学习率调度
常见学习率调度策略#
学习率调度策略对比
1. 步长衰减(Step Decay)#
策略:每N个epoch,学习率乘以衰减系数(如0.1)
适用场景:
传统图像分类任务(ImageNet训练标配)
当验证集损失不再下降时手动降低学习率
# 每30个epoch,学习率乘以0.1
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)
特点:阶梯式下降,简单有效,但需要预设衰减时机。
2. 指数衰减(Exponential Decay)#
策略:每个epoch学习率按指数衰减
适用场景:
需要平滑连续衰减
训练轮数较多时
# 每个epoch学习率乘以0.95
scheduler = optim.lr_scheduler.ExponentialLR(optimizer, gamma=0.95)
3. 余弦退火(Cosine Annealing)#
策略:学习率按余弦函数从初始值降到接近0
适用场景:
现代Transformer模型(BERT、GPT等)
配合Warmup使用效果更佳
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=100)
4. 预热(Warmup)#
问题:训练初期参数随机初始化,梯度可能很大且不稳定,大学习率会导致震荡。
策略:从很小的学习率开始,线性增加到目标学习率。
Warmup + Cosine Annealing
# 组合使用:Warmup + Cosine Annealing
scheduler1 = optim.lr_scheduler.LinearLR(
optimizer,
start_factor=0.01, # 从1%的目标学习率开始
end_factor=1.0,
total_iters=5 # 前5个epoch预热
)
scheduler2 = optim.lr_scheduler.CosineAnnealingLR(
optimizer,
T_max=95 # 剩余95个epoch余弦退火
)
scheduler = optim.lr_scheduler.SequentialLR(
optimizer,
schedulers=[scheduler1, scheduler2],
milestones=[5]
)
选择建议#
策略 |
推荐场景 |
优点 |
缺点 |
|---|---|---|---|
Step Decay |
图像分类、CNN |
简单有效 |
需预设衰减时机 |
Exponential |
长周期训练 |
平滑连续 |
衰减过快 |
Cosine |
Transformer、NLP |
效果通常最好 |
相对复杂 |
Warmup + Cosine |
大模型训练 |
稳定+效果好 |
需调两个参数 |
PyTorch实践#
基本训练循环
import torch import torch.nn as nn import torch.optim as optim # 定义模型、损失函数、优化器 model = MyModel() criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) # 训练循环 for epoch in range(num_epochs): for data, target in dataloader: # 前向传播 output = model(data) loss = criterion(output, target) # 反向传播 optimizer.zero_grad() # 清空梯度 loss.backward() # 计算梯度 optimizer.step() # 更新参数
学习率调度
# 组合调度:预热 + 余弦退火 scheduler1 = optim.lr_scheduler.LinearLR(optimizer, start_factor=0.1, total_iters=5) scheduler2 = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=45) scheduler = optim.lr_scheduler.SequentialLR(optimizer, [scheduler1, scheduler2], milestones=[5]) for epoch in range(num_epochs): train(...) scheduler.step() # 更新学习率
训练技巧#
梯度裁剪
防止梯度爆炸:
loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) optimizer.step()
批归一化(BatchNorm)
稳定训练、加速收敛:
self.bn = nn.BatchNorm1d(num_features)
早停(Early Stopping)
验证集损失不再下降时停止训练,防止过拟合。
总结#
梯度下降是深度学习训练的基石:
基本原理:沿反向传播算法计算的负梯度方向迭代更新参数
学习率:最关键的超参数,过大震荡,过小收敛慢
Mini-batch:平衡效率和稳定性,深度学习标配
Adam:默认首选优化器,自适应、易用
学习率调度:训练过程中调整学习率能提升效果
参考文献#
Diederik P Kingma and Jimmy Ba. Adam: a method for stochastic optimization. In International Conference on Learning Representations. 2015.
贡献者与修订历史
查看详细修订记录
-
b20ef3e2026-04-28 - Heyan Zhu: docs: update pytorch practice section with detailed explanations and code examples -
59126f42026-04-26 - Heyan Zhu: docs(math-fundamentals): update content structure and add citations -
ae2053f2026-04-26 - Heyan Zhu: docs(math-fundamentals): add task-formulations and update related content -
756a7932026-04-25 - Heyan Zhu: docs(math-fundamentals): update content structure and improve explanations