BP神经网络反向传播算法实战:从数学推导到Python实现

1次阅读
没有评论

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

image.webp

问题描述与网络结构

我们以一个简单的二分类问题为例:给定二维平面上的点,根据它们的坐标判断属于类别 0 还是类别 1。假设数据呈圆形分布,内圈为类别 0,外圈为类别 1。

BP 神经网络反向传播算法实战:从数学推导到 Python 实现

网络结构采用单隐藏层的 BP 神经网络:

  • 输入层:2 个神经元(对应 x,y 坐标)
  • 隐藏层:3 个神经元,使用 Sigmoid 激活函数
  • 输出层:1 个神经元,使用 Sigmoid 激活函数

前向传播实现

前向传播过程可以表示为:

  1. 输入层到隐藏层:
    $$h = \sigma(W_1 X + b_1)$$
  2. 隐藏层到输出层:
    $$\hat{y} = \sigma(W_2 h + b_2)$$

其中 $\sigma$ 是 Sigmoid 函数:
$$\sigma(z) = \frac{1}{1+e^{-z}}$$

损失函数选择与计算

我们使用交叉熵损失函数:
$$L = -\frac{1}{N}\sum_{i=1}^N [y_i\log(\hat{y}_i) + (1-y_i)\log(1-\hat{y}_i)]$$

反向传播推导

反向传播的核心是链式法则,计算损失对各个参数的梯度:

  1. 计算输出层梯度:
    $$\frac{\partial L}{\partial W_2} = (\hat{y}-y) \cdot h^T$$
    $$\frac{\partial L}{\partial b_2} = \hat{y}-y$$

  2. 计算隐藏层梯度:
    $$\frac{\partial L}{\partial W_1} = [(\hat{y}-y)W_2^T \odot h \odot (1-h)] \cdot X^T$$
    $$\frac{\partial L}{\partial b_1} = (\hat{y}-y)W_2^T \odot h \odot (1-h)$$

其中 $\odot$ 表示逐元素相乘。

权重更新

使用梯度下降法更新参数:
$$W = W – \eta \frac{\partial L}{\partial W}$$
$$b = b – \eta \frac{\partial L}{\partial b}$$

其中 $\eta$ 是学习率。

Python 实现

import numpy as np
import matplotlib.pyplot as plt

# 生成数据
np.random.seed(42)
N = 200
X = np.random.randn(N, 2)
radius = np.sqrt((X**2).sum(axis=1))
y = (radius > 1).astype(int)

# 网络参数
input_size = 2
hidden_size = 3
output_size = 1
learning_rate = 0.1
epochs = 10000

# 初始化权重
W1 = np.random.randn(input_size, hidden_size)
W2 = np.random.randn(hidden_size, output_size)
b1 = np.zeros((1, hidden_size))
b2 = np.zeros((1, output_size))

# 训练过程
losses = []
for epoch in range(epochs):
    # 前向传播
    h = 1 / (1 + np.exp(-(X.dot(W1) + b1)))
    y_hat = 1 / (1 + np.exp(-(h.dot(W2) + b2)))

    # 计算损失
    loss = -np.mean(y * np.log(y_hat) + (1-y) * np.log(1-y_hat))
    losses.append(loss)

    # 反向传播
    dL_dy_hat = (y_hat - y.reshape(-1,1)) / N

    # 输出层梯度
    dL_dW2 = h.T.dot(dL_dy_hat)
    dL_db2 = np.sum(dL_dy_hat, axis=0, keepdims=True)

    # 隐藏层梯度
    dh = dL_dy_hat.dot(W2.T) * h * (1 - h)
    dL_dW1 = X.T.dot(dh)
    dL_db1 = np.sum(dh, axis=0, keepdims=True)

    # 更新参数
    W1 -= learning_rate * dL_dW1
    b1 -= learning_rate * dL_db1
    W2 -= learning_rate * dL_dW2
    b2 -= learning_rate * dL_db2

    if epoch % 1000 == 0:
        print(f'Epoch {epoch}, Loss: {loss:.4f}')

# 绘制损失曲线
plt.plot(losses)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss')
plt.show()

实验结果分析

  1. 学习率影响:
  2. 学习率过大可能导致震荡甚至发散
  3. 学习率过小导致收敛缓慢
  4. 建议从 0.01-0.1 开始尝试

  5. 梯度消失 / 爆炸问题:

  6. 深层网络中,Sigmoid 的导数最大为 0.25,可能导致梯度逐层衰减
  7. 可以通过 ReLU 等激活函数缓解
  8. 权重初始化也很关键

  9. 激活函数比较:

  10. Sigmoid:输出范围(0,1),适合二分类
  11. Tanh:输出范围(-1,1),中心对称
  12. ReLU:计算简单,缓解梯度消失

避坑指南

  1. 梯度计算错误:
  2. 确保链式法则应用正确
  3. 建议先手推小例子验证

  4. 数值不稳定:

  5. 避免 log(0)等情况
  6. 可以加小常数 eps=1e-8

  7. 初始化问题:

  8. 不要全零初始化
  9. 推荐 Xavier 或 He 初始化

  10. 过拟合:

  11. 增加正则化项
  12. 使用验证集监控

延伸思考

  1. 如何扩展到多分类问题?
  2. 如何处理更复杂的非线性边界?
  3. 如何实现批量归一化(BatchNorm)?
  4. 如何结合动量 (Momentum) 优化训练?

通过这个简单的例子,我们完整实现了 BP 神经网络的反向传播算法。关键在于理解梯度计算的过程,并注意实现细节。希望这篇笔记能帮助你更好地掌握这一核心算法。

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