共计 2004 个字符,预计需要花费 6 分钟才能阅读完成。
理解 BP 算法的核心逻辑
反向传播(Backpropagation,简称 BP)是神经网络训练的核心算法。它的本质是链式法则的递归应用,通过计算损失函数对每个参数的梯度来更新权重。我们先从一个简单的 3 层网络(输入层、隐藏层、输出层)开始推导。

数学推导步骤
-
前向传播计算
设隐藏层激活函数为 σ,输出层为线性激活,则:h = σ(W^{(1)}x + b^{(1)})y_{pred} = W^{(2)}h + b^{(2)} -
损失函数定义
采用均方误差(MSE):L = \frac{1}{2}(y_{true} - y_{pred})^2 -
反向传播梯度计算
- 输出层梯度:
\frac{\partial L}{\partial W^{(2)}} = (y_{pred} - y_{true}) \cdot h^T - 隐藏层梯度(链式法则应用):
\frac{\partial L}{\partial W^{(1)}} = (W^{(2)T}(y_{pred} - y_{true}) \odot σ'(z)) \cdot x^T其中⊙表示逐元素乘法,z 是隐藏层的加权输入。
Python 实现详解
下面是用 NumPy 实现的完整代码,关键部分添加了数学注释:
import numpy as np
class NeuralNetwork:
def __init__(self, input_size, hidden_size):
# 初始化权重(He 初始化)self.W1 = np.random.randn(input_size, hidden_size) * np.sqrt(2./input_size)
self.b1 = np.zeros(hidden_size)
self.W2 = np.random.randn(hidden_size, 1) * np.sqrt(2./hidden_size)
self.b2 = np.zeros(1)
def sigmoid(self, z):
return 1/(1 + np.exp(-z))
def forward(self, x):
self.z1 = np.dot(x, self.W1) + self.b1 # 隐藏层加权输入
self.h = self.sigmoid(self.z1) # 隐藏层激活输出
self.y_pred = np.dot(self.h, self.W2) + self.b2
return self.y_pred
def backward(self, x, y_true, lr=0.01):
m = x.shape[0] # 样本数量
# 输出层梯度 ∂L/∂W2 = (y_pred - y_true) * h^T
dy = (self.y_pred - y_true)
dW2 = np.dot(self.h.T, dy) / m
db2 = np.sum(dy, axis=0) / m
# 隐藏层梯度 ∂L/∂W1 = (W2^T * dy) ⊙ σ'(z1) * x^T
dh = np.dot(dy, self.W2.T)
dz1 = dh * self.h * (1 - self.h) # sigmoid 导数
dW1 = np.dot(x.T, dz1) / m
db1 = np.sum(dz1, axis=0) / m
# 参数更新
self.W2 -= lr * dW2
self.b2 -= lr * db2
self.W1 -= lr * dW1
self.b1 -= lr * db1
常见问题与优化策略
梯度爆炸与消失
- 现象:深层网络中梯度指数级增大或减小
- 解决方案:
- 使用 ReLU 及其变体替代 sigmoid
- 梯度裁剪(
np.clip(grad, -1, 1)) - 批归一化(BatchNorm)
学习率选择
- 经验法则:
- 从 0.01 开始尝试
- 使用学习率衰减:
lr = initial_lr / (1 + decay_rate * epoch)
进阶优化方向
-
向量化加速
当前实现已支持批量处理,可通过np.einsum进一步优化矩阵运算。 -
优化器升级
# Adam 优化器示例 def update_with_adam(self, grads, beta1=0.9, beta2=0.999, eps=1e-8): self.m = beta1 * self.m + (1-beta1) * grads # 一阶矩估计 self.v = beta2 * self.v + (1-beta2) * (grads**2) # 二阶矩估计 self.W -= lr * self.m / (np.sqrt(self.v) + eps)
验证推导正确性
通过与 PyTorch 自动微分对比验证:
import torch
x_tensor = torch.tensor(x, requires_grad=True)
# 构建相同网络结构...
loss = F.mse_loss(y_pred, y_true)
loss.backward()
print("PyTorch 梯度:\n", W1.grad)
print("手动计算梯度:\n", dW1)
总结
通过手写推导我们深入理解了:
1. 链式法则如何逐层传递梯度
2. 矩阵维度对齐的实际意义
3. 激活函数导数对梯度流动的影响
建议读者尝试:
– 增加隐藏层数量观察梯度变化
– 用 MNIST 数据集测试实现效果
– 比较不同优化器的收敛速度
正文完
