共计 2580 个字符,预计需要花费 7 分钟才能阅读完成。
引言
异或(XOR)问题是神经网络学习中的经典案例,它简单却足够说明神经网络的强大能力。本文将带你从零开始,理解为什么单层感知机无法解决异或问题,以及如何用 BP 神经网络解决它。我们会用 Python 一步步实现一个 2 -2- 1 结构的 BP 网络,并详细解释每个环节的原理。

1. 异或问题为什么难以解决
异或运算的真值表如下:
| A | B | A XOR B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
从数学上看,异或问题就是寻找一个函数 $f$ 使得:
$$
\begin{cases}
f(0,0) = 0 \
f(0,1) = 1 \
f(1,0) = 1 \
f(1,1) = 0
\end{cases}
$$
单层感知机本质上是一个线性分类器,只能解决线性可分的问题。而异或问题是典型的非线性问题,在二维平面上,我们无法用一条直线将 (0,1) 和(1,0)与 (0,0) 和(1,1)分开。这就是为什么单层感知机无法解决异或问题。
2. BP 神经网络简介
BP(Back Propagation)神经网络是一种多层前馈神经网络,通过反向传播算法来训练网络。它通常包含:
- 输入层:接收外部输入
- 隐含层:进行非线性变换
- 输出层:输出最终结果
对于异或问题,我们采用 2 -2- 1 结构:
– 输入层:2 个神经元(对应两个输入)
– 隐含层:2 个神经元
– 输出层:1 个神经元
3. Python 实现
下面是用 Python 实现 BP 神经网络解决异或问题的完整代码:
import numpy as np
import matplotlib.pyplot as plt
# Sigmoid 激活函数及其导数
def sigmoid(x):
return 1/(1+np.exp(-x))
def sigmoid_derivative(x):
return x*(1-x)
# 输入数据
X = np.array([[0,0],[0,1],[1,0],[1,1]])
y = np.array([[0],[1],[1],[0]])
# 网络参数
input_neurons = 2
hidden_neurons = 2
output_neurons = 1
# 权重初始化
np.random.seed(42)
weights_input_hidden = np.random.uniform(size=(input_neurons,hidden_neurons))
weights_hidden_output = np.random.uniform(size=(hidden_neurons,output_neurons))
# 训练参数
learning_rate = 0.5
epochs = 10000
loss_history = []
# 训练过程
for epoch in range(epochs):
# 前向传播
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
loss = np.mean(np.square(error))
loss_history.append(loss)
# 反向传播
d_predicted_output = error * sigmoid_derivative(predicted_output)
error_hidden_layer = d_predicted_output.dot(weights_hidden_output.T)
d_hidden_layer = error_hidden_layer * sigmoid_derivative(hidden_layer_output)
# 更新权重
weights_hidden_output += hidden_layer_output.T.dot(d_predicted_output) * learning_rate
weights_input_hidden += X.T.dot(d_hidden_layer) * learning_rate
# 绘制损失曲线
plt.plot(loss_history)
plt.title('Training Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.show()
# 测试
print("Final predictions:")
for i in range(len(X)):
hidden_layer_input = np.dot(X[i], 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)
print(f"Input: {X[i]} Output: {predicted_output}")
4. 关键参数说明
-
学习率(learning_rate): 控制权重更新的步长。太大会导致震荡,太小会使训练缓慢。0.1-0.5 是常用范围。
-
迭代次数(epochs): 训练轮数。需要足够大才能收敛,但太大可能导致过拟合。
-
权重初始化 : 使用随机初始化打破对称性,通常范围在[-1,1] 或[0,1]之间。
5. 避坑指南
-
梯度消失问题: 当网络层数较多时,梯度可能在反向传播过程中变得极小。解决方法包括使用 ReLU 等激活函数,或采用残差连接。
-
学习率设置技巧: 可以尝试学习率衰减策略,随着训练进行逐渐减小学习率。
-
权重初始化方法: Xavier 初始化或 He 初始化往往比纯随机初始化效果更好。
6. 扩展思考
- 如何修改网络结构来处理三输入异或问题?
- 除了 Sigmoid,还可以尝试哪些激活函数?
- 如何将这个网络扩展到解决其他逻辑运算问题?
结语
通过这个简单的例子,我们看到了 BP 神经网络强大的非线性建模能力。虽然现代深度学习框架已经帮我们封装好了这些底层细节,但理解这些基本原理对于调参和解决问题仍然至关重要。希望这篇文章能帮助你入门神经网络的世界!
