共计 1501 个字符,预计需要花费 4 分钟才能阅读完成。
背景与痛点
在神经网络训练中,我们需要调整权重参数使得模型的预测结果尽可能接近真实值。传统的梯度计算方法需要为每个参数单独计算梯度,这在深层网络中计算量会呈指数级增长。反向传播算法通过链式法则,实现了高效计算所有参数梯度的目的。

举个例子,在一个 3 层网络中:
– 传统方法需要分别计算每层参数的梯度
– BP 算法可以一次性计算所有参数的梯度
数学推导
BP 算法的核心是链式法则。我们以简单的全连接网络为例,推导权重更新公式:
- 定义损失函数 L
$$L = \frac{1}{2}(y – \hat{y})^2$$
- 计算输出层梯度
$$\frac{\partial L}{\partial w^{(2)}} = \frac{\partial L}{\partial \hat{y}} \cdot \frac{\partial \hat{y}}{\partial z^{(2)}} \cdot \frac{\partial z^{(2)}}{\partial w^{(2)}}$$
- 计算隐藏层梯度
$$\frac{\partial L}{\partial w^{(1)}} = \frac{\partial L}{\partial h} \cdot \frac{\partial h}{\partial z^{(1)}} \cdot \frac{\partial z^{(1)}}{\partial w^{(1)}}$$
Python 实现
以下是使用 NumPy 实现的完整 BP 算法:
import numpy as np
# 定义网络结构
input_size = 2
hidden_size = 3
output_size = 1
# 初始化权重
W1 = np.random.randn(input_size, hidden_size)
W2 = np.random.randn(hidden_size, output_size)
# 前向传播
def forward(X):
self.z1 = np.dot(X, W1)
self.a1 = sigmoid(self.z1)
self.z2 = np.dot(self.a1, W2)
self.a2 = sigmoid(self.z2)
return self.a2
# 反向传播
def backward(X, y, output):
# 计算输出层误差
error = output - y
delta2 = error * sigmoid_derivative(output)
# 计算隐藏层误差
error_hidden = delta2.dot(W2.T)
delta1 = error_hidden * sigmoid_derivative(self.a1)
# 更新权重
W2 -= learning_rate * self.a1.T.dot(delta2)
W1 -= learning_rate * X.T.dot(delta1)
工程实践
梯度消失 / 爆炸问题
- 成因分析:
- 梯度消失:当使用 sigmoid 激活函数时,梯度会在反向传播过程中不断减小
-
梯度爆炸:当权重初始化过大时,梯度会指数级增长
-
解决方案:
- 使用 ReLU 激活函数
- 采用 Xavier 或 He 初始化
- 加入梯度裁剪
学习率选择
- 经验法则:
- 初始值通常设为 0.001-0.1
- 使用学习率衰减策略
- 考虑采用自适应优化器(Adam)
性能优化
- 计算图优化:
- 合并重复计算
-
使用矩阵运算代替循环
-
并行计算:
- 数据并行
- 模型并行
避坑指南
- 常见错误:
- 维度不匹配:确保每层的输入输出维度一致
- 激活函数选择不当:根据不同场景选择合适的激活函数
-
初始化问题:避免全零初始化
-
解决方法:
- 添加维度检查
- 进行梯度检查
- 使用标准化初始化
思考题
- 如何将 BP 算法应用到 CNN 中?
- BP 算法在 RNN 中的实现有哪些不同?
- 如何证明 BP 算法计算的梯度是正确的?
希望通过这篇文章,你能深入理解 BP 算法的原理和实现,并在实际项目中灵活应用。
