torch.nn概念辨析:正向传播 vs 反向传播

106 阅读7分钟

我们用生活化比喻 + 图解流程 + 代码示例 + 一句话口诀,向初学者彻底讲清楚:


🎯 正向传播 vs 反向传播 —— 它们的关系就像“做菜”和“复盘改进”

💡 一句话总结:
正向传播 = 做菜(输入食材 → 输出菜品)
反向传播 = 吃完打分后,倒推“哪个调料放多了/少了” → 下次改进!

两者缺一不可,共同组成神经网络的“学习闭环”!


一、生活化比喻:开餐厅的厨师 👨‍🍳

你是一个AI厨师,目标是做出顾客打分最高的菜。

🍳 正向传播 = 做菜过程

  1. 你拿到食材(输入数据)
  2. 按配方加调料(模型参数)
  3. 炒菜、炖煮、摆盘(网络层计算)
  4. 端出成品(预测结果)
  5. 顾客打分(计算损失)

输出:一道菜 + 一个分数


📊 反向传播 = 复盘改进过程

  1. 你看顾客打分(损失值)
  2. 倒着想:“为什么分低?是盐多了?火候过了?”
  3. 计算每个调料对打分的“影响程度”(梯度)
  4. 记录:“下次盐少放0.5克,火候减10秒”
  5. 更新你的菜谱(更新模型参数)

输出:每个参数的调整方向和幅度(梯度)


二、神经网络中的对应关系

做菜流程神经网络流程PyTorch 代码示例
食材输入数据 xx = torch.randn(1, 784)
菜谱(调料比例)模型参数 w, bw = torch.randn(..., requires_grad=True)
炒菜步骤前向计算 y = f(x)y_pred = model(x)
成品预测结果y_pred
顾客打分损失函数 lossloss = criterion(y_pred, y_true)
复盘分析反向传播loss.backward()
改进菜谱更新参数optimizer.step()

三、图解:正向与反向的数据流向

正向传播(Forward)→ 从左到右
输入 x → Layer1 → Layer2 → ... → 输出 y_pred → 计算 Loss

反向传播(Backward)→ 从右到左
Loss → 计算 ∂Loss/∂y_pred → ∂Loss/∂w_last → ... → ∂Loss/∂w_first

📌 关键点

  • 正向传播构建计算图(PyTorch 在后台默默记录每一步操作)
  • 反向传播沿着计算图反向求导(用链式法则)
  • 没有正向 → 就没有计算图 → 反向传播无从谈起!
  • 没有反向 → 就不知道怎么改参数 → 模型不会学习!

正向传播 (Forward Propagation / Forward Pass)

  • ​目的:​​ ​​计算预测结果​​。它是模型根据输入数据,一步步通过每一层(线性层、激活函数等)进行计算,最终得到输出(预测值)的过程。
  • 过程:​​ 输入数据 → 层1计算 → 层2计算 → ... → 输出预测。
  • 在 PyTorch 中:​​ 这就是你调用 model(x)或 model.forward(x)时发生的事情。这是你​​显式​​或​​隐式​​地在执行的操作。

反向传播 (Backward Propagation / Backward Pass):​

  • 目的:​​ ​​计算梯度​​。在得到预测结果后,我们将其与真实标签进行比较,计算损失(Loss)。反向传播就是根据这个损失值,从输出层开始,逆向地计算损失函数相对于​​每一个模型参数​​(权重、偏置)的梯度(导数)。
  • 过程:​​ 计算损失 → 从损失开始反向求导(应用链式法则)→ 得到每一层参数的梯度。
  • 在 PyTorch 中:​​ 这是通过调用 loss.backward()来​​显式​​触发的。这一步会利用正向传播过程中自动记录的计算图来完成。

正向传播和反向传播是一个紧密耦合的循环过程,模型训练就是不断重复这个循环:

  • 第1步:正向传播 (Forward Pass)​ predictions = model(inputs) # 执行正向传播!
  • 第2步:计算损失 (Loss Calculation)​ loss_function = torch.nn.MSELoss(); loss = loss_function(predictions, labels)
  • 第3步:反向传播 (Backward Pass)​ model.zero_grad(); loss.backward()
  • 第4步:更新参数 (Parameter Update)​ optimizer.step() # 执行更新!

四、代码示例:完整闭环

import torch
import torch.nn as nn

# 1️⃣ 定义参数(菜谱)
w = torch.tensor(2.0, requires_grad=True)  # 盐的量
b = torch.tensor(1.0, requires_grad=True)  # 酱油的量

# 2️⃣ 准备数据(食材)
x = torch.tensor(3.0)        # 食材份量
y_true = torch.tensor(8.0)   # 顾客期望的味道值

