共计 1725 个字符,预计需要花费 5 分钟才能阅读完成。
反向传播(Backpropagation)是深度学习中最核心的算法之一,它通过高效计算梯度让神经网络能够从错误中学习。没有反向传播,现代多层神经网络将无法有效训练。本文将从数学原理推导到 Python 实现,带你彻底掌握这一关键技术。

数学原理:链式求导与梯度下降
1. 链式求导推导
假设我们有一个三层网络(输入层、隐藏层、输出层),以平方损失函数为例:
$$ L = \frac{1}{2}(y – \hat{y})^2 $$
对于输出层权重 $w_{ij}^o$ 的梯度:
$$\frac{\partial L}{\partial w_{ij}^o} = \frac{\partial L}{\partial \hat{y}} \cdot \frac{\partial \hat{y}}{\partial z^o} \cdot \frac{\partial z^o}{\partial w_{ij}^o}$$
其中 $z^o$ 是输出层加权输入,具体展开为:
$$\frac{\partial L}{\partial w_{ij}^o} = -(y – \hat{y}) \cdot f'(z^o) \cdot h_j$$
2. 梯度下降的几何意义
梯度方向是函数值增长最快的方向,负梯度则是下降最快的方向。每次权重更新:
$$ w_{new} = w_{old} – \eta \cdot \nabla L $$
其中 $\eta$ 是学习率,控制着参数更新的步长。
Python 实现
import numpy as np
class BPNN:
def __init__(self, input_size, hidden_size, output_size):
# 初始化权重(Xavier 初始化)self.W1 = np.random.randn(input_size, hidden_size) * 0.01
self.W2 = np.random.randn(hidden_size, output_size) * 0.01
def sigmoid(self, x):
return 1 / (1 + np.exp(-x))
def forward(self, X):
self.h = self.sigmoid(np.dot(X, self.W1)) # 隐藏层输出
self.y_hat = self.sigmoid(np.dot(self.h, self.W2)) # 最终输出
return self.y_hat
def backward(self, X, y, lr=0.1):
# 输出层梯度
dL_dy = -(y - self.y_hat)
dy_dz2 = self.y_hat * (1 - self.y_hat) # sigmoid 导数
dz2_dW2 = self.h
# 隐藏层梯度(链式法则)dL_dh = np.dot(dL_dy * dy_dz2, self.W2.T)
dh_dz1 = self.h * (1 - self.h)
dz1_dW1 = X
# 参数更新
self.W2 -= lr * np.dot(dz2_dW2.T, dL_dy * dy_dz2)
self.W1 -= lr * np.dot(dz1_dW1.T, dL_dh * dh_dz1)
实验对比
激活函数对比
- Sigmoid:容易导致梯度消失,深层网络训练困难
- ReLU:缓解梯度消失,但可能出现神经元死亡
# 训练循环示例
losses = []
for epoch in range(1000):
y_hat = model.forward(X)
loss = np.mean(0.5 * (y - y_hat)**2)
losses.append(loss)
model.backward(X, y, lr=0.1)
学习率影响
- 过大:loss 震荡甚至发散
- 过小:收敛速度过慢
避坑指南
- 梯度爆炸 :当梯度值突然变得极大时
-
解决方法:梯度裁剪(gradient clipping)
grad = np.clip(grad, -1, 1) # 限制梯度范围 -
Batch Size 选择 :
- 小 batch(32-256):泛化性好但波动大
- 全 batch:计算稳定但内存需求高
思考题
- 如何修改代码实现带动量(Momentum)的梯度下降?提示:需要保存前一步的更新方向
- 隐层神经元数量越多越好吗?什么情况下会导致过拟合?
通过这篇文章,我们不仅理解了反向传播的数学本质,还实现了完整的训练流程。建议读者尝试用不同数据集测试代码,观察不同超参数对训练过程的影响。
