阶段:S3-模型构建 | 预计耗时:75-90 分钟(自动跑 5 组,约每组 1-2 分钟)| 前置:S3-01 nn.Module 解剖 代码:本篇代码要你自己敲成
model-learn/s3_ablation.py(一个脚本带--variant参数自动跑)
学习目标
- 掌握消融实验(ablation)方法论:固定其他一切,只拆一个零件,看性能掉多少——这是工程上证明"某个设计有用"的标准手段
- 实测卷积层、池化层、模型宽度/深度各贡献几个百分点
- 建立"参数量 vs 准确率"的直觉:加参数不是一定涨分
核心知识
1. 消融实验 = 模型界的控制变量法
- 每个变体只改一处,种子固定、数据固定、训练轮数固定
- 结论格式:"去掉 X 后准确率从 A 降到 B,所以 X 值 (A−B) 个百分点"
- 工业界论文里的 ablation table 就是这么来的
2. 五个变体(其余与基线完全一致)
| 变体 | 改动 | 预期 |
|---|---|---|
| baseline | SmallCNN 原样 | ~98%(全量时) |
| no_conv | 只留 Flatten+Linear | 卷积的空间特征没了,明显降 |
| no_pool | 去掉两个 MaxPool | 参数暴增(算算 32×14×14×128!),收敛更慢 |
| wider | Linear 隐层 128→512 | 变化不大——瓶颈不在宽度 |
| deeper | 加一层 conv+relu | 小幅波动——小任务深度已够 |
3. 本机运行规模
5000 样本 × 2 epoch,每组约 1-2 分钟,5 组约 10 分钟——同一份脚本连跑,避免人为时差。
实验任务
任务 1:写消融脚本(40 分钟)
骨架(变体函数 + 统一训练入口):
# s3_ablation.py —— 一次只拆一个零件
import argparse, torch, torch.nn as nn
from torchvision import datasets, transforms
from torch.utils.data import DataLoader, Subset
def build(variant):
layers = []
if variant != "no_conv":
layers += [nn.Conv2d(1, 16, 3, padding=1), nn.ReLU()]
if variant != "no_pool": layers += [nn.MaxPool2d(2)]
layers += [nn.Conv2d(16, 32, 3, padding=1), nn.ReLU()]
if variant != "no_pool": layers += [nn.MaxPool2d(2)]
flat = 32 * (14 if variant == "no_pool" else 7) ** 2 # no_pool 不减半!
else:
flat = 784
if variant == "deeper":
layers += [nn.Conv2d(32, 32, 3, padding=1), nn.ReLU(),
nn.MaxPool2d(2)]; flat = 32 * 3 * 3
layers += [nn.Flatten(), nn.Linear(flat, 512 if variant == "wider" else 128),
nn.ReLU(), nn.Linear(512 if variant == "wider" else 128, 10)]
return nn.Sequential(*layers)
# 训练入口:5000 样本 / 2 epoch / seed 42 / Adam 1e-3,返回测试准确率与参数量
#(把 S1-01 的训练五行抄进一个 train_one(variant) 函数;结尾 argparse 循环五个变体)
任务 2:跑全 5 组并制表(15 分钟)
for v in baseline no_conv no_pool wider deeper; do
.venv/Scripts/python.exe s3_ablation.py --variant $v
done
结果按"变体/参数量/准确率/耗时"四列抄进观察记录表。
任务 3:解读三个数字(15 分钟)
- 卷积贡献了几个百分点?
- no_pool 的参数量是 baseline 的几倍?分数反而怎样?
- wider/deeper 几乎不动——说明什么?(小数据集,瓶颈在数据不在模型)
验收标准
衔接下一阶段
知道每个零件值多少分 → S3-03 保存加载与推理:把训好的模型真正用起来。