共计 2388 个字符,预计需要花费 6 分钟才能阅读完成。
引言
BP(Backpropagation)神经网络是深度学习的基础,其核心在于通过反向传播算法调整网络权重。本文将从数学角度详细推导 BP 算法的公式,并通过 Python 代码实现一个完整的 BP 神经网络。本文假设读者已经了解神经网络的基本概念和单层感知机的工作原理。

1. 单层感知机的梯度计算
单层感知机的输出可表示为:
$$
y = \sigma(w^T x + b)
$$
其中,$\sigma$ 是激活函数,$w$ 是权重向量,$x$ 是输入向量,$b$ 是偏置。
损失函数(以均方误差为例):
$$
L = \frac{1}{2}(y – t)^2
$$
其中,$t$ 是目标值。
通过链式法则,权重 $w$ 的梯度为:
$$
\frac{\partial L}{\partial w} = \frac{\partial L}{\partial y} \frac{\partial y}{\partial (w^T x + b)} \frac{\partial (w^T x + b)}{\partial w} = (y – t) \sigma'(w^T x + b) x
$$
2. 多层网络的反向传播
对于多层神经网络,我们需要从输出层逐层反向传播误差。以两层网络为例:
前向传播
- 输入层到隐藏层:
$$
z_1 = W_1 x + b_1 \
h = \sigma(z_1)
$$
- 隐藏层到输出层:
$$
z_2 = W_2 h + b_2 \
y = \sigma(z_2)
$$
反向传播
- 输出层误差:
$$
\delta_2 = \frac{\partial L}{\partial z_2} = (y – t) \sigma'(z_2)
$$
- 隐藏层误差:
$$
\delta_1 = \frac{\partial L}{\partial z_1} = (W_2^T \delta_2) \odot \sigma'(z_1)
$$
- 权重更新:
$$
\frac{\partial L}{\partial W_2} = \delta_2 h^T \
\frac{\partial L}{\partial W_1} = \delta_1 x^T
$$
3. Python 实现
以下是 BP 神经网络的完整 Python 实现(使用 NumPy):
import numpy as np
class NeuralNetwork:
def __init__(self, input_size, hidden_size, output_size):
# 初始化权重
self.W1 = np.random.randn(input_size, hidden_size) * 0.01
self.b1 = np.zeros((1, hidden_size))
self.W2 = np.random.randn(hidden_size, output_size) * 0.01
self.b2 = np.zeros((1, output_size))
def sigmoid(self, x):
return 1 / (1 + np.exp(-x))
def sigmoid_derivative(self, x):
return x * (1 - x)
def forward(self, x):
# 前向传播
self.z1 = np.dot(x, self.W1) + self.b1
self.h = self.sigmoid(self.z1)
self.z2 = np.dot(self.h, self.W2) + self.b2
self.y = self.sigmoid(self.z2)
return self.y
def backward(self, x, t, learning_rate=0.01):
# 反向传播
m = x.shape[0] # 样本数量
# 输出层误差
delta2 = (self.y - t) * self.sigmoid_derivative(self.y)
dW2 = np.dot(self.h.T, delta2) / m
db2 = np.sum(delta2, axis=0, keepdims=True) / m
# 隐藏层误差
delta1 = np.dot(delta2, self.W2.T) * self.sigmoid_derivative(self.h)
dW1 = np.dot(x.T, delta1) / m
db1 = np.sum(delta1, axis=0, keepdims=True) / m
# 更新权重
self.W2 -= learning_rate * dW2
self.b2 -= learning_rate * db2
self.W1 -= learning_rate * dW1
self.b1 -= learning_rate * db1
def train(self, x, t, epochs=1000):
for epoch in range(epochs):
y = self.forward(x)
self.backward(x, t)
# 每 100 次迭代打印损失
if epoch % 100 == 0:
loss = np.mean(0.5 * (y - t) ** 2)
print(f'Epoch {epoch}, Loss: {loss:.4f}')
4. 避坑指南
梯度消失 / 爆炸问题
- 梯度消失 :当使用 sigmoid 或 tanh 激活函数时,梯度可能会变得非常小,导致权重更新缓慢。解决方法:
- 使用 ReLU 等激活函数
- 使用批归一化(Batch Normalization)
-
调整初始化权重(如 Xavier 初始化)
-
梯度爆炸 :梯度值变得过大,导致权重更新不稳定。解决方法:
- 梯度裁剪(Gradient Clipping)
- 使用权重正则化(L1/L2)
5. 思考题
- 激活函数的选择如何影响梯度传播?比较 sigmoid、tanh 和 ReLU 在反向传播中的表现。
- 批量大小(batch size)与学习率(learning rate)之间有什么关系?如何根据批量大小调整学习率?
- 除了均方误差,还有哪些损失函数适合分类问题?它们在反向传播中的梯度计算有何不同?
结语
本文详细推导了 BP 神经网络的数学公式,并提供了完整的 Python 实现。理解这些基础原理对于深入学习更复杂的神经网络模型至关重要。建议读者动手实现代码,并通过调整参数观察网络性能的变化。
