跳到主要内容

不改那句话,改编码器外面 — 外挂一个小网络,或者干脆查表

这一章讲三件事: 除了改那句话,还能改哪儿; 「一次训练都不做」为什么可能赢过训了 14 小时的对手; 以及一张把四项成本并排摆出来的表——这是全书最实用的一页。 它在全书链条里的位置: 第 08 章那条路走到头(泛化不稳、推理变慢), 这一章换一个地方下手,而且它证明了「改哪儿」比「改多少」更要紧。

1. 顶层全景:同一个任务,六条路

本章的主走查: 一个千类图像分类任务,每类只给 16 张标注图, 显卡是一张消费级的 RTX 3090,批量 32。六条路各走一遍,四项成本并排量1:

走法准确率训练时长单次推理显存
① 什么都不做(零样本)60.33010.22 ms2227 MiB
② 只训一个线性分类器56.13(比零样本还低 4.20)13 分钟
③ 学那句话(第 08 章那条路)62.9514 小时 40 分299.64 ms7193 MiB
④ 外挂一个两层小网络63.5950 分钟10.59 ms2227 MiB
干脆不训练:查表62.030 分钟10.42 ms2227 MiB
⑥ 查表 + 微调 20 轮65.515 分钟10.53 ms2227 MiB

先把这张表里最刺眼的三处点出来:

  1. 第 ② 行是负的:只在特征上训一个分类器,比什么都不做还低 4.20;
  2. 第 ③ 行的推理时延是别人的 29 倍,显存是 3 倍多;
  3. 第 ⑤ 行零训练拿到 62.03,而第 ⑥ 行只花 5 分钟就到了全场最高的 65.51—— 对照第 ③ 行的 14 小时 40 分。

下面三节把 ④ ⑤ ⑥ 这三条路各拆开讲,第 6 节回来解释第 ③ 行为什么那么慢。

2. 第一条路:在冻住的编码器后面外挂一个小网络

先看问题

第 08 章改的是输入端那句话。但模型的输出端也是可以动的: 图片过完编码器得到一串数,在它去和类名比之前,先让一个小网络加工一下。

做法:两层小网络 + 按比例混合

书里的做法只有两步2:

一张图 → 冻住的图像编码器 → 一串数 f

┌───────────┴───────────┐
│ │
过一个两层小网络 原样保留
得到 f_a(加工过的) f(原来的)
│ │
└──── 按 α 比例混合 ─────┘

再去和各类名的那串数比

分类结果

图说:那个小网络是「先把 512 个数压到很少几个,再升回 512 个」的两层结构;
**只有它是可训练的,CLIP 两个编码器全部冻住。**

混合比例 α 有明确的含义,书里写得很清楚:

α 大,表示更依赖新学的东西;α 小,表示更依赖原来的零样本知识。 预训练与下游任务差得越远,α 就该越大2

为什么要混合而不是直接用新学的: 因为只有 16 张样例,学出来的东西本来就不可靠; 原来的零样本知识是一个兜底。(第 04 章第 ⑦ 堵墙:数据少 → 过拟合。)

还有一个细节值得记: 书里说原始的那种「适配器」是嵌在主干每一层里面的, 而这一版只挂在最后——理由是「CLIP 预训练的编码器已经给出了很强的表示, 只需要极小的残差调整」2。(嵌在每一层里的那一族,第 10 章整章讲。)

3. 第二条路:连训练都不要,把样例存成一张表

这是全章最值得记住的一招。

做法:样例图的特征当索引,标签当内容

准备阶段(只做一次,不是训练):
1000 个类 × 16 张样例 = 16000 张图
│ 各过一遍冻住的图像编码器

16000 串数 ← 这些当「索引」(书里叫 key)
16000 个标签 ← 各写成一个「只有正确那一位是 1」的清单(书里叫 value)

用的时候:
一张测试图 → 一串数
│ 和 16000 个索引各算一次相似度

16000 个相似度 → 各自换算成一个权重
│ 按权重把 16000 个标签加权加起来

「样例们的投票结果」
│ 再和原来的零样本结果按比例混合(和第 2 节同一个混合)

最终分类

整个过程只有两次矩阵乘法,一次梯度都不用算3

走查:三个类、每类两张样例,一张测试图

**下面这些数是为演示编的,不是真实数值。**

