阶段:S1-基础概念与第一个实验 | 预计耗时:45 分钟 | 前置:S1-00 训练是什么(手算 3 步梯度下降) 代码:本篇代码要你自己敲成
model-learn/s1_autograd_toy.py(15 行)
学习目标
- 理解
requires_grad/.backward()/.grad三个机制:PyTorch 怎么自动算梯度 - 用代码验证你在 S1-00 的手算——两边数字一致,反向传播对你就不神秘了
- 亲手制造"忘记清梯度"的事故,理解训练循环里
zero_grad()为什么必须存在
核心知识
1. autograd 的三件套
w = torch.tensor([0.0], requires_grad=True):打开"记录梯度"开关(参数才有,数据没有)loss.backward():沿计算图反向走一遍,把每个开了开关的张量的梯度填进.gradw.grad.zero_():.grad是累加的,不清零就会越加越大
2. 反向传播没有新东西
它只是"微积分链式法则的自动化"。你手算 loss=(w−2)² 的梯度是 2(w−2),backward() 算出的一模一样——模型再大,本质也是这一件事,只是链条长了几十万节。
3. 为什么更新参数要包在 torch.no_grad() 里
参数更新本身也是张量运算,如果不关记录,计算图会越缠越长、内存爆掉。训练循环里这和 zero_grad() 一样是标配。
实验任务
任务 1:敲出脚本并预测输出(15 分钟)
# s1_autograd_toy.py —— 用 15 行代码看见"反向传播"
# 目标:让参数 w 逼近 2(模型 y = w * x,数据 x=1,真实答案 y=2)
import torch
w = torch.tensor([0.0], requires_grad=True) # 参数:记录梯度
x = torch.tensor([1.0])
y_true = torch.tensor([2.0])
lr = 0.1
for step in range(3):
loss = (w * x - y_true) ** 2 # 前向:算损失
loss.backward() # 反向:自动算出 2*(w-2)
print(f"第{step+1}步 w={w.item():.4f} loss={loss.item():.4f} grad={w.grad.item():.4f}")
with torch.no_grad(): # 更新时不记录梯度
w -= lr * w.grad
w.grad.zero_() # 清梯度
运行后与 S1-00 的手算表逐行核对(w 应该是 0 → 0.4 → 0.72)。
任务 2:制造事故——删掉清梯度那行(15 分钟)
注释掉 w.grad.zero_(),再跑。观察 grad 列:第 2 步不再是 −3.2 而是 −7.2(−4 和 −3.2 累加),w 很快冲过 2 开始震荡。
这就是训练脚本忘写 zero_grad() 的典型症状——你以后一眼能认出来。
任务 3:把步数加到 20(10 分钟)
把 range(3) 改成 range(20),看 w 和 loss 怎么一步步贴近 2 和 0。把最后 5 行输出抄进观察记录表(S1-02),解读:梯度为什么会自己变小?
验收标准
衔接下一阶段
你已从"概念"和"机制"两端理解了训练核心 → 完成 S1-01 的 3 个改动实验后,进入 S2-01 数据处理:数据怎么流进这个循环。