AIGC标识 机器学习数学基础专题【左扬精讲】—— 前向传播与反向传播:从数据流到梯度流的完整闭环

机器学习数学基础专题【左扬精讲】—— 前向传播与反向传播:从数据流到梯度流的完整闭环

在前面的梯度下降系列文章中,我们反复提到两个核心概念:前向传播(Forward Propagation)反向传播(Backpropagation)。这两个概念构成了神经网络的"任督二脉"——前向传播是数据从输入到输出的流动过程,反向传播是梯度从输出到输入的回传过程。没有前向传播,神经网络无法产生预测;没有反向传播,神经网络无法学习。

本文将从 What-How-Why 三段式框架出发,不带任何生活类比,纯粹从数学和计算图的角度,把这两个核心机制讲清楚。所有公式均来自深度学习领域的公认定义(主要参考 Ian Goodfellow 的《深度学习》第三章)。

本文涉及的开源库/工具:
torch (PyTorch)      ← autograd 自动微分实现
torch.nn             ← 神经网络模块封装
torch.optim          ← 优化器(SGD/Adam)
numpy                ← 数值计算验证

核心概念:
前向传播 (Forward Propagation)  ← 数据流:输入 → 输出
反向传播 (Backpropagation)       ← 梯度流:输出 → 输入
链式法则 (Chain Rule)            ← 连接前向与反向的数学纽带
计算图 (Computational Graph)     ← PyTorch autograd 的核心数据结构

深度学习 前向传播 反向传播 链式法则 计算图 梯度下降 PyTorch

学习重点提示

★ 必须掌握

  • 必须掌握:前向传播的定义——输入数据如何通过网络层层计算,最终产生输出
  • 必须掌握:反向传播的定义——如何通过链式法则高效计算损失函数对每个参数的梯度
  • 必须掌握:前向传播与反向传播的时序关系——先 forward 再 backward
  • 必须掌握:链式法则是反向传播的数学基础,理解链式法则才能理解 BP
  • 必须掌握:反向传播不是学习算法,而是梯度计算方法;真正的学习算法是 SGD/Adam 等优化器

★ 建议掌握

  • 建议掌握:计算图(Computational Graph)的概念及其在前向/反向传播中的作用
  • 建议掌握:反向传播的三种模式——符号式、命令式、混合式(以 PyTorch 为代表的动态图)
  • 建议掌握:梯度消失与梯度爆炸在反向传播中的表现
  • 建议掌握:PyTorch 中 tensor.backward() 的底层机制

本文目录

  1. 开篇:从一个例子理解前向与反向的关系
  2. 一、前向传播(Forward Propagation)—— What & How
  3. 二、反向传播(Backpropagation)—— What & How
  4. 三、链式法则——前向与反向的数学纽带
  5. 四、前向传播与反向传播的对比
  6. 五、PyTorch 源码视角:autograd 如何实现反向传播
  7. 六、常见问题 FAQ(20 组)
  8. 七、Roadmap 后续预告

一、开篇:从一个例子理解前向与反向的关系

在深入理论之前,我们先用一个最简单的例子建立直观认识。假设有一个单层神经网络,用于二分类:

import torch

# 输入: x = [1.0, 2.0]
# 真实标签: y = 1.0
# 权重: W = [[0.5, 0.3], [0.2, 0.4]]  (2x2 权重矩阵)
# 偏置: b = [0.1, 0.2]

x = torch.tensor([1.0, 2.0], requires_grad=True)
W = torch.tensor([[0.5, 0.3], [0.2, 0.4]], requires_grad=True)
b = torch.tensor([0.1, 0.2], requires_grad=True)
y = torch.tensor([1.0])

# ===== 前向传播 =====
z = torch.matmul(W, x) + b      # 线性变换: z = Wx + b
a = torch.sigmoid(z)              # 激活函数: a = sigmoid(z)
loss = torch.binary_cross_entropy(a, y)  # 损失函数

print(f"前向传播输出: {a}")

# ===== 反向传播 =====
loss.backward()

