BP神经网络反向传播计算例题详解:从数学推导到Python实现

1次阅读
没有评论

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

image.webp

前言

反向传播算法是训练神经网络的核心,但很多初学者在面对公式推导时容易感到困惑。本文将通过一个具体的数值例题,手把手带你完成前向传播和反向传播的全过程计算,最后用 Python 代码实现。我们将采用 3 层网络结构(输入层、隐藏层、输出层),使用 Sigmoid 激活函数,并假设损失函数为均方误差(MSE)。

BP 神经网络反向传播计算例题详解:从数学推导到 Python 实现

网络结构与例题数据

假设我们有一个简单的 3 层神经网络:

  • 输入层:2 个神经元(x1, x2)
  • 隐藏层:2 个神经元(h1, h2)
  • 输出层:1 个神经元(y)

初始权重和偏置随机初始化如下:

w1 = [[0.1, 0.2], [0.3, 0.4]]  # 输入层到隐藏层的权重
b1 = [0.5, 0.6]                # 隐藏层的偏置
w2 = [[0.7], [0.8]]            # 隐藏层到输出层的权重
b2 = [0.9]                     # 输出层的偏置 

输入数据 x = [0.1, 0.2],真实标签 y_true = 0.5。

前向传播计算

  1. 输入层到隐藏层的计算:
z1 = x * w1 + b1
h = sigmoid(z1)

具体数值计算:

z1_1 = 0.1*0.1 + 0.2*0.3 + 0.5 = 0.01 + 0.06 + 0.5 = 0.57
z1_2 = 0.1*0.2 + 0.2*0.4 + 0.6 = 0.02 + 0.08 + 0.6 = 0.70

h1 = sigmoid(0.57) ≈ 0.638
h2 = sigmoid(0.70) ≈ 0.668
  1. 隐藏层到输出层的计算:
z2 = h * w2 + b2
y_pred = sigmoid(z2)

具体数值计算:

z2 = 0.638*0.7 + 0.668*0.8 + 0.9 ≈ 0.4466 + 0.5344 + 0.9 ≈ 1.881
y_pred = sigmoid(1.881) ≈ 0.868

反向传播计算

  1. 计算输出层误差:
δ_output = (y_pred - y_true) * sigmoid_derivative(z2)

其中 sigmoid_derivative(z) = sigmoid(z)*(1-sigmoid(z))

数值计算:

δ_output = (0.868 - 0.5) * (0.868*(1-0.868)) ≈ 0.368 * 0.114 ≈ 0.042
  1. 计算隐藏层误差:
δ_hidden = (w2 * δ_output) * sigmoid_derivative(z1)

数值计算:

δ_hidden1 = (0.7 * 0.042) * (0.638*(1-0.638)) ≈ 0.0294 * 0.231 ≈ 0.0068
δ_hidden2 = (0.8 * 0.042) * (0.668*(1-0.668)) ≈ 0.0336 * 0.222 ≈ 0.0075
  1. 更新权重和偏置:

学习率 η 设为 0.5

  • 更新 w2 和 b2:

    Δw2 = -η * δ_output * h
    Δb2 = -η * δ_output

    数值计算:

    Δw2_1 = -0.5 * 0.042 * 0.638 ≈ -0.0134
    Δw2_2 = -0.5 * 0.042 * 0.668 ≈ -0.0140
    Δb2 = -0.5 * 0.042 ≈ -0.021
    
    w2_1_new = 0.7 - 0.0134 ≈ 0.6866
    w2_2_new = 0.8 - 0.0140 ≈ 0.7860
    b2_new = 0.9 - 0.021 ≈ 0.879
  • 更新 w1 和 b1:

    Δw1 = -η * δ_hidden * x
    Δb1 = -η * δ_hidden

    数值计算:

    Δw1_11 = -0.5 * 0.0068 * 0.1 ≈ -0.00034
    Δw1_12 = -0.5 * 0.0068 * 0.2 ≈ -0.00068
    Δw1_21 = -0.5 * 0.0075 * 0.1 ≈ -0.000375
    Δw1_22 = -0.5 * 0.0075 * 0.2 ≈ -0.00075
    Δb1_1 = -0.5 * 0.0068 ≈ -0.0034
    Δb1_2 = -0.5 * 0.0075 ≈ -0.00375
    
    w1_11_new = 0.1 - 0.00034 ≈ 0.09966
    w1_12_new = 0.2 - 0.00068 ≈ 0.19932
    w1_21_new = 0.3 - 0.000375 ≈ 0.299625
    w1_22_new = 0.4 - 0.00075 ≈ 0.39925
    b1_1_new = 0.5 - 0.0034 ≈ 0.4966
    b1_2_new = 0.6 - 0.00375 ≈ 0.59625

