共计 2809 个字符,预计需要花费 8 分钟才能阅读完成。
BP 神经网络是深度学习的基础构件,通过反向传播(Backpropagation)实现了多层网络的参数优化。其核心思想是利用链式法则逐层计算梯度,再用梯度下降更新权重。理解 BP 算法不仅能帮我们调试模型,更是掌握现代神经网络架构的必经之路。

数学原理精讲
1. 前向传播的矩阵表示
前向传播的通用公式为:
$$\mathbf{z}^{(l)} = \mathbf{W}^{(l)}\mathbf{a}^{(l-1)} + \mathbf{b}^{(l)}$$
$$\mathbf{a}^{(l)} = \sigma(\mathbf{z}^{(l)})$$
其中 $\sigma$ 代表激活函数,以 sigmoid 为例:
$$\sigma(z) = \frac{1}{1+e^{-z}}$$
- 输入层 $\mathbf{a}^{(0)}$ 即原始特征
- 每层的权重矩阵 $\mathbf{W}$ 维度为 (当前层神经元数, 前一层神经元数)
- 偏置项 $\mathbf{b}$ 始终为列向量
2. 反向传播的梯度计算
定义损失函数 $L$(如交叉熵),关键推导步骤如下:
-
输出层误差:
$$\delta^{(L)} = \nabla_{\mathbf{a}}L \odot \sigma'(\mathbf{z}^{(L)})$$ -
隐藏层误差反向传播:
$$\delta^{(l)} = (\mathbf{W}^{(l+1)})^T\delta^{(l+1)} \odot \sigma'(\mathbf{z}^{(l)})$$ -
参数梯度计算:
$$\frac{\partial L}{\partial \mathbf{W}^{(l)}} = \delta^{(l)}(\mathbf{a}^{(l-1)})^T$$
$$\frac{\partial L}{\partial \mathbf{b}^{(l)}} = \delta^{(l)}$$
对于 sigmoid 激活,其导数有简洁形式:
$$\sigma'(z) = \sigma(z)(1-\sigma(z))$$
3. 学习率与梯度下降
权重更新公式:
$$\mathbf{W} := \mathbf{W} – \eta \frac{\partial L}{\partial \mathbf{W}}$$
其中 $\eta$ 为学习率:
- 过大导致震荡甚至发散
- 过小收敛缓慢
- 经验值通常在 0.001 到 0.1 之间
Python 实现详解
import numpy as np
class TwoLayerNet:
def __init__(self, input_size, hidden_size, output_size):
# He 初始化缓解梯度消失
self.W1 = np.random.randn(hidden_size, input_size) * np.sqrt(2/input_size)
self.b1 = np.zeros((hidden_size, 1))
self.W2 = np.random.randn(output_size, hidden_size) * np.sqrt(2/hidden_size)
self.b2 = np.zeros((output_size, 1))
def sigmoid(self, z):
return 1 / (1 + np.exp(-z))
def forward(self, X):
# 向量化计算(X.shape=(feature_dim, batch_size))self.z1 = np.dot(self.W1, X) + self.b1
self.a1 = self.sigmoid(self.z1)
self.z2 = np.dot(self.W2, self.a1) + self.b2
self.a2 = self.sigmoid(self.z2)
return self.a2
def backward(self, X, y, learning_rate):
m = X.shape[1] # 样本数
# 输出层误差
delta2 = (self.a2 - y) * self.a2 * (1 - self.a2)
dW2 = np.dot(delta2, self.a1.T) / m
db2 = np.sum(delta2, axis=1, keepdims=True) / m
# 隐藏层误差
delta1 = np.dot(self.W2.T, delta2) * self.a1 * (1 - self.a1)
dW1 = np.dot(delta1, X.T) / m
db1 = np.sum(delta1, axis=1, keepdims=True) / m
# 参数更新
self.W2 -= learning_rate * dW2
self.b2 -= learning_rate * db2
self.W1 -= learning_rate * dW1
self.b1 -= learning_rate * db1
# 梯度检查实现
def gradient_check(net, X, y, epsilon=1e-7):
# 计算数值梯度
grad_W1 = np.zeros_like(net.W1)
for i in range(net.W1.shape[0]):
for j in range(net.W1.shape[1]):
net.W1[i,j] += epsilon
loss_plus = compute_loss(net.forward(X), y)
net.W1[i,j] -= 2*epsilon
loss_minus = compute_loss(net.forward(X), y)
net.W1[i,j] += epsilon # 恢复原值
grad_W1[i,j] = (loss_plus - loss_minus) / (2*epsilon)
# 与反向传播结果对比
diff = np.linalg.norm(grad_W1 - net.dW1) / (np.linalg.norm(grad_W1) + np.linalg.norm(net.dW1))
print(f"梯度相对误差: {diff}" if diff < 1e-7 else "警告:梯度计算异常")
实践避坑指南
1. 梯度消失问题
现象 :
– 深层网络早期层梯度接近 0
– 参数几乎不更新
解决方案 :
– 使用 ReLU 等非饱和激活函数
– 残差连接(ResNet)
– 梯度裁剪(Gradient Clipping)
2. 学习率策略
常用方法对比 :
| 方法 | 优点 | 缺点 |
|---|---|---|
| 固定学习率 | 实现简单 | 需要手动调参 |
| 指数衰减 | 初期快速收敛 | 后期可能停滞 |
| Adam | 自适应各参数学习率 | 需要更多内存 |
3. 权重初始化
推荐方案 :
– ReLU 网络:He 初始化(方差 =2/n)
– Tanh 网络:Xavier 初始化(方差 =1/n)
– 避免全零初始化导致对称性问题
思考题
- 如何证明反向传播计算的梯度指向局部最优方向?考虑 Hessian 矩阵的正定性条件
- 批量归一化(BatchNorm)改变了各层的输入分布,这对反向传播的梯度计算会产生哪些影响?
- 共轭梯度法、L-BFGS 等二阶优化算法能否直接应用于 BP 网络?计算复杂度如何?
理解 BP 神经网络的数学本质后,可以更灵活地调整网络结构。建议读者尝试修改代码实现 dropout 或批归一化层,观察训练过程的变化。