print(f"W 的梯度: {W.grad}")
print(f"b 的梯度: {b.grad}")
print(f"x 的梯度: {x.grad}")

核心观察:前向传播与反向传播的时序关系

在这个例子中:

  • 前向传播xz = Wx + ba = sigmoid(z)loss。数据从输入流向输出。
  • 反向传播:调用 loss.backward() 后,梯度从 loss 开始,沿着与前向相反的方向传播,最终计算出 W.gradb.gradx.grad
  • 时序关系:必须先完成前向传播,才能进行反向传播。

二、前向传播(Forward Propagation)—— What & How

2.1 前向传播的定义(What)

What — 前向传播是什么?

前向传播(Forward Propagation)是指神经网络在推理阶段,输入数据从输入层开始,依次经过每一层的线性变换(矩阵乘法 + 偏置)和非线性激活函数,最终到达输出层,产生预测结果的过程。

在数学上,对于一个有 L 层的神经网络,前向传播可以表示为:

a^(l) = f^(l)(z^(l)) = f^(l)(W^(l) · a^(l-1) + b^(l))

其中:

  • a^(l) 是第 l 层的输出(激活值),也是第 l+1 层的输入
  • W^(l) 是第 l 层的权重矩阵
  • b^(l) 是第 l 层的偏置向量
  • f^(l) 是第 l 层的激活函数
  • z^(l) = W^(l) · a^(l-1) + b^(l) 是线性变换的结果

2.2 前向传播的数学推导(How)

How — 前向传播是怎么计算的?

让我们以一个三层神经网络为例,详细推导前向传播的计算过程:

输入层: a^(0) = x ∈ R^(d_in)

隐藏层1: z^(1) = W^(1) · a^(0) + b^(1), a^(1) = f(z^(1))

隐藏层2: z^(2) = W^(2) · a^(1) + b^(2), a^(2) = f(z^(2))

输出层: z^(3) = W^(3) · a^(2) + b^(3), a^(3) = output

以一个具体的数值例子来说明。假设:

  • 输入:x = [1.0, 2.0],即 a^(0) = [1.0, 2.0]
  • 第一层权重:W^(1) = [[1, 0], [0, 1]],b^(1) = [0, 0],激活函数:ReLU
# 前向传播数值示例
import torch

# 输入
x = torch.tensor([1.0, 2.0])

# 第一层
W1 = torch.tensor([[1.0, 0.0], [0.0, 1.0]])
b1 = torch.tensor([0.0, 0.0])
z1 = torch.matmul(W1, x) + b1  # z1 = [1.0, 2.0]
a1 = torch.relu(z1)            # a1 = [1.0, 2.0]  (ReLU: max(0, x))

# 第二层
W2 = torch.tensor([[1.0, 1.0]])
b2 = torch.tensor([0.5])
z2 = torch.matmul(W2, a1) + b2  # z2 = [1.0*1.0 + 1.0*2.0 + 0.5] = [3.5]
a2 = torch.sigmoid(z2)          # a2 = sigmoid(3.5) ≈ 0.9707

print(f"z1 (第一层线性变换): {z1}")
print(f"a1 (第一层激活): {a1}")
print(f"z2 (第二层线性变换): {z2}")
print(f"a2 (第二层输出): {a2}")

2.3 为什么需要前向传播?(Why)

Why — 为什么需要前向传播?

前向传播是神经网络的预测过程,它的价值在于:

  • 推理与预测:训练完成后,前向传播是模型产生预测结果的唯一方式。没有前向传播,模型无法回答任何问题。
  • 学习的前提:反向传播需要先计算损失函数,而损失函数需要前向传播产生的预测结果。

没有前向传播会发生什么?

  • 模型无法产生任何输出,无法进行推理
  • 无法计算损失函数,无法评估预测质量
  • 整个深度学习系统瘫痪

三、反向传播(Backpropagation)—— What & How

3.1 反向传播的定义(What)

What — 反向传播是什么?

反向传播(Backpropagation,简称 BP)是一种高效计算梯度的算法。它利用链式法则,从输出层开始,沿与前向传播相反的方向,逐层计算损失函数对每个参数的梯度。

