01 / 我们要解决什么
少量带标签图像,识别四种方向
输入是一张 24×24 灰度图,输出“横线、竖线、左上到右下、左下到右上”中的一种。源域图片清晰,目标域图片对比度低、噪声多,还可能有亮点和线条断口。两个域的标签含义相同,图像分布不同。
每类 180 张训练图,训练并保存 CNN 权重。
每类 8 张或 32 张,对照三种训练方案。
迁移有无收益?何时冻结?哪类图最容易误判?
02 / 先观察输入
同一类图像,视觉条件变了
深色表示小像素值,浅色表示大像素值。切换方向,观察清晰源域与嘈杂目标域。
源域验证集中的一张
目标域测试集中的一张
模型只看到像素数组与训练标签,不会看到“横线”这两个字。训练、验证、测试用不同种子独立生成。
03 / 先定协议
验证集挑方案,测试集报告结果
三种目标方案使用相同的图像划分、12 轮上限和分类头初值。每种方案按目标验证损失选择最佳轮次,然后分别报告测试成绩。每类数量相等,随机猜测的期望准确率约 25%。绝不使用测试集来训练或挑轮次。
像素统一裁剪到 [0,1];本项目没有用全数据拟合均值、方差等预处理参数。每类 8 张的目标训练集是每类 32 张训练集的子集,验证和测试集固定。
04 / 把形状算清楚
小型 CNN 的每一层
三层卷积均是 3×3、padding=1、stride=1,因此不改变高宽。两次 2×2 池化使 24→12→6。最后每张图有 24×6×6=864 个特征。全部参数 8188 个:卷积特征层 4728 个,分类头 3460 个。
训练时将 logits 直接交给 CrossEntropyLoss;展示各类概率时才调用 softmax。PyTorch 交叉熵文档。
05 / 预训练权重如何复用
加载特征层,换一个新分类头
代码先在源域训练模型,保存 source-pretrained.pt,之后从文件加载卷积特征层。目标任务的分类头重新初始化,不会直接沿用源域输出层。
冻结指特征参数设 requires_grad=False,优化器只更新新分类头。微调指从预训练权重出发,让整个网络继续学习。代码会核对冻结后的特征权重完全没变,微调后确实变了。PyTorch 官方迁移学习教程也展示了这两种方式。
06 / 比较真实结果
目标标签数量会改变结论吗?
| 方案 | 验证准确率 | 测试准确率 | 最佳轮次 | 可训练参数 |
|---|
绿色:训练损失;橙色:验证损失;蓝色虚线:按验证损失选出的轮次。
这是固定随机种子的一次教学实验,没有进行多种子统计比较。数个百分点的差别不应直接当成普遍规律。
07 / 看错在哪里
混淆矩阵与逐张图像
矩阵行是真实类别,列是预测类别;对角线是预测正确的数量。
展示集合包含每类代表图、各方案的一部分误判和低置信度图;不是全部 320 张测试图。置信度未校准,不能直接理解为“正确概率”。单张误判可启发假设,不能证明原因。
08 / 成本也要一起看
预训练不是免费的
秒数来自本机极短的 CPU 实验,有环境噪声,只适合理解量级。冻结时特征层不反向传播,但代码仍每批计算卷积前向;若缓存固定特征,可进一步减少重复计算,代价是额外存储。比较总成本时应加上源域预训练;已有权重的“新增训练成本”可以另报。
09 / 自己复现
运行、改一个因素、写项目报告
项目仅使用已安装的 CPU PyTorch 和 NumPy。程序会生成数据、训练预训练权重和六种目标实验,输出 JSON 与 CSV。直接打开本地 HTML 不会重新训练,也无需启动服务。
uv run python lessons/lesson-09-project.py完整代码 · 结果 CSV · 完整 JSON · 预训练权重
- 先手算逐层形状,解释为什么分类头输入为 864。
- 比较每类 8 张与 32 张时三种方案。指出验证集和测试集各自用途。
- 选两张误判,分别写下“观察到什么”和“可能原因”。
- 只改一个因素再运行,例如目标图像噪声、每类训练图数量或是否冻结卷积层;记录变化。
- 写一页 Markdown 报告:任务、数据划分、模型、结果、误判、成本、限制和下一步。
10 / 自测