表里存了 6 条(3 个类 × 2 张样例):
索引①(柯基照片1) → 内容 [1,0,0]
索引②(柯基照片2) → 内容 [1,0,0]
索引③(猫照片1) → 内容 [0,1,0]
索引④(猫照片2) → 内容 [0,1,0]
索引⑤(飞机照片1) → 内容 [0,0,1]
索引⑥(飞机照片2) → 内容 [0,0,1]

来了一张新的柯基照片:
和六个索引的相似度 0.82 / 0.75 / 0.31 / 0.28 / 0.05 / 0.03
换算成权重后 0.41 / 0.33 / 0.13 / 0.09 / 0.02 / 0.02
加权求和 [0.74, 0.22, 0.04] ← 样例们的投票
零样本那一路的结果 [0.72, 0.21, 0.07] ← 第 01 章那条链
按 α=0.5 混合 [0.73, 0.215, 0.055] → 判为柯基

图说:「换算成权重」那一步有一个可调的锐度参数,它决定
「最像的那张样例说了算」还是「大家一起投票」。

为什么它能工作:因为它没有在学,它在回忆

这一招之所以成立,全靠第 01 章那个前提:CLIP 的特征本身已经很好用了。

表里存的不是「训练出来的知识」,是「16000 张图各自长什么样」。 查表 = 在这 16000 张里找最像的几张,看它们是什么类。

4. 不训练怎么可能赢?看样例只有一张的时候

书里给了一条随样例数变化的曲线,最左边那一列最能说明问题4:

每类给几张样例只训一个线性分类器学那句话零训练查表查表 + 微调
1 张22.1757.1560.7061.32
2 张31.9057.8160.9661.69
4 张41.2059.9960.9862.52
8 张49.5261.5661.4564.00
16 张56.1362.9562.0365.51

三条读法:

  1. 1 张样例时,零训练的 60.70 赢过训了 14 小时 40 分的 57.15;
  2. 只训一个分类器那一列从 22.17 起步——样例太少时,从零学一个分类器基本是灾难;
  3. 样例变多之后,零训练那一列涨得很慢(60.70 → 62.03), 而微调版涨得快(61.32 → 65.51)——书里说这个差距从 +0.62 涨到 +3.444

第 3 条的含义:表越大,「怎么用这张表」就越值得学一学。 微调版做的正是这件事:把表里的索引解冻,当成可训练的数去调 20 轮 (5 分钟),而表里的内容(标签)一个字不动—— 书里给的理由是「类别信息必须保持完整」5

5. 这两条路其实是同一条

书里做了一件很干净的事:证明第 2 节那个小网络是第 3 节那张表的一个特例。

怎么对上的: 把小网络第一层的权重换成「16000 个索引」、 第二层的权重换成「16000 个标签」、偏置(每一层额外加上去的那个固定值)设成 0、 中间那个激活函数(夹在两层之间、把结果做一次非线性变形的小函数) 换成查表用的那个换算方式——两条式子就完全一样了6

既然是同一件事,差别就只剩三处6:

差在哪外挂小网络查表
索引和内容怎么来随机初始化,靠训练调出来直接由数据造出来,不用训
中间那层多宽故意压得很窄,防过拟合就是样例总数(16000),能把全部样例信息装下
中间那个换算一种常见的激活函数一种天然落在 0 到 1 之间的换算(因为输入是相似度)

这一节的价值不在结论,在方法: 当两个方法看起来不同,先试试能不能把一个写成另一个的特例。 写得出来,差别就从「两种思路」缩小成「三个具体选择」——这才是能讨论的东西。

判断(我们的,不是书里的): 把第 08 章和这一章连起来看, 决定成败的是「在哪儿动手」,不是「动多少」。 同样是拿 16 张样例学点东西:动在文本那一侧,推理慢 29 倍; 动在图像特征之后,时延几乎不变;完全不动、只把样例存起来查,1 张样例时就赢。 如果错,会错在: 这三条路的准确率差距其实不大(62.03 到 65.51), 真正拉开的是成本。如果某个场景里准确率的边际收益极高(比如医疗筛查), 那「动多少」就重新变成主要问题,这条判断就该反过来读。

6. 回到那张表:为什么学那句话推理慢 29 倍

这一节回答第 1 节留下的问题,答案朴素得让人意外7:

学那句话(第 08 章那条路):
提示词是可训练的 → 每次训练迭代、每次推理,
都要拿着当前的提示词**重新跑一遍文本编码器**,
把 1000 个类名重新算成 1000 串数。