关键区分(来自 Wikipedia "Backpropagation" 条目原文):

  • 反向传播不是学习算法,而是一种梯度计算方法
  • 真正的学习算法是随机梯度下降(SGD)、Adam 等优化器,它们使用反向传播计算出的梯度去更新权重

3.2 反向传播的数学推导(How)

How — 反向传播是怎么计算的?

反向传播的核心是链式法则。假设我们有一个损失函数 L,它最终依赖于第 l 层的权重 W^(l)。通过链式法则:

∂L/∂W^(l) = ∂L/∂a^(L) · ∂a^(L)/∂z^(L) · … · ∂a^(l+1)/∂z^(l+1) · ∂z^(l+1)/∂a^(l) · ∂a^(l)/∂z^(l) · ∂z^(l)/∂W^(l)

这个公式看起来复杂,但核心思想是:梯度从后往前传,每层只计算自己的局部梯度

以之前的三层网络为例,假设使用均方误差损失:

L = (1/2) ||y - a^(3)||²

# 反向传播数值示例
import torch

# 重新构建计算图
x = torch.tensor([1.0, 2.0], requires_grad=True)
W1 = torch.tensor([[1.0, 0.0], [0.0, 1.0]], requires_grad=True)
b1 = torch.tensor([0.0, 0.0], requires_grad=True)
W2 = torch.tensor([[1.0, 1.0]], requires_grad=True)
b2 = torch.tensor([0.5], requires_grad=True)
y = torch.tensor([1.0])

# 前向传播
z1 = torch.matmul(W1, x) + b1
a1 = torch.relu(z1)
z2 = torch.matmul(W2, a1) + b2
a2 = torch.sigmoid(z2)
loss = torch.mean((y - a2) ** 2)

print(f"前向传播 - 损失: {loss.item():.4f}")

# 反向传播
loss.backward()

print(f"\n反向传播 - 梯度:")
print(f"W1.grad:\n{W1.grad}")
print(f"b1.grad: {b1.grad}")
print(f"W2.grad: {W2.grad}")
print(f"b2.grad: {b2.grad}")

3.3 为什么需要反向传播?(Why)

Why — 为什么需要反向传播?

反向传播解决了深层神经网络梯度计算效率的问题。

假设我们用数值微分(Numerical Differentiation)计算每个参数的梯度:对于一个有 1000 万参数的神经网络,每次需要计算 1000 万次前向传播——这在计算上是不可能的。

反向传播利用动态规划的思想,将中间计算结果缓存复用,将复杂度从 O(N) 降低到 O(1)(每参数)。

没有反向传播会发生什么?

  • 只能用数值微分,计算量巨大,无法训练深层网络
  • 无法高效计算梯度,深度学习的训练成本将指数级上升
  • 现代深度学习框架(PyTorch/TensorFlow)将无法工作

四、链式法则——前向与反向的数学纽带

4.1 链式法则的定义

What — 链式法则是什么?

链式法则(Chain Rule)是微积分中计算复合函数导数的基本规则。假设 y = f(g(x)),则:

dy/dx = (dy/dg) · (dg/dx)

在多变量情况下,假设 y = f(u1, u2, ...),且每个 ui = gi(x1, x2, ...),则:

∂y/∂xi = ∑k (∂y/∂uk) · (∂uk/∂xi)

4.2 链式法则在反向传播中的应用

How — 链式法则如何应用于反向传播?

在神经网络中,每个运算节点都应用链式法则。假设我们有:

L = f(z^(L)) = f(W^(L) · a^(L-1) + b^(L))

要计算 ∂L/∂W^(L)

∂L/∂W^(L) = (∂L/∂z^(L)) · (∂z^(L)/∂W^(L))

其中:

  • ∂L/∂z^(L) 称为"局部梯度",可以通过上一层的梯度传递得到
  • ∂z^(L)/∂W^(L) = a^(L-1) 是当前层的局部梯度
# 手动实现链式法则验证
import torch

