bp反向传播算法入门指南:从数学原理到Python实现

1次阅读
没有评论

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

image.webp

为什么需要反向传播?

刚开始接触神经网络时,最让我困惑的就是:那么多参数(比如权重矩阵 Weight Matrix 和偏置 Bias),究竟该怎么调整才能让模型表现更好?手动计算每个参数的梯度简直是噩梦——尤其是当网络层数增加时,这种计算量会指数级增长。这就是反向传播(Backpropagation)算法要解决的核心问题:高效计算损失函数(Loss Function)对每个参数的梯度。

数学原理拆解:链式法则的威力

反向传播的本质是 链式法则(Chain Rule)的巧妙应用。我们以一个最简单的单隐藏层网络为例:

输入层 → 隐藏层(含 Sigmoid 激活)→ 输出层(线性变换)

假设损失函数用均方误差(MSE),那么对隐藏层权重 $W^{(1)}$ 的梯度计算步骤如下:

  1. 前向传播计算各层输出:
    $$h = \sigma(W^{(1)}x + b^{(1)})$$
    $$\hat{y} = W^{(2)}h + b^{(2)}$$

  2. 从输出层反向计算误差:
    $$\frac{\partial L}{\partial \hat{y}} = 2(\hat{y} – y)$$

  3. 通过链式法则逐层回传:
    $$\frac{\partial L}{\partial W^{(1)}} = \frac{\partial L}{\partial h} \cdot \frac{\partial h}{\partial z} \cdot \frac{\partial z}{\partial W^{(1)}}$$
    其中 $z = W^{(1)}x + b^{(1)}$

bp 反向传播算法入门指南:从数学原理到 Python 实现(注:此处应为示意图链接)

Python 实现:手写 NumPy 版本

import numpy as np

# 定义网络结构
input_size = 3
hidden_size = 4
output_size = 1

# 初始化参数
W1 = np.random.randn(input_size, hidden_size) * 0.01
b1 = np.zeros((1, hidden_size))
W2 = np.random.randn(hidden_size, output_size) * 0.01
b2 = np.zeros((1, output_size))

# 前向传播
def forward(x):
    z1 = np.dot(x, W1) + b1  # shape: (batch_size, hidden_size)
    a1 = 1 / (1 + np.exp(-z1))  # Sigmoid 激活
    z2 = np.dot(a1, W2) + b2  # shape: (batch_size, output_size)
    return z2, a1

# 反向传播
def backward(x, y, z2, a1):
    m = x.shape[0]  # batch 大小

    # 输出层梯度
    dz2 = (z2 - y) / m  # dL/dz2 shape 同 z2
    dW2 = np.dot(a1.T, dz2)  # shape 同 W2
    db2 = np.sum(dz2, axis=0, keepdims=True)

    # 隐藏层梯度
    dz1 = np.dot(dz2, W2.T) * (a1 * (1 - a1))  # 注意 Sigmoid 导数
    dW1 = np.dot(x.T, dz1)  # shape 同 W1
    db1 = np.sum(dz1, axis=0, keepdims=True)

    return dW1, db1, dW2, db2

# 参数更新示例
learning_rate = 0.1
for epoch in range(100):
    z2, a1 = forward(x_train)
    dW1, db1, dW2, db2 = backward(x_train, y_train, z2, a1)

    W1 -= learning_rate * dW1
    b1 -= learning_rate * db1
    W2 -= learning_rate * dW2
    b2 -= learning_rate * db2

新手常踩的坑及解决方案

  1. 梯度消失问题:
  2. 现象:深层网络中梯度越来越小,导致浅层参数几乎不更新
  3. 解决方案:改用 ReLU 激活函数、残差连接(ResNet)、梯度裁剪

  4. 学习率设置不当:

  5. 现象:损失值震荡(过大)或下降过慢(过小)
  6. 经验值:通常从 0.01 开始尝试,配合学习率衰减(如每 10 轮×0.5)

  7. 数值不稳定:

  8. 现象:出现 NaN 或异常大的值
  9. 检查点:参数初始化范围、激活函数输出范围(如 Softmax 需要做数值稳定处理)

延伸思考与实践

  1. 如何扩展为 mini-batch 训练?
  2. 提示:在 backward()函数中注意梯度求平均的位置
  3. 进阶:尝试实现动量(Momentum)或 Adam 优化器

  4. 不同激活函数的影响:

  5. 任务:将代码中的 Sigmoid 改为 ReLU,观察训练曲线变化
  6. 思考:为什么深层网络更偏好 ReLU?

理解反向传播后,你会突然发现神经网络不再是个黑箱——那些神秘的梯度下降过程,原来都是扎实的微积分计算。建议亲手调试代码中的参数,比如把学习率调大 10 倍,观察损失值的变化规律,这种直观感受比任何理论讲解都更深刻。

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