BP神经网络反向传播原理详解:从数学推导到Python实现

1次阅读
没有评论

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

image.webp

从单层感知机到多层神经网络

单层感知机(Perceptron)是最简单的神经网络结构,但它只能解决线性可分的问题。这意味着对于像异或(XOR)这样的非线性问题,单层感知机就无能为力了。多层神经网络(如 BP 神经网络)通过引入隐藏层和非线性激活函数,可以解决复杂的非线性分类和回归任务。

BP 神经网络反向传播原理详解:从数学推导到 Python 实现

反向传播的数学推导

反向传播(Backpropagation)是训练神经网络的核心算法,其核心思想是通过链式法则(Chain Rule)计算损失函数对权重的梯度,从而更新权重。以下是详细的数学推导过程:

  1. 前向传播 :输入数据通过网络的每一层,最终得到输出。假设我们有一个简单的两层网络(输入层、隐藏层、输出层),隐藏层和输出层的激活函数为 Sigmoid 函数。

  2. 损失函数 :使用均方误差(MSE)作为损失函数,定义为:
    [E = \frac{1}{2} \sum_{k} (y_k – t_k)^2 ]
    其中,(y_k) 是输出层的第 k 个神经元的输出,(t_k) 是对应的目标值。

  3. 输出层的梯度计算
    输出层的权重梯度为:
    [\frac{\partial E}{\partial w_{jk}} = \frac{\partial E}{\partial y_k} \cdot \frac{\partial y_k}{\partial z_k} \cdot \frac{\partial z_k}{\partial w_{jk}} ]
    其中,(z_k) 是输出层的加权输入。具体展开为:
    [\frac{\partial E}{\partial y_k} = y_k – t_k ]
    [\frac{\partial y_k}{\partial z_k} = y_k (1 – y_k) \quad \text{(Sigmoid 函数的导数)} ]
    [\frac{\partial z_k}{\partial w_{jk}} = h_j \quad \text{(隐藏层的输出)} ]
    因此,最终的梯度为:
    [\frac{\partial E}{\partial w_{jk}} = (y_k – t_k) \cdot y_k (1 – y_k) \cdot h_j ]

  4. 隐藏层的梯度计算
    类似地,隐藏层的权重梯度为:
    [\frac{\partial E}{\partial w_{ij}} = \frac{\partial E}{\partial h_j} \cdot \frac{\partial h_j}{\partial z_j} \cdot \frac{\partial z_j}{\partial w_{ij}} ]
    其中,(\frac{\partial E}{\partial h_j} ) 需要通过输出层的梯度反向传播得到:
    [\frac{\partial E}{\partial h_j} = \sum_{k} \frac{\partial E}{\partial z_k} \cdot \frac{\partial z_k}{\partial h_j} = \sum_{k} (y_k – t_k) \cdot y_k (1 – y_k) \cdot w_{jk} ]
    其余部分与输出层类似。

Python 实现

以下是一个用 NumPy 实现的简单 BP 神经网络,包含前向传播和反向传播的完整流程:

import numpy as np

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

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

# 初始化网络参数
input_size = 2
hidden_size = 3
output_size = 1
learning_rate = 0.1

# 随机初始化权重
weights_input_hidden = np.random.rand(input_size, hidden_size)
weights_hidden_output = np.random.rand(hidden_size, output_size)

# 训练数据
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([[0], [1], [1], [0]])

# 训练循环
for epoch in range(10000):
    # 前向传播
    hidden_layer_input = np.dot(X, weights_input_hidden)
    hidden_layer_output = sigmoid(hidden_layer_input)

    output_layer_input = np.dot(hidden_layer_output, weights_hidden_output)
    predicted_output = sigmoid(output_layer_input)

    # 计算损失
    error = y - predicted_output

    # 反向传播
    d_output = error * sigmoid_derivative(predicted_output)
    error_hidden = d_output.dot(weights_hidden_output.T)
    d_hidden = error_hidden * sigmoid_derivative(hidden_layer_output)

    # 更新权重
    weights_hidden_output += hidden_layer_output.T.dot(d_output) * learning_rate
    weights_input_hidden += X.T.dot(d_hidden) * learning_rate

# 测试
hidden_layer_output = sigmoid(np.dot(X, weights_input_hidden))
predicted_output = sigmoid(np.dot(hidden_layer_output, weights_hidden_output))
print(predicted_output)

避坑指南

  1. 梯度消失问题
    当使用 Sigmoid 激活函数时,其导数在输入值较大或较小时会趋近于 0,导致梯度在反向传播过程中逐渐消失。这使得深层网络的训练变得困难。解决方案包括使用 ReLU 激活函数或 Batch Normalization。

  2. 学习率设置
    学习率过大可能导致震荡甚至无法收敛,学习率过小则训练速度过慢。建议开始时使用较小的学习率(如 0.01),然后根据训练效果动态调整。

  3. 权重初始化
    权重初始化过大会导致梯度爆炸,过小则可能导致梯度消失。常用的初始化方法包括 Xavier 初始化和 He 初始化。

思考题

  1. 如何用 ReLU 激活函数修改本代码?
    ReLU 函数的定义为 (f(x) = \max(0, x) ),其导数为:
    [f'(x) = \begin{cases} 1 & \text{if} x > 0 \ 0 & \text{otherwise} \end{cases} ]
    只需替换代码中的 Sigmoid 函数及其导数即可。

  2. 批量训练与在线训练的梯度计算差异:
    批量训练(Batch Training)是在所有训练样本上计算梯度后更新权重,而在线训练(Online Training)是每输入一个样本就更新一次权重。批量训练的梯度更稳定,但在线训练可以更快地适应数据的变化。

总结

本文详细介绍了 BP 神经网络的反向传播原理,从数学推导到 Python 实现,帮助初学者理解权重更新的核心机制。通过避坑指南和思考题,读者可以进一步探索神经网络的优化和应用。希望这篇笔记对你有所帮助!

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