反向传播算法是深度学习的核心驱动力,它使得神经网络的训练成为可能。本章深入探讨了反向传播的数学原理和实际应用。核心概念包括梯度下降、链式法则和计算图。梯度下降通过沿损失函数梯度的反方向更新参数来最小化损失。链式法则用于高效计算每个参数的梯度,而计算图则帮助可视化神经网络的运算流程。通过前向传播计算损失,再利用反向传播从右向左传递梯度,实现参数的更新。反向传播的高效性在于每个节点的局部梯度只计算一次并复用,极大降低了计算复杂度。读者将学会如何手动推导两层神经网络的反向传播过程,并理解现代框架如PyTorch如何通过自动微分简化这一过程。此外,读者还将了解梯度消失和爆炸问题及其解决方案,如使用ReLU激活函数、批归一化和残差连接。学完本章,读者能够理解并手动实现反向传播算法,并具备解决深层网络训练中常见问题的能力。
反向传播算法
反向传播(Backpropagation)是深度学习的引擎——没有它,神经网络根本无法训练。这一章手推一遍,把直觉和数学都讲透。
核心问题
神经网络有几百几千个参数(w 和 b),我们要找一组参数让损失函数 L 最小。
梯度下降告诉我们:沿梯度的反方向更新参数:
w := w - eta * (dL/dw)
问题来了:对于一个百万参数的网络,dL/dw 怎么算?
反向传播就是用链式法则高效计算每个参数的梯度。
计算图:把神经网络画成图
我们用计算图(computation graph)来跟踪运算:
x ──→ [*w1] ──→ z1 ──→ [+b] ──→ z2 ──→ [sigmoid] ──→ y_hat ──→ [Loss] ──→ L
↑ w1 ↑ b
每个节点是一个运算,边是数据流。前向传播就是从左到右算出 L。
链式法则:核心数学
假设 L = f(g(h(x))),那:
dL/dx = (dL/df) * (df/dg) * (dg/dh) * (dh/dx)
每个 d 都是局部的、容易算的。反向传播就是从右往左乘这些局部梯度。
完整例子:两层网络
假设网络是:
- z1 = w1*x + b1
- a1 = sigmoid(z1)
- z2 = w2*a1 + b2
- y_hat = sigmoid(z2)
- L = (1/2) * (y_hat - y)^2
我们要算 dL/dw1, dL/dw2, dL/db1, dL/db2。
步骤 1:前向传播
依次算出 z1, a1, z2, y_hat, L。
步骤 2:反向传播
从 L 开始,往左推:
最后一步(y_hat → L):
dL/dy_hat = y_hat - y
sigmoid 处(y_hat → z2):
dL/dz2 = (dL/dy_hat) * sigmoid'(z2) = (y_hat - y) * y_hat * (1 - y_hat)
w2 处的梯度:
dL/dw2 = (dL/dz2) * a1
a1 处的梯度(要继续往左传):
dL/da1 = (dL/dz2) * w2
z1 处的梯度:
dL/dz1 = (dL/da1) * sigmoid'(z1)
w1 处的梯度:
dL/dw1 = (dL/dz1) * x
为什么高效?
关键洞察:每个节点的局部梯度只算一次,然后复用给所有需要它的上游节点。
如果用数值微分(每个参数微扰一下),复杂度 O(N) —— 百万参数要算百万次。 反向传播一次正向 + 一次反向,复杂度 O(N) —— 但只跑一次!这是深度学习能 scale 的根本原因。
自动微分:现代框架帮你做
手推一遍理解原理就好。实际训练用 PyTorch / TensorFlow 的自动微分(autograd):
import torch
# 1. 定义参数(requires_grad=True 表示要算梯度)
w1 = torch.tensor(0.5, requires_grad=True)
b1 = torch.tensor(0.1, requires_grad=True)
w2 = torch.tensor(0.3, requires_grad=True)
b2 = torch.tensor(0.2, requires_grad=True)
# 2. 前向传播
x = torch.tensor(1.0)
y = torch.tensor(0.8)
z1 = w1 * x + b1
a1 = torch.sigmoid(z1)
z2 = w2 * a1 + b2
y_pred = torch.sigmoid(z2)
loss = 0.5 * (y_pred - y) ** 2
# 3. 反向传播(一行搞定!)
loss.backward()
# 4. 查看梯度
print(f"dL/dw1 = {w1.grad.item():.4f}")
print(f"dL/dw2 = {w2.grad.item():.4f}")
print(f"dL/db1 = {b1.grad.item():.4f}")
print(f"dL/db2 = {b2.grad.item():.4f}")
PyTorch 内部构建了动态计算图(requires_grad=True 的张量构成图节点),loss.backward() 自动沿图反向传播计算所有梯度。
常见坑:梯度消失与爆炸
当网络很深时,反向传播要连乘很多局部导数。如果每个局部导数都小于 1,乘起来指数级趋向 0——这就是梯度消失,前面层的参数根本更新不了。
解决方案:
- 用 ReLU 激活(导数是 1 或 0,不会指数衰减)
- Batch Normalization(归一化每层输入)
- 残差连接(ResNet 的核心思想)
- 合理的权重初始化(He 初始化、Xavier 初始化)
反过来如果每个导数都大于 1,梯度会指数级爆炸——参数更新飞出去,数值溢出。梯度裁剪(torch.nn.utils.clip_grad_norm_)能缓解。
小结
- 反向传播 = 链式法则的高效实现
- 一次正向 + 一次反向 = 算所有参数梯度
- 现代框架(PyTorch)用 autograd 自动搞定
- 深层网络会梯度消失/爆炸,需要 ReLU + BN + 残差连接
练习思考
- 用纸笔把上面那个两层网络的反向传播完整推一遍,确认你能从 dL/dy_hat 一路推到 dL/dw1。
- 在 PyTorch 里,如果一个张量
requires_grad=False,反向传播到它会怎样? - 为什么 sigmoid 网络深度超过 8 层就基本训不动了?用梯度消失的角度解释。
📐 补充: 数学形式化
如果你喜欢数学,这一节用公式再走一遍。
梯度下降更新规则
其中 是学习率, 是损失 对参数 的偏导。
链式法则
对复合函数 :
Sigmoid 函数的导数
这是为什么反向传播中 这么方便——直接用前向算出的 就行,不用重算。
损失函数 (MSE)
对 的梯度:
梯度消失的定量理解
如果每一层的局部导数都是 (sigmoid 在饱和区),反向传播 层后:
时, ,几乎为 0——这就是梯度消失。
章末小测验
检验你对《反向传播算法》的掌握程度。
反向传播本质上是什么?
为什么深度网络会梯度消失?