阶段:S3-模型构建 | 预计耗时:60 分钟 | 前置:S3-02 模型消融实验 代码:本篇代码要你自己敲成
model-learn/s3_inference.py(加载你已训好的mnist_cnn.pt)
学习目标
- 理解
state_dict:保存/加载的只是参数字典,不含模型结构——所以加载前必须先建同结构模型 - 掌握推理模式三件套:
load_state_dict+model.eval()+torch.no_grad() - 写出一个"加载现成模型 → 对新图片做预测"的推理脚本——这就是"部署"的最小雏形
核心知识
1. 训练侧和推理侧的分界
| 训练时 | 推理时 | |
|---|---|---|
| 前向 | 需要,记录计算图 | 需要,no_grad() 不记录(省内存提速) |
| 反向 | backward() |
没有 |
| Dropout | 开(随机丢) | 关(eval()) |
| BatchNorm | 按批统计 | 用训练期的固定统计量(eval()) |
| 权重 | 每步在变 | 冻结(就是 state_dict 里那本字典) |
诚实说明:SmallCNN 没有 Dropout/BN,本实验 eval() 恰好无区别——但习惯必须现在养成,S4 加 Dropout 后立刻见效。
2. 保存什么、怎么加载
torch.save(model.state_dict(), "mnist_cnn.pt") # 只存参数字典
model.load_state_dict(torch.load("mnist_cnn.pt")) # 先建同结构模型,再灌参数
- 为什么不存整个模型(
torch.save(model)):依赖类定义路径、版本脆弱——state_dict是标准做法 - 你的
mnist_cnn.pt只有约 1MB:206,922 × 4 字节 ≈ 0.8MB。几亿参数的大模型才需要考虑磁盘
实验任务
任务 1:写推理脚本(30 分钟)
# s3_inference.py —— 加载现成模型,对测试集前 10 张做预测并画出来
import torch, torch.nn as nn
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
# 1) 先按原样重建 SmallCNN(类定义从 01_mnist_cnn.py 抄过来)
model = SmallCNN()
model.load_state_dict(torch.load("mnist_cnn.pt"))
model.eval() # 推理模式
test_ds = datasets.MNIST("data", train=False, download=False,
transform=transforms.ToTensor())
imgs = [test_ds[i] for i in range(10)]
with torch.no_grad(): # 不记录梯度
x = torch.stack([im for im, _ in imgs]) # (10,1,28,28)
pred = model(x).argmax(1)
for (im, label), p in zip(imgs, pred):
pic = "\n".join("".join("#" if v > 0.5 else "." for v in row)
for row in im[0].tolist())
print(f"真值={label} 预测={p.item()} {'√' if label == p.item() else '×'}")
print(pic, "\n")
任务 2:对账实验(15 分钟)
把 model.eval() 换成 model.train() 跑一遍对比(本模型无 Dropout,结果应完全一致——验证 S3-01 的知识),再口头回答:什么模型下这两个模式结果会不同?
任务 3:破坏实验(15 分钟)
把 SmallCNN() 换成一个结构不同的模型(比如去掉一个 conv)再 load_state_dict——读报错里的 "size mismatch"/"Missing key"。这类报错 99% 是"结构与参数文件对不上"。
验收标准
衔接下一阶段
模型全生命周期(建→训→存→用)已打通 → S4-01 loss 曲线解读:回到训练现场,学会"看曲线诊病"。