梯度下降是深度学习的核心优化算法,反向传播是高效计算梯度的关键。本课深入理解梯度的数学本质,手动推导反向传播,并通过PyTorch验证。
梯度是多元函数偏导数组成的向量,指向函数值增长最快的方向。梯度下降就是沿着梯度的反方向移动,从而最小化函数值。
对于函数 f(x₁, x₂, ..., xₙ),梯度定义为:
∇f = (∂f/∂x₁, ∂f/∂x₂, ..., ∂f/∂xₙ)
梯度下降更新规则:
x ← x - η · ∇f(x)
PyTorch通过计算图和链式法则自动计算梯度:
import torch
# 手动计算梯度
x = torch.tensor([2.0], requires_grad=True)
y = x**2 + 3*x + 1 # y = x² + 3x + 1
y.backward()
print(f"函数: y = x² + 3x + 1")
print(f"x = {x.item():.1f} 时, y = {y.item():.1f}")
print(f"dy/dx = 2x + 3 = {x.grad.item():.1f}")
print(f"验证: 2*{x.item():.1f} + 3 = {2*x.item()+3:.1f}")
# 多变量梯度
print("\n=== 多变量梯度 ===")
w1 = torch.tensor([1.0], requires_grad=True)
w2 = torch.tensor([2.0], requires_grad=True)
loss = w1**2 + w2**2 + w1*w2
loss.backward()
print(f"函数: L = w1² + w2² + w1*w2")
print(f"w1=1.0, w2=2.0 时, L={loss.item():.1f}")
print(f"∂L/∂w1 = 2w1 + w2 = {w1.grad.item():.1f}")
print(f"∂L/∂w2 = 2w2 + w1 = {w2.grad.item():.1f}")
反向传播的核心是链式法则。对于复合函数 y = f(g(x)):
dy/dx = dy/dg · dg/dx
对于神经网络:L = Loss(f₂(f₁(x)))
∂L/∂W₁ = ∂L/∂f₂ · ∂f₂/∂f₁ · ∂f₁/∂W₁
| 阶段 | 方向 | 计算内容 |
|---|---|---|
| 前向传播 | 输入→输出 | 各层输出值 |
| 反向传播 | 输出→输入 | 各层梯度值 |
import torch
import torch.nn as nn
# 手动实现反向传播 vs PyTorch自动求导
torch.manual_seed(42)
# 2层网络
X = torch.randn(4, 3)
y = torch.randn(4, 1)
# 方式1:PyTorch自动求导
model = nn.Sequential(
nn.Linear(3, 4),
nn.Sigmoid(),
nn.Linear(4, 1)
)
loss_fn = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
print("=== 梯度下降训练过程 ===")
for epoch in range(0, 501, 100):
optimizer.zero_grad()
output = model(X)
loss = loss_fn(output, y)
loss.backward()
optimizer.step()
# 打印梯度范数
grad_norms = []
for p in model.parameters():
if p.grad is not None:
grad_norms.append(f"{p.grad.norm().item():.6f}")
print(f"Epoch {epoch:3d}: loss={loss.item():.6f}, grad_norms=[{', '.join(grad_norms)}]")
# 查看最终参数
print("\n=== 最终模型参数 ===")
for name, param in model.named_parameters():
print(f"{name}: shape={param.shape}, norm={param.data.norm().item():.4f}")
import torch
import torch.nn as nn
import numpy as np
# 可视化梯度下降轨迹(1D)
torch.manual_seed(0)
x_start = 5.0
lr_values = [0.01, 0.1, 0.5]
def f(x):
return x**2
def df(x):
return 2*x
print("=== 不同学习率的梯度下降轨迹 ===")
print("目标函数: f(x) = x², 最小值在 x=0")
print()
for lr in lr_values:
x = x_start
trajectory = [x]
for step in range(20):
grad = df(x)
x = x - lr * grad
trajectory.append(x)
traj_str = " → ".join([f"{v:.3f}" for v in trajectory[:8]])
print(f"lr={lr}: {traj_str} ... → {trajectory[-1]:.6f}")
# 梯度消失 vs 梯度爆炸演示
print("\n=== 梯度消失/爆炸演示 ===")
depths = [5, 10, 20, 50]
for depth in depths:
x = torch.tensor([1.0])
for i in range(depth):
w = torch.tensor([0.9], requires_grad=True)
x = w * x + 0.1
x.backward()
print(f"深度={depth:2d}: 最终输出={x.item():.8f}, 初始梯度估计~{0.9**depth:.8f}")
| 变体 | 每次用多少数据 | 优点 | 缺点 |
|---|---|---|---|
| 批量梯度下降(BGD) | 全部数据 | 稳定收敛 | 慢,内存大 |
| 随机梯度下降(SGD) | 1个样本 | 快,可逃脱局部最优 | 震荡剧烈 |
| 小批量SGD | batch_size个 | 平衡速度和稳定性 | 需调batch_size |
在深层网络中,梯度通过链式法则连乘,容易导致:
| 特性 | 静态图(TF1.x) | 动态图(PyTorch) |
|---|---|---|
| 构建时机 | 先定义后运行 | 运行时构建 |
| 调试 | 困难 | 容易(可用pdb) |
| 优化 | 全局优化机会多 | 优化空间较小 |
| 灵活性 | 低(条件循环困难) | 高(原生Python) |
| 部署 | 方便(图导出) | 需TorchScript转换 |
当GPU内存不足以容纳大batch时:
1. 将大batch分成多个小batch
2. 前向+反向累积梯度(不更新参数)
3. 累积N个小batch后,执行一步参数更新
等效batch_size = micro_batch × accumulation_steps
学习率预热(Warmup):训练初期用小学习率,逐步增大到目标值。防止初期参数变化太剧烈。
梯度裁剪(Gradient Clipping):当梯度范数超过阈值时,按比例缩小。防止梯度爆炸。
if ‖g‖ > max_norm: g = g × (max_norm / ‖g‖)
PyTorch: torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
💡 推荐资源:
对于网络 y = σ(w₂·σ(w₁x + b₁) + b₂),手动推导 ∂L/∂w₁ 的表达式。
在PyTorch中实现梯度裁剪(torch.nn.utils.clip_grad_norm_),观察对训练稳定性的影响。
使用torchviz库可视化一个简单网络的计算图,理解前向和反向传播的流程。
如何验证你的梯度计算是否正确?使用数值梯度对比:
数值梯度: ∂f/∂xᵢ ≈ (f(x+εeᵢ) - f(x-εeᵢ)) / (2ε)
ε通常取1e-5,如果解析梯度与数值梯度的差 < 1e-7,则正确
| 问题 | 症状 | 解决方案 |
|---|---|---|
| 梯度消失 | 损失不下降,参数不更新 | ReLU、BatchNorm、残差连接 |
| 梯度爆炸 | 损失突然变为NaN | 梯度裁剪、降低学习率 |
| 梯度死区 | 部分参数梯度恒为0 | LeakyReLU、调整初始化 |
| 梯度震荡 | 损失上下波动不收敛 | 降低学习率、增加动量 |
标准反向传播需要存储前向传播的所有中间值(O(depth)内存)。优化方法:
你已经掌握了深度学习的核心引擎——梯度下降和反向传播。
理解梯度,就理解了神经网络如何学习!