共计 2558 个字符,预计需要花费 7 分钟才能阅读完成。
数学推导与计算图构建
BP 算法的核心在于通过链式法则计算损失函数对网络参数的梯度。设网络有 $L$ 层,第 $l$ 层的权重矩阵为 $W^l$,偏置为 $b^l$,激活函数为 $\sigma(\cdot)$,则前向传播过程为:

$$
z^l = W^l a^{l-1} + b^l \
a^l = \sigma(z^l)
$$
定义损失函数 $J$ 对第 $l$ 层净输入 $z^l$ 的误差项为 $\delta^l = \frac{\partial J}{\partial z^l}$,根据链式法则可得反向传播公式:
$$
\delta^l = (W^{l+1})^T \delta^{l+1} \odot \sigma'(z^l)
$$
计算图构建的关键在于:
- 前向传播时缓存每一层的 $z^l$ 和 $a^l$
- 反向传播时按拓扑逆序计算各层 $\delta^l$
- 最终梯度计算为 $\frac{\partial J}{\partial W^l} = \delta^l (a^{l-1})^T$
激活函数对比与选择
常见激活函数特性对比:
- Sigmoid:输出范围 (0,1),导数最大 0.25,易导致梯度消失
- Tanh:输出范围 (-1,1),导数最大 1,缓解梯度消失但仍不理想
- ReLU:$max(0,x)$,正区间导数为 1,有效缓解梯度消失
工程建议:
- 隐藏层优先使用 ReLU 及其变体(LeakyReLU, PReLU)
- 输出层根据任务选择:二分类用 Sigmoid,多分类用 Softmax,回归用线性
Python 实现(NumPy 版)
import numpy as np
class NeuralNetwork:
def __init__(self, layers, learning_rate=0.01, momentum=0.9):
self.weights = [np.random.randn(y, x) * 0.1
for x, y in zip(layers[:-1], layers[1:])]
self.biases = [np.zeros((y, 1)) for y in layers[1:]]
self.velocity_w = [np.zeros_like(w) for w in self.weights]
self.velocity_b = [np.zeros_like(b) for b in self.biases]
self.lr = learning_rate
self.momentum = momentum
def relu(self, z):
return np.maximum(0, z)
def relu_prime(self, z):
return (z > 0).astype(float)
def forward(self, x):
self.activations = [x]
self.zs = []
for w, b in zip(self.weights, self.biases):
z = np.dot(w, self.activations[-1]) + b
self.zs.append(z)
self.activations.append(self.relu(z))
return self.activations[-1]
def backward(self, x, y):
# 误差反向传播
delta = (self.activations[-1] - y) * self.relu_prime(self.zs[-1])
nabla_w = [np.zeros_like(w) for w in self.weights]
nabla_b = [np.zeros_like(b) for b in self.biases]
nabla_w[-1] = np.dot(delta, self.activations[-2].T)
nabla_b[-1] = delta
for l in range(2, len(self.weights)+1):
delta = np.dot(self.weights[-l+1].T, delta) * self.relu_prime(self.zs[-l])
nabla_w[-l] = np.dot(delta, self.activations[-l-1].T)
nabla_b[-l] = delta
# 带动量的参数更新
for idx in range(len(self.weights)):
self.velocity_w[idx] = self.momentum * self.velocity_w[idx] + nabla_w[idx]
self.velocity_b[idx] = self.momentum * self.velocity_b[idx] + nabla_b[idx]
self.weights[idx] -= self.lr * self.velocity_w[idx]
self.biases[idx] -= self.lr * self.velocity_b[idx]
性能优化技巧
批量归一化实现
在每层激活前添加 BN 层:
def batchnorm_forward(x, gamma, beta, eps=1e-5):
mu = np.mean(x, axis=0)
var = np.var(x, axis=0)
x_hat = (x - mu) / np.sqrt(var + eps)
out = gamma * x_hat + beta
cache = (x, x_hat, mu, var, gamma, beta, eps)
return out, cache
GPU 加速建议
- 使用 CuPy 替换 NumPy 实现无缝 GPU 加速
- 将权重矩阵转为 torch.Tensor 并调用 cuda()
- 采用混合精度训练(FP16+FP32)
避坑指南
梯度爆炸应对
- 监控梯度范数:
np.linalg.norm(gradients) - 采用梯度裁剪:
grad = np.clip(grad, -threshold, threshold) - 权重初始化使用 He 初始化:
w = np.random.randn(n,m) * np.sqrt(2/n)
隐层神经元设置
经验公式:
$$
N_h = \frac{N_s}{\alpha(N_i + N_o)}
$$
其中 $N_s$ 为样本数,$N_i$ 为输入维度,$N_o$ 为输出维度,$\alpha$ 通常取 2~10
开放性问题
如何将 BP 算法与卷积操作结合实现 CNN?关键在于:
1. 将全连接层的矩阵乘法替换为卷积运算
2. 在反向传播时计算卷积核的梯度
3. 处理池化层的不可导问题(如 Max Pooling 的梯度路由)
欢迎在评论区分享你的实现方案!
正文完