# 手动计算梯度
x = torch.tensor([1.0, 2.0])
W = torch.tensor([[0.5, 0.3], [0.2, 0.4]], requires_grad=True)
b = torch.tensor([0.1, 0.2], requires_grad=True)

# 前向传播
z = torch.matmul(W, x) + b
a = torch.relu(z)
loss = torch.sum(a ** 2)

# PyTorch 自动求导
loss.backward()

# 手动验证梯度
# z = Wx + b, a = relu(z), loss = sum(a^2)
# dl/dz = d(sum(a^2))/da * da/dz
# d(sum(a^2))/da = 2a
# da/dz = relu'(z) = 1 if z > 0 else 0
# dl/dW = dl/dz * dz/dW = dl/dz * x^T

manual_dL_dz = torch.where(z > 0, 2 * a, torch.zeros_like(a))
manual_dL_dW = torch.outer(manual_dL_dz, x)
manual_dL_db = manual_dL_dz

print(f"PyTorch 计算的 dL/dW:\n{W.grad}")
print(f"手动计算的 dL/dW:\n{manual_dL_dW}")
print(f"梯度一致: {torch.allclose(W.grad, manual_dL_dW)}")

五、前向传播与反向传播的对比

核心对比

维度前向传播(Forward)反向传播(Backward)
方向 从输入到输出(正向) 从输出到输入(反向)
计算内容 激活值 a^(l)、线性变换结果 z^(l) 损失函数对各参数的梯度
数学基础 矩阵乘法 + 激活函数 链式法则
作用阶段 推理阶段(Inference) 训练阶段(Training)
必须先执行? 是反向传播的前提 需要前向传播的结果
输出 预测值、损失值 梯度(用于参数更新)
是否必须 推理必须,训练也必须 训练必须,推理不需要
深度学习的完整工作流程

理解了前向传播和反向传播后,我们可以完整理解深度学习的训练流程:

# 深度学习训练的一个完整步骤
for epoch in range(num_epochs):
    for batch in dataloader:
        # Step 1: 前向传播 - 计算预测和损失
        predictions = model(batch.x)           # forward
        loss = criterion(predictions, batch.y)
        
        # Step 2: 反向传播 - 计算梯度
        optimizer.zero_grad()                 # 清除旧梯度
        loss.backward()                        # backward - 计算 dL/dW
        
        # Step 3: 参数更新 - 使用优化器更新权重
        optimizer.step()                      # W = W - lr * dL/dW

一个完整的训练步骤 = 1次前向 + 1次反向 + 1次参数更新

六、PyTorch 源码视角:autograd 如何实现反向传播

PyTorch autograd 的核心机制

PyTorch 的自动微分(autograd)系统是实现反向传播的核心。理解它的实现,可以从根本上理解反向传播的工作原理。

源码视角一:Tensor 的 requires_grad 属性

当创建一个 Tensor 时,默认 requires_grad=False。设置为 True 后,PyTorch 会跟踪所有在该 Tensor 上的操作,构建计算图。

# requires_grad 决定了是否跟踪梯度
x = torch.tensor([1.0, 2.0], requires_grad=True)  # 跟踪梯度
y = torch.tensor([3.0, 4.0])                       # 不跟踪梯度

z = x * 2  # z 会构建计算图,依赖 x
w = y * 2  # w 不构建计算图,不依赖 y

z.backward()  # OK,z 有 grad_fn
# w.backward()  # ERROR,w 没有 grad_fn

源码视角二:计算图的构建

在 PyTorch 中,每个 Tensor 都存储了 grad_fn,它是 Function 的子类,记录了创建该 Tensor 的操作。计算图是一个有向无环图(DAG)。

# 查看 grad_fn
x = torch.tensor([1.0], requires_grad=True)
y = x * 2
z = y + 3
w = z ** 2

print(f"y.grad_fn: {y.grad_fn}")  # <MulBackward0>
print(f"z.grad_fn: {z.grad_fn}")  # <AddBackward0>
print(f"w.grad_fn: {w.grad_fn}")  # <PowBackward0>

源码视角三:backward() 的底层实现

