从零推导bp反向传播算法:新手入门指南与数学实现

1次阅读
没有评论

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

image.webp

神经网络与反向传播基础

要理解反向传播(backpropagation),我们得先从神经网络的前向传播说起。假设我们有一个最简单的三层网络(输入层、隐藏层、输出层),前向传播就是数据从输入层流向输出层的过程。

从零推导 bp 反向传播算法:新手入门指南与数学实现

  1. 前向传播公式 :
  2. 隐藏层输入:$z_h = w_{ih}^T x + b_h$
  3. 隐藏层输出:$a_h = \sigma(z_h)$(σ 表示激活函数)
  4. 输出层输入:$z_o = w_{ho}^T a_h + b_o$
  5. 最终输出:$a_o = \sigma(z_o)$

  6. 损失函数 :
    我们用均方误差(MSE)衡量预测值与真实值的差距:
    $$ L = \frac{1}{2}(y – a_o)^2 $$

反向传播的数学推导

反向传播的核心是链式法则。我们需要计算损失函数对每个权重($w_{ho}$ 和 $w_{ih}$)的偏导数。

输出层权重更新

  1. 计算损失对输出层输入的梯度:
    $$ \frac{\partial L}{\partial z_o} = \frac{\partial L}{\partial a_o} \cdot \frac{\partial a_o}{\partial z_o} = -(y – a_o) \cdot \sigma'(z_o) $$

  2. 由此得到输出层权重梯度:
    $$ \frac{\partial L}{\partial w_{ho}} = \frac{\partial L}{\partial z_o} \cdot a_h $$

隐藏层权重更新

  1. 损失对隐藏层输出的梯度:
    $$ \frac{\partial L}{\partial a_h} = \frac{\partial L}{\partial z_o} \cdot w_{ho} $$

  2. 通过激活函数传递到隐藏层输入:
    $$ \frac{\partial L}{\partial z_h} = \frac{\partial L}{\partial a_h} \cdot \sigma'(z_h) $$

  3. 最终得到输入层权重梯度:
    $$ \frac{\partial L}{\partial w_{ih}} = \frac{\partial L}{\partial z_h} \cdot x $$

Python 实现代码

import numpy as np

# Sigmoid 激活函数及其导数
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

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

# 网络初始化
input_size = 3
hidden_size = 4
output_size = 1
learning_rate = 0.1

# 随机初始化权重(-0.5 到 0.5 之间)weights_ih = np.random.rand(input_size, hidden_size) - 0.5
weights_ho = np.random.rand(hidden_size, output_size) - 0.5

# 前向传播
def forward(x):
    hidden_input = np.dot(x, weights_ih)
    hidden_output = sigmoid(hidden_input)
    output_input = np.dot(hidden_output, weights_ho)
    output = sigmoid(output_input)
    return hidden_output, output

# 反向传播
def train(x, y):
    hidden_output, output = forward(x)

    # 输出层误差
    output_error = y - output
    output_delta = output_error * sigmoid_derivative(output)

    # 隐藏层误差
    hidden_error = output_delta.dot(weights_ho.T)
    hidden_delta = hidden_error * sigmoid_derivative(hidden_output)

    # 更新权重
    weights_ho += hidden_output.T.dot(output_delta) * learning_rate
    weights_ih += x.T.dot(hidden_delta) * learning_rate

避坑指南

  1. 梯度消失问题 :
  2. 当使用 sigmoid 激活函数时,其导数最大值为 0.25,多层连乘会导致梯度指数级减小
  3. 解决方案:使用 ReLU 等激活函数,或采用残差连接

  4. 学习率选择 :

  5. 太大:震荡无法收敛
  6. 太小:训练速度过慢
  7. 建议:从 0.01 开始尝试,配合学习率衰减策略

  8. 权重初始化 :

  9. 全零初始化会导致神经元对称性问题
  10. 推荐:Xavier 初始化(权重范围±1/√n)

思考题

  1. 如何修改代码实现 ReLU 激活函数?需要注意什么?
  2. 批量训练(batch)与在线训练(online)在实现上有哪些差异?
  3. 相比梯度下降,Adam 等二阶优化算法有哪些优势?

希望这篇指南能帮你打通反向传播的任督二脉。记住,推导时多画计算图,写代码时多 print 中间结果,实践出真知!

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