跳到主要内容

训一个读懂型的 — 成对训练,和「32 条标注也能干活」

这一章讲三件事: 全书那条暗线到底是什么(答案:成对训练,而且书自己没点破); 训一个模型时,哪一处设定的杠杆最大(答案不是模型,是「怎么衡量差得远不远」); 以及四个非常实际的问题——非得整个网络一起训吗、一条标注都没有怎么办、 标注很少但不是零怎么办、只有 32 条标注还能不能干活。 在全书链条里,这一章是那条「先不训练」的顺序走到尽头之后的第一步需要先读第 02、04 章,其余生词都在当场解释。

1. 先看现象:同一份数据、同一个底子,只换一样东西

这一节回答:训练里到底什么最值钱。

书做了一组极其干净的对照。数据一样、底座模型一样、训练轮数一样, 只换「怎么衡量它答得离目标有多远」这一件事——结果是1:

只换了这一样分数
最早那种衡量方式0.59
换成「按余弦相似度衡量」0.72
换成「多负样本排序」那种衡量方式0.80
底座换成一个已经练好的嵌入模型,再用同样的方式微调0.85

这个分数是什么意思? 它衡量的是:模型判断的「两句话有多像」, 和人工打分的「两句话有多像」,吻合到什么程度。0 到 1,越高越吻合。 用的是一套公开的句子相似度考卷,里面每一对句子都由人打过 1 到 5 分2

这张表的读法只有一条:

换「怎么衡量差得远不远」的杠杆(0.59 → 0.80), 大于换模型的杠杆(0.80 → 0.85)。

损失就是**「模型这次答得离目标有多远」的那个数**——越小越好。损失函数就是算这个数的那条规则。 训练的全部动作,就是「让这个数一点点变小」。

这一章后面凡是说「换损失」,说的就是换这条规则。

所以上面那张表里换来换去的「怎么衡量差得远不远」,正式名字就叫损失函数。

2. 顶层全景:一个念头,四个名字,四个实际问题

【那个念头】成对训练
│ 不要教它「什么是狗」,要教它「为什么这是狗、不是猫」
│ 做法:喂一对「该靠近的」+ 一对「该远离的」,反复调

【它的四个化名】
① 判断两个词是不是邻居 (第 02 章,最早)
② 判断一张图和一句配文配不配 (第 07 章)
③ 判断两句话是不是一个意思 (本章)
④ 判断两个样本是不是同一类 (本章 3.7 节,最省数据的那招)

【四个实际问题】前三个在 3.6 节,**第四个单独放在 3.7 节**
Q1 非得整个网络一起训吗? → 3.6 节的 Q1,有硬数据
Q2 一条标注都没有怎么办? → 3.6 节的 Q2
Q3 标注很少、但不是零呢? → 3.6 节的 Q3
Q4 每类只标 16 条还能干吗? → 3.7 节(能,0.84)

图说:**书把这四个化名分散在四章里讲,从来没有并排放过。**
并排一放,你会发现它们是同一件事。

这一章全程跟着同一对句子走:

问句:「阿姆斯特丹住着多少人?」 答句:「阿姆斯特丹住着近一百万人。」

这一对是 3.5 节书自己用的例子,我们把它提到最前面,让它从头贯到尾—— 先变成两串数(3.2 节),再被当成一个「该靠近的一对」(3.3 节), 然后三种衡量方式各拿它算一次(3.4 节),最后给它配一个「像却不对」的反例(3.5 节)。 除第 1 节那四个分数是书的实测外,这条走查上的数值都是为演示编的。

3. 核心原理

3.1 那个念头:为什么必须给出对立面

它解决什么问题: 怎么让模型学会「像不像」这种没法直接标注的东西。

书讲这件事用了一个特别好的段子,值得原样带走:

记者问抢劫犯:「你为什么抢银行?」 他回答:「因为钱在那里。」3

这个回答事实上完全正确,可它答的不是记者想问的那件事。 书的分析:提问的意图不是问他为什么专门抢「银行」,而是问他为什么要「抢劫」

问题出在哪?出在没有给出对立面。 同一句话可以有好几种理解方式, 而最好的建模方式是提供一个替代方案——把问题改成:

「你为什么抢银行(而不是遵守法律)?」4

这就是这一章的全部思想。 书把它搬到模型上:

你可以教模型认识「狗」,让它去找「尾巴」「鼻子」「四条腿」。 但这些特征通常没有明确定义——一个有尾巴、有鼻子、四条腿的生物,也可能是猫。

为了帮模型聚焦到我们关心的东西上,我们本质上是在问它: 「为什么这是狗而不是猫?」5

书对这套做法的正式定义是:

训练嵌入模型,使得相似文档在向量空间中更接近,而不相似文档则更疏远。6

它的名字叫对比学习。「对比」就是「摆出对立面来比」的意思。

书还点明了一件容易被忽略的事: 这不是新东西—— 第 02 章那个判断「两个词是不是邻居」的做法,就是自然语言处理里 最早也最流行的对比学习示例之一7

书原话:虽然并不广为人知,但它是这个领域首批利用对比学习与神经网络的重大突破之一。7

3.2 一个绕不开的岔路:为什么不能直接让模型打分

这一节解释一个结构选择,而它是这一章所有做法的前提。

最直接的想法:把两句话一起塞给模型,让它输出一个「像不像」的分数。 这就是第 06 章讲过的交叉编码器——两句话同时进模型,模型看着两边打分。

这条路准,但有一个致命的成本问题。 书把账算得很清楚:

你要在一万个句子里找出最相似的那一对。 需要的计算次数是 n×(n-1)/2 = 49,995,000 次。8

将近五千万次。而且这还只是一万个句子。

更麻烦的是第二点:交叉编码器根本不产出嵌入。 它只输出一个分数9所以你没法把结果存起来复用——每来一个新查询,全部重算。

