共计 3619 个字符,预计需要花费 10 分钟才能阅读完成。
神经网络基础回顾
在开始反向传播之前,我们需要明确几个基本概念。BP 神经网络通常由输入层、隐藏层和输出层组成,每层包含若干神经元。每个神经元接收前一层神经元的输出,经过加权求和和激活函数处理后,传递给下一层。

神经网络的训练过程分为两个阶段:
- 前向传播:输入数据从输入层经过隐藏层传递到输出层,得到预测结果。
- 反向传播:根据预测结果与真实值的误差,从输出层向输入层反向传播,调整各层的权重和偏置。
反向传播之所以必要,是因为它提供了一种高效计算梯度的方法,使得神经网络能够通过梯度下降等优化算法进行参数更新。
反向传播数学推导
反向传播的核心是链式法则的应用。我们以一个三层神经网络(输入层、隐藏层、输出层)为例,推导反向传播的过程。
设:
- $x_i$:输入层第 i 个神经元的输入
- $w_{ij}^{(1)}$:输入层到隐藏层的权重
- $b_j^{(1)}$:隐藏层的偏置
- $z_j^{(1)}$:隐藏层的加权输入
- $a_j^{(1)}$:隐藏层的输出(激活值)
- $w_{jk}^{(2)}$:隐藏层到输出层的权重
- $b_k^{(2)}$:输出层的偏置
- $z_k^{(2)}$:输出层的加权输入
- $a_k^{(2)}$:输出层的输出(预测值)
- $y_k$:真实值
- $L$:损失函数(如均方误差)
前向传播公式:
$$z_j^{(1)} = \sum_i w_{ij}^{(1)}x_i + b_j^{(1)}$$
$$a_j^{(1)} = \sigma(z_j^{(1)})$$
$$z_k^{(2)} = \sum_j w_{jk}^{(2)}a_j^{(1)} + b_k^{(2)}$$
$$a_k^{(2)} = \sigma(z_k^{(2)})$$
$$L = \frac{1}{2}\sum_k (y_k – a_k^{(2)})^2$$
反向传播时,我们需要计算损失函数对各参数的梯度。以输出层权重 $w_{jk}^{(2)}$ 为例:
$$\frac{\partial L}{\partial w_{jk}^{(2)}} = \frac{\partial L}{\partial a_k^{(2)}} \cdot \frac{\partial a_k^{(2)}}{\partial z_k^{(2)}} \cdot \frac{\partial z_k^{(2)}}{\partial w_{jk}^{(2)}}$$
计算各项:
$$\frac{\partial L}{\partial a_k^{(2)}} = -(y_k – a_k^{(2)})$$
$$\frac{\partial a_k^{(2)}}{\partial z_k^{(2)}} = \sigma'(z_k^{(2)})$$
$$\frac{\partial z_k^{(2)}}{\partial w_{jk}^{(2)}} = a_j^{(1)}$$
因此:
$$\frac{\partial L}{\partial w_{jk}^{(2)}} = -(y_k – a_k^{(2)}) \cdot \sigma'(z_k^{(2)}) \cdot a_j^{(1)}$$
类似地,我们可以推导出其他参数的梯度。
Python 代码实现
下面是一个使用 NumPy 实现的简单 BP 神经网络:
import numpy as np
class NeuralNetwork:
def __init__(self, input_size, hidden_size, output_size):
# 初始化权重和偏置
self.W1 = np.random.randn(input_size, hidden_size)
self.b1 = np.zeros((1, hidden_size))
self.W2 = np.random.randn(hidden_size, output_size)
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.a1 = self.sigmoid(self.z1)
self.z2 = np.dot(self.a1, self.W2) + self.b2
self.a2 = self.sigmoid(self.z2)
return self.a2
def backward(self, X, y, output, learning_rate):
# 反向传播
error = y - output
d_output = error * self.sigmoid_derivative(output)
error_hidden = d_output.dot(self.W2.T)
d_hidden = error_hidden * self.sigmoid_derivative(self.a1)
# 更新权重和偏置
self.W2 += self.a1.T.dot(d_output) * learning_rate
self.b2 += np.sum(d_output, axis=0, keepdims=True) * learning_rate
self.W1 += X.T.dot(d_hidden) * learning_rate
self.b1 += np.sum(d_hidden, axis=0, keepdims=True) * learning_rate
def train(self, X, y, epochs, learning_rate):
for i in range(epochs):
output = self.forward(X)
self.backward(X, y, output, learning_rate)
if i % 1000 == 0:
loss = np.mean(np.square(y - output))
print(f'Epoch {i}, Loss: {loss}')
# 示例使用
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([[0], [1], [1], [0]])
nn = NeuralNetwork(input_size=2, hidden_size=4, output_size=1)
nn.train(X, y, epochs=10000, learning_rate=0.1)
具体计算例题演示
让我们通过一个具体例子演示前向传播和反向传播的过程。考虑一个简单的神经网络:
- 输入层:2 个神经元(x1, x2)
- 隐藏层:2 个神经元
- 输出层:1 个神经元
初始权重和偏置:
- W1 = [[0.1, 0.2], [0.3, 0.4]]
- b1 = [0.5, 0.5]
- W2 = [[0.5], [0.6]]
- b2 = [0.7]
输入 x = [1, 0.5],真实值 y = 1
前向传播:
- 计算隐藏层输入:
z1 = [10.1 + 0.50.3 + 0.5, 10.2 + 0.50.4 + 0.5] = [0.75, 0.9] - 计算隐藏层输出(使用 sigmoid):
a1 = [σ(0.75), σ(0.9)] ≈ [0.679, 0.711] - 计算输出层输入:
z2 = 0.6790.5 + 0.7110.6 + 0.7 ≈ 1.424 - 计算输出:
a2 = σ(1.424) ≈ 0.806
反向传播:
- 计算输出层误差:
δ2 = (1 – 0.806) * σ'(0.806) ≈ 0.194 * 0.151 ≈ 0.029 - 更新 W2:
ΔW2 = learning_rate * a1.T * δ2 - 计算隐藏层误差:
δ1 = δ2 * W2 * σ'(a1) ≈ [0.0290.50.197, 0.0290.60.205] ≈ [0.0029, 0.0036] - 更新 W1:
ΔW1 = learning_rate * x.T * δ1
常见问题与优化建议
梯度消失 / 爆炸
梯度消失发生在深层网络中,当梯度通过多层传播时变得越来越小。解决方法:
- 使用 ReLU 等激活函数
- 使用 Batch Normalization
- 使用残差连接
梯度爆炸则相反,梯度变得非常大。解决方法:
- 梯度裁剪
- 权重正则化
学习率选择
- 初始学习率通常设为 0.1 或 0.01
- 可以使用学习率衰减策略
- 自适应优化器(如 Adam)可以自动调整学习率
激活函数选择
- ReLU:最常用,计算简单
- LeakyReLU:解决 ReLU 的 ” 死亡 ” 问题
- Sigmoid:输出层用于二分类
- Tanh:隐藏层有时使用
性能优化建议
- 向量化计算:使用 NumPy 等库进行矩阵运算
- GPU 加速:使用 CUDA 或深度学习框架
- 批量训练:使用 mini-batch 而非单个样本
- 并行化:多线程或多进程处理
总结与思考
本文详细介绍了 BP 神经网络的反向传播算法,从数学推导到 Python 实现。通过一个具体例子演示了前向传播和反向传播的计算过程,并讨论了实际应用中的常见问题和优化建议。
思考题:如何将反向传播算法应用到卷积神经网络中?答案在于理解卷积层的梯度计算和误差传播方式。卷积层的反向传播需要计算对卷积核的梯度,这可以通过互相关运算实现。池化层的反向传播则需要将误差分配到前一层相应的位置。
