梯度下降收敛原理
本章问题: 训练神经网络时, 为什么 loss 会下降? 又为什么会震荡不下降? 学完这一章, 你能从数学角度回答这两个问题, 不再把"调参"当成玄学。
1. 优化 ≠ 深度学习的目标
很多人混淆两个概念:
| 概念 | 目标 | 衡量 |
|---|---|---|
| 优化 (Optimization) | 最小化训练集上的损失函数 | 训练误差 (training error) |
| 深度学习 (Deep Learning) | 在新数据上表现好 | 泛化误差 (generalization error) |
优化只是达到目的的手段, 不是目的本身。
# 一个例子: 训练 loss 一直降, 但测试 loss 在涨 → 过拟合
# 这时优化"成功"了, 但深度学习的目标失败了
import matplotlib.pyplot as plt
epochs = range(1, 51)
train_loss = [1.0 * (0.9 ** e) + 0.05 for e in epochs] # 持续降
test_loss = [1.0 * (0.9 ** e) + 0.05 + (e-30)*0.01 if e > 30 else 1.0 * (0.9 ** e) + 0.05 for e in epochs]
plt.plot(epochs, train_loss, label='train loss')
plt.plot(epochs, test_loss, label='test loss')
plt.axvline(30, color='red', ls='--', alpha=0.5, label='过拟合开始')
plt.xlabel('epoch'); plt.ylabel('loss'); plt.legend(); plt.grid(alpha=0.3)
plt.title('优化成功 ≠ 模型好')
plt.show()
💡 关键区分: 训练误差低不代表模型好。本章讲优化(怎么让 loss 降下去), 不讲泛化(怎么在新数据上也好)。
2. 深度学习的 3 大优化挑战
2.1 局部最小值
对于目标函数 f(x):
- 局部最小: 在 x 附近, f(x) 是最小值, 但不一定是全局最小
- 全局最小: 在整个定义域上, f(x) 是最小值
深度学习的目标函数往往是非凸的, 有成千上万个局部最小值。但这未必是坏事:
- 现代研究表明, 很多局部最小值的损失值相近 (Bottou et al.)
- 真正卡住训练的是鞍点, 而不是局部最小
2.2 鞍点
在鞍点处, 一维方向是极小, 另一维方向是极大, 梯度 = 0 但不是极值。
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 鞍点函数: f(x, y) = x^2 - y^2
x = np.linspace(-2, 2, 50)
y = np.linspace(-2, 2, 50)
X, Y = np.meshgrid(x, y)
Z = X**2 - Y**2
fig = plt.figure(figsize=(10, 6))
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, Z, cmap='coolwarm', alpha=0.7)
ax.set_xlabel('x'); ax.set_ylabel('y'); ax.set_zlabel('f(x,y)')
ax.set_title('鞍点: x 方向是极小, y 方向是极大, 梯度=0')
plt.show()
2.3 梯度消失
深层网络反向传播时, 梯度从输出层传到输入层, 每层乘 Jacobian 矩阵。如果 Jacobian 的特征值 < 1, 梯度指数衰减, 前面几层的参数几乎不更新。
# 梯度消失的简化示意
# 假设每层 Jacobian 的最大奇异值是 0.5
# L=10 层: 梯度到达第一层时, 0.5^10 ≈ 0.001
import numpy as np
print(f"10 层网络, 每层 σ=0.5, 起点梯度 1.0")
print(f"到达第 1 层时梯度: {0.5**10:.5f}") # 0.00098
# 所以前面几层基本不更新
🎯 本章主线: 怎么选学习率 η 和 batch size, 才能让梯度下降收敛?
3. 凸性入门
3.1 为什么先讲凸性?
凸优化有 优雅的收敛性证明 和 唯一全局最小值。虽然深度学习是非凸的, 但凸优化的分析给我们直觉——在凸问题上有效的算法, 在非凸问题上通常也有效。
3.2 凸集
集合 X 中任意两点的连线段都在 X 内, 则 X 是凸的。
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(figsize=(8, 6))
# 画一个凸集 (圆)
theta = np.linspace(0, 2*np.pi, 100)
x = np.cos(theta); y = np.sin(theta)
ax.plot(x, y, 'b-', lw=2, label='凸集 (圆)')
# 两个点 + 连线段
p1 = np.array([0.7, 0.3]); p2 = np.array([-0.5, 0.6])
ax.plot([p1[0], p2[0]], [p1[1], p2[1]], 'r--', lw=2)
ax.plot(p1[0], p1[1], 'ro', markersize=10)
ax.plot(p2[0], p2[1], 'ro', markersize=10)
ax.text(p1[0]+0.05, p1[1], 'A', fontsize=14)
ax.text(p2[0]-0.1, p2[1], 'B', fontsize=14)
ax.set_xlim(-1.5, 1.5); ax.set_ylim(-1.5, 1.5)
ax.grid(alpha=0.3); ax.legend(); ax.set_aspect('equal')
ax.set_title('凸集: A→B 的整条连线段都在集合内')
plt.show()
3.3 凸函数
对任意 x, y ∈ X 和 λ ∈ [0, 1], 满足:
几何含义: 函数图像上任意两点的连线段, 永远在函数图像上方。
# 凸函数: f(x) = x^2 (满足凸性)
# 非凸函数: f(x) = x^3 - x (有局部最小)
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-2, 2, 200)
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
# 凸函数
axes[0].plot(x, x**2, 'b-', lw=2)
axes[0].set_title('凸函数: f(x)=x²')
axes[0].grid(alpha=0.3)
# 非凸
axes[1].plot(x, x**3 - x, 'r-', lw=2)
axes[1].axhline(0, color='gray', lw=0.5)
axes[1].set_title('非凸: f(x)=x³-x (有局部最小)')
axes[1].grid(alpha=0.3)
plt.show()
3.4 凸优化 = 容易优化
凸优化的核心定理:
- 凸函数的局部最小值 = 全局最小值
- 凸优化问题没有鞍点, 只有极小/极大
- 梯度下降能找到全局最小 (只要学习率合适)
深度学习不是凸的, 所以这套理论不能直接套用。但很多实践中, 深度学习的损失函数**"足够凸"**——大方向上是凸的, 只有局部小坑。所以凸优化的经验仍然有效。
4. 梯度下降收敛性证明
4.1 算法
目标: 最小化 f(x), 其中 f 是凸函数 + L-光滑 (Lipschitz 梯度)。
η 是学习率 (step size)。
4.2 收敛定理
定理: 如果 f 是凸函数 + L-光滑, 且学习率 η ≤ 1/L, 则梯度下降收敛到全局最优:
解读:
- T 是迭代次数, 右边分母有 T → 迭代越多次, 误差越小
- η 越大, 单步走得远, 但容易震荡
- η 太小, 走得慢, 但稳定
- η > 1/L → 可能发散
4.3 怎么选学习率? 3 个测试
# 经验法则: 用小数据集 (1-2 个 batch) 测试几个学习率
import torch
import matplotlib.pyplot as plt
# 一个简单二次函数
def f(x): return x**2
def grad(x): return 2*x
# 测试不同学习率
etas = [0.001, 0.01, 0.1, 0.5, 1.0, 1.1]
fig, ax = plt.subplots(figsize=(10, 6))
for eta in etas:
x = 10.0 # 起点
traj = [x]
for _ in range(50):
x = x - eta * grad(x)
traj.append(x)
ax.plot(traj, label=f'η={eta}', lw=2)
ax.set_xlabel('迭代次数'); ax.set_ylabel('x')
ax.set_yscale('symlog'); ax.legend(); ax.grid(alpha=0.3)
ax.set_title('学习率对收敛的影响 (f=x²)')
plt.show()
# η=0.001: 太慢
# η=0.1: 快速收敛
# η=1.0: 震荡但收敛
# η=1.1: 发散!
🎯 经验: 学习率选对数尺度网格 [1e-5, 1e-4, ..., 1], 从大往小试。最开始 loss 爆炸就是 η 太大, loss 完全不动就是 η 太小。
4.4 强凸 + 梯度下降 = 线性收敛
如果 f 是强凸 (比凸更强), 梯度下降有线性收敛率:
其中 μ 是强凸系数。线性收敛意味着指数级下降, 比凸函数的次线性收敛 (1/T) 快得多。
5. 随机梯度下降 (SGD)
5.1 为什么用 SGD?
全量梯度下降每次迭代要算所有样本的梯度, 大数据集上太慢。
随机梯度下降每次只用一个样本估梯度:
其中 f_i 是第 i 个样本的损失。
# 全量 vs 随机对比
import numpy as np
# 假设数据集 1000 个样本
n = 1000
# 全量梯度下降: 每次算 n 个样本的梯度
def full_grad(theta):
return np.mean(2 * (theta - np.random.randn(n))) # O(n)
# SGD: 每次只算 1 个
def sgd_grad(theta, i):
return 2 * (theta - np.random.randn()) # O(1)
# SGD 的梯度是噪声版: E[∇f_i] = ∇f
# 优势: 单次迭代快 1000 倍
# 代价: 噪声大, 收敛路径震荡
5.2 SGD 的收敛性
定理 (Robbins-Monro 1951): 学习率满足以下条件时, SGD 收敛:
解读:
- η_t 之和 → ∞: 学习率不能降太快, 否则停在小点出不来
- η_t² 之和 < ∞: 学习率必须最终降到 0, 否则噪声持续存在
常用 schedule:
- η_t = η_0 / (1 + t/τ) (多项式衰减)
- η_t = η_0 · 0.95^(t/τ) (指数衰减)
- η_t = η_0 · cos(πt/2T) (余弦衰减, 详见 D2L 11.11)
5.3 实践: 噪声反而是好事
SGD 的梯度噪声有一个意外好处: 帮助跳出局部最小 / 鞍点。
# 对比: 全量梯度下降卡在鞍点, SGD 越过
import numpy as np
import matplotlib.pyplot as plt
# 鞍点函数: f(x, y) = x^2 - y^2
def f(x, y): return x**2 - y**2
def grad(x, y): return np.array([2*x, -2*y])
# 全量梯度下降 (无噪声): 卡在 (0, 0)
x_full = np.array([1.0, 1.0])
traj_full = [x_full.copy()]
for _ in range(50):
x_full = x_full - 0.05 * grad(*x_full)
traj_full.append(x_full.copy())
# SGD (有噪声): 越过鞍点
np.random.seed(42)
x_sgd = np.array([1.0, 1.0])
traj_sgd = [x_sgd.copy()]
for t in range(50):
noise = np.random.randn(2) * 0.1 / (t+1)**0.5
x_sgd = x_sgd - 0.05 * (grad(*x_sgd) + noise)
traj_sgd.append(x_sgd.copy())
traj_full = np.array(traj_full); traj_sgd = np.array(traj_sgd)
fig, ax = plt.subplots(figsize=(8, 8))
ax.plot(traj_full[:, 0], traj_full[:, 1], 'b-o', label='全量 GD (卡在 0,0)', alpha=0.7)
ax.plot(traj_sgd[:, 0], traj_sgd[:, 1], 'r-o', label='SGD (越过鞍点)', alpha=0.7)
ax.plot(0, 0, 'k*', markersize=20)
ax.set_xlabel('x'); ax.set_ylabel('y'); ax.legend(); ax.grid(alpha=0.3)
ax.set_title('SGD 的噪声帮助跳出鞍点')
plt.show()
6. 小批量随机梯度下降 (Mini-batch SGD)
6.1 三种粒度的对比
| 粒度 | 单次迭代时间 | 收敛速度 | 内存 | 实际用法 |
|---|---|---|---|---|
| 批量 GD (BGD) | O(n) | 稳定但慢 | 高 | 很少用 |
| SGD | O(1) | 快但震荡 | 低 | 在线学习 |
| Mini-batch SGD | O(b) | 平衡 | 中 | 主流 |
# PyTorch 默认 mini-batch
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
# 数据集
dataset = datasets.MNIST('./data', train=True, download=True,
transform=transforms.ToTensor())
# batch_size=32 就是 mini-batch SGD
loader = DataLoader(dataset, batch_size=32, shuffle=True)
# 每个 iter 从 loader 拿 32 个样本
for X, y in loader: # X: (32, 1, 28, 28)
# 算 32 个样本的梯度 → 更新参数
break
6.2 怎么选 batch size?
| 数据集大小 | 推荐 batch size | 理由 |
|---|---|---|
| < 1000 | 32-128 | 全量或稍大 |
| 1万-100万 | 64-512 | 平衡速度/稳定 |
| > 1000万 | 256-8192 | 用大 batch + 多 GPU |
| 极端 (BERT/GPT) | 百万级 | 配合 LAMB/AdamW |
经验法则:
- batch_size 太小 (1-8): 噪声大, 难收敛, GPU 浪费
- batch_size 太大 (>1k): 泛化差, 等价于全量 GD
- 推荐起步: 32-256
6.3 batch size 与学习率的关系
线性缩放规则 (Linear Scaling Rule): batch_size 翻倍, 学习率也翻倍。
# baseline: bs=32, lr=1e-3
# bs=64 → lr=2e-3
# bs=128 → lr=4e-3
# bs=256 → lr=8e-3
# 原理: batch 越大, 梯度估计越准, 可以走更大学习率
7. 收敛性总结: 3 个关键参数
收敛性 ≈ f(凸性, 学习率, batch_size)
| 参数 | 影响 | 调参原则 |
|---|---|---|
| 凸性 | 凸 → 易证明收敛, 非凸 → 局部 | 不能改 (模型决定) |
| 学习率 η | 太小慢, 太大震荡/发散 | 从 1e-3 开始, 网格搜索 |
| batch size b | 太大钝, 太小噪声 | 32-256 起步, 受显存限制 |
8. 本章小结
| 概念 | 一句话 |
|---|---|
| 优化 ≠ 深度学习 | 优化降训练误差, 深度学习降泛化误差 |
| 3 大挑战 | 局部最小、鞍点、梯度消失 |
| 凸优化 | 局部最小 = 全局最小, 梯度下降收敛 |
| 学习率 | η ≤ 1/L 收敛, > 1/L 发散 |
| SGD 收敛 | 学习率之和 → ∞, 平方和 < ∞ |
| Mini-batch SGD | 平衡 BGD 和 SGD, 默认选择 |
| batch ↔ lr | batch_size 翻倍, lr 也翻倍 |
🎯 一句话总结: 梯度下降在凸问题上保证收敛; SGD 通过加噪声跳出鞍点; mini-batch SGD 是深度学习的事实标准; 学习率和 batch_size 是两个最关键的旋钮。
练习
- 推导: 写出 f(x) = x² 的梯度下降更新式, 证明当 η ≤ 1 时单调下降。
- 实验: 在 MNIST 上用不同 batch_size (16, 64, 256) 训练, 比较收敛速度。
- 思考: 为什么 Adam 在早期训练比 SGD 稳定? (提示: 看下一章)
- 实战: 找一个你之前的训练任务, 把学习率调成 1/10 和 10x, 观察 loss 曲线变化。
拓展阅读
- 《动手学深度学习》第 11 章 (李沐等): 本章主要参考
- Bottou et al. (2018) Optimization Methods for Large-Scale Machine Learning
- Robbins & Monro (1951) A Stochastic Approximation Method (SGD 原始论文)
章末小测验
检验你对《梯度下降收敛原理》的掌握程度。
gradient-descent 的核心概念是?
学完这章, 你可能想看
还有疑问? 问问 AI (v19.5)
基于全站 19 门课 68 章内容检索 + LLM 总结, 会引用具体章节作为出处