那退一步:直接拿一个现成模型,把它每个位置的输出取个平均行不行?

书的回答很干脆:不行。 这种做法已被证明比简单的词向量平均效果更差10—— 连更老、更笨的办法都不如。

所以真正的解法是第三条路:两句话各走各的,但用同一个模型。

句子 A ──▶ ┌────────────┐ ──▶ 一串数 A
│ 同一个模型 │ (两边权重完全相同,
句子 B ──▶ │ 跑两次 │ ──▶ 一串数 B 所以其实只有一个模型)
└────────────┘


比一比这两串数像不像

图说:书管这个结构叫**孪生架构**(两边一模一样,像双胞胎),
也叫**双编码器**。产出的模型叫 SBERT(句子级的 BERT)[^11]。

关键的一句:因为两边权重相同,实际上只需要一个模型、逐个送句子进去11

走查第一步:那两句话各自变成什么。

「阿姆斯特丹住着多少人?」 ──▶ 768 个数,前五个 [ 0.11, -0.24, 0.07, 0.33, -0.02]
「阿姆斯特丹住着近一百万人。」──▶ 768 个数,前五个 [ 0.09, -0.19, 0.12, 0.30, -0.05]


两串数的余弦相似度 = 0.71

图说:**训练开始前这个数可能只有 0.2 上下;训练要做的就是把它往 1 推。**

这里已经能看见上一段那个成本问题的答案: 这两串数是各算各的, 算完就能存起来——下次换一个问句,答句那 768 个数不必重算。 交叉编码器存不下任何东西,因为它吐出来的只有一个分数。

代价是明码标价的,书没有隐瞒:

下面这句里的「双编码器」就是上面那张图——同一个模型跑两次的那种结构:

交叉编码器通常比双编码器取得更好的性能,但不生成嵌入。12

判断(我们的,不是书里的): 这两条路不是二选一,是分工—— 第 06 章那套「先粗筛再重排」就是把它们串起来用了: 双编码器负责快(粗筛全库),交叉编码器负责准(只处理捞上来的那一批)。 读到这一节应该立刻回想第 06 章那张三道工序的图。 如果错,会错在: 如果你的库小到几千条、而且查询不频繁, 那直接全用交叉编码器更省事也更准——判据是:库的大小 × 查询频率, 小到你能接受每次都全量重算,就不需要双编码器。

3.3 训练数据从哪来:一个很巧的借用

它解决什么问题: 「这两句话该靠近还是该远离」,谁来标?

书用了一个现成的、为别的目的而造的数据集,而这个借用很聪明。

那类数据本来是给一个叫自然语言推理的任务用的—— 给一个前提和一个假设,判断三者之一13:

关系意思书给的例子
蕴含前提成立,假设就成立前提「他在电影院里看《寻梦环游记》」,假设「他正在电影院看迪士尼电影《寻梦环游记》」
矛盾两者不可能同时成立前提同上,假设「他在家看《冰雪奇缘》」
中立既不蕴含也不矛盾——

书的洞察只有一句:如果你仔细审视,蕴含和矛盾描述的就是「两句话有多像」14

蕴含 ──▶ 拿来当「该靠近的那一对」
矛盾 ──▶ 拿来当「该远离的那一对」

图说:**一份为「逻辑判断」造的数据,被原样借去训「像不像」。**
这是这一行最常见的省钱手法:找一个已经有标注、而且标注含义能转译的数据集。

书用的具体数据是一套公开语言理解考卷里的一项,共 392,702 对句子; 它只取了 5 万对做演示15

并且它老实交代了这么做的代价:

数据集越小,训练或微调嵌入模型就越不稳定。 如果可能的话,在数据质量有保证的前提下,优先使用更大的数据集。15

3.4 三种「怎么衡量差得远不远」,以及它们的适用面

回到第 1 节那张表。现在解释那三行分别在干什么。

第一种:最早那套(0.59)

书用它只是为了说明最早的那批模型是怎么训的, 并且明确表态:通常不建议使用它,因为存在性能更好的选择16

它的做法是:把两个句子的表示、以及两者之差拼在一起,再过一个判别层17记住结论就够:这是历史,不要用。

第二种:按余弦相似度衡量(0.72)

它解决什么问题: 很多数据不是非黑即白的「像/不像」,而是有程度

它怎么做: 不用严格的正负对,而是假设句子对在一定程度上相似或不相似, 给一个 0 到 1 之间的数18。然后:

算出两段文字那两串数之间的余弦相似度,和标注的相似度分数比一比。19

要用它,得先把数据转成 0 到 1。 书的转法很直白: 蕴含 = 1(高度相似);中立和矛盾都 = 0(不相似)20

走查第二步:那一对在这种衡量方式下发生了什么。 标注说它们该有多像?按上面那个转法,蕴含 = 1;而模型现在算出来是 0.71差 0.29——这 0.29 就是这一对贡献的「损失」, 训练就朝「让这个 0.29 变小」的方向,把模型里的数挪一丁点。

适用面:你手上的数据带「像的程度」这种连续分数时,这是最直观的选择。

第三种:多负样本排序(0.80)

这是这一节的重点,而且它的做法有一个很妙的省钱设计。

它解决什么问题: 上一种要你给出「像的程度」,可现实里你往往只有「这两个是配对的」。

它怎么做:只用正例,负例现场拼。

你手上只有正例对:问题↔答案、图↔配文、论文标题↔论文摘要 ……
│ 这类对的好处:你能非常确信它们是配的

把一批正例对放在一起,然后——
│ 把 A 的问题,配上 B 的答案 → 得到一个负例
│ 把 A 的标题,配上 C 的摘要 → 又一个负例

书管这种叫「批内负样本」——负例不用另外准备,从同一批数据里现拼[^21]

图说:**正例是你给的,负例是免费的。** 这就是它比上一种好用的根本原因。

