跳到主要内容

迁移学习 — 站在预训练的肩膀上(以及把模型搬去部署)

这一章讲三件事: 迁移学习的两条路(特征提取与微调)各自怎么走、代价与收益; 数据增强怎么「免费」造数据;以及原书另一章的「迁移」——训好的模型怎么搬去部署。 这是第 06 章那条 68%→95% 精度阶梯的最后两级,也是全书最实用的一章: 你自己接下来的大部分项目,都应该从本章的姿势开始。

1. 顶层全景:两种「迁移」

迁移学习(transfer learning)
任务 A(大数据)训好的模型 → 当任务 B(你的小数据)的起点
路线一:特征提取 —— 冻结骨干,只训新的最后分类层
路线二:微调 —— 以预训练权重为起点,小学习率更新全部/部分参数

模型迁移(原书第13章,同名不同义)
训好的模型 → ONNX 通用格式 → 搬到别的框架/设备上推理部署

原书把两件事各写了一章,都叫「迁移」:前者搬能力,后者搬模型文件。本章一起讲,因为它们共享同一个背景——训模型贵,能复用就别重训

2. 特征提取:把预训练网络当「特征机器」

预训练模型哪里来?torchvision 的 models 模块,pretrained=True 就会下载在 ImageNet(1000 类大数据集)上训好的权重,可选 AlexNet、VGG、ResNet、GoogLeNet 等1进料有规范:所有预训练模型要求 3 通道 RGB、尺寸 224 附近,像素先压到 [0,1],再用固定的均值方差归一化(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])——这组数是 ImageNet 的统计量,不是你的数据的2。进料不合规范,精度悄悄掉,而且不报错。

主走查:冻结前后,可训练参数从 1118 万到 5130。 做法:requires_grad=False 冻结全部参数(backward 时不再为它们算梯度)3,再把最后一层换成 10 类的新层。冻结后清点账本:

resnet18 总参数: 11,181,642 个
冻结后可训练参数: 5,130 个(就是新换的那层 512×10 + 偏置)
↓ 意味着
反向传播只走这 5130 个数;优化器也只注册它们
优化器写法:optim.SGD(net.fc.parameters(), …) ← 只交最后一层[^4]

效果:CIFAR-10 上从第 06 章的 68% 涨到 75% 左右,每轮约 3 分 22 秒4。一亿分之五千的参数在动,精度涨了近 10 个百分点——预训练骨干学到的「边缘→纹理→部件」特征,对小图任务照样值钱。

3. 微调:让整张网适配新任务

特征提取不动骨干,骨于是「ImageNet 的理解方式」;如果你的任务与之差异大,就该微调(fine-tuning):以预训练权重为起点(不是随机初始化),用小学习率更新网络——小,是为了别把辛苦训好的参数一把冲坏5。书里的实践判断:微调优于特征提取,因为它能继续优化参数去适配新任务;常用姿势是固定底层(通用特征)、放开顶层(任务相关特征)6

书里的微调实验在特征提取之上再加两手:

  1. 训练数据加数据增强:随机裁剪(256 起步、缩放 0.8~1.0)、±15° 随机旋转、色彩抖动、随机水平翻转,再转张量归一化7;
  2. 换掉最后层(1000 类→10 类),全参数 SGD(学习率比特征提取稍大一点,书里解释太小效果差)8

结果与代价都摆在书里:验证准确率到 95%(0.951334),但每轮约 9 分 15 秒——特征提取 3 分 22 秒换 75%,微调 9 分 15 秒换 95%9这就是「时间换精度」的挂牌价,下单前先想清楚你要哪头。

4. 数据增强:免费的更多数据

提高泛化最重要的三因素是数据、模型、损失函数,而数据最重要、又最难多要10。增强的思路:对图片做不改变标签的随机变换,一份变 N 份。常用件:缩放、随机/中心裁剪、水平垂直翻转、旋转、色彩抖动(亮度/对比度/色调),用 Compose 串成管道11

这条边界比清单更重要:增强必须保持语义。 书里给的例子是手写数字——旋转 90 度会把 6 变成 9、9 变成 6,标签就错了12。判断一件变换能不能用,只问一句:「人看变换后的图,还会标同一个标签吗?」会,才能用。

判断(我们的,不是书里的): 本章的 68→75→95 阶梯给出一个反直觉的优先级:换预训练骨干(+微调)的收益,远大于任何超参调优。 68% 的简单网无论怎么调学习率也到不了 90%;而 2020 年之后这个杠杆更长——换更新的预训练权重常常再涨一截,几乎零成本。实践中「先问用了什么预训练模型,再谈调参」。 如果错,会错在: 如果任务与 ImageNet 分布相距极远(医学影像、遥感、频谱图),预训练的收益会缩水甚至为负,那时从零训练或换领域预训练才是正解。

5. 模型的另一侧:迁移与部署(原书第 13 章)

模型训完还有后半程:搬到生产环境去推理。原书第 13 章讲的正是这条,书写的时代背景是:2017 年 Facebook 把 PyTorch 与 Caffe2 合并——Caffe2 强在移动端部署(iOS/Android/树莓派),PyTorch 强在研究易用;Caffe2 用「计算图+算子」表征网络,有 400 多个算子13

