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

1次阅读
没有评论

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

image.webp

理解 BP 算法的核心逻辑

反向传播(Backpropagation,简称 BP)是神经网络训练的核心算法。它的本质是链式法则的递归应用,通过计算损失函数对每个参数的梯度来更新权重。我们先从一个简单的 3 层网络(输入层、隐藏层、输出层)开始推导。

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

数学推导步骤

  1. 前向传播计算
    设隐藏层激活函数为 σ,输出层为线性激活,则:

    h = σ(W^{(1)}x + b^{(1)})
    y_{pred} = W^{(2)}h + b^{(2)}

  2. 损失函数定义
    采用均方误差(MSE):

    L = \frac{1}{2}(y_{true} - y_{pred})^2

  3. 反向传播梯度计算

  4. 输出层梯度:
    \frac{\partial L}{\partial W^{(2)}} = (y_{pred} - y_{true}) \cdot h^T
  5. 隐藏层梯度(链式法则应用):
    \frac{\partial L}{\partial W^{(1)}} = (W^{(2)T}(y_{pred} - y_{true}) \odot σ'(z)) \cdot x^T

    其中⊙表示逐元素乘法,z 是隐藏层的加权输入。

Python 实现详解

下面是用 NumPy 实现的完整代码,关键部分添加了数学注释:

import numpy as np

class NeuralNetwork:
    def __init__(self, input_size, hidden_size):
        # 初始化权重(He 初始化)self.W1 = np.random.randn(input_size, hidden_size) * np.sqrt(2./input_size)
        self.b1 = np.zeros(hidden_size)
        self.W2 = np.random.randn(hidden_size, 1) * np.sqrt(2./hidden_size)
        self.b2 = np.zeros(1)

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

    def forward(self, x):
        self.z1 = np.dot(x, self.W1) + self.b1  # 隐藏层加权输入
        self.h = self.sigmoid(self.z1)          # 隐藏层激活输出
        self.y_pred = np.dot(self.h, self.W2) + self.b2
        return self.y_pred

    def backward(self, x, y_true, lr=0.01):
        m = x.shape[0]  # 样本数量

        # 输出层梯度 ∂L/∂W2 = (y_pred - y_true) * h^T
        dy = (self.y_pred - y_true)
        dW2 = np.dot(self.h.T, dy) / m
        db2 = np.sum(dy, axis=0) / m

        # 隐藏层梯度 ∂L/∂W1 = (W2^T * dy) ⊙ σ'(z1) * x^T
        dh = np.dot(dy, self.W2.T)
        dz1 = dh * self.h * (1 - self.h)  # sigmoid 导数
        dW1 = np.dot(x.T, dz1) / m
        db1 = np.sum(dz1, axis=0) / m

        # 参数更新
        self.W2 -= lr * dW2
        self.b2 -= lr * db2
        self.W1 -= lr * dW1
        self.b1 -= lr * db1

常见问题与优化策略

梯度爆炸与消失

  • 现象:深层网络中梯度指数级增大或减小
  • 解决方案
  • 使用 ReLU 及其变体替代 sigmoid
  • 梯度裁剪(np.clip(grad, -1, 1)
  • 批归一化(BatchNorm)

学习率选择

  • 经验法则
  • 从 0.01 开始尝试
  • 使用学习率衰减:lr = initial_lr / (1 + decay_rate * epoch)

进阶优化方向

  1. 向量化加速
    当前实现已支持批量处理,可通过 np.einsum 进一步优化矩阵运算。

  2. 优化器升级

    # Adam 优化器示例
    def update_with_adam(self, grads, beta1=0.9, beta2=0.999, eps=1e-8):
        self.m = beta1 * self.m + (1-beta1) * grads  # 一阶矩估计
        self.v = beta2 * self.v + (1-beta2) * (grads**2)  # 二阶矩估计
        self.W -= lr * self.m / (np.sqrt(self.v) + eps)

验证推导正确性

通过与 PyTorch 自动微分对比验证:

import torch
x_tensor = torch.tensor(x, requires_grad=True)
# 构建相同网络结构...
loss = F.mse_loss(y_pred, y_true)
loss.backward()
print("PyTorch 梯度:\n", W1.grad)
print("手动计算梯度:\n", dW1)

总结

通过手写推导我们深入理解了:
1. 链式法则如何逐层传递梯度
2. 矩阵维度对齐的实际意义
3. 激活函数导数对梯度流动的影响

建议读者尝试:
– 增加隐藏层数量观察梯度变化
– 用 MNIST 数据集测试实现效果
– 比较不同优化器的收敛速度

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