单次推理 299.64 ms,显存 7193 MiB

改编码器外面(本章两条路):
文本那边**从头到尾没变过** → 1000 个类名的那 1000 串数
可以在开始前算一次、存起来、反复用。

单次推理 10.42–10.59 ms,显存 2227 MiB

29 倍这个数就是这么来的:299.64 ÷ 10.22 ≈ 29.3(我们的算术)。

这条经验可以直接带走: 凡是「可训练的东西在文本那一侧」,文本特征就不能预先算好; 凡是「可训练的东西在图像特征之后」,它就能。 这个差别不影响准确率,但影响你能不能上线。

7. 挂在哪一层:挂满反而最差

一个自然的想法是:既然挂一个有用,那每一层都挂是不是更好?书里试了,结论是反的8:

⚠️ 换设定了,先说清楚,否则这几个数会被读错。 这一组出自原书另一张表,图像那半边用的是 ViT-B/16; 而第 1 节那张成本表用的是 RN50同样是「外挂小网络」,第 1 节是 63.59、这里是 70.88——差的不是方法,是主干。 第 02 章第 6 节讲过的那条规矩在这里第一次真正咬人: 看到任何一个分数,第一件事是问「这是哪个主干」。 下面这四行只能彼此比,不能和第 1 节那张表的任何一行比。

挂在哪(主干 ViT-B/16)准确率参数量
所有层都挂67.67(最差)5.20M
只挂在最后一层(第 12 层)70.880.52M
挂在第 10 层71.85(这张表里最高的一个数)0.78M
挂在更靠前的层70.55 → 69.05,总体下滑0.78M

这里原书自己和自己打架,照实记下: 书里的正文写的是 「插在第 12 层(也就是末尾)效果最好」,可它自己那张表里 第 10 层的 71.85 高过第 12 层的 70.88。 书里没有解释这处不一致,也没有说 71.85 是哪里来的例外8我们照表取数,同时把书里的说法原样摆在这儿,由你判断。

书里给的解释有两条:

  1. 挂满会让参数量从 0.52M 涨到 5.20M,而样例只有 16 张一类——过拟合;
  2. 挂在靠前的层会打断 CLIP 已经学好的东西(原文:有「扰乱预训练知识」的风险)8

这条结果和第 08 章那个「向量越多越不稳」是同一个道理的两次出现: 在少样本这个前提下,能动的东西越多,越危险。

8. 一个反直觉的附加结论:不训练的反而更抗漂移

书里还测了「换个画风」——和第 08 章第 5 节同一类实验9:

方法原数据集另一批照片素描版
什么都不做(零样本)60.3353.2735.44
只训一个线性分类器56.1345.6119.13
学那句话62.9554.5831.04(低于零样本)
外挂小网络63.5955.6935.68
零训练查表62.0354.6035.90(高于零样本)
查表 + 微调65.5157.1136.00

书里的解释:查表法的索引和内容是直接从数据造出来的、没有经过训练, 所以它天然规避了「过拟合到源数据集」的风险9

这一行和第 08 章第 6 节那个「换数据集就废」放在一起看,才是完整的图景: 训练得越少,漂移时掉得越少。 而完全不训练的那一条,在素描版上甚至高于零样本。

9. 作者的判断与证据

书里给了证据的:

说法证据
零训练能赢过训 14 小时的1 张样例时 60.70 对 57.15
微调 20 轮就能到最好65.51,而对照的两条路都要 200 轮
学那句话推理慢、占显存299.64 ms / 7193 MiB 对 10.22 ms / 2227 MiB
挂满所有层最差67.67 对最好的 71.85,参数量 5.20M 对 0.78M
不训练的更抗漂移素描版 35.90 对 31.04 对 19.13
两个方法是同一件事给出了把一个改写成另一个的具体条件

属于作者的推断:

  • 「挂在靠前的层会扰乱预训练知识」——原文用的是「risks disrupting」, 是解释,不是实验结论(实验只测了准确率下降这个事实);
  • 「查表法更稳是因为它的构造不经过训练」——同样是解释; 不过它和第 08 章那条「提示词不扭曲预训练特征」互相印证。

10. 边界与局限

① 那张成本表只有一台机器、一个数据集、一个批量。 一张 RTX 3090、千类分类、批量 32——换配置这些绝对值都会变能带走的是比例关系(29 倍、3 倍),不是绝对值。

