反向传播算法详解
反向传播(Backpropagation)是训练深度神经网络的核心算法,是链式法则在计算图中的高效应用。1
1. 概述
1.1 问题定义
给定:
- 神经网络结构(层数、每层神经元数)
- 损失函数
- 训练数据集
目标:计算 对所有权重
1.2 核心思想
反向传播通过两次计算图遍历实现高效梯度计算:
- 前向传播:从输入到输出,计算预测值和损失
- 反向传播:从输出到输入,利用链式法则计算梯度
时间复杂度:,而非暴力差分的
2. 数学基础
2.1 链式法则
一元链式法则:
多元链式法则:
2.2 梯度定义
对于标量函数 ,梯度定义为:
对于向量函数 ,Jacobian矩阵:
3. 神经网络前向传播
3.1 单层计算
考虑第 层:
其中:
- 是权重矩阵
- 是偏置向量
- 是激活输出
- 是逐元素激活函数
3.2 损失函数
均方误差(MSE):
交叉熵损失(多分类):
4. 反向传播四方程
令 表示第 层第 个神经元的误差。
4.1 输出层误差(方程1)
向量形式:
其中 是Hadamard积(逐元素乘积)。
MSE损失:
交叉熵 + Softmax(组合使用):
(此时 恰好约掉)
4.2 隐藏层误差传递(方程2)
已知第 层误差,计算第 层误差:
向量形式:
4.3 权重梯度(方程3)
矩阵形式:
4.4 偏置梯度(方程4)
向量形式:
5. 具体示例
5.1 两层网络推导
考虑简单网络:
- 输入
- 隐藏层:,
- 输出层:,
- 损失:
Step 1:前向传播
Step 2:计算输出层误差
Step 3:隐藏层误差反向传播
Step 4:计算梯度
对 :
对 :
5.2 矩阵形式完整推导
import numpy as np
def backpropagation_example(X, Y, W1, b1, W2, b2, lr=0.01):
"""
简化的两层神经网络反向传播示例
"""
m = X.shape[1] # 样本数
# ============ 前向传播 ============
# 隐藏层
Z1 = W1 @ X + b1
A1 = np.tanh(Z1) # 激活函数
# 输出层
Z2 = W2 @ A1 + b2
A2 = 1 / (1 + np.exp(-Z2)) # Sigmoid
# 损失 (MSE)
loss = np.sum((A2 - Y) ** 2) / m
# ============ 反向传播 ============
# 输出层误差 (δ²)
dZ2 = (A2 - Y) * A2 * (1 - A2) # dL/dZ²
# 权重梯度
dW2 = (1/m) * dZ2 @ A1.T
db2 = (1/m) * np.sum(dZ2, axis=1, keepdims=True)
# 隐藏层误差 (δ¹)
dZ1 = (W2.T @ dZ2) * (1 - A1**2) # tanh的导数
# 权重梯度
dW1 = (1/m) * dZ1 @ X.T
db1 = (1/m) * np.sum(dZ1, axis=1, keepdims=True)
# ============ 梯度下降 ============
W1 -= lr * dW1
b1 -= lr * db1
W2 -= lr * dW2
b2 -= lr * db2
return loss6. 常见激活函数导数
| 激活函数 | 函数 | 导数 |
|---|---|---|
| Sigmoid | ||
| Tanh | ||
| ReLU | ||
| Leaky ReLU | ||
| Softmax |
7. 向量化实现
7.1 Mini-batch梯度计算
实际应用中,梯度按mini-batch计算:
矩阵形式:
# 误差: (n_l, batch_size)
# 激活: (n_{l-1}, batch_size)
# 梯度: (n_l, n_{l-1})
dW = delta @ a_prev.T / batch_size7.2 PyTorch自动微分
现代框架使用计算图和自动微分:
import torch
import torch.nn as nn
class SimpleNet(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super().__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.fc2 = nn.Linear(hidden_size, output_size)
self.relu = nn.ReLU()
def forward(self, x):
x = self.relu(self.fc1(x))
x = self.fc2(x)
return x
# 使用示例
model = SimpleNet(784, 256, 10)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# 前向传播
x = torch.randn(32, 784) # batch_size=32
y = torch.randint(0, 10, (32,))
output = model(x)
loss = criterion(output, y)
# 反向传播(自动计算梯度)
loss.backward()
# 查看梯度
print(model.fc1.weight.grad.shape) # torch.Size([256, 784])8. 梯度检查(Gradient Checking)
数值梯度近似作为验证:
相对误差:
检查标准:
- :非常好
- :可能有bug
- :几乎肯定有bug
def gradient_check(model, X, Y, epsilon=1e-7):
"""梯度检查实现"""
for name, param in model.named_parameters():
if param.grad is None:
continue
# 解析梯度
analytical_grad = param.grad.data.numpy()
# 数值梯度
numerical_grad = np.zeros_like(analytical_grad)
param_flat = param.data.numpy().flatten()
for i in range(len(param_flat)):
# f(θ + ε)
param.data.flatten()[i] += epsilon
loss_plus = compute_loss(model, X, Y)
# f(θ - ε)
param.data.flatten()[i] -= 2*epsilon
loss_minus = compute_loss(model, X, Y)
# 恢复
param.data.flatten()[i] += epsilon
numerical_grad.flatten()[i] = (loss_plus - loss_minus) / (2*epsilon)
# 计算相对误差
diff = np.linalg.norm(numerical_grad - analytical_grad)
norm = np.linalg.norm(numerical_grad) + np.linalg.norm(analytical_grad)
relative_error = diff / norm
if relative_error > 1e-5:
print(f"Warning: {name}, error={relative_error}")
return True9. 计算复杂度分析
9.1 前向传播
每层计算量:
总计算量:
9.2 反向传播
反向传播计算量约为前向传播的两倍:
- 误差反向传播:
- 梯度计算:
9.3 内存复杂度
存储内容:
- 前向激活值:(训练时需要)
- 梯度:
10. 常见问题与解决方案
10.1 梯度消失/爆炸
问题:深层网络中梯度可能指数级衰减或增长
解决方案:
- 合适的权重初始化(Xavier, He)
- Batch Normalization
- 残差连接(ResNet)
- LSTM/GRU的门控机制
10.2 鞍点问题
问题:在高维空间中,鞍点比局部最小值更常见
解决方案:
- 动量法
- Adam等自适应学习率优化器
- 学习率调度
10.3 数值稳定性
# Sigmoid数值稳定实现
def stable_sigmoid(x):
return np.where(x >= 0,
1 / (1 + np.exp(-x)),
np.exp(x) / (1 + np.exp(x)))
# Softmax数值稳定实现
def stable_softmax(x):
x_max = np.max(x, axis=-1, keepdims=True)
x_exp = np.exp(x - x_max)
return x_exp / np.sum(x_exp, axis=-1, keepdims=True)11. 总结
反向传播的核心要点:
- 链式法则:梯度的反向传播本质
- 四方程框架:系统化梯度计算
- 计算效率: 而非暴力差分
- 计算图:现代框架自动微分的基础
- 数值稳定:工程实现的关键考虑
理解反向传播的数学原理,对于:
- 调试梯度计算问题
- 设计新架构/层
- 理解优化器行为
- 进行理论研究
都至关重要。
参考文献
Footnotes
-
Rumelhart, D. E., Hinton, G. E., & Williams, R. J. (1986). Learning representations by back-propagating errors. Nature. ↩