跨框架搬运的通用格式是 ONNX(Open Neural Network Exchange):开放的计算图格式+标准算子集,让「研究框架里训的模型」能搬到「部署框架里跑」,书里点明了它的动机——提高 AI 社区的创新速度,简化从研究到产品化的过程14。操作是「跟踪式」导出:喂一个假输入让模型跑一遍,记录算子轨迹存成 .onnx,再在 Caffe2 后端加载运行,两边输出逐数值比对(assert_almost_equal)确认导出正确15

这段的时代注脚(书出版后被改写): Caffe2 作为独立框架已并入 PyTorch 生态,书里的 torch.onnx._export 老接口也已换名——今天的 torch.onnx.export 默认走基于 torch.export 的新导出路径(补充(不在书里,依据我们的 frontier 书架):依据: shelf=ai-frontier-reference/pytorch@src:torch/onnx/init.py:78 事实=当前源码中 export 的 dynamo 参数默认 True,基于 torch.export,TorchScript 导出路径已注明 deprecated)。要带走的是 ONNX 这个「中间格式」思想——它还活着且更主流;Caffe2 与具体 API 是历史。

6. 一个开胃小菜:去雾网络

本章末尾书里放了一个不走分类路线的小实例:加载预训练的去雾模型,5 层卷积(带特征拼接)输出 clean_image = relu(x5·x − x5 + 1),把雾霾图变清晰16。它的价值在展示「预训练模型的用途不止分类」——同一个迁移思想,换一个输出头。细节本书从略,原书也就一页。

7. 作者的判断与证据

给了证据的: 11181642→5130 的参数清点、75% 与 95% 两级精度、3:22 与 9:15 的每轮耗时,全部是书里的实际运行输出;增强前后的对比图齐全。

给了理由的: 「微调要优于特征提取」给了理由(能继续优化参数适配新任务)6;学习率「稍大一点」给了理由(太小效果差)——都是经验断言,方向可信、数值别照搬。

作者的结构选择: 把数据增强放在本章与微调同讲,而非放进第 5 章正则化——从「免费扩数据」的视角看这个安排成立,但读者应记得它也是正则化手段之一(第 04 章的口径)。

8. 边界与局限

  • 本章冻结的是「全部骨干」。 实战常用「分层学习率」(底层冻结、顶层大学习率),书里只在文字里提了「固定底层、调整顶层」的思路,没给实现。
  • 没有领域自适应。 源域与目标域差异大时的专门技术(如对抗域适应)不在书内;「预训练失效」的场景只在你自己的实验里才会撞见。
  • 部署一章的 API 双重过时(见 §5),但书里留下的排坑笔记(protobuf 版本、import 顺序段错误)仍有参考价值——坑的形状变了,「有坑」这件事没变。
  • ONNX 导出对动态形状、控制流(程序里的分支循环)的支持书里没讲;今天的导出仍有这些边角。

9. 可带走的

  1. 自己的项目默认从预训练模型起步;进料规范(ImageNet 统计量归一化)不合,精度静默掉;
  2. 特征提取=冻结骨干只训新末层:1118 万参数里只动 5130 个,反向传播也只走这 5130 个;
  3. 微调=小学习率整网更新,精度上限更高(75% vs 95%),每轮时间约三倍(3:22 vs 9:15)——挂牌价先看清楚;
  4. 数据增强的合法性判据只有一条:变换后标签不变;旋转 90° 会把 6 变 9;
  5. 优先级:换预训练/微调 > 调参;调参救不了错误的骨干选择;
  6. 「迁移」的另一半是部署:模型→ONNX→其他运行时;中间格式思想活到今天,Caffe2 与旧 API 已成历史;
  7. 迁移的边界:目标域与 ImageNet 相差太远时,预训练收益会缩水,要重新评估。

10. 原文地图

