BP神经网络反向传播过程详解:从数学推导到Python实现

1次阅读
没有评论

共计 2036 个字符,预计需要花费 6 分钟才能阅读完成。

image.webp

1. 反向传播的数学基础

反向传播算法的核心是 链式求导法则。设网络有 $L$ 层,第 $l$ 层的权重为 $W^l$,偏置为 $b^l$,激活函数为 $\sigma(\cdot)$。对于单个样本 $(x,y)$,前向传播过程可表示为:

BP 神经网络反向传播过程详解:从数学推导到 Python 实现

$$
\begin{aligned}
z^l &= W^l a^{l-1} + b^l \
a^l &= \sigma(z^l)
\end{aligned}
$$

定义损失函数 $J$(如交叉熵),反向传播需要计算 $\frac{\partial J}{\partial W^l}$ 和 $\frac{\partial J}{\partial b^l}$。根据链式法则:

$$
\frac{\partial J}{\partial W^l} = \frac{\partial J}{\partial z^l} \cdot \frac{\partial z^l}{\partial W^l} = \delta^l (a^{l-1})^T
$$

$$
\frac{\partial J}{\partial b^l} = \delta^l
$$

其中 $\delta^l = \frac{\partial J}{\partial z^l}$ 称为误差项,其递推公式为:

$$
\delta^l = (W^{l+1})^T \delta^{l+1} \odot \sigma'(z^l)
$$

输出层的 $\delta^L$ 需单独计算,例如使用交叉熵损失 +softmax 时:

$$
\delta^L = a^L – y
$$

2. 优化器对比

优化器 权重更新公式 特点
SGD $W \leftarrow W – \eta \nabla_W J$ 简单但容易震荡
Momentum $v \leftarrow \gamma v + \eta \nabla_W J$ 积累动量减少震荡
$W \leftarrow W – v$
Adam 综合动量与自适应学习率 实际应用最广泛的优化器

3. Python 实现

import numpy as np

class BPNetwork:
    def __init__(self, layers):
        """layers: 每层神经元数量,如[784, 256, 10]"""
        self.weights = [np.random.randn(y, x)*0.01 
                        for x,y in zip(layers[:-1], layers[1:])]
        self.biases = [np.zeros((y,1)) for y in layers[1:]]

    def forward(self, x):
        """前向传播"""
        a = x
        for w, b in zip(self.weights, self.biases):
            z = np.dot(w, a) + b
            a = 1/(1+np.exp(-z))  # sigmoid 激活
        return a

    def backward(self, x, y):
        """反向传播"""
        # 前向计算并保存中间结果
        activations = [x]
        zs = []
        a = x
        for w, b in zip(self.weights, self.biases):
            z = np.dot(w, a) + b
            zs.append(z)
            a = 1/(1+np.exp(-z))
            activations.append(a)

        # 反向计算误差
        delta = (activations[-1] - y) * activations[-1] * (1-activations[-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, activations[-2].T)
        nabla_b[-1] = delta

        # 逐层反向传播
        for l in range(2, len(self.weights)+1):
            z = zs[-l]
            sp = 1/(1+np.exp(-z)) * (1 - 1/(1+np.exp(-z)))
            delta = np.dot(self.weights[-l+1].T, delta) * sp
            nabla_w[-l] = np.dot(delta, activations[-l-1].T)
            nabla_b[-l] = delta

        return nabla_w, nabla_b

4. 避坑指南

  • 学习率设置:过大的学习率会导致梯度爆炸(可添加梯度裁剪)
  • ReLU 死亡问题:部分神经元可能永远无法激活(可改用 LeakyReLU)
  • 批量归一化:应在激活函数前应用,保持均值 0 方差 1

5. 激活函数比较

函数 导数表达式 梯度特性
Sigmoid $\sigma(1-\sigma)$ 易饱和导致梯度消失
tanh $1-\tanh^2(z)$ 梯度比 sigmoid 更稳定
ReLU $1_{{z>0}}$ 缓解梯度消失但可能神经元死亡

6. 思考题

要实现 Mini-batch 训练,需要:
1. 修改 forward/backward 支持矩阵输入(每列一个样本)
2. 在反向传播时对 batch 内梯度取平均
3. 添加 shuffle 逻辑打乱训练数据

完整的实现应该包括数据加载、分批处理和迭代训练等模块。通过向量化计算可以显著提升 GPU 利用率。

正文完
 0
评论(没有评论)