共计 2659 个字符,预计需要花费 7 分钟才能阅读完成。
理解 BP 算法的数学本质
反向传播(Backpropagation)是神经网络训练的核心算法,其本质是复合函数的链式求导法则。我们先从一个简单的 3 层神经网络(输入层、隐藏层、输出层)入手推导。

设神经网络有以下参数:
– 输入层到隐藏层权重矩阵 (W^{(1)})
– 隐藏层到输出层权重矩阵 (W^{(2)})
– 隐藏层激活函数 (f(\cdot))
– 输出层激活函数 (g(\cdot))
– 损失函数 (L(y, \hat{y}))
前向传播过程
- 隐藏层输入:
[z^{(1)} = xW^{(1)} ] - 隐藏层输出:
[a^{(1)} = f(z^{(1)}) ] - 输出层输入:
[z^{(2)} = a^{(1)}W^{(2)} ] - 最终输出:
[\hat{y} = g(z^{(2)}) ]
反向传播推导关键步骤
我们需要计算损失函数对各个权重的偏导数。以输出层权重 (W^{(2)}) 为例:
- 计算损失函数对输出的偏导:
[\frac{\partial L}{\partial \hat{y}} ] - 计算输出对 (z^{(2)}) 的偏导(取决于输出层激活函数):
[\frac{\partial \hat{y}}{\partial z^{(2)}} = g'(z^{(2)}) ] - 计算 (z^{(2)}) 对 (W^{(2)}) 的偏导:
[\frac{\partial z^{(2)}}{\partial W^{(2)}} = a^{(1)T} ] - 组合得到权重梯度:
[\frac{\partial L}{\partial W^{(2)}} = \frac{\partial L}{\partial \hat{y}} \cdot \frac{\partial \hat{y}}{\partial z^{(2)}} \cdot \frac{\partial z^{(2)}}{\partial W^{(2)}} ]
同理可以推导隐藏层权重 (W^{(1)}) 的梯度。这个链式求导过程会从输出层逐层反向传播到输入层。
Python 实现 BP 神经网络
下面我们实现一个简单的全连接神经网络类:
import numpy as np
class SimpleNN:
def __init__(self, input_size, hidden_size, output_size):
# 初始化权重(使用 Xavier 初始化)self.W1 = np.random.randn(input_size, hidden_size) * np.sqrt(1/input_size)
self.W2 = np.random.randn(hidden_size, output_size) * np.sqrt(1/hidden_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.a1 = self.sigmoid(self.z1)
self.z2 = np.dot(self.a1, self.W2)
y_hat = self.sigmoid(self.z2)
return y_hat
def backward(self, X, y, y_hat, learning_rate):
# 输出层误差
error = y_hat - y
d_output = error * self.sigmoid_derivative(y_hat)
# 隐藏层误差
error_hidden = np.dot(d_output, self.W2.T)
d_hidden = error_hidden * self.sigmoid_derivative(self.a1)
# 更新权重
self.W2 -= learning_rate * np.dot(self.a1.T, d_output)
self.W1 -= learning_rate * np.dot(X.T, d_hidden)
def train(self, X, y, epochs, learning_rate):
for i in range(epochs):
y_hat = self.forward(X)
self.backward(X, y, y_hat, learning_rate)
# 每 100 次迭代打印损失
if i % 100 == 0:
loss = np.mean(np.square(y - y_hat))
print(f'Epoch {i}, Loss: {loss:.4f}')
代码关键点说明
- 前向传播 :
- 计算每一层的加权输入和激活输出
-
使用 sigmoid 作为激活函数
-
反向传播 :
- 从输出层开始计算误差
- 通过链式法则逐层计算各权重的梯度
-
使用梯度下降更新权重
-
训练过程 :
- 多次迭代前向传播和反向传播
- 监控损失函数的变化
验证与自动微分对比
为了验证我们的手动实现是否正确,可以使用 PyTorch 的自动微分进行对比验证:
import torch
import torch.nn as nn
# 使用相同参数初始化 PyTorch 模型
torch_model = nn.Sequential(nn.Linear(input_size, hidden_size),
nn.Sigmoid(),
nn.Linear(hidden_size, output_size),
nn.Sigmoid())
# 复制权重
with torch.no_grad():
torch_model[0].weight.copy_(torch.FloatTensor(self.W1.T))
torch_model[2].weight.copy_(torch.FloatTensor(self.W2.T))
# 使用相同输入计算梯度
...
通过比较手动计算梯度和自动微分结果,可以验证我们推导的正确性。
常见问题与优化建议
- 梯度爆炸与消失 :
- 使用合适的权重初始化(如 Xavier 初始化)
- 添加梯度裁剪(gradient clipping)
-
考虑使用 ReLU 等缓解梯度消失的激活函数
-
学习率选择 :
- 初始学习率建议设置较小值(如 0.01)
-
实现学习率衰减策略
-
批量训练优化 :
- 计算小批量数据的平均梯度
- 使用动量(momentum)加速训练
延伸思考方向
- 向量化优化 :
- 当前实现已经向量化处理整个批量的数据
-
可以进一步优化矩阵运算顺序减少临时变量
-
高级优化器 :
- 实现 Adam 等自适应学习率优化器
-
比较不同优化器在相同网络上的表现
-
正则化技术 :
- 添加 L2 权重衰减(正则化)
- 实现 dropout 层防止过拟合
通过这次手写推导和代码实现,我们对 BP 算法有了更深入的理解。建议读者可以尝试扩展这个基础实现,比如添加更多隐藏层、实现不同的激活函数,或者尝试在其他数据集上训练。
正文完
