BP反向传播算法计算题实战:从数学推导到Python实现

1次阅读
没有评论

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

image.webp

为什么需要反向传播?

神经网络的核心是通过调整权重来最小化损失函数。想象一个婴儿学习识别猫:每次猜错后,大脑会反向追踪哪些神经元连接导致了错误,并调整这些连接的强度。反向传播就是实现这个过程的数学工具。

BP 反向传播算法计算题实战:从数学推导到 Python 实现

传统机器学习中,我们可以直接计算导数。但神经网络有大量参数(ResNet-152 有 6000 万参数!),手动计算不现实。反向传播通过链式法则,将误差从输出层反向传递,高效计算所有参数的梯度。

数学推导:拆解链式法则

基础符号定义

  • $x_i$: 输入层第 i 个节点
  • $w_{ij}^{(l)}$: 第 l 层第 i 个节点到第 (l+1) 层第 j 个节点的权重
  • $z_j^{(l)}$: 第 l 层第 j 个节点的加权输入
  • $a_j^{(l)}$: 第 l 层第 j 个节点的激活输出($a_j^{(l)}=\sigma(z_j^{(l)})$)
  • $L$: 损失函数

关键推导步骤(以 2 层网络为例)

  1. 输出层误差计算:
    $$
    \frac{\partial L}{\partial w_{ij}^{(2)}} = \underbrace{\frac{\partial L}{\partial a_j^{(3)}}}{\text{误差项}} \cdot \underbrace{\frac{\partial a_j^{(3)}}{\partial z_j^{(3)}}}
    $$}\cdot \underbrace{\frac{\partial z_j^{(3)}}{\partial w_{ij}^{(2)}}}_{a_i^{(2)}

  2. 隐藏层误差传递(链式法则展开):
    $$
    \frac{\partial L}{\partial w_{ij}^{(1)}} = \left(\sum_k \frac{\partial L}{\partial a_k^{(2)}}\frac{\partial a_k^{(2)}}{\partial z_k^{(2)}}w_{jk}^{(2)}\right) \cdot \frac{\partial a_j^{(2)}}{\partial z_j^{(2)}} \cdot x_i
    $$

Python 实现详解

import numpy as np

class NeuralNetwork:
    def __init__(self, input_size, hidden_size, output_size):
        # 初始化权重(使用 Xavier 初始化)self.W1 = np.random.randn(input_size, hidden_size) * np.sqrt(1./input_size)
        self.W2 = np.random.randn(hidden_size, output_size) * np.sqrt(1./hidden_size)

    def sigmoid(self, x, derivative=False):
        if derivative:
            return x * (1 - x)  # 假设 x 已经是 sigmoid 输出
        return 1 / (1 + np.exp(-x))

    def forward(self, X):
        self.z1 = np.dot(X, self.W1)
        self.a1 = self.sigmoid(self.z1)
        self.z2 = np.dot(self.a1, self.W2)
        self.a2 = self.sigmoid(self.z2)
        return self.a2

    def backward(self, X, y, learning_rate):
        # 输出层误差
        output_error = self.a2 - y  # 假设使用 MSE 损失
        output_delta = output_error * self.sigmoid(self.a2, derivative=True)

        # 隐藏层误差
        hidden_error = np.dot(output_delta, self.W2.T)
        hidden_delta = hidden_error * self.sigmoid(self.a1, derivative=True)

        # 更新权重
        self.W2 -= learning_rate * np.dot(self.a1.T, output_delta)
        self.W1 -= learning_rate * np.dot(X.T, hidden_delta)

常见计算陷阱

矩阵维度问题

  • 典型错误:当输入 X 是 (100,784),权重 W1 是(785,64) 时,会因广播机制导致静默错误
  • 检查方法:print(f"X.shape: {X.shape}, W1.shape: {self.W1.shape}")

梯度消失实例

# 使用 tanh 激活的深层网络
for l in range(10):
    grad = grad * (1 - np.tanh(z_l)**2)  # 梯度逐层缩小
    print(f"Layer {l} gradient norm: {np.linalg.norm(grad):.6f}")

MNIST 实战示例

from sklearn.datasets import fetch_openml
mnist = fetch_openml('mnist_784', version=1)
X = mnist.data / 255.0  # 归一化

# 转换为 one-hot 编码
y = np.zeros((len(mnist.target), 10))
y[np.arange(len(mnist.target)), mnist.target.astype(int)] = 1

nn = NeuralNetwork(784, 128, 10)
for epoch in range(10):
    nn.forward(X_train)
    nn.backward(X_train, y_train, 0.01)
    print(f"Epoch {epoch} Loss: {np.mean((nn.a2 - y_train)**2)}")

延伸思考

  1. 当使用 ReLU 激活函数时,反向传播公式会发生什么变化?
  2. 如果批量处理 100 个样本,权重更新矩阵的维度应该如何调整?
  3. 为什么在初始化权重时使用 * np.sqrt(1./input_size) 的缩放因子?

理解反向传播就像学习骑自行车——开始可能摇摇晃晃,但一旦掌握平衡(理解链式法则),就能自如地探索深度学习的世界。建议读者在 Jupyter Notebook 中逐步执行上述代码,观察每个变量的变化,这才是真正的『Aha!』时刻。

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