② 表越大,查表越慢——这笔账书里没算。 索引数量是「类别数 × 每类样例数」。这个实验里是 16000 条,查一次要和 16000 条比。 如果是一万个类、每类 100 张,索引就是一百万条。 书里没有讨论这种情况。

③ 零训练那一条的天花板不高。 从 1 张到 16 张只涨了 1.33(60.70 → 62.03), 样例再多也涨不上去——它的上限由 CLIP 特征本身决定。

④ 这一章的所有实验都是分类。 检测、分割这类任务能不能这么干,本章没有回答。 (第 14 章会把这个「外挂小网络」原样搬到 3D 上用,那是本书里唯一一次迁移。)

11. 可带走的

  1. 「改哪儿」有三个位置:输入端那句话(第 08 章)、编码器输出之后(本章)、 主干每一层内部(第 10 章);
  2. 外挂做法 = 一个两层小网络 + 按比例和原来的零样本结果混合; 混合比例的含义是「新学的和原有的各占多少」,领域差得越远,新学的占比该越大;
  3. 查表做法 = 样例图的特征当索引、标签当内容,来了新图算相似度加权投票; 全程两次矩阵乘法,零训练;
  4. 1 张样例时零训练就赢:60.70 对 57.15,而对手训了 14 小时 40 分;
  5. 样例少时从零训一个分类器是灾难:1 张样例只有 22.17;
  6. 微调 20 轮(5 分钟)拿到全场最高 65.51——只解冻索引,不动标签;
  7. 两个方法是同一件事:一个是另一个的特例,差别只有三处(初始化、宽度、换算方式);
  8. 推理慢 29 倍的原因是文本特征不能预先算好—— 可训练的东西在文本那一侧,每次都要重跑文本编码器;
  9. 挂满所有层反而最差(67.67 对 71.85),因为参数从 0.52M 涨到 5.20M,过拟合; 这四个数出自原书另一张表、主干是 ViT-B/16,不能和上面那些 RN50 的数放在一起比;
  10. 训练得越少,换环境时掉得越少:素描版上零训练查表 35.90,比零样本还高;
  11. 零训练那条路的上限由 CLIP 特征本身决定,样例再多也就涨 1.33 个点。

12. 原文地图

主题原书章原文位置
六行成本表(准确率/时长/时延/显存)7. Learning Efficient Feature Adapterstext/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:43(搜「single NVIDIA GeForce RTX 3090」) · text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:105(搜「299.64 ms」)
外挂小网络:两层瓶颈 + 残差混合、α 的含义同上text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:187(搜「lightweight two-layer Multi-Layer Perceptron」) · text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:233(搜「domain gap between the pretrained and downstream」)
只挂最后一层的理由同上text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:37(搜「embedded across all layers of a backbone」)
查表:索引与内容怎么造、两次矩阵乘法同上text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:219(搜「keys, while the one-hot label vectors」) · text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:223(搜「efficient matrix-vector multiplications」)
微调 20 轮、只解冻索引同上text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:239(搜「only 20 epochs of fine-tuning」) · text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:241(搜「unfreezes the cached keys」)
一个是另一个的特例、三处差别同上text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:247(搜「specific case of Tip-Adapter」) · text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:261(搜「Bottleneck Dimensions」)
随样例数变化的五行数字同上text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:353(搜「60.70」) · text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:295(搜「+0.62% in the 1-shot setting」)
为什么慢:每次都要重跑文本编码器同上text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:387(搜「repeated computation through the entire textual encoder」)
插入位置的对照 67.67 / 70.88 / 71.85同上text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:475(搜「risks disrupting CLIP’s pretrained knowledge」) · text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:501(搜「71.85」)
分布漂移:素描版 35.90 / 31.04 / 19.13同上text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:557(搜「superior robustness to distribution shifts」) · text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:615(搜「35.90」)

