BP神经网络之BP算法手写推导:从数学原理到Python实现

1次阅读
没有评论

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

image.webp

前向传播的数学原理

神经网络的前向传播本质是加权求和与非线性变换的堆叠。以单隐层网络为例,设输入层为 $\mathbf{x}$,隐藏层输出为:

BP 神经网络之 BP 算法手写推导:从数学原理到 Python 实现

$$
\mathbf{h} = \sigma(\mathbf{W}_1\mathbf{x} + \mathbf{b}_1)
$$

输出层结果为:

$$
\mathbf{y} = \text{softmax}(\mathbf{W}_2\mathbf{h} + \mathbf{b}_2)
$$

其中 $\sigma$ 代表 sigmoid 激活函数:

$$
\sigma(z) = \frac{1}{1+e^{-z}}
$$

反向传播的链式法则

  1. 输出层梯度计算
    使用交叉熵损失函数 $L$,对输出层权重求偏导:

$$
\frac{\partial L}{\partial \mathbf{W}_2} = \frac{\partial L}{\partial \mathbf{y}} \cdot \frac{\partial \mathbf{y}}{\partial (\mathbf{W}_2\mathbf{h})} \cdot \frac{\partial (\mathbf{W}_2\mathbf{h})}{\partial \mathbf{W}_2}
$$

  1. 隐藏层梯度计算
    误差继续向隐藏层传播:

$$
\frac{\partial L}{\partial \mathbf{W}_1} = \frac{\partial L}{\partial \mathbf{h}} \cdot \frac{\partial \mathbf{h}}{\partial (\mathbf{W}_1\mathbf{x})} \cdot \frac{\partial (\mathbf{W}_1\mathbf{x})}{\partial \mathbf{W}_1}
$$

其中 sigmoid 导数有重要性质:

$$
\sigma'(z) = \sigma(z)(1-\sigma(z))
$$

Python 实现核心代码

import numpy as np

class NeuralNetwork:
    def __init__(self, input_size, hidden_size):
        # 初始化权重(避免过大值)self.W1 = np.random.randn(input_size, hidden_size) * 0.01
        self.b1 = np.zeros(hidden_size)
        self.W2 = np.random.randn(hidden_size, 1) * 0.01
        self.b2 = np.zeros(1)

    def forward(self, x):
        self.z1 = np.dot(x, self.W1) + self.b1
        self.h = 1 / (1 + np.exp(-self.z1))  # sigmoid 激活
        self.z2 = np.dot(self.h, self.W2) + self.b2
        return self.z2

    def backward(self, x, y, lr=0.1):
        # 输出层梯度
        dL_dz2 = self.z2 - y  # 假设使用 MSE 损失
        dW2 = np.dot(self.h.T, dL_dz2)

        # 隐藏层梯度
        dL_dh = np.dot(dL_dz2, self.W2.T)
        dh_dz1 = self.h * (1 - self.h)  # sigmoid 导数
        dW1 = np.dot(x.T, dL_dh * dh_dz1)

        # 参数更新
        self.W2 -= lr * dW2
        self.W1 -= lr * dW1

常见问题与优化

  1. 梯度消失问题
  2. sigmoid 函数在输入绝对值较大时梯度接近 0
  3. 解决方案:使用 ReLU 激活函数 $\max(0,x)$

  4. 权重初始化技巧

  5. 过大初始化导致梯度爆炸
  6. Xavier 初始化:$W \sim N(0, \sqrt{1/n_{in}})$

  7. 学习率衰减

    lr = initial_lr / (1 + decay_rate * epoch)

梯度检查实现

def gradient_check(x, y, epsilon=1e-7):
    # 计算数值梯度
    original = network.W1[0,0]
    network.W1[0,0] = original + epsilon
    loss_plus = compute_loss(x, y)

    network.W1[0,0] = original - epsilon
    loss_minus = compute_loss(x, y)

    numerical_grad = (loss_plus - loss_minus) / (2*epsilon)
    # 与反向传播结果对比
    print(f"数值梯度: {numerical_grad}, 解析梯度: {analytical_grad}")

延伸思考

  1. 如何修改代码实现带动量的随机梯度下降?
  2. 批量归一化 (BatchNorm) 层应该加在什么位置?
  3. 当训练集准确率高但测试集差时可能是什么原因?

通过这次推导实践,我深刻体会到神经网络本质就是复合函数的梯度计算。建议初学者一定要亲手推导一次 BP 算法,这会比看十篇教程更有收获。

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