拼好之后干什么?算相似度,然后当成一道二选一的判断题来优化—— 这一对是配的还是不配的21

先讲一个词:「批」就是「一次同时处理多少条样本」。 一批 32 条,就意味着 每个正例要和另外 31 个混搭出来的负例竞争;一批 256 条,就要和 255 个竞争——难得多。

走查第三步:同一对在这种衡量方式下发生了什么。 一批 32 条,我们那一对是其中之一。做法是拿那个问句,和这一批全部 32 个答句各算一次余弦:

和谁比余弦
它自己的答句(正例)0.71
另外 31 个里最高的那个0.44
剩下 30 个0.05 ~ 0.31

要求只有一条:在这 32 个里把 0.71 那个挑出来。

书给了一条很有用的经验,而且解释了为什么:

较大的批越有利,因为较大的批使任务更加困难—— 模型需要从更大的潜在句子对集合中找出最佳匹配的那一个。22

落到上面那张表上就是: 批从 32 加到 256,竞争者从 31 个变成 255 个, 能混进来的「看着也挺像」的答句多了八倍,挑对的难度跟着涨—— 而第 1 节那张表里 0.72 → 0.80 的那一跳,就是换到这种衡量方式换来的。

3.5 一个更大的杠杆:挑「像却不对」的反例

这一节回答:上面那招有什么毛病,以及怎么治。

毛病书自己说了:随手拼出来的负例往往和问题毫无关系, 于是「找出正确答案」这件事变得相当容易23

太容易 = 学不到东西。

治法是故意挑难的。 书给的例子极其清楚24:

内容为什么
问题「阿姆斯特丹住着多少人?」——
正确答案「阿姆斯特丹住着近一百万人。」——
简单反例随机抽一句,比如「今天天气不错」太好排除了,模型学不到东西
难反例「乌得勒支住着一百多万人,比阿姆斯特丹多。」它提到了阿姆斯特丹、提到了人数、句式也像——但它不是答案

书对效果的表述很直接:因为这会使任务更加困难(需要学习更细微的表示), 模型的性能通常会有显著提升25

怎么弄到难反例?书给了三档,而且成本递增26:

档次怎么弄代价
简单随机抽免费
中等用一个现成的嵌入模型,把所有句子嵌了,挑相似度高的便宜。但书提醒:这找到的是「相似的句子」,不是「像答案的错答案」
通常需要人工标注,或者用生成模型来造

判断(我们的,不是书里的): 这一节和第 1 节那张表合起来,给出了训练里的杠杆排序: ① 换损失函数(0.59 → 0.80)> ② 挑难反例 > ③ 换更好的底座(0.80 → 0.85)> ④ 加更多同质数据。 书没有把这四条排过序,但它给的数字支持这个顺序。 如果错,会错在: 如果你的任务和通用语义相似度差得很远 (比如判断两段代码是不是同一个漏洞),那底座模型的领域匹配度可能压过一切, 顺序要变成 ③ 优先。判据是:换一个同领域的底座,分数动得比换损失还多,那就先换底座。

3.6 三个非常实际的问题

Q1:非得整个网络一起训吗?

这是书给的最硬的一组对照数据,而且答案有点反直觉27:

训哪些层分数
只训最外面那个判别头,底下全冻住0.63
放开最后一个处理块 + 判别头0.80
整个网络一起训0.85

第一行到第二行,只多放开了一块,分数就追回了大半。

书还画了一条曲线,给了一个更实用的结论: 仅训练前五个处理块就足以接近训练所有处理块的性能—— 训练更多的块会带来性能提升,但很早就趋于稳定28

而且冻住的部分是实打实省时间的: 书说只训判别头时 训练变得更快了……与微调整个模型相比,这提供了显著的加速29

书给的经验规则值得抄:通常我们希望冻结层之后是可训练层30—— 别在中间挖洞,要冻就从最底下往上连续地冻。

还有一条只有做过才知道的提醒: 当你要训练多个轮次时,冻结和不冻结之间的差异(在训练时间和资源方面)通常会变得更大31

「轮次」就是「把整份训练数据从头到尾过一遍」。 过一遍叫一轮,过三遍叫三轮。 书在这一章的演示里为了跑得快,大多只用了一轮,并且说明通常建议增加这个值32

Q2:一条标注都没有怎么办?

书给了一个完全不需要标注的办法,思路和「完形填空」很像33:

拿一句原句:「Grim faces and hardened jaws are not people-friendly.」

│ ① 随机删掉一部分词 → 「Grim jaws are.」

把这句残句送进编码器,压成一串数

│ ② 让一个解码器只拿着这串数,去把原句还原出来

还原得越准,说明这串数抓住的信息越全

图说:**训练目标是「从残句还原原句」,而这不需要任何人工标注。**
训完之后解码器就扔掉,只留编码器——它才是你要的嵌入模型[^35]。

结果是 0.7034完全没有标注,只比用了 5 万对标注数据的那种衡量方式(0.72)低一点点。

书还提了一个和常规相反的细节:这里用那个特殊的分类词元当输出,比取平均更好—— 理由是平均池化会丢失位置信息35

Q3:标注很少但不是零,怎么办?

书给了两条路,而且它们方向相反,很值得对照着看。

路线一:让准的那个去教快的那个。

① 拿一小批真人标注过的数据(书叫「黄金数据集」),训一个准但慢的交叉编码器


② 弄一大批没标注的句子对


③ 让刚训好的交叉编码器去给它们打标签(书叫「白银数据集」)
│ 白银 = 标全了,但标签是机器给的,不是真值

④ 黄金 + 白银合在一起,训那个快的双编码器

图说:**用少量真标注,撬动大量伪标注。**

效果:只用了两成的数据,拿到 0.71,而用全量数据的原版是 0.7236

书还给了一条很实用的操作细节:如果你连未标注的句子对都没有,可以自己拼—— 从一行取前提、另一行取假设,就能造出十倍以上的对37

