概述
反向传播(Backpropagation)是训练深度神经网络的核心算法,通过链式法则高效计算损失函数对参数的梯度。梯度流理论则分析梯度在深度网络中如何传播,以及为何某些架构能够有效训练。1
反向传播算法
链式法则回顾
对于复合函数 :
对于多元复合函数:
计算图表示
深度神经网络可以表示为计算图,每个节点代表一个操作:
输入 x → [线性变换 W₁] → z₁ = W₁x → [激活 σ] → a₁ = σ(z₁)
→ [线性变换 W₂] → z₂ = W₂a₁ → [激活 σ] → a₂ = σ(z₂)
→ ... → [线性变换 W_L] → z_L = W_La_{L-1} → [损失 ℓ]
反向传播核心思想
前向传播:从输入到输出,计算每个中间节点的值。
反向传播:从输出到输入,使用链式法则计算每个参数对损失的梯度。
单层神经网络反向传播
设单层网络为:
损失函数为 。
1. 前向传播
def forward(x, W, b, y):
z = np.dot(W, x) + b # 线性变换
a = sigmoid(z) # 激活
loss = cross_entropy(y, a) # 损失
cache = (x, W, b, z, a) # 缓存用于反向传播
return loss, cache2. 反向传播
def backward(dout, cache):
x, W, b, z, a = cache
n = x.shape[0]
# 损失函数梯度 (以交叉熵 + softmax 为例)
dloss = a - y # (batch, hidden)
# 对 z = Wx + b 的梯度
dz = dloss * sigmoid_prime(z) # (batch, hidden)
# 对权重 W 的梯度: dℓ/dW = dz^T · x
dW = np.dot(dz.T, x) / n # (hidden, input)
# 对偏置 b 的梯度
db = np.sum(dz, axis=0) / n # (hidden,)
# 对输入 x 的梯度 (传递到前一层)
dx = np.dot(W.T, dz) # (batch, input)
return dx, dW, db多层网络反向传播
矩阵形式的反向传播
设网络有 层,第 层为:
损失函数梯度
其中 称为误差项。
误差反向传播
PyTorch 实现
import torch
import torch.nn as nn
import torch.nn.functional as F
class DeepMLP(nn.Module):
"""多层感知机的反向传播"""
def __init__(self, layer_sizes):
super().__init__()
self.layers = nn.ModuleList()
for i in range(len(layer_sizes) - 1):
self.layers.append(nn.Linear(layer_sizes[i], layer_sizes[i+1]))
def forward(self, x):
self.activations = [x] # 保存激活值
for i, layer in enumerate(self.layers):
z = layer(self.activations[-1])
self.activations.append(z)
if i < len(self.layers) - 1: # 除了最后一层都激活
self.activations[-1] = F.relu(z)
return self.activations[-1]
def backward(self, y, y_pred):
"""手动反向传播(用于理解)"""
batch_size = y.shape[0]
# 损失梯度 (MSE)
dLoss = 2 * (y_pred - y) / batch_size
# 存储梯度
self.grads = []
# 从后往前传播
d_out = dLoss
for l in reversed(range(len(self.layers))):
# 对激活的梯度
if l < len(self.layers) - 1:
d_out = d_out * F.relu derivative(self.activations[l+1])
# 权重梯度
dW = torch.outer(d_out, self.activations[l])
db = d_out.clone()
# 存储梯度
self.grads.insert(0, (dW, db))
# 传递到下一层
d_out = self.layers[l].weight.T @ d_out
return self.grads
def relu_derivative(x):
"""ReLU 导数"""
return (x > 0).float()梯度流分析
深度网络梯度问题
梯度消失(Vanishing Gradient)
当网络很深时,梯度在反向传播过程中指数衰减:
设 ,,则:
当 时,梯度指数衰减。
梯度爆炸(Exploding Gradient)
当 时,梯度指数增长,导致训练不稳定。
梯度流稳定性条件
雅可比矩阵范数:
稳定训练的条件:
- :梯度既不消失也不爆炸
- 这要求权重初始化恰好在临界状态
信号传播理论(Signal Propagation)
随机网络分析
考虑一个无限宽的全连接网络,权重从均值为 0、方差为 的分布初始化( 为层宽)。
前向传播
定理(均值方差保持):
如果输入 各分量独立同分布,均值为 0,方差为 ,且权重初始化满足上述方差,则每一层的激活值也满足各分量独立同分布,且:
激活函数的影响
| 激活函数 | 临界值 | 行为 |
|---|---|---|
| ReLU | 2.0 | 激活值方差增长,需要 |
| Tanh | 1.0 | 激活值方差稳定在 |
| Sigmoid | 1.0 | 饱和导致方差收缩 |
反向传播
类似地,梯度满足:
其中 是偏置的方差。
边缘稳定性(Edge of Chaos)
定义:当 时,网络处于边缘稳定性(Edge of Chaos)状态。
| 条件 | ||
|---|---|---|
| 状态 | Ordered (有序) | Chaotic (混沌) |
| 激活值 | 收缩到 0 | 指数增长 |
| 梯度 | 消失 | 爆炸 |
| 特征学习 | 浅层表示 | 随机特征 |
关键洞察:只有处于边缘稳定性状态时,信息才能有效传播,同时保持特征学习能力。
深度线性网络的精确分析
线性网络梯度流
对于没有激活函数的线性网络 :
谱范数乘积
梯度范数与权重矩阵的谱范数乘积相关:
初始化策略的合理性
Xavier 初始化(2010):
对于线性网络,这使得 ,梯度流稳定。
He 初始化(2015):
对于 ReLU 网络,这补偿了 ReLU 消除一半激活值的影响。
残差网络的梯度流
残差连接的作用
考虑残差块:
梯度传递:
关键性质:即使 很小,梯度仍然包含 (单位矩阵),保证梯度不会完全消失!
数学证明
设从第 层到第 层的梯度:
其中 。
引理: 的特征值至少包含 个 1。
这解释了为何残差网络能够训练数千层。
层归一化与梯度流
层归一化前向/反向
class LayerNormManual:
def __init__(self, normalized_shape, eps=1e-5):
self.gamma = nn.Parameter(torch.ones(normalized_shape))
self.beta = nn.Parameter(torch.zeros(normalized_shape))
self.eps = eps
def forward(self, x):
# x: (batch, seq_len, features) 或 (batch, features)
mean = x.mean(dim=-1, keepdim=True)
var = x.var(dim=-1, keepdim=True, unbiased=False)
x_norm = (x - mean) / torch.sqrt(var + self.eps)
return self.gamma * x_norm + self.beta
def backward(self, dout):
# 层归一化的反向传播
N = dout.shape[-1]
dgamma = (dout * self.x_norm).sum(dim=-1)
dbeta = dout.sum(dim=-1)
dx_norm = dout * self.gamma
dvar = (-0.5 * N * dx_norm * (self.x - self.mean)).sum(dim=-1) / (self.var + self.eps)**1.5
dx = 2/N * dx_norm * (self.x - self.mean) + dvar * 2 * (self.x - self.mean) / N
return dx, dgamma, dbeta层归一化对梯度的影响
层归一化将每层的激活值标准化到固定范围,间接稳定梯度流:
- 保持激活值方差稳定
- 减少内部协变量偏移
- 使优化 landscape 更平滑
优化器的梯度流效应
SGD 与动量
class SGDWithMomentum:
def __init__(self, params, lr=0.01, momentum=0.9):
self.params = params
self.lr = lr
self.momentum = momentum
self.v = {} # 速度
def step(self, grads):
for i, (name, p) in enumerate(self.params.items()):
if name not in self.v:
self.v[name] = torch.zeros_like(p)
# 动量更新
self.v[name] = self.momentum * self.v[name] + grads[name]
# 参数更新
p.data -= self.lr * self.v[name]动量的梯度平滑效应:
有效梯度 是历史梯度的指数加权平均:
当方向一致时,加速;当方向不一致时,平滑。
自适应优化器与梯度流
Adam 的梯度流效应:
class Adam:
def __init__(self, params, lr=0.001, beta1=0.9, beta2=0.999, eps=1e-8):
self.params = params
self.lr = lr
self.beta1 = beta1
self.beta2 = beta2
self.eps = eps
self.m = {} # 一阶矩估计
self.v = {} # 二阶矩估计
self.t = 0 # 时间步
def step(self, grads):
self.t += 1
for name, p in self.params.items():
# 一阶矩(梯度的指数移动平均)
self.m[name] = self.beta1 * self.m.get(name, 0) + (1 - self.beta1) * grads[name]
# 二阶矩(梯度平方的指数移动平均)
self.v[name] = self.beta2 * self.v.get(name, 0) + (1 - self.beta2) * grads[name]**2
# 偏差修正
m_hat = self.m[name] / (1 - self.beta1**self.t)
v_hat = self.v[name] / (1 - self.beta2**self.t)
# 更新
p.data -= self.lr * m_hat / (torch.sqrt(v_hat) + self.eps)关键洞察:Adam 的二阶动量项 自适应地调整每个参数的学习率,对于梯度幅度大的参数降低学习率,对于梯度幅度小的参数增大学习率。
梯度流的可视化分析
奇异值分解分析
def analyze_gradient_flow(model, x, y):
"""分析网络的梯度流"""
model.eval()
# 前向传播
output = model(x)
loss = F.cross_entropy(output, y)
# 反向传播
loss.backward()
results = {
'layer_grads': [],
'singular_values': [],
'grad_norms': []
}
for name, param in model.named_parameters():
if param.grad is not None:
grad_norm = param.grad.norm().item()
results['grad_norms'].append((name, grad_norm))
# 奇异值分析(对权重矩阵)
if param.dim() == 2:
# 权重矩阵的奇异值
U, S, V = torch.svd(param.data)
results['singular_values'].append((name, S.cpu().numpy()))
# 梯度与权重的对齐程度
grad_proj = (param.grad * param).sum() / (param.grad.norm() * param.norm())
results['layer_grads'].append((name, grad_proj.item()))
return results
# 可视化
import matplotlib.pyplot as plt
def plot_gradient_flow(results):
"""可视化梯度流"""
names = [r[0] for r in results['grad_norms']]
norms = [r[1] for r in results['grad_norms']]
plt.figure(figsize=(12, 6))
plt.bar(range(len(names)), norms)
plt.xticks(range(len(names)), names, rotation=45)
plt.xlabel('Layer')
plt.ylabel('Gradient Norm')
plt.title('Gradient Flow Analysis')
plt.yscale('log')
plt.tight_layout()
plt.show()诊断指标
| 指标 | 理想值 | 问题诊断 |
|---|---|---|
| 梯度范数 | 稳定 | 消失或爆炸 |
| 权重-梯度对齐 | 高 | 优化困难 |
| 奇异值分布 | 集中在 1 附近 | 条件数差 |
| 激活值方差 | 稳定 | 协变量偏移 |
实践中的梯度流优化
1. 适当的权重初始化
def custom_init(model):
for m in model.modules():
if isinstance(m, nn.Linear):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
if m.bias is not None:
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')2. 梯度裁剪
# 范数裁剪
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
# 值裁剪
torch.nn.utils.clip_grad_value_(model.parameters(), clip_value=1.0)3. 学习率调度
# 学习率预热 + 余弦衰减
scheduler = torch.optim.lr_scheduler.CosineAnnealingWarmRestarts(
optimizer, T_0=10, T_mult=2
)
# 阶梯衰减
scheduler = torch.optim.lr_scheduler.StepLR(
optimizer, step_size=30, gamma=0.1
)4. 残差连接
class ResidualBlock(nn.Module):
def __init__(self, dim):
super().__init__()
self.net = nn.Sequential(
nn.Linear(dim, dim),
nn.ReLU(),
nn.Linear(dim, dim)
)
def forward(self, x):
return x + self.net(x) # 残差连接理论前沿:Mean-Field 理论
无限宽网络极限
当网络宽度 时,网络的行为可以被精确描述。
核心结果:
- 神经网络的高斯过程极限:无限宽网络等价于一个高斯过程
- 批量归一化的效果:引入层间相关性,破坏独立性假设
- 训练的收敛轨迹:可以在无限宽极限下精确预测
训练动态的闭式解
在 Mean-Field 极限下,参数的训练轨迹可以用常微分方程描述:
其中 是神经正切核定义的损失 landscape。
参考
相关阅读
- ResNet 与残差学习 — 残差连接如何解决梯度问题
- Sharp vs Flat Minima — 优化 landscape 与泛化
- 自适应优化器理论 — Adam 等优化器的理论基础
- 高斯过程 — 无限宽网络的概率解释
Footnotes
-
Goodfellow, I., Bengio, Y., & Courville, A. (2016). Deep Learning. MIT Press. ↩