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

1次阅读
没有评论

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

image.webp

数学原理推导

反向传播的核心在于链式法则的应用。设网络有 $L$ 层,第 $l$ 层权重为 $W^l$,偏置为 $b^l$,激活函数为 $\sigma(\cdot)$。定义损失函数 $J$ 对第 $l$ 层参数的梯度为:

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

  1. 输出层梯度计算

对于输出层 $L$,首先计算损失函数对输出的偏导:
$$\frac{\partial J}{\partial a^L} = \nabla J(a^L)$$
其中 $a^L$ 为输出层激活值。

  1. 隐藏层梯度传播

通过链式法则逐层回传误差:
$$\delta^l = ((W^{l+1})^T \delta^{l+1}) \odot \sigma'(z^l)$$
这里 $z^l$ 为第 $l$ 层的加权输入,$\odot$ 表示 Hadamard 积。

  1. 参数更新公式

最终得到权重和偏置的更新量:
$$\frac{\partial J}{\partial W^l} = \delta^l (a^{l-1})^T$$
$$\frac{\partial J}{\partial b^l} = \delta^l$$

优化器对比

  • SGD:直接使用当前 batch 的梯度更新参数
    $$\theta_{t+1} = \theta_t – \eta \nabla J(\theta_t)$$

  • Momentum:引入动量项积累历史梯度
    $$v_{t+1} = \gamma v_t + \eta \nabla J(\theta_t)$$
    $$\theta_{t+1} = \theta_t – v_{t+1}$$

Python 实现

import numpy as np

class NeuralNetwork:
    def __init__(self, layers):
        # 初始化权重和偏置
        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 = self.sigmoid(z)
        return a

    def backward(self, x, y):
        """反向传播"""
        # 初始化梯度容器
        grad_w = [np.zeros_like(w) for w in self.weights]
        grad_b = [np.zeros_like(b) for b in self.biases]

        # 前向计算并保存中间结果
        activation = x
        activations = [x]
        zs = []
        for w, b in zip(self.weights, self.biases):
            z = np.dot(w, activation) + b
            zs.append(z)
            activation = self.sigmoid(z)
            activations.append(activation)

        # 输出层误差
        delta = (activations[-1] - y) * self.sigmoid_prime(zs[-1])
        grad_b[-1] = delta
        grad_w[-1] = np.dot(delta, activations[-2].T)

        # 反向传播误差
        for l in range(2, len(self.weights)+1):
            z = zs[-l]
            delta = np.dot(self.weights[-l+1].T, delta) * self.sigmoid_prime(z)
            grad_b[-l] = delta
            grad_w[-l] = np.dot(delta, activations[-l-1].T)

        return grad_w, grad_b

    def sigmoid(self, z):
        return 1/(1+np.exp(-z))

    def sigmoid_prime(self, z):
        return self.sigmoid(z)*(1-self.sigmoid(z))

避坑指南

  1. 学习率设置
  2. 过大导致梯度爆炸(可添加梯度裁剪)
  3. 过小导致收敛缓慢(建议使用学习率衰减)

  4. ReLU 死亡神经元

  5. 使用 LeakyReLU:$f(x)=max(\alpha x, x)$
  6. 初始化时设置偏置项为小正数

  7. 批量归一化技巧

  8. 在激活函数前进行归一化
  9. 测试阶段使用移动平均统计量

激活函数对比

函数类型 导数表达式 梯度特性 适用场景
Sigmoid $\sigma'(x)=\sigma(x)(1-\sigma(x))$ 易饱和 二分类输出层
Tanh $1-\tanh^2(x)$ 零中心化 隐藏层
ReLU $1_{x>0}$ 稀疏激活 深层网络

思考题

要实现 Mini-batch 训练,需要修改代码中的以下部分:
1. 前向 / 反向传播需支持矩阵输入(样本按列排列)
2. 梯度计算改为 batch 内平均
3. 添加 shuffle 逻辑确保随机采样

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