主题原书章原文位置
迁移定义 A→B第10章 迁移学习实例text/11-ch10.txt:27(搜「任务A」)
两条路线第10章 迁移学习实例text/11-ch10.txt:42(搜「特征提取」) · text/11-ch10.txt:44(搜「微调」)
pretrained=True第10章 迁移学习实例text/11-ch10.txt:62(搜「pretrained=True」)
ImageNet 归一化规范第10章 迁移学习实例text/11-ch10.txt:104(搜「0.485」)
冻结 requires_grad第10章 迁移学习实例text/11-ch10.txt:111(搜「requires_grad」)
参数账本 1118 万→5130第10章 迁移学习实例text/11-ch10.txt:203(搜「11181642」) · text/11-ch10.txt:205(搜「5130」)
只优化 fc第10章 迁移学习实例text/11-ch10.txt:214(搜「net.fc.parameters」)
特征提取 75%第10章 迁移学习实例text/11-ch10.txt:222(搜「0.748905」) · text/11-ch10.txt:233(搜「75%」)
数据三因素第10章 迁移学习实例text/11-ch10.txt:210(搜「损失函数」)
9 变 6 的边界第10章 迁移学习实例text/11-ch10.txt:252(搜「9变成6」)
增强手段清单第10章 迁移学习实例text/11-ch10.txt:294(搜「翻转猫还是猫」) · text/11-ch10.txt:305(搜「ColorJitter」) · text/11-ch10.txt:319(搜「Compose」)
微调做法与小学习率第10章 迁移学习实例text/11-ch10.txt:341(搜「较小的学习率」) · text/11-ch10.txt:346(搜「微调要优于特征提取」)
增强组合第10章 迁移学习实例text/11-ch10.txt:355(搜「RandomRotation」)
换末层 1000→10第10章 迁移学习实例text/11-ch10.txt:388(搜「512, 10」)
微调 95% 与耗时第10章 迁移学习实例text/11-ch10.txt:413(搜「0.951334」) · text/11-ch10.txt:415(搜「9分钟」)
去雾实例第10章 迁移学习实例text/11-ch10.txt:418(搜「雾霾」) · text/11-ch10.txt:475(搜「clean_image」)
站在巨人肩膀第10章 迁移学习实例text/11-ch10.txt:521(搜「巨人的肩膀」)
PyTorch 与 Caffe2 合并第13章 Caffe2模型迁移实例text/14-ch13-13-caffe2.txt:2(搜「整合在一起」) · text/14-ch13-13-caffe2.txt:4(搜「树莓派」)
Caffe2 计算图与算子第13章 Caffe2模型迁移实例text/14-ch13-13-caffe2.txt:35(搜「计算图」) · text/14-ch13-13-caffe2.txt:53(搜「400个算子」)
ONNX 定位第13章 Caffe2模型迁移实例text/14-ch13-13-caffe2.txt:107(搜「ONNX」) · text/14-ch13-13-caffe2.txt:112(搜「从研究到产品化」)
导出与比对第13章 Caffe2模型迁移实例text/14-ch13-13-caffe2.txt:175(搜「_export」) · text/14-ch13-13-caffe2.txt:199(搜「assert_almost_equal」)

Footnotes

  1. 出处:「第10章 迁移学习实例」第 62 段(text/11-ch10.txt:62,搜「pretrained=True」)与第 67 段(text/11-ch10.txt:67,搜「AlexNet」)。

  2. 出处:「第10章 迁移学习实例」第 104 段(text/11-ch10.txt:104,搜「0.485」)。

  3. 出处:「第10章 迁移学习实例」第 111 段(text/11-ch10.txt:111,搜「requires_grad」)。

  4. 出处:「第10章 迁移学习实例」第 233 段(text/11-ch10.txt:233,搜「75%」)与第 222 段(text/11-ch10.txt:222,搜「Time 00:03:22」)。

  5. 出处:「第10章 迁移学习实例」第 341 段(text/11-ch10.txt:341,搜「较小的学习率」)。

  6. 出处:「第10章 迁移学习实例」第 344 段(text/11-ch10.txt:344,搜「固定底层」)与第 346 段(text/11-ch10.txt:346,搜「微调要优于特征提取」)。 2

  7. 出处:「第10章 迁移学习实例」第 354 段(text/11-ch10.txt:354,搜「RandomResizedCrop」)与第 355 段(text/11-ch10.txt:355,搜「RandomRotation」)。

  8. 出处:「第10章 迁移学习实例」第 388 段(text/11-ch10.txt:388,搜「512, 10」)与第 393 段(text/11-ch10.txt:393,搜「稍大一点学习率」)。

  9. 出处:「第10章 迁移学习实例」第 413 段(text/11-ch10.txt:413,搜「0.951334」)与第 415 段(text/11-ch10.txt:415,搜「9分钟」);特征提取耗时对比见第 415 段(搜「3分钟左右」)。

  10. 出处:「第10章 迁移学习实例」第 210 段(text/11-ch10.txt:210,搜「损失函数」)。

  11. 出处:「第10章 迁移学习实例」第 294 段(text/11-ch10.txt:294,搜「翻转猫还是猫」)与第 319 段(text/11-ch10.txt:319,搜「Compose」)。

  12. 出处:「第10章 迁移学习实例」第 252 段(text/11-ch10.txt:252,搜「9变成6」)。

  13. 出处:「第13章 Caffe2模型迁移实例」第 2 段(text/14-ch13-13-caffe2.txt:2,搜「整合在一起」)与第 35 段(text/14-ch13-13-caffe2.txt:35,搜「计算图」)、第 53 段(text/14-ch13-13-caffe2.txt:53,搜「400个算子」)。

  14. 出处:「第13章 Caffe2模型迁移实例」第 112 段(text/14-ch13-13-caffe2.txt:112,搜「从研究到产品化」)。

  15. 出处:「第13章 Caffe2模型迁移实例」第 175 段(text/14-ch13-13-caffe2.txt:175,搜「_export」)与第 199 段(text/14-ch13-13-caffe2.txt:199,搜「assert_almost_equal」)。

  16. 出处:「第10章 迁移学习实例」第 475 段(text/11-ch10.txt:475,搜「clean_image」)。