Footnotes

  1. 出处:「7. Learning Efficient Feature Adapters for Vision-Language Models」第 43 段(text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:43,搜「single NVIDIA GeForce RTX 3090」)与表 7.1 所在的第 105 段(text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:105,搜「299.64 ms」)。表注写明:全部实验在一张 RTX 3090 上、批量 32、16 张样例的千类分类任务上测得。29.3 倍这个比值是我们算的(299.64 ÷ 10.22);书里只给了两个绝对值。

  2. 出处:同上第 187 段(text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:187,搜「lightweight two-layer Multi-Layer Perceptron」)与第 233 段(text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:233,搜「domain gap between the pretrained and downstream」)。原文:在冻住的 CLIP 后面接一个轻量的两层网络,由它预测「残差特征」;再用一个 0 到 1 之间的超参 α 把加工过的特征与原始特征线性组合;当预训练与下游任务的领域差距大时,α 应该更大,因为模型需要更多来自少样本数据的知识,反之则更小。「只挂最后一层」的理由见第 37 段(text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:37,搜「embedded across all layers of a backbone」):原始的适配器是嵌在主干所有层里的,而这一版只放在最后几层,因为 CLIP 的预训练编码器已经提供了很强的表示,只需要极小的残差调整。 2 3

  3. 出处:同上第 219 段(text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:219,搜「keys, while the one-hot label vectors」)与第 223 段(text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:223,搜「efficient matrix-vector multiplications」)。原文:把每张样例图过一遍视觉编码器得到归一化的特征当作 key,把它的标签转成 one-hot 向量当作 value;推理时测试图的特征当 query,与所有 key 算余弦相似度,经一个带锐度参数 β 的指数换算变成非负权重,再对 value 加权求和;最后与 CLIP 原来的零样本结果按 α 做残差组合。走查里那些相似度和权重是为演示编的。

  4. 出处:同上第 353 段(text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:353,搜「60.70」)与第 295 段(text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:295,搜「+0.62% in the 1-shot setting」)。表 7.2 给了五档样例数下五种方法的数字;原文特别指出:微调版对零训练版的增益从 1 张样例时的 +0.62% 涨到 16 张样例时的 +3.44%,说明样例越多,把表里的索引训一训越划算。(表格最后一行给的是 +3.48,与正文的 +3.44 有 0.04 的出入,这是原书内部的不一致,照实记下。) 2

  5. 出处:同上第 239 段(text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:239,搜「only 20 epochs of fine-tuning」)与第 241 段(text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:241,搜「unfreezes the cached keys」)。原文:把表里的索引解冻当作可训练参数,用随机梯度下降微调;只要 20 轮就达到最好水平,而对照的两条路都需要 200 轮;表里的内容(one-hot 标签)保持不动,以确保类别信息不被破坏

  6. 出处:同上第 247 段(text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:247,搜「specific case of Tip-Adapter」)与第 261 段(text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:261,搜「Bottleneck Dimensions」)。原文给出了具体的对应条件(第一层权重取样例特征矩阵、第二层权重取标签矩阵与文本分类器的组合、偏置取零、激活换成查表用的那个指数换算),并列出三点差别:key/value 的初始化方式、瓶颈维度(样例总数 vs 故意压低)、激活函数(天然有界 vs ReLU)。 2

  7. 出处:同上第 387 段(text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:387,搜「repeated computation through the entire textual encoder」)。原文:本章这几种方法都受益于可以预先算好并复用 CLIP 的文本特征;而学那句话的做法用的是可训练提示词,每一次训练迭代都要重新走一遍整个文本编码器

  8. 出处:同上第 475 段(text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:475,搜「risks disrupting CLIP’s pretrained knowledge」)与表 7.4 所在的第 501 段(text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:501,搜「71.85」)。原文:插在更靠前的层会增加计算开销并有扰乱 CLIP 预训练知识的风险;而把适配器分布到所有层会显著增加参数(5.20M 对第 12 层的 0.52M),导致在少样本数据上过拟合。表 7.4 的表注写明这一组用的图像主干是 ViT-B/16,与第 1 节那张表(表 7.1)的 RN50 不是同一个设定,两张表的数不可直接比。另外这一段的正文写的是「插在第 12 层(末尾)效果最好」(同一段,搜「12th layer (end) achieves the best performance」),而它自己表里第 10 层的 71.85 高于第 12 层的 70.88——原书没有解释这处不一致,我们照实记下。 2 3

  9. 出处:同上第 557 段(text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:557,搜「superior robustness to distribution shifts」)与表 7.5 所在的第 615 段(text/37-ch07-7-learning-efficient-feature-adapters-for-vision.txt:615,搜「35.90」)。原文:查表法即使不训练也表现出更好的抗漂移能力,在一个变体上超过学那句话的做法、在另一个变体上超过外挂小网络;这凸显了缓存模型的优势——它的构造不需要训练,从而缓解了对源数据集过拟合的风险。 2