# ⚡⚡⚡ 训练循环开始 ⚡⚡⚡
for step in range(3):
    print(f"\n=== 第 {step+1} 次尝试 ===")
    
    # 🍳 正向传播:做菜
    y_pred = w * x + b           # 预测味道 = 盐*x + 酱油
    loss = (y_pred - y_true)**2  # 顾客打分(越小越好)
    print(f"预测: {y_pred.item():.2f}, 损失: {loss.item():.2f}")
    
    # 📊 反向传播:复盘分析
    loss.backward()              # 自动计算梯度
    print(f"盐的梯度: {w.grad.item():.2f}, 酱油的梯度: {b.grad.item():.2f}")
    
    # 🔧 更新参数:改进菜谱
    with torch.no_grad():
        w -= 0.1 * w.grad    # 根据梯度调整盐量
        b -= 0.1 * b.grad    # 调整酱油量
    
    # 🧹 清空梯度:准备下次复盘
    w.grad.zero_()
    b.grad.zero_()

输出示例

=== 第 1 次尝试 ===
预测: 7.00, 损失: 1.00
盐的梯度: -6.00, 酱油的梯度: -2.00

=== 第 2 次尝试 ===
预测: 7.80, 损失: 0.04
盐的梯度: -1.20, 酱油的梯度: -0.40

=== 第 3 次尝试 ===
预测: 7.96, 损失: 0.00
盐的梯度: -0.24, 酱油的梯度: -0.08

✅ 看!通过“做菜→打分→复盘→改进”的循环,模型越来越接近目标!


五、关键关系总结

方面正向传播反向传播关系说明
方向输入 → 输出损失 → 输入参数方向相反,形成闭环
目的得到预测结果得到参数梯度一个出结果,一个出改进方案
依赖无依赖必须先有正向传播没有forward,backward会报错!
计算内容y = f(x; w)∂Loss/∂w, ∂Loss/∂x一个算值,一个算导数
触发方式直接调用 model(x)调用 loss.backward()顺序执行
是否可导构建计算图(记录操作)沿图反向求导正向是反向的前提

总结与对比

特性正向传播 (Forward Pass)反向传播 (Backward Pass)
​目的​计算模型的​​预测输出​计算模型参数的​​梯度​
​触发方式​调用 model(input)调用 loss.backward()
​核心作用​​推理(Inference)​​ 和 ​​训练的第一步​​训练的关键​​,用于优化参数
​PyTorch机制​​动态构建​​计算图​遍历计算图​​进行自动求导
​类比​​沿着路向前走​​,到达目的地(预测结果)​沿着来时的路往回走​​,记录下每一步的坡度(梯度)

六、初学者常见问题解答

❓ 1. 为什么一定要先正向再反向?

因为反向传播需要“计算图”——这个图是在正向传播时动态构建的!

w = torch.tensor(2.0, requires_grad=True)
# loss.backward()  # ❌ 报错!还没有计算图!

y = w * 3         # ✅ 正向传播 → 构建计算图
loss = (y - 5)**2
loss.backward()   # ✅ 现在可以了!

🧠 比喻:你还没做菜,顾客怎么打分?没打分,你怎么知道哪里要改进?


❓ 2. 正向传播时为什么要设置 requires_grad=True

只有设置了 requires_grad=True 的张量,PyTorch 才会为它计算梯度!

w = torch.tensor(2.0, requires_grad=True)  # 我要优化这个参数!
x = torch.tensor(3.0)                      # 输入数据,通常不需要梯度

y = w * x
loss = (y - 8)**2
loss.backward()

print(w.grad)  # tensor(-6.) ← 有梯度!
print(x.grad)  # None ← 没设置 requires_grad,不计算梯度

🧠 比喻:你只想知道“盐和酱油”怎么调,不关心“食材份量”怎么变 → 只标记调料!


❓ 3. 为什么每次反向传播前要 zero_grad()

因为 PyTorch 默认梯度累加!不清零会把上次的梯度加进来!

w = torch.tensor(2.0, requires_grad=True)

for i in range(2):
    y = w * 3
    loss = (y - 5)**2
    loss.backward()
    print(f"第{i+1}次: w.grad = {w.grad}")  # 第一次: -6, 第二次: -12(累加了!)

# ✅ 正确做法:
for i in range(2):
    optimizer.zero_grad()  # 或 w.grad.zero_()
    y = w * 3
    loss = (y - 5)**2
    loss.backward()
    print(f"第{i+1}次: w.grad = {w.grad}")  # 第一次: -6, 第二次: -6

🧠 比喻:复盘时要把上次的改进建议清空,否则会和新建议混在一起!


七、终极口诀送给初学者:

“先正向,再反向,
正向出结果,反向出梯度,
梯度指方向,参数跟着调,
清零不能忘,循环步步高!”


🎁 小测验(巩固理解):

Q1:反向传播计算的是什么?
👉 梯度(导数)

Q2:没有正向传播,能直接反向传播吗?
👉 不能!会报错

Q3:requires_grad=True 的作用是?
👉 告诉 PyTorch:这个参数我要求梯度!

Q4:为什么需要 zero_grad()
👉 防止梯度累加,保证每次更新基于当前 batch


🎉 恭喜你!现在你已经彻底理解了正向传播和反向传播的“相爱相杀”关系!
它们是深度学习的心脏和大脑 —— 一个负责“感知世界”,一个负责“学习改进”!