算法概述#
什么是反向传播?#
反向传播(Backpropagation) 是训练神经网络的核心算法,用于高效计算损失函数对网络参数的梯度。
为什么需要反向传播?#
问题:神经网络有大量参数,如何高效计算梯度?
解决方案:
- 前向传播:计算预测值
- 反向传播:从输出层向输入层传播误差,计算梯度
核心思想#
- 前向传播:计算每层的输出
- 计算损失:比较预测值和真实值
- 反向传播:从输出层开始,逐层计算梯度
- 更新参数:使用梯度下降更新参数
链式法则#
数学基础#
链式法则(Chain Rule) 是反向传播的数学基础。
对于复合函数 $y = f(g(x))$ :
$$ \frac{dy}{dx} = \frac{dy}{dg} \cdot \frac{dg}{dx} $$多变量链式法则#
对于 $z = f(x, y)$ ,其中 $x = g(t)$ , $y = h(t)$ :
$$ \frac{dz}{dt} = \frac{\partial z}{\partial x} \cdot \frac{dx}{dt} + \frac{\partial z}{\partial y} \cdot \frac{dy}{dt} $$神经网络中的应用#
在神经网络中,损失函数是参数的复合函数:
$$ L = L(y, \hat{y}) = L(y, f_N(\ldots f_2(f_1(x, w_1, b_1), w_2, b_2) \ldots, w_N, b_N)) $$使用链式法则计算梯度:
$$ \frac{\partial L}{\partial w_1} = \frac{\partial L}{\partial \hat{y}} \cdot \frac{\partial \hat{y}}{\partial z_N} \cdot \ldots \cdot \frac{\partial z_2}{\partial a_1} \cdot \frac{\partial a_1}{\partial z_1} \cdot \frac{\partial z_1}{\partial w_1} $$反向传播原理#
符号定义#
- $L$ :损失函数
- $z^{(l)}$ :第 $l$ 层的线性输出(logits)
- $a^{(l)}$ :第 $l$ 层的激活输出
- $W^{(l)}$ :第 $l$ 层的权重矩阵
- $b^{(l)}$ :第 $l$ 层的偏置向量
- $\delta^{(l)}$ :第 $l$ 层的误差项(error term)
前向传播回顾#
$$ z^{(l)} = W^{(l)} \cdot a^{(l-1)} + b^{(l)} $$ $$ a^{(l)} = f(z^{(l)}) $$反向传播步骤#
1. 输出层误差#
对于输出层 $L$ :
$$ \delta^{(L)} = \frac{\partial L}{\partial z^{(L)}} = \frac{\partial L}{\partial a^{(L)}} \cdot f'(z^{(L)}) $$2. 隐藏层误差传播#
对于隐藏层 $l$ :
$$ \delta^{(l)} = (W^{(l+1)})^T \cdot \delta^{(l+1)} \odot f'(z^{(l)}) $$其中 $\odot$ 表示逐元素相乘(Hadamard积)。
3. 参数梯度#
权重梯度:
$$ \frac{\partial L}{\partial W^{(l)}} = \delta^{(l)} \cdot (a^{(l-1)})^T $$偏置梯度:
$$ \frac{\partial L}{\partial b^{(l)}} = \delta^{(l)} $$完整流程#
前向传播:
$$ x → z¹ → a¹ → z² → a² → ... → z^L → a^L → L $$反向传播:
L → δ^L → δ^(L-1) → ... → δ² → δ¹
↓ ↓ ↓
∂W^L/∂b^L ∂W^(L-1)/∂b^(L-1) ∂W¹/∂b¹梯度计算#
输出层梯度#
均方误差损失#
对于回归问题,使用MSE损失:
$$ L = \frac{1}{2}(y - \hat{y})^2 $$输出层误差:
$$ \delta^{(L)} = \hat{y} - y $$交叉熵损失#
对于分类问题,使用交叉熵损失:
$$ L = -\sum_i y_i \log(\hat{y}_i) $$如果使用Softmax + 交叉熵:
$$ \delta^{(L)} = \hat{y} - y \quad \text{(形式与MSE相同!)} $$激活函数导数#
Sigmoid#
$$ f(z) = \frac{1}{1 + e^{-z}} $$ $$ f'(z) = f(z)(1 - f(z)) $$Tanh#
$$ f(z) = \tanh(z) $$ $$ f'(z) = 1 - \tanh^2(z) $$ReLU#
$$ f(z) = \max(0, z) $$ $$ f'(z) = \begin{cases} 1 & \text{if } z > 0 \\ 0 & \text{if } z \leq 0 \end{cases} $$Leaky ReLU#
$$ f(z) = \max(\alpha z, z) \quad \text{($\alpha$ 通常为 0.01)} $$ $$ f'(z) = \begin{cases} 1 & \text{if } z > 0 \\ \alpha & \text{if } z \leq 0 \end{cases} $$实现细节#
批量梯度计算#
对于批量数据(batch size = $m$ ):
误差项#
$$ \Delta^{(l)} = [\delta_1^{(l)}, \delta_2^{(l)}, \ldots, \delta_m^{(l)}] \quad \text{($[m, \text{输出维度}]$)} $$权重梯度#
$$ \frac{\partial L}{\partial W^{(l)}} = \frac{1}{m} \cdot (\Delta^{(l)})^T \cdot A^{(l-1)} $$形状:
- $\Delta^{(l)}$ :$[m, \text{输出维度}]$
- $A^{(l-1)}$ :$[m, \text{输入维度}]$
- $\frac{\partial L}{\partial W^{(l)}}$ :$[\text{输出维度}, \text{输入维度}]$
偏置梯度#
$$ \frac{\partial L}{\partial b^{(l)}} = \frac{1}{m} \sum_{i=1}^{m} \delta_i^{(l)} $$梯度累积#
对于多个样本,梯度是平均的:
$$ \frac{\partial L}{\partial W} = \frac{1}{m} \sum_{i=1}^{m} \frac{\partial L_i}{\partial W} $$数值稳定性#
梯度消失问题#
问题:深层网络中,梯度可能变得非常小。
原因:
- Sigmoid/Tanh的导数在饱和区域很小
- 多层相乘导致梯度指数衰减
解决方案:
- 使用ReLU等激活函数
- 残差连接(ResNet)
- 梯度裁剪
梯度爆炸问题#
问题:梯度可能变得非常大。
原因:
- 权重初始化不当
- 深层网络梯度累积
解决方案:
- 梯度裁剪(gradient clipping)
- 权重初始化(Xavier、He初始化)
- 批量归一化
梯度裁剪#
# 方法1:按值裁剪
torch.nn.utils.clip_grad_value_(model.parameters(), clip_value=1.0)
# 方法2:按范数裁剪
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)代码实现#
手动实现反向传播#
import torch
import torch.nn as nn
class SimpleNet(nn.Module):
def __init__(self):
super(SimpleNet, self).__init__()
self.fc1 = nn.Linear(2, 3)
self.fc2 = nn.Linear(3, 1)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
z1 = self.fc1(x)
a1 = self.sigmoid(z1)
z2 = self.fc2(a1)
a2 = self.sigmoid(z2)
return a2
# 手动计算梯度(仅用于理解)
def manual_backward(model, x, y_true, y_pred):
# 假设使用MSE损失
loss = (y_pred - y_true) ** 2
# 输出层误差
delta2 = 2 * (y_pred - y_true) * model.sigmoid.derivative(z2)
# 隐藏层误差
delta1 = delta2 @ model.fc2.weight * model.sigmoid.derivative(z1)
# 计算梯度
grad_w2 = delta2 @ a1.T
grad_b2 = delta2
grad_w1 = delta1 @ x.T
grad_b1 = delta1
return grad_w1, grad_b1, grad_w2, grad_b2PyTorch自动求导#
# 定义模型
model = SimpleNet()
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
# 训练循环
for epoch in range(100):
# 前向传播
y_pred = model(x)
loss = criterion(y_pred, y_true)
# 反向传播(自动计算梯度)
optimizer.zero_grad() # 清零梯度
loss.backward() # 计算梯度
optimizer.step() # 更新参数
if epoch % 10 == 0:
print(f'Epoch {epoch}, Loss: {loss.item():.4f}')检查梯度#
# 打印梯度
for name, param in model.named_parameters():
if param.grad is not None:
print(f'{name}: {param.grad.norm():.6f}')
else:
print(f'{name}: No gradient')梯度可视化#
import matplotlib.pyplot as plt
def plot_gradients(model):
gradients = []
names = []
for name, param in model.named_parameters():
if param.grad is not None:
gradients.append(param.grad.norm().item())
names.append(name)
plt.bar(names, gradients)
plt.xlabel('Layer')
plt.ylabel('Gradient Norm')
plt.title('Gradient Norms by Layer')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()总结#
- 反向传播:高效计算梯度的算法
- 链式法则:反向传播的数学基础
- 误差传播:从输出层向输入层传播误差
- 梯度计算:使用误差项计算参数梯度
- 数值稳定性:注意梯度消失和爆炸问题
关键要点:
- 反向传播利用链式法则高效计算梯度
- 误差从输出层向输入层反向传播
- PyTorch等框架自动实现反向传播
- 注意梯度消失和爆炸问题