但它紧接着自我拆台了,这句很重要:

这种策略可能会生成显著更多的不相似对而非相似对。 更好的做法是用一个现成的嵌入模型把候选都嵌了,给每个句子找出最相似的几个, 这样能专注在「可能更相似」的那些对上37

路线二:先让模型适应你的领域,再训。

书把这条叫领域自适应,目标是让一个在别处训好的模型,适应你这边的主题和词汇38。 做法是两步39:

  1. 先用上面 Q2 那种不需要标注的办法,在你自己领域的语料上训一遍;
  2. 再用有标注的数据(哪怕是别的领域的)去微调。

书说得很实在:虽然首选目标领域的数据,但领域外的数据也有效, 因为我们已经在目标领域上做过无监督训练了39—— 无监督就是完全不用人工标注的那种训练,也就是上面「一条标注都没有怎么办」那一招。

3.7 最省数据的那一招:每类 16 条

这一节是这一章最该记住的实操结论。

它解决什么问题: 我一共只能标几十条,还能不能训?

能。书演示的做法叫 SetFit,三步40:

① 造对:同一类里的两条 = 正例对;不同类的两条 = 负例对
│ 算一算:每类 16 条、两类,同类内部就能配出 16×15/2 = 120 对[^43]
│ **32 条标注,被变成了 1280 个训练对**[^44]

② 拿这些对去微调一个现成的嵌入模型(就是这一章前面那套成对训练)


③ 用微调后的模型把所有句子变成数,再在上面训一个最简单的判别器

图说:**关键在第一步——它把「分类标注」变成了「成对标注」。**
这一步一做,原本少得可怜的数据量就被平方级地放大了。

结果:32 条标注,F1 拿到 0.8441—— 而第 04 章用 8500 条数据训判别器的那一档是 0.85。

判断(我们的,不是书里的): 这一招的真正价值不在分数,在于它改变了项目的启动方式。 「先标 32 条试试」和「先标 8500 条」是两种完全不同的立项决策—— 前者是一个下午,后者是一个项目。 如果错,会错在: 如果类别很多(几十上百类)、或者类内差异极大, 那每类 16 条根本盖不住分布,配出来的「同类对」会自相矛盾。 判据是:随机挑几对「同类」的样本自己看一眼,如果你觉得它们不像一回事,这招就不适用。

书还提了一个更极端的版本:连标签都没有也能用。 做法是从标签的名字生成合成样本——目标标签是「happy」和「sad」, 就造出「The example is happy」「This example is sad」这样的句子,再拿它们去训42

3.8 另外两件顺带讲清的事

让通用模型先「熟悉」你的领域

它解决什么问题: 通用模型是在百科这类很泛的文字上训的,可能没见过你的行话43

它怎么做:在「拿现成模型」和「针对任务微调」之间,插一步。 用你自己的文字,接着做一遍原来那种「遮住一部分让它猜」的训练44

书给了一个能直接看出效果的验证,做法很聪明: 拿同一个填空题「What a horrible ___!」问两个模型45:

模型它填的词
原始通用模型idea、dream、thing、day、thought(都是很泛的词)
用影评数据接着训过的movie、film、mess、comedy、story(全都跟电影有关)

这个对照比任何分数都直观:它确实「入乡随俗」了。

先讲一个词:收敛就是「训练过程稳定下来、各项指标不再明显改善」的那个状态

书还给了一个词汇上的顺带说明: 遮的时候可以遮词元,也可以遮整个词; 遮整词更难、模型学得更准,但需要更多时间才能收敛46。遮的比例书用的是 15%47

逐词分类:不是给整段贴标签,是给每个词贴

它解决什么问题: 你要从一段话里找出人名、地名、机构名——这叫命名实体识别

它和前面所有分类的区别只有一个:分类的粒度48

整段分类:一段文字 ────────▶ 一个标签
逐词分类:一段文字 ──▶ 每个词元各一个标签

图说:**换的只是第 03 章说的那个「头部」。** 别的全一样。

这里有两个坑,书都讲了。

坑一:标签是按「词」标的,但模型是按「词元」处理的。 书举的例子:Maarten 会被切成 Ma##arte##n 三段—— 你不能给三段都标上「这是一个人」,那等于说这里有三个人49

解法是用两种前缀区分开头和后续:

前缀意思
B(Begin)这一段是某个实体的开头
I(Inside)这一段接着上一段,属于同一个实体
O(Outside)不属于任何实体

所以 Ma 标 B-人名,##arte##n 都标 I-人名——它们合起来才是一个人50

同样的规则也用在多词短语上:Dean Palmer 里,Dean 是 B,Palmer 是 I, 所以我们知道它是一个人,而不是两个人51

坑二:那些特殊词元怎么办? 它们不对应任何真实文字,不该参与打分。 书的做法是给它们一个特殊的标签值,让算分时直接跳过52

4. 作者的判断与证据

说法属于哪一类说明
换损失函数:0.59 → 0.72 → 0.80有实测,条件严格控制同数据、同底座、同轮数,只换损失1
冻结层的代价:0.63 / 0.80 / 0.85有实测,而且书还画了逐块曲线这是全书最有决策价值的一组数据之一2728
32 条标注拿到 0.84有实测但书正文写的是「0.85」,而它自己贴出来的评估输出是 0.8363——见下一节
难反例能显著提升性能断言 + 一个很好的例子,但没有对照实验书没有跑「简单反例 vs 难反例」的 A/B25
「较大的批更有利」有解释,无数据解释站得住(候选变多、任务变难),但书没测22
「不建议使用最早那种损失」明确的负面建议而且它自己的数据支持这个建议16
「数据集越小越不稳定」作者主动坦白就写在它自己只取 5 万对的那一段旁边15
「训练或微调的主要困难在于找到正确的数据」作者的判断,而且是这一章的落点原话还补了一句:「构建正样本对通常很直接,但添加难负样本对显著增加了创建高质量数据的难度」53
0.85 那一行的水分作者主动坦白它明说了:用来微调的那个预训练模型已经在完整数据集上训练过,而我们只用了 5 万个样本54

