ML 学习站
跳到正文

梯度下降收敛原理

深度学习的优化挑战 + 凸性 + 梯度下降收敛性证明 + SGD / 小批量 SGD。

45 分钟1 / 34,356
加载中...

梯度下降收敛原理

本章问题: 训练神经网络时, 为什么 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)稳定但慢很少用
SGDO(1)快但震荡在线学习
Mini-batch SGDO(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理由
< 100032-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 ↔ lrbatch_size 翻倍, lr 也翻倍

🎯 一句话总结: 梯度下降在凸问题上保证收敛; SGD 通过加噪声跳出鞍点; mini-batch SGD 是深度学习的事实标准; 学习率和 batch_size 是两个最关键的旋钮。

练习

  1. 推导: 写出 f(x) = x² 的梯度下降更新式, 证明当 η ≤ 1 时单调下降。
  2. 实验: 在 MNIST 上用不同 batch_size (16, 64, 256) 训练, 比较收敛速度。
  3. 思考: 为什么 Adam 在早期训练比 SGD 稳定? (提示: 看下一章)
  4. 实战: 找一个你之前的训练任务, 把学习率调成 1/10 和 10x, 观察 loss 曲线变化。

拓展阅读

  • 《动手学深度学习》第 11 章 (李沐等): 本章主要参考
  • Bottou et al. (2018) Optimization Methods for Large-Scale Machine Learning
  • Robbins & Monro (1951) A Stochastic Approximation Method (SGD 原始论文)

章末小测验

检验你对《梯度下降收敛原理》的掌握程度。

1

gradient-descent 的核心概念是?

学完这章?

标记完成, 进入下一章

下一章: 深度学习优化器

还有疑问? 问问 AI (v19.5)

基于全站 19 门课 68 章内容检索 + LLM 总结, 会引用具体章节作为出处

讨论区(0)

加载评论中...