反向传播算法详解

反向传播(Backpropagation)是训练深度神经网络的核心算法,是链式法则在计算图中的高效应用。1

1. 概述

1.1 问题定义

给定:

  • 神经网络结构(层数、每层神经元数)
  • 损失函数
  • 训练数据集

目标:计算 对所有权重

1.2 核心思想

反向传播通过两次计算图遍历实现高效梯度计算:

  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 loss

6. 常见激活函数导数

激活函数函数导数
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_size

7.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 True

9. 计算复杂度分析

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. 总结

反向传播的核心要点:

  1. 链式法则:梯度的反向传播本质
  2. 四方程框架:系统化梯度计算
  3. 计算效率 而非暴力差分
  4. 计算图:现代框架自动微分的基础
  5. 数值稳定:工程实现的关键考虑

理解反向传播的数学原理,对于:

  • 调试梯度计算问题
  • 设计新架构/层
  • 理解优化器行为
  • 进行理论研究

都至关重要。

参考文献

Footnotes

  1. Rumelhart, D. E., Hinton, G. E., & Williams, R. J. (1986). Learning representations by back-propagating errors. Nature.