共计 2960 个字符,预计需要花费 8 分钟才能阅读完成。
从单层感知机到多层神经网络
单层感知机(Perceptron)是最简单的神经网络结构,但它只能解决线性可分的问题。这意味着对于像异或(XOR)这样的非线性问题,单层感知机就无能为力了。多层神经网络(如 BP 神经网络)通过引入隐藏层和非线性激活函数,可以解决复杂的非线性分类和回归任务。

反向传播的数学推导
反向传播(Backpropagation)是训练神经网络的核心算法,其核心思想是通过链式法则(Chain Rule)计算损失函数对权重的梯度,从而更新权重。以下是详细的数学推导过程:
-
前向传播 :输入数据通过网络的每一层,最终得到输出。假设我们有一个简单的两层网络(输入层、隐藏层、输出层),隐藏层和输出层的激活函数为 Sigmoid 函数。
-
损失函数 :使用均方误差(MSE)作为损失函数,定义为:
[E = \frac{1}{2} \sum_{k} (y_k – t_k)^2 ]
其中,(y_k) 是输出层的第 k 个神经元的输出,(t_k) 是对应的目标值。 -
输出层的梯度计算 :
输出层的权重梯度为:
[\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 ] -
隐藏层的梯度计算 :
类似地,隐藏层的权重梯度为:
[\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)
避坑指南
-
梯度消失问题 :
当使用 Sigmoid 激活函数时,其导数在输入值较大或较小时会趋近于 0,导致梯度在反向传播过程中逐渐消失。这使得深层网络的训练变得困难。解决方案包括使用 ReLU 激活函数或 Batch Normalization。 -
学习率设置 :
学习率过大可能导致震荡甚至无法收敛,学习率过小则训练速度过慢。建议开始时使用较小的学习率(如 0.01),然后根据训练效果动态调整。 -
权重初始化 :
权重初始化过大会导致梯度爆炸,过小则可能导致梯度消失。常用的初始化方法包括 Xavier 初始化和 He 初始化。
思考题
-
如何用 ReLU 激活函数修改本代码?
ReLU 函数的定义为 (f(x) = \max(0, x) ),其导数为:
[f'(x) = \begin{cases} 1 & \text{if} x > 0 \ 0 & \text{otherwise} \end{cases} ]
只需替换代码中的 Sigmoid 函数及其导数即可。 -
批量训练与在线训练的梯度计算差异:
批量训练(Batch Training)是在所有训练样本上计算梯度后更新权重,而在线训练(Online Training)是每输入一个样本就更新一次权重。批量训练的梯度更稳定,但在线训练可以更快地适应数据的变化。
总结
本文详细介绍了 BP 神经网络的反向传播原理,从数学推导到 Python 实现,帮助初学者理解权重更新的核心机制。通过避坑指南和思考题,读者可以进一步探索神经网络的优化和应用。希望这篇笔记对你有所帮助!
