共计 2871 个字符,预计需要花费 8 分钟才能阅读完成。
前向传播的数学原理
神经网络的前向传播可以看作是多层复合函数的计算过程。以单隐层网络为例,设输入层为 $\mathbf{x}$,隐藏层输出为 $\mathbf{h}$,最终输出为 $\mathbf{\hat{y}}$,则计算过程如下:

-
隐藏层计算:
$$\mathbf{z}^{(1)} = \mathbf{W}^{(1)}\mathbf{x} + \mathbf{b}^{(1)}$$
$$\mathbf{h} = \sigma(\mathbf{z}^{(1)})$$ -
输出层计算:
$$\mathbf{z}^{(2)} = \mathbf{W}^{(2)}\mathbf{h} + \mathbf{b}^{(2)}$$
$$\mathbf{\hat{y}} = \text{softmax}(\mathbf{z}^{(2)})$$
其中 $\sigma$ 表示 sigmoid 激活函数,softmax 用于多分类问题。
反向传播的链式法则推导
反向传播的核心是计算损失函数对权重和偏置的偏导数。以均方误差损失 $L = \frac{1}{2}(y-\hat{y})^2$ 为例:
-
输出层权重梯度:
$$\frac{\partial L}{\partial W_{ij}^{(2)}} = \frac{\partial L}{\partial \hat{y}i} \frac{\partial \hat{y}_i}{\partial z_i^{(2)}} \frac{\partial z_i^{(2)}}{\partial W_i)\cdot h_j$$}^{(2)}} = (\hat{y}_i-y_i)\cdot \hat{y}_i(1-\hat{y -
隐藏层权重梯度:
$$\frac{\partial L}{\partial W_{jk}^{(1)}} = \sum_i \frac{\partial L}{\partial \hat{y}i} \frac{\partial \hat{y}_i}{\partial z_i^{(2)}} \frac{\partial z_i^{(2)}}{\partial h_j} \frac{\partial h_j}{\partial z_j^{(1)}} \frac{\partial z_j^{(1)}}{\partial W$$}^{(1)}
Python 实现核心代码
import numpy as np
class NeuralNetwork:
def __init__(self, input_size, hidden_size, output_size):
# 初始化权重(使用较小随机值)self.W1 = np.random.randn(hidden_size, input_size) * 0.01
self.b1 = np.zeros((hidden_size, 1))
self.W2 = np.random.randn(output_size, hidden_size) * 0.01
self.b2 = np.zeros((output_size, 1))
def forward(self, x):
# 前向传播
self.z1 = np.dot(self.W1, x) + self.b1
self.h = 1 / (1 + np.exp(-self.z1)) # sigmoid 激活
self.z2 = np.dot(self.W2, self.h) + self.b2
self.y_hat = np.exp(self.z2) / np.sum(np.exp(self.z2)) # softmax
return self.y_hat
def backward(self, x, y, learning_rate):
# 反向传播
m = x.shape[1] # 样本数
# 输出层梯度
dz2 = self.y_hat - y
dW2 = np.dot(dz2, self.h.T) / m
db2 = np.sum(dz2, axis=1, keepdims=True) / m
# 隐藏层梯度
dz1 = np.dot(self.W2.T, dz2) * (self.h * (1 - self.h))
dW1 = np.dot(dz1, x.T) / m
db1 = np.sum(dz1, 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
常见问题与优化
梯度消失问题
当使用 sigmoid 激活函数时,其导数 $\sigma'(x) = \sigma(x)(1-\sigma(x))$ 最大值仅为 0.25,在深层网络中梯度会指数级减小。解决方案:
- 使用 ReLU 激活函数:$\text{ReLU}(x) = \max(0, x)$
- 加入批量归一化(BatchNorm)层
- 使用残差连接(ResNet)
参数初始化技巧
- 权重初始化过大会导致梯度爆炸
- 推荐使用 Xavier 初始化:$W \sim N(0, \sqrt{\frac{2}{n_{in}+n_{out}}})$
学习率调整
实现学习率衰减可提升后期训练稳定性:
learning_rate = initial_lr * (1. / (1. + decay_rate * epoch))
梯度检查实现
数值梯度检查是验证反向传播正确性的有效方法:
def gradient_check(x, y, epsilon=1e-7):
# 计算解析梯度
nn.forward(x)
nn.backward(x, y, learning_rate=0, update=False)
# 对每个参数进行数值梯度近似
for param, dparam in zip([nn.W1, nn.b1, nn.W2, nn.b2],
[nn.dW1, nn.db1, nn.dW2, nn.db2]):
for i in range(param.shape[0]):
for j in range(param.shape[1]):
original = param[i,j]
# 计算 f(x+epsilon)
param[i,j] = original + epsilon
loss_plus = nn.compute_loss(x, y)
# 计算 f(x-epsilon)
param[i,j] = original - epsilon
loss_minus = nn.compute_loss(x, y)
# 恢复原值
param[i,j] = original
# 数值梯度
grad_approx = (loss_plus - loss_minus) / (2 * epsilon)
# 比较数值梯度和解析梯度
assert np.abs(dparam[i,j] - grad_approx) < 1e-5
延伸思考
- 如何修改代码实现带动量的随机梯度下降(Momentum SGD)?
- 如果改用交叉熵损失函数,反向传播公式会有哪些变化?
- 尝试将 sigmoid 激活函数替换为 LeakyReLU,观察训练效果差异
- 如何实现早停(Early Stopping)机制防止过拟合?
通过这次推导和实现,我深刻理解了 BP 算法的数学本质。建议读者在理解基本原理后,可以尝试用 PyTorch 等框架实现相同功能,对比理解自动微分的实现原理。