当调用 tensor.backward() 时,PyTorch 会:

  1. 从当前 Tensor 开始,沿着 grad_fn 反向遍历计算图
  2. 对每个节点应用链式法则,计算局部梯度
  3. 将梯度累积到 requires_grad=True 的叶节点的 grad 属性中
# 验证梯度累积机制
x = torch.tensor([1.0], requires_grad=True)

# 第一次 backward
y = x * 2
y.backward()
print(f"第一次后: x.grad = {x.grad}")  # tensor([2.])

# 第二次 backward(梯度会累加!)
y = x * 2
y.backward()
print(f"第二次后: x.grad = {x.grad}")  # tensor([4.])

# 正确的做法:每次前先清零
x = torch.tensor([1.0], requires_grad=True)
y = x * 2
y.backward()
print(f"清零前: x.grad = {x.grad}")  # tensor([2.])
x.grad.zero_()
print(f"清零后: x.grad = {x.grad}")  # tensor([0.])
y = x * 2
y.backward()
print(f"重新计算后: x.grad = {x.grad}")  # tensor([2.])

源码视角四:retain_graph 参数

默认情况下,backward() 会释放计算图。如果需要多次反向传播,需要使用 retain_graph=True

# 多次反向传播需要 retain_graph=True
x = torch.tensor([1.0], requires_grad=True)
y = x ** 2

# 默认情况:backward() 后计算图被释放
# y.backward()  # OK
# y.backward()  # ERROR: grad can be implicitly created only for scalar outputs

# 使用 retain_graph=True
y.backward(retain_graph=True)
print(f"第一次: x.grad = {x.grad}")  # tensor([2.])
x.grad.zero_()

# 重新创建梯度
(y ** 1).backward()  # 等价于 y.backward()
print(f"第二次: x.grad = {x.grad}")  # tensor([2.])

七、常见问题 FAQ(20 组)

FAQ 常见问题解答

Q1. 前向传播和反向传播的时间复杂度是多少?

相同。对于一个有 L 层、每层有 n 个参数的神经网络,前向传播和反向传播的计算复杂度都是 O(L · n)。这是反向传播最重要的优势之一——它不会引入额外的计算开销。

Q2. 为什么反向传播比数值微分高效?

因为反向传播利用了计算图的结构。数值微分需要对每个参数单独做一次前向传播,时间复杂度是 O(N · M)(N 为参数量)。反向传播只做一次前向传播和一次反向传播,时间复杂度是 O(M)(M 为计算量),每个参数的梯度计算被复用。

Q3. 前向传播和反向传播的顺序能颠倒吗?

不能。反向传播需要前向传播的计算结果(激活值、中间变量)。必须先完成前向传播,才能进行反向传播。

Q4. 推理时需要反向传播吗?

不需要。推理(Inference)只需要前向传播来产生预测结果,不需要计算梯度。只有训练(Training)阶段才需要反向传播。

Q5. 反向传播是学习算法吗?

不是。反向传播只是梯度计算方法,真正的学习算法是 SGD、Adam 等优化器。它们使用反向传播计算的梯度去更新权重。

Q6. 为什么需要非线性激活函数?

如果没有非线性激活函数,多层神经网络会退化为单层。因为连续的线性变换等价于一个线性变换:W2 · (W1 · x + b1) + b2 = (W2 · W1) · x + (W2 · b1 + b2)。非线性激活函数打破了这种等价性,使网络能够学习非线性模式。

Q7. 反向传播和梯度下降是什么关系?

梯度下降是优化器使用的策略,反向传播是计算梯度的方法。梯度下降定义了参数更新的方向和步长(负梯度方向),反向传播负责计算这个梯度。

Q8. 什么是计算图?

计算图是表达数学表达式的有向无环图(DAG)。节点表示变量(Tensor),边表示操作(Function)。PyTorch 使用动态计算图,在前向传播时实时构建。

Q9. PyTorch 的动态图和 TensorFlow 1.x 的静态图有什么区别?

动态图在运行时构建,静态图在执行前构建并编译。PyTorch 使用动态图(eager execution),每个前向传播都会重新构建计算图。TensorFlow 1.x 使用静态图(graph execution),需要先定义再执行。TensorFlow 2.x 默认使用动态图。