Python 实现

import numpy as np

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def sigmoid_derivative(x):
    return x * (1 - x)

# 初始化参数
w1 = np.array([[0.1, 0.2], [0.3, 0.4]])
b1 = np.array([0.5, 0.6])
w2 = np.array([[0.7], [0.8]])
b2 = np.array([0.9])

# 输入数据
x = np.array([0.1, 0.2])
y_true = 0.5

# 前向传播
z1 = np.dot(x, w1) + b1
h = sigmoid(z1)
z2 = np.dot(h, w2) + b2
y_pred = sigmoid(z2)

# 计算损失
loss = 0.5 * (y_pred - y_true) ** 2

# 反向传播
# 输出层误差
delta_output = (y_pred - y_true) * sigmoid_derivative(y_pred)

# 隐藏层误差
delta_hidden = np.dot(w2, delta_output) * sigmoid_derivative(h)

# 更新参数
learning_rate = 0.5

# 更新 w2 和 b2
w2 -= learning_rate * np.dot(h.reshape(-1,1), delta_output.reshape(1,-1))
b2 -= learning_rate * delta_output

# 更新 w1 和 b1
w1 -= learning_rate * np.dot(x.reshape(-1,1), delta_hidden.reshape(1,-1))
b1 -= learning_rate * delta_hidden

print(f"预测值: {y_pred[0]:.4f}")
print(f"更新后的 w1:\n{w1}")
print(f"更新后的 b1: {b1}")
print(f"更新后的 w2:\n{w2}")
print(f"更新后的 b2: {b2}")

常见问题

  1. 梯度消失问题的成因:
  2. 当使用 sigmoid 或 tanh 激活函数时,它们的导数在输入值较大或较小时会趋近于 0
  3. 在深层网络中,这些小的梯度会通过链式法则连乘,导致前面层的梯度变得极小
  4. 解决方案包括使用 ReLU 激活函数、残差连接或梯度裁剪

  5. 学习率对收敛的影响:

  6. 学习率过大可能导致震荡甚至发散
  7. 学习率过小会导致收敛速度过慢
  8. 实践中可以使用学习率衰减或自适应学习率算法(如 Adam)

  9. 激活函数选择的考量:

  10. sigmoid 适合二分类输出层,但容易出现梯度消失
  11. ReLU 计算简单且能缓解梯度消失,但可能有 ” 死亡神经元 ” 问题
  12. tanh 输出范围 (-1,1),适合需要负输出的场景
  13. 输出层激活函数选择取决于任务类型(如 softmax 用于多分类)

延伸思考

  1. 如何验证反向传播实现的正确性?
  2. 可以使用数值梯度检验(finite difference method)
  3. 对每个参数加入微小扰动,观察损失函数变化
  4. 比较数值梯度与反向传播计算的梯度是否接近

  5. 批量训练与在线训练的差异比较:

  6. 批量训练使用全部数据计算梯度,更新更准确但计算量大
  7. 在线训练(随机梯度下降)每次用一个样本,更新快但波动大
  8. 小批量梯度下降是折中方案,兼顾稳定性和效率

总结

通过这个具体的计算示例,我们完整走了一遍 BP 神经网络的前向传播和反向传播过程。关键点包括:

  1. 前向传播时按层计算激活值
  2. 反向传播时从输出层开始,逐层计算误差并传播
  3. 参数更新时要使用学习率控制步长
  4. 激活函数的选择和实现细节会影响训练效果

建议读者尝试修改网络结构、激活函数或学习率,观察对训练效果的影响,这能帮助更深入地理解反向传播算法。

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