5. 边界与局限

第一,书里有两处「文字和数字对不上」,读的时候要以数字为准。

位置正文怎么说它自己贴的输出怎么说
32 条标注那一节「我们就得到了 0.85 的 F1 分数」0.836341
接着预训练那一节「我们训练 20 个轮次」代码里写的是 1055

判断(我们的,不是书里的): 这两处不影响结论,但它们说明一件事—— 这本书的正文数字是手写的,没有和代码输出核对过。 引用它的具体分数时,应该以它贴出来的输出为准,不以正文叙述为准。 如果错,会错在: 如果是中文译本在转写时出的错、英文原版并无此问题, 那这就是翻译问题不是原书问题——但对读中文版的人来说,该做的防范是一样的。

第二,所有分数都来自同一套句子相似度考卷。 书自己说了,更全面的评测应该用那套八类任务、58 个数据集、112 种语言的大考卷, 但在整套考卷上测一个模型可能需要数小时,所以它退回用单项56

补充(不在书里): 那套大考卷的论文摘要给的主要发现是一句泼冷水的话: 没有哪一种嵌入方法在所有任务上都占优。 来源:《MTEB: Massive Text Embedding Benchmark》(Muennighoff、Tazi、Magne、Reimers,2022-10-13) https://arxiv.org/abs/2210.07316(查阅于 2026-08-25)

这条比书里任何一个分数都重要:单项分数高,不代表在你的任务上也高。

第三,「难反例怎么造」这个最关键的问题,书基本没有回答。 它承认难反例最有效、也承认「通常需要人工标注」,然后就没有然后了。 用生成模型来造难反例是今天的常规做法,书只提了一句名字。

第四,评测里有一个书完全没提的陷阱。 用蕴含关系的数据训、再用句子相似度考卷评——这两者的分布是接近的。 分数好看,不代表换到你的业务数据上也好看。

第五,那些具体的库和写法要以最新文档为准。 这一章的代码依赖两个专门的训练库,两年里接口都动过。思路全部有效,写法要重查。

6. 可带走的

  1. 全书的暗线是成对训练,它顶着四个名字散在四章里:词与词是不是邻居、图和配文配不配、两句话是不是一个意思、两个样本是不是同一类;
  2. 核心念头一句话:不要教它「什么是狗」,要教它「为什么这是狗不是猫」——给出对立面,信息量才够;
  3. 换「怎么衡量差得远不远」的杠杆最大:同数据同底座,0.59 → 0.72 → 0.80;
  4. 不能直接让模型给每一对打分:一万个句子要算近五千万次,而且它不产出可以复用的嵌入;
  5. 所以用「同一个模型跑两次」的结构:快、可复用,代价是比逐对打分略差;
  6. 这两条路不是二选一,是分工——第 06 章那套「先粗筛再重排」就是把它们串起来了;
  7. 训练数据可以借:一份为「逻辑判断」造的数据,蕴含当正例、矛盾当负例,直接拿来用;
  8. 负例可以现拼:把这一批里 A 的问题配上 B 的答案,就是一个免费负例;
  9. 批越大越难,所以效果越好——因为每个正例要和更多混搭负例竞争;
  10. 更大的杠杆是「挑像却不对的反例」:随机反例太好排除,模型学不到东西;
  11. 不必整个网络一起训,但省得越多代价越实:0.63 / 0.80 / 0.85;训到第五块左右基本就够了;
  12. 要冻就从最底下往上连续地冻,别在中间挖洞;
  13. 一条标注都没有也能训:随机删词、逼模型从残句还原原句,拿到 0.70;
  14. 标注很少时有两条路:用少量真标注训一个准模型,去成批地打伪标签;或者先在自己领域上无标注训一遍再微调;
  15. 每类只标 16 条也能干活:把「分类标注」变成「成对标注」,32 条就能放大成 1280 个训练对,拿到 0.84;
  16. 让通用模型熟悉你的行话,只需要在自己的文字上接着做一遍「遮住让它猜」;效果可以用填空题直接验;
  17. 逐词分类换的只是最外面那个头部,难点在把「按词标的标签」和「按词元切的输入」一条条对上;
  18. B / I / O 三个前缀解决这件事:开头标 B,后续标 I,不属于任何实体标 O;
  19. 这一章的落点是书自己那句话:训练的主要困难在于找到正确的数据——尤其是难反例;
  20. 单项考卷分数高,不代表在你的任务上也高。 没有哪种嵌入方法在所有任务上都占优。

7. 原文地图