Q10. 反向传播会梯度消失怎么办?

有多种解决方案【推测】:① 使用 ReLU 替代 Sigmoid/Tanh;② 使用残差连接(ResNet);③ 使用批量归一化(BatchNorm);④ 使用 LSTM/GRU 等特殊结构;⑤ 使用梯度裁剪防止梯度爆炸。

Q11. 什么是一次前向传播、一次反向传播?

这是深度学习训练的一个基本单位。一次前向传播计算损失值,一次反向传播计算梯度。一次完整的参数更新需要:前向传播 → 反向传播 → 参数更新。

Q12. 为什么梯度要清零(zero_grad)?

因为 PyTorch 默认会累积梯度。如果不清零,梯度会不断累加,导致参数更新错误。正确做法是在每个 batch 开始前调用 optimizer.zero_grad()

Q13. 反向传播中哪些 Tensor 需要 requires_grad=True?

需要梯度的参数必须设置为 True。模型参数(权重、偏置)需要 requires_grad=True,输入数据通常不需要(除非是学习的内容),标签不需要。

Q14. 为什么说反向传播是"反向模式自动微分"?

因为它应用了反向模式的自动微分算法。自动微分有两种模式:前向模式(从输入到输出)和反向模式(从输出到输入)。对于神经网络(多输入少输出),反向模式更高效。

Q15. 梯度爆炸会导致什么后果?

参数更新步长过大。可能导致损失函数发散、权重变成 NaN、训练不稳定。解决方案包括梯度裁剪(gradient clipping)和合适的权重初始化(如 Xavier/He)。

Q16. 反向传播可以并行化吗?

可以,但有依赖关系【推测】。同一层的不同神经元可以并行计算,但层与层之间必须按顺序执行。现代 GPU 通过向量化操作实现层内并行,分布式训练实现多设备并行。

Q17. 什么是梯度检查(Gradient Checking)?

一种验证反向传播实现正确性的方法。用数值微分计算近似梯度,与反向传播计算的梯度对比,相对误差小于 1e-7 表示实现正确。这是调试神经网络的重要工具。

Q18. 反向传播计算图太大会有什么影响?

内存占用增大。计算图需要存储前向传播的中间变量用于反向传播。如果图太大,可能导致内存不足(OOM)。解决方案:使用 torch.no_grad() 推理、用 del 删除中间变量、减少 batch size。

Q19. PyTorch 中 tensor.backward() 和 optimizer.step() 的关系是什么?

backward() 计算梯度,step() 使用梯度更新参数。backward() 将梯度写入参数的 grad 属性,optimizer.step() 根据梯度更新参数(如 SGD 的 W = W - lr * grad)。

Q20. 深度学习框架如何选择使用 CPU 还是 GPU?

通过 .to(device) 或 .cuda() 移动张量。模型和数据都需要移动到同一设备(CPU 或 GPU)。GPU 适合大规模矩阵运算,可显著加速前向和反向传播。

本文核心知识点回顾

  • 1 个核心区别:前向传播计算输出(数据流),反向传播计算梯度(梯度流)
  • 1 个数学基础:链式法则是反向传播的核心
  • 2 个必须顺序:必须先 forward 再 backward
  • 3 个关键澄清:反向传播不是学习算法、是梯度计算方法、真正的学习是优化器
  • 4 个常见问题:梯度消失、梯度爆炸、梯度清零、计算图内存

Roadmap 后续预告

在理解了前向传播和反向传播之后,后续我们将深入探讨以下主题:

  • PyTorch autograd 进阶:自定义 autograd Function,实现前向和反向的完全控制
  • 梯度下降家族:SGD、Momentum、Adam 等优化器的对比与选择
  • 神经网络训练技巧:BatchNorm、Dropout、Early Stopping 等
  • 分布式训练:DataParallel、DistributedDataParallel 原理
posted @ 2026-07-11 20:55  左扬  阅读(33)  评论(0)    收藏  举报