反向传播算法详解

反向传播(Backpropagation)是训练神经网络的核心算法,通过链式法则高效计算损失函数对参数的梯度。本文档详细推导反向传播的数学原理。

链式法则基础

回顾:一元链式法则

对于复合函数

多元链式法则

对于多变量函数,设 ,每个

单神经元梯度推导

网络结构

考虑单个神经元:

  • 输入:
  • 权重:
  • 偏置:
  • 加权和:
  • 输出:

前向传播

import numpy as np
 
def sigmoid(z):
    return 1 / (1 + np.exp(-z))
 
def forward(x, w, b):
    z = np.dot(x, w) + b  # x: (n,), w: (n,), z: scalar
    y_hat = sigmoid(z)
    return y_hat

反向传播推导

设损失函数为均方误差:

步骤1:输出层梯度

步骤2:激活函数梯度

步骤3:应用链式法则

步骤4:权重梯度

完整梯度计算代码

def backward(x, y, y_hat):
    # 前向传播值
    z = np.dot(x, w) + b
    sigma_z = sigmoid(z)
    
    # dC/d(y_hat)
    d_loss_d_yhat = y_hat - y
    
    # d(y_hat)/dz = sigmoid'(z)
    d_sigmoid = sigma_z * (1 - sigma_z)
    
    # dC/dz
    d_loss_d_z = d_loss_d_yhat * d_sigmoid
    
    # dC/dw_i = dC/dz * dz/dw_i = dC/dz * x_i
    d_loss_d_w = d_loss_d_z * x
    
    # dC/db = dC/dz * dz/db = dC/dz * 1
    d_loss_d_b = d_loss_d_z
    
    return d_loss_d_w, d_loss_d_b

多层感知机完整推导

网络结构

考虑 层神经网络:

  • 个神经元
  • :权重矩阵
  • :偏置向量
  • :层输入的线性变换
  • :层输出(激活值)

Nielsen四个核心方程

BP1:输出层误差

定义层 的误差

对于输出层

向量形式:

MSE损失的特例
,则

BP2:误差反向传播

推导:

BP3:偏置梯度

BP4:权重梯度

梯度计算示例

示例网络

输入(2) → 隐藏层(3) → 输出(2)
import numpy as np
 
class MLP:
    def __init__(self, layer_sizes):
        """layer_sizes: [input_dim, hidden1, ..., output_dim]"""
        self.L = len(layer_sizes) - 1
        self.W = []
        self.b = []
        
        for i in range(self.L):
            # Xavier初始化
            scale = np.sqrt(2.0 / layer_sizes[i])
            self.W.append(np.random.randn(layer_sizes[i], layer_sizes[i+1]) * scale)
            self.b.append(np.zeros((1, layer_sizes[i+1])))
    
    def sigmoid(self, z):
        return 1 / (1 + np.exp(-np.clip(z, -500, 500)))
    
    def sigmoid_prime(self, z):
        s = self.sigmoid(z)
        return s * (1 - s)
    
    def forward(self, X):
        self.z = []  # 存储每层的线性输出
        self.a = [X]  # 存储每层的激活输出
        
        for i in range(self.L):
            z = self.a[-1] @ self.W[i] + self.b[i]
            self.z.append(z)
            a = self.sigmoid(z) if i < self.L - 1 else z  # 输出层不用激活
            self.a.append(a)
        
        return self.a[-1]
    
    def backward(self, y, loss='mse'):
        m = y.shape[0]
        self.grad_W = []
        self.grad_b = []
        
        # 输出层误差 (BP1)
        if loss == 'mse':
            delta = (self.a[-1] - y) * self.sigmoid_prime(self.z[-1])
        elif loss == 'cross_entropy':
            delta = self.a[-1] - y  # softmax + CE 的简化形式
        
        # 从输出层向输入层反向传播
        for l in range(self.L - 1, -1, -1):
            # BP3: 偏置梯度
            self.grad_b.append(delta.sum(axis=0, keepdims=True) / m)
            
            # BP4: 权重梯度
            self.grad_W.append(self.a[l].T @ delta / m)
            
            # BP2: 传播到下一层(如果不是输入层)
            if l > 0:
                delta = (delta @ self.W[l].T) * self.sigmoid_prime(self.z[l-1])
        
        # 反转,因为我们是反向遍历的
        self.grad_W = self.grad_W[::-1]
        self.grad_b = self.grad_b[::-1]
    
    def update_params(self, learning_rate):
        for i in range(self.L):
            self.W[i] -= learning_rate * self.grad_W[i]
            self.b[i] -= learning_rate * self.grad_b[i]

训练循环

def train(model, X_train, y_train, epochs, lr):
    losses = []
    
    for epoch in range(epochs):
        # 前向传播
        y_pred = model.forward(X_train)
        
        # 计算损失
        loss = 0.5 * np.sum((y_pred - y_train) ** 2) / X_train.shape[0]
        losses.append(loss)
        
        # 反向传播
        model.backward(y_train, loss='mse')
        
        # 更新参数
        model.update_params(lr)
        
        if epoch % 100 == 0:
            print(f"Epoch {epoch}, Loss: {loss:.4f}")
    
    return losses

梯度消失与梯度爆炸

问题分析

梯度消失

在深层网络中,梯度逐层指数衰减:

,梯度逐层衰减至接近0。

梯度爆炸

,梯度指数增长。

解决方案

方法原理
ReLU激活,缓解消失
残差连接梯度可直接传递
BatchNorm稳定每层输入分布
梯度裁剪$\
合适的初始化Xavier/He初始化
# 梯度裁剪
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
 
# He初始化
W = np.random.randn(n_in, n_out) * np.sqrt(2.0 / n_in)

计算复杂度分析

操作前向传播反向传播
每层乘法
总复杂度

反向传播与前向传播的计算量基本相同,这使得梯度计算非常高效。

常见激活函数的梯度

激活函数正向 梯度
Sigmoid
Tanh
ReLU if , else
Leaky ReLU if , else

参考