共计 2526 个字符,预计需要花费 7 分钟才能阅读完成。
为什么需要反向传播?
神经网络的核心是通过调整权重来最小化损失函数。想象一个婴儿学习识别猫:每次猜错后,大脑会反向追踪哪些神经元连接导致了错误,并调整这些连接的强度。反向传播就是实现这个过程的数学工具。

传统机器学习中,我们可以直接计算导数。但神经网络有大量参数(ResNet-152 有 6000 万参数!),手动计算不现实。反向传播通过链式法则,将误差从输出层反向传递,高效计算所有参数的梯度。
数学推导:拆解链式法则
基础符号定义
- $x_i$: 输入层第 i 个节点
- $w_{ij}^{(l)}$: 第 l 层第 i 个节点到第 (l+1) 层第 j 个节点的权重
- $z_j^{(l)}$: 第 l 层第 j 个节点的加权输入
- $a_j^{(l)}$: 第 l 层第 j 个节点的激活输出($a_j^{(l)}=\sigma(z_j^{(l)})$)
- $L$: 损失函数
关键推导步骤(以 2 层网络为例)
-
输出层误差计算:
$$
\frac{\partial L}{\partial w_{ij}^{(2)}} = \underbrace{\frac{\partial L}{\partial a_j^{(3)}}}{\text{误差项}} \cdot \underbrace{\frac{\partial a_j^{(3)}}{\partial z_j^{(3)}}}
$$}\cdot \underbrace{\frac{\partial z_j^{(3)}}{\partial w_{ij}^{(2)}}}_{a_i^{(2)} -
隐藏层误差传递(链式法则展开):
$$
\frac{\partial L}{\partial w_{ij}^{(1)}} = \left(\sum_k \frac{\partial L}{\partial a_k^{(2)}}\frac{\partial a_k^{(2)}}{\partial z_k^{(2)}}w_{jk}^{(2)}\right) \cdot \frac{\partial a_j^{(2)}}{\partial z_j^{(2)}} \cdot x_i
$$
Python 实现详解
import numpy as np
class NeuralNetwork:
def __init__(self, input_size, hidden_size, output_size):
# 初始化权重(使用 Xavier 初始化)self.W1 = np.random.randn(input_size, hidden_size) * np.sqrt(1./input_size)
self.W2 = np.random.randn(hidden_size, output_size) * np.sqrt(1./hidden_size)
def sigmoid(self, x, derivative=False):
if derivative:
return x * (1 - x) # 假设 x 已经是 sigmoid 输出
return 1 / (1 + np.exp(-x))
def forward(self, X):
self.z1 = np.dot(X, self.W1)
self.a1 = self.sigmoid(self.z1)
self.z2 = np.dot(self.a1, self.W2)
self.a2 = self.sigmoid(self.z2)
return self.a2
def backward(self, X, y, learning_rate):
# 输出层误差
output_error = self.a2 - y # 假设使用 MSE 损失
output_delta = output_error * self.sigmoid(self.a2, derivative=True)
# 隐藏层误差
hidden_error = np.dot(output_delta, self.W2.T)
hidden_delta = hidden_error * self.sigmoid(self.a1, derivative=True)
# 更新权重
self.W2 -= learning_rate * np.dot(self.a1.T, output_delta)
self.W1 -= learning_rate * np.dot(X.T, hidden_delta)
常见计算陷阱
矩阵维度问题
- 典型错误:当输入 X 是 (100,784),权重 W1 是(785,64) 时,会因广播机制导致静默错误
- 检查方法:
print(f"X.shape: {X.shape}, W1.shape: {self.W1.shape}")
梯度消失实例
# 使用 tanh 激活的深层网络
for l in range(10):
grad = grad * (1 - np.tanh(z_l)**2) # 梯度逐层缩小
print(f"Layer {l} gradient norm: {np.linalg.norm(grad):.6f}")
MNIST 实战示例
from sklearn.datasets import fetch_openml
mnist = fetch_openml('mnist_784', version=1)
X = mnist.data / 255.0 # 归一化
# 转换为 one-hot 编码
y = np.zeros((len(mnist.target), 10))
y[np.arange(len(mnist.target)), mnist.target.astype(int)] = 1
nn = NeuralNetwork(784, 128, 10)
for epoch in range(10):
nn.forward(X_train)
nn.backward(X_train, y_train, 0.01)
print(f"Epoch {epoch} Loss: {np.mean((nn.a2 - y_train)**2)}")
延伸思考
- 当使用 ReLU 激活函数时,反向传播公式会发生什么变化?
- 如果批量处理 100 个样本,权重更新矩阵的维度应该如何调整?
- 为什么在初始化权重时使用
* np.sqrt(1./input_size)的缩放因子?
理解反向传播就像学习骑自行车——开始可能摇摇晃晃,但一旦掌握平衡(理解链式法则),就能自如地探索深度学习的世界。建议读者在 Jupyter Notebook 中逐步执行上述代码,观察每个变量的变化,这才是真正的『Aha!』时刻。