主题原书章原文位置
嵌入模型的目标、语义相似度创建文本嵌入模型text/12-fm.txt:15(搜「数值表示」) · text/12-fm.txt:21(搜「语义相似度」)
也可以按情感而非语义来训创建文本嵌入模型text/12-fm.txt:25(搜「情感分类器」) · text/12-fm.txt:27(搜「引导这一过程」)
对比学习的定义创建文本嵌入模型text/12-fm.txt:31(搜「对比学习」) · text/12-fm.txt:35(搜「更接近,而不相似文档则更疏远」)
抢银行那个段子与「为什么是狗不是猫」创建文本嵌入模型text/12-fm.txt:41(搜「因为钱在那里」) · text/12-fm.txt:45(搜「为什么这是狗而不是猫」)
word2vec 是最早的对比学习之一创建文本嵌入模型text/12-fm.txt:51(搜「首批利用对比学习」)
交叉编码器的开销:近五千万次创建文本嵌入模型text/12-fm.txt:59(搜「49,995,000」)
取平均比词向量平均还差创建文本嵌入模型text/12-fm.txt:61(搜「比简单的词向量平均」)
孪生架构、双编码器、SBERT创建文本嵌入模型text/12-fm.txt:67(搜「孪生架构」) · text/12-fm.txt:73(搜「双编码器或 SBERT」)
数据集:蕴含/矛盾/中立创建文本嵌入模型text/12-fm.txt:87(搜「自然语言推理」) · text/12-fm.txt:89(搜「寻梦环游记」) · text/12-fm.txt:93(搜「数据集来生成对比学习的负样本(矛盾)和正样本(蕴含)」)
只取 5 万对与那句自我提醒创建文本嵌入模型text/12-fm.txt:97(搜「392,702」)
最早那种损失与 0.59创建文本嵌入模型text/12-fm.txt:139(搜「SoftmaxLoss」) · text/12-fm.txt:210(搜「我们得到了0.59的分数」)
「不建议使用」创建文本嵌入模型text/12-fm.txt:252(搜「不建议使用 Softmax 损失」)
余弦相似度损失与 0.72创建文本嵌入模型text/12-fm.txt:272(搜「计算两段文本的两个嵌入之间的余弦相似度」) · text/12-fm.txt:364(搜「皮尔逊余弦得分为0.72」)
标签转成 0/1 的做法创建文本嵌入模型text/12-fm.txt:274(搜「蕴含表示句子之间的高相似度」)
多负样本排序与批内负样本创建文本嵌入模型text/12-fm.txt:370(搜「多负样本排序(MNR)损失,6 通常称为 InfoNCE 7 或 NTXentLoss」) · text/12-fm.txt:372(搜「批内负样本」) · text/12-fm.txt:376(搜「交叉熵损失」)
0.80 与批大小的经验创建文本嵌入模型text/12-fm.txt:471(搜「0.80)的模型似乎准确得多」) · text/12-fm.txt:475(搜「较大的批大小通常对 MNR 损失更有利」)
难负样本与阿姆斯特丹例子创建文本嵌入模型text/12-fm.txt:477(搜「难负样本」) · text/12-fm.txt:479(搜「乌得勒支」) · text/12-fm.txt:483(搜「三种方式」)
换底座微调到 0.85,以及那句水分说明创建文本嵌入模型text/12-fm.txt:567(搜「0.8509」) · text/12-fm.txt:577(搜「已经在完整的 MNLI 数据集上训练过」)
「主要困难在于找到正确的数据」创建文本嵌入模型text/12-fm.txt:583(搜「找到正确的数据」)
增强型 SBERT 四步、黄金与白银创建文本嵌入模型text/12-fm.txt:591(搜「这个过程被称为增强型 SBERT」) · text/12-fm.txt:605(搜「黄金数据集是一个小型但完全标注的数据集」)
只用两成数据拿到 0.71创建文本嵌入模型text/12-fm.txt:763(搜「就获得了0.71的分数」)
自己拼句子对的技巧与它的毛病创建文本嵌入模型text/12-fm.txt:670(搜「显著更多的不相似对」)
无监督四种方法与 TSDAE创建文本嵌入模型text/12-fm.txt:771(搜「SimCSE」) · text/12-fm.txt:779(搜「移除一定比例的词语」) · text/12-fm.txt:783(搜「解码器仅用于判断」)
残句实例与 0.70创建文本嵌入模型text/12-fm.txt:819(搜「Grim jaws are」) · text/12-fm.txt:892(搜「0.6991」)
为什么用分类词元而非平均创建文本嵌入模型text/12-fm.txt:835(搜「平均池化会丢失位置信息」)
领域自适应两步创建文本嵌入模型text/12-fm.txt:581(搜「领域自适应」) · text/12-fm.txt:912(搜「自适应预训练」)
大考卷:八类任务、58 个数据集创建文本嵌入模型text/12-fm.txt:218(搜「MTEB」) · text/12-fm.txt:244(搜「可能需要数小时」)
整个网络一起训:0.85微调表示模型用于分类text/13-fm.txt:153(搜「0.8492」) · text/13-fm.txt:162(搜「只需要几分钟的训练时间」)
只训判别头:0.63微调表示模型用于分类text/13-fm.txt:270(搜「0.6331」) · text/13-fm.txt:265(搜「显著的加速」)
放开最后一块:0.8微调表示模型用于分类text/13-fm.txt:332(搜「'eval_f1': 0.8,」) · text/13-fm.txt:343(搜「仅训练前五个编码器块」)
「冻结层之后是可训练层」与多轮次差异微调表示模型用于分类text/13-fm.txt:219(搜「冻结层之后是可训练层」) · text/13-fm.txt:349(搜「差异(在训练时间和资源方面)通常会变得更大」)
每类 16 条、造对、1280 对微调表示模型用于分类text/13-fm.txt:363(搜「采样训练数据」) · text/13-fm.txt:375(搜「16 * (16 - 1) / 2 = 120」) · text/13-fm.txt:462(搜「1,280 个句子对」)
少样本的结果微调表示模型用于分类text/13-fm.txt:496(搜「0.8363」) · text/13-fm.txt:497(搜「仅用 32 个标注文档」)
连标签都没有的版本微调表示模型用于分类text/13-fm.txt:501(搜「合成样本」)
接着预训练与那个填空对照微调表示模型用于分类text/13-fm.txt:511(搜「即继续预训练一个已经预训练好的 BERT 模型」) · text/13-fm.txt:641(搜「What a horrible idea!」) · text/13-fm.txt:663(搜「What a horrible movie!」)
遮词元 vs 遮整词、15%微调表示模型用于分类text/13-fm.txt:549(搜「整词掩码」) · text/13-fm.txt:553(搜「mlm_probability」)
逐词分类与词元对齐微调表示模型用于分类text/13-fm.txt:692(搜「对序列中的单个词元进行预测」) · text/13-fm.txt:832(搜「Maarten」) · text/13-fm.txt:769(搜「B(开始)或 I(内部)」)
Dean Palmer 那个例子微调表示模型用于分类text/13-fm.txt:771(搜「Dean Palmer」)
特殊词元排除在打分外微调表示模型用于分类text/13-fm.txt:912(搜「Updated: [-100, 1, 2」)

Footnotes

  1. 出处:「创建文本嵌入模型」第 210 段(text/12-fm.txt:210,搜「我们得到了0.59的分数」)、第 364 段(text/12-fm.txt:364,搜「皮尔逊余弦得分为0.72」)、第 471 段(text/12-fm.txt:471,搜「0.80)的模型似乎准确得多」)与第 567 段(text/12-fm.txt:567,搜「0.8509」)。把这四个数排成一张表是我们做的,书里它们分散在四节、没有并列过。 2

  2. 出处:「创建文本嵌入模型」第 146 段(text/12-fm.txt:146,搜「相似度分数在1到5之间」)与第 210 段(text/12-fm.txt:210,搜「其中我们最感兴趣的是 ‘pearson_cosine’」)。书取的是其中「按余弦相似度算的皮尔逊相关系数」这一项——皮尔逊相关系数衡量的是「两组数值涨跌是否同步」,取值 -1 到 1。

  3. 出处:「创建文本嵌入模型」第 41 段(text/12-fm.txt:41,搜「因为钱在那里」)。这个段子书引自 Alan Garfinkel 的《Forms of Explanation》。

  4. 出处:「创建文本嵌入模型」第 41 段(text/12-fm.txt:41,搜「对比性解释」)。书引的概念出自 Tim Miller 关于对比性解释的论文。

  5. 出处:「创建文本嵌入模型」第 45 段(text/12-fm.txt:45,搜「为什么这是狗而不是猫」)。

  6. 出处:「创建文本嵌入模型」第 35 段(text/12-fm.txt:35,搜「更接近,而不相似文档则更疏远」)。

  7. 出处:「创建文本嵌入模型」第 51 段(text/12-fm.txt:51,搜「首批利用对比学习」)。这段在原书里是一条「注」。 2

  8. 出处:「创建文本嵌入模型」第 59 段(text/12-fm.txt:59,搜「49,995,000」)。

  9. 出处:「创建文本嵌入模型」第 59 段(text/12-fm.txt:59,搜「交叉编码器通常不生成嵌入」)。

  10. 出处:「创建文本嵌入模型」第 61 段(text/12-fm.txt:61,搜「比简单的词向量平均」)。书对比的是 GloVe 这类更老的词向量方法。

  11. 出处:「创建文本嵌入模型」第 67 段(text/12-fm.txt:67,搜「孪生架构」)与第 73 段(text/12-fm.txt:73,搜「双编码器或 SBERT」)。原文:「由于两个 BERT 模型的权重相同,我们可以使用单个模型并逐个输入句子。」

  12. 出处:「创建文本嵌入模型」第 73 段(text/12-fm.txt:73,搜「交叉编码器通常比双编码器取得更好的性能」)。

  13. 出处:「创建文本嵌入模型」第 87 段(text/12-fm.txt:87,搜「自然语言推理」)与第 89 段(text/12-fm.txt:89,搜「寻梦环游记」)。

  14. 出处:「创建文本嵌入模型」第 93 段(text/12-fm.txt:93,搜「数据集来生成对比学习的负样本(矛盾)和正样本(蕴含)」)。

  15. 出处:「创建文本嵌入模型」第 97 段(text/12-fm.txt:97,搜「392,702」)。用的是一套公开语言理解考卷里的多类型自然语言推理语料。 2 3

  16. 出处:「创建文本嵌入模型」第 252 段(text/12-fm.txt:252,搜「不建议使用 Softmax 损失」)。 2

  17. 出处:「创建文本嵌入模型」第 71 段(text/12-fm.txt:71,搜「嵌入之间的差异拼接在一起」)。

  18. 出处:「创建文本嵌入模型」第 268 段(text/12-fm.txt:268,搜「0和1之间」)。

  19. 出处:「创建文本嵌入模型」第 272 段(text/12-fm.txt:272,搜「计算两段文本的两个嵌入之间的余弦相似度」)。

  20. 出处:「创建文本嵌入模型」第 274 段(text/12-fm.txt:274,搜「蕴含表示句子之间的高相似度」)。

  21. 出处:「创建文本嵌入模型」第 376 段(text/12-fm.txt:376,搜「交叉熵损失」)。原文:「它被当作一个分类任务。」

  22. 出处:「创建文本嵌入模型」第 475 段(text/12-fm.txt:475,搜「较大的批大小通常对 MNR 损失更有利」)。这段在原书里是一条「提示」框,而且出现了两次。 2

  23. 出处:「创建文本嵌入模型」第 477 段(text/12-fm.txt:477,搜「变得相当容易」)。

  24. 出处:「创建文本嵌入模型」第 479 段(text/12-fm.txt:479,搜「乌得勒支」)。

  25. 出处:「创建文本嵌入模型」第 477 段(text/12-fm.txt:477,搜「难负样本」)。 2

  26. 出处:「创建文本嵌入模型」第 483 段(text/12-fm.txt:483,搜「三种方式」)。

  27. 出处:「微调表示模型用于分类」第 153 段(text/13-fm.txt:153,搜「0.8492」)、第 270 段(text/13-fm.txt:270,搜「0.6331」)与第 332 段(text/13-fm.txt:332,搜「'eval_f1': 0.8,」)。 2

  28. 出处:「微调表示模型用于分类」第 343 段(text/13-fm.txt:343,搜「仅训练前五个编码器块」)与第 345 段(text/13-fm.txt:345,搜「很早就趋于稳定」)。 2

  29. 出处:「微调表示模型用于分类」第 265 段(text/13-fm.txt:265,搜「显著的加速」)。

  30. 出处:「微调表示模型用于分类」第 219 段(text/13-fm.txt:219,搜「冻结层之后是可训练层」)。

  31. 出处:「微调表示模型用于分类」第 349 段(text/13-fm.txt:349,搜「差异(在训练时间和资源方面)通常会变得更大」)。这段在原书里是一条「注意」框。

  32. 出处:「创建文本嵌入模型」第 180 段(text/12-fm.txt:180,搜「通常建议增加此值」)。同一段还解释了另外几个训练设定:一次同时处理多少条、学习率从零线性升到设定值要几步、以及用 16 位而非 32 位浮点数来省内存加速。

  33. 出处:「创建文本嵌入模型」第 779 段(text/12-fm.txt:779,搜「移除一定比例的词语」)。这套做法书叫 TSDAE,书的评价是「在无监督任务和领域自适应方面都表现出了优异的性能」(第 773 段,text/12-fm.txt:773,搜「优异的性能」)。

  34. 出处:「创建文本嵌入模型」第 892 段(text/12-fm.txt:892,搜「0.6991」)与第 902 段(text/12-fm.txt:902,搜「完全使用未标注数据」)。

  35. 出处:「创建文本嵌入模型」第 835 段(text/12-fm.txt:835,搜「平均池化会丢失位置信息」)。

  36. 出处:「创建文本嵌入模型」第 763 段(text/12-fm.txt:763,搜「就获得了0.71的分数」)。书用的黄金数据是 1 万条,白银是另外 4 万条。

  37. 出处:「创建文本嵌入模型」第 670 段(text/12-fm.txt:670,搜「显著更多的不相似对」)。这段在原书里是一条「提示」框。 2

  38. 出处:「创建文本嵌入模型」第 581 段(text/12-fm.txt:581,搜「领域自适应」)与第 908 段(text/12-fm.txt:908,搜「目标领域」)。

  39. 出处:「创建文本嵌入模型」第 912 段(text/12-fm.txt:912,搜「自适应预训练」)。 2

  40. 出处:「微调表示模型用于分类」第 363 段(text/13-fm.txt:363,搜「采样训练数据」)。这套做法书叫 SetFit。补充(不在书里):它的原论文摘要声称「用比现有技术少几个数量级的参数达到高准确率」,并且「与参数高效微调和提示类方法效果相当,但训练快一个数量级」。来源:《Efficient Few-Shot Learning Without Prompts》(Tunstall、Reimers、Jo、Bates、Korat、Wasserblat、Pereg,2022-09-22)https://arxiv.org/abs/2209.11055(查阅于 2026-08-25)

  41. 出处:「微调表示模型用于分类」第 496 段(text/13-fm.txt:496,搜「0.8363」)与第 497 段(text/13-fm.txt:497,搜「仅用 32 个标注文档」)。注意:正文那句说的是 0.85,而紧挨着的评估输出是 0.8363。 2

  42. 出处:「微调表示模型用于分类」第 501 段(text/13-fm.txt:501,搜「合成样本」)。这段在原书里是一条「提示」框。

  43. 出处:「微调表示模型用于分类」第 509 段(text/13-fm.txt:509,搜「领域特定词汇」)。

  44. 出处:「微调表示模型用于分类」第 511 段(text/13-fm.txt:511,搜「即继续预训练一个已经预训练好的 BERT 模型」)与第 513 段(text/13-fm.txt:513,搜「已被证明可以提高模型在分类任务中的性能」)。书举的例子是从通用模型 → 医疗领域模型 → 微调后的药物分类模型这条谱系。

  45. 出处:「微调表示模型用于分类」第 641 段(text/13-fm.txt:641,搜「What a horrible idea!」)与第 663 段(text/13-fm.txt:663,搜「What a horrible movie!」)。

  46. 出处:「微调表示模型用于分类」第 549 段(text/13-fm.txt:549,搜「整词掩码」)与第 553 段(text/13-fm.txt:553,搜「需要更多时间才能收敛」)。

  47. 出处:「微调表示模型用于分类」第 553 段(text/13-fm.txt:553,搜「mlm_probability」)。

  48. 出处:「微调表示模型用于分类」第 692 段(text/13-fm.txt:692,搜「对序列中的单个词元进行预测」)与第 686 段(text/13-fm.txt:686,搜「去标识化和匿名化」)。书特别提到这项技术在处理敏感数据时用于去标识化。

  49. 出处:「微调表示模型用于分类」第 832 段(text/13-fm.txt:832,搜「Maarten」)。

  50. 出处:「微调表示模型用于分类」第 769 段(text/13-fm.txt:769,搜「B(开始)或 I(内部)」)。书用的数据集是一套 2003 年的公开命名实体识别语料,约 1.4 万条训练样本(第 698 段,text/13-fm.txt:698,搜「CoNLL-2003」)。

  51. 出处:「微调表示模型用于分类」第 771 段(text/13-fm.txt:771,搜「Dean Palmer」)。

  52. 出处:「微调表示模型用于分类」第 912 段(text/13-fm.txt:912,搜「Updated: [-100, 1, 2」)。

  53. 出处:「创建文本嵌入模型」第 583 段(text/12-fm.txt:583,搜「找到正确的数据」)。

  54. 出处:「创建文本嵌入模型」第 577 段(text/12-fm.txt:577,搜「已经在完整的 MNLI 数据集上训练过」)。

  55. 出处:「微调表示模型用于分类」第 609 段(text/13-fm.txt:609,搜「我们训练 20 个轮次」)。而同一节的代码里写的是 num_train_epochs=10(第 582 段,text/13-fm.txt:582,搜「num_train_epochs=10」)。

  56. 出处:「创建文本嵌入模型」第 244 段(text/12-fm.txt:244,搜「可能需要数小时」)与第 218 段(text/12-fm.txt:218,搜「MTEB」)。