一句话怎么变成一串数 — 按掩码求平均、384 维,和三种「远近」
这一章讲三件事: 一段文字被压成一串数的中间发生了什么; 「意思近的东西这串数也近」这条性质是从哪来的;以及「远近」到底有哪几种量法。 它在全书链条里的位置: 这是六站里的第三站。前面两站的产物到这里被换了形态 —— 从此后面所有的「像不像」,都变成了对这串数的算术。
1. 顶层全景
一段文字
│
① 切成标记(第 03 章讲过的那个单位)
▼
["retrieval", "aug", "##mented", "generation", …]
│
② 每个标记各出一串数
▼
一叠向量:标记 1 一串、标记 2 一串、……
│
③ 按「哪些位置是真内容」求平均,压成一串
▼
这一段文字的一串数(384 个)
│
④ 除以自己的长度,让它长度恰好等于 1
▼
可以直接拿去比远近了
图说:①②③ 是模型内部干的事,④ 是可选的一步收尾。
书把 ①②③ 的 代码原样摆了出来,却把小标题写成「Tokenize the documents」,一个字不解释。
一句话链条: 机器只会算数 → 那就把文字换成数 → 但要让「意思近」变成「数也近」→ 这条性质靠训练逼出来 → 于是「像不像」变成「远不远」→ 而「远近」有三种量法,方向还不一样。
2. 先看现象:按字面找有一个躲不开的死角
书自己把这个死角说得很清楚。这里说的关键词搜索(拿查询里的字,到文档里逐字去对, 对上的多就排前面)靠的是字面匹配,而查询换一种说法,它就找不到目标了1。
举个例子:你的文档里写的是「员工离职需提前一个月提交书面申请」, 而用户问的是「辞职要提前多久打报告」。两句话讲的是同一件事,共同的字一个都没有。
所以需要一种「按意思比」的表示 —— 这就是这一站存在的全部理由。
照实说: 书从头到尾没有演示过一次「换个说法也能命中」。 它的每个演示语料里,查询和目标文档总是共享关键词。 所以这一条我们只能引用书的立论,不能引用书的证据。
3. 不是给一个号,是给一 串数
最容易想到的办法行不通
给每个词编个号:「猫」是 3542 号,「狗」是 3543 号。
这条路走不通,理由很简单:编号是随便定的。 3542 和 3543 挨着,不代表猫和狗有什么关系; 你换个排法,它们就离得很远。号码之间的距离不携带任何意思。
真正的办法
不给一个数,给一串数;并且让意思相近的东西,这串数也相近。
「辞职要提前多久打报告」 → [ 0.12, -0.03, 0.44, …… ] 共 384 个数
「员工离职需提前一个月……」 → [ 0.11, -0.05, 0.41, …… ] 共 384 个数
↑ 每一位都很接近
「今天中午吃什么」 → [-0.31, 0.28, -0.07, …… ]
↑ 每一位都对不上
图说:这些数是为演示编的,不是真实数值。要看真数,见第 5 节的走查。
这一步做成了,「像不像」就变成了「远不远」—— 一个机器算得出来的量。
第 01 章说过,这串数叫这段文字的嵌入,也叫它的向量。
4. 「意思近 ⇒ 数也近」这条性质是从哪来的
这一节是这一章的地基。跳过它,后面所有「为什么这条没检索到」都推不出来。
先把一个误会拆掉:这不是天生的
一个刚训练完的通用语言模型,它内部那些数并不满 足这条性质 —— 语义相近的两句话,在它的输出空间里并不相近2。
所以这条性质是被专门训出来的。 干这件事的那类模型有个专名叫句向量模型
(英文 sentence-transformers,你在模型名字里看到的 all-MiniLM-L6-v2 就是其中一个)。
怎么训出来的
做法朴素得出奇:
准备一大堆「该像的一对」:
(一个问题,它真正的答案)
(一句话,它的另一种说法)
(一个查询,它命中的那篇文档)
每次拿一小批出来:
├─ 把「该像的这一对」往一起拉
└─ 把这一批里其余所有的组合往开推
重复几百万次。
图说:「推远谁」这件事决定了训练信号的质量——同一批里凑巧也很像的那些,
被硬推开会伤到模型;所以怎么挑「推远的对象」是这类训练最讲究的地方。
「意思近的东西这串数也近」,就是这么被逼出来的副产品。 没有人写规则告诉它什么叫「意思近」, 它只是被反复奖惩到这个程度3。
边界:它只在训练过的那类文本上守信
这条性质不是普适定律,是一个训练结果。所以它有明确的失效范围:
| 什么时候它不守信 | 为什么 |
|---|---|
| 你的文本是它没见过的类型(内部工单编号、化学式、代码) | 训练时没有这类「该像 的一对」,它压出来的位置没有依据 |
| 你把正文洗成了不成句的词串(第 02 章那个去停用词的配方) | 它训练时见的全是完整句子 |
| 你的语言它训得少(很多模型是英文为主) | 同上 |
这三条,书一条都没提。 而它们是「为什么这条明明相关却没检索到」最常见的三个答案。
5. 主走查:一句话被压成一串数,一步步看数字
这一节是本章的主走查。上面每个机制,在下面都占一步。
先说清楚:这一章书里没有一条连贯的线 —— 四个配方各带各的语料。 下面这条线是我们接起来的,每一步标出配方号,接缝处照实说书换了什么。
| 步 | 出自 | 具体的数 / 状态 |
|---|---|---|
| ① 三句话进去 | 配方 42 | "RAG is a powerful technique for combining retrieval with language models." 等三句 |
| ② 切成标记、补齐长度 | 配方 42 | 书只印了代码没印标记,这一步的中间结果书里没有 |
| ③ 每个标记各出一串数 | 配方 42 | 同上 |
| ④ 按掩码只对真内容求平均 | 配方 42 | 3 个向量;第一句的前 5 个数是 tensor([-0.2326, 0.1575, 0.2014, -0.0072, -0.3706]) |
| 接缝 | 配方 41 换了输入(一句话) | 走的是包好的完整流水线,比上面多一步收尾 |
| ⑤ 一句话 → 一串数,有多少个 | 配方 41 | 384 个;前 10 个是 [-0.0455, -0.0481, -0.0086, 0.0733, …] |
| 接缝 | 配方 50 换了输入(三条只差标点和大小写的句子) | —— |
| ⑥ 除以自己的长度之后 | 配方 50 | 三条的长度全都是 norm=1.00 |
| 接缝 | 配方 43 换了输入(四句话) | —— |
| ⑦ 拿两串数比远近 | 配方 43 | 问「How to search a vector embeddings?」→ 距离 1.0279 和 1.3194 |
下面逐步展开。
第 ①②③ 步:先切成标记,每个标记各出一串数
书有一个配方不用现成的库,直接手写了一遍这个过程4。它是全书唯一一次露出模型内部:
encoded = tokenizer(docs, padding=True, truncation=True, return_tensors='pt')
output = model(**encoded)
output.last_hidden_state ← 每个标记一串数
图说:第一行把三句话切成标记;因为三句长短不一,要一起喂进模型就得补齐到同样长,
补出来的位置塞的是占位符。第三行拿到的是「每个标记各一串数」的一叠。
注意「补齐」这件事 —— 它直接导致了下一步必须多做一个动作。
第 ④ 步:按掩码求平均,把一叠压成一串
现在手上是一叠向量,一个标记一串。可我们要的是「整句话一串」。
最直接的办法是求平均。但不能全都平均 —— 补出来的那些占位符不是内容,平均进去会稀释真内容。
所以模型另外给了一串 0 和 1,标出哪些位置是真内容,哪些是补的。这串 0/1 叫掩码。
三句话补齐到同样长(假设 12 个位置):
句 1: 真 真 真 真 真 真 真 真 真 补 补 补
掩码: 1 1 1 1 1 1 1 1 1 0 0 0
做法: 每个标记的那串数 × 它的掩码 → 再全部相加 → 再除以掩码之和(这里是 9)
图说:乘 0 就等于把补出来的位置整个抹掉;除以 9 而不是除以 12,
所以句子长短不同也不会影响结果的尺度。这一步的正式名字叫 **mean pooling**(按掩码求平均)。
上面的位置数是为演示编的,书里没印标记数。
书里这段代码的小标题写的是「3. Tokenize the documents」—— 它把这一整套动作叫成了「切标记」,mean pooling 是什么、为什么要除以掩码之和,一个字没解释。
这一步跑完,三句话各得到一串数。第一句的前 5 个是:
tensor([-0.2326, 0.1575, 0.2014, -0.0072, -0.3706])
第 ⑤ 步(接缝):走包好的流水线,是 384 个数
书换了输入。 另一个配方用现成的库嵌一句话,打印出5:
Embedding dimension: 384
First 10 values of the embedding:
[-0.04551849886775017, -0.04808536171913147, -0.008631237782537937, 0.07327593117952347, …]
图说:一句话进去,384 个小数出来。上面只印了前 10 个。
这个 384 有个名字叫这串数的维度:说的就是「这串数一共有多少个数」。
384 是多少? 拿那句英文比一下:输入是一句 15 个词左右的话,出来 384 个数。 这一步不是压缩,是换一种表示 —— 数比词还多。
而这里有个值得停一下的差别。 把第 ④ 步和第 ⑤ 步的数字并排看:
手写那一版(第 ④ 步): -0.2326、0.1575、0.2014、-0.0072、-0.3706
包好那一版(第 ⑤ 步): -0.0455、-0.0481、-0.0086、 0.0733、……
图说:同一个模型,手写那一版的数明显大一圈——大约五倍。
判断(我们的,不是书里的): 差别在于包好的那条流水线最后多做了一步: 把向量除以自己的长度。 算一下就知道:384 个数如果平方和等于 1,平均每个数的大小 只有 1÷√384 ≈ 0.051 —— 和第 ⑤ 步那几个数正好一个量级;而第 ④ 步那几个数大到 0.37, 一个长度为 1 的 384 维向量不可能有这么大的分量。 如果错, 会错在: 如果两版的差别其实来自输入不同(它们确实是不同的句子), 那这个解释就站不住。判据是量级:换句子会让每个数变,但不会让整体大小系统性地差五倍。
第 ⑥ 步(接缝):除以自己的长度之后,长度恰好是 1
书又换了输入,这次是三条只差标点和大小写的句子。 它先算出向量,再手动除以每个向量自己的长度,打印出来的是6:
Sentence 1 (norm=1.00): [-0.11171343 0.01787068 -0.02697249 0.0171152 -0.0706844 ] ...
Sentence 2 (norm=1.00): [-0.11091074 0.01590041 -0.02369356 0.002776 -0.07597825] ...
Sentence 3 (norm=1.00): [-0.11298969 0.01687417 -0.04100865 0.02022843 -0.07349404] ...
图说:`norm` 就是这串数的长度,三条全是 1.00——这是「除以自己的长度」这个动作的定义,
不是什么发现。真正值得看的是三行数字本身:它们几乎一模一样,只差在小数点后第三位。
这一步正式的名字叫归一化(L2 归一化)。 它到底有什么用、书在这里把它说成了什么,第 8 节专门讲。
第 ⑦ 步(接缝):终于可以比远近了
书最后一次换输入,四句话,建一个最朴素的索引,问「How to search a vector embeddings?」, 取最近的两条7:
Match 1: 'Embedding turns text into numeric vectors.' (distance: 1.0279)
Match 2: 'FAISS enables fast similarity search.' (distance: 1.3194)
图说:这两个数是**距离**——越小越近。
参照物很好记:两串数完全一样时距离是 0;这里第一名是 1.03,第二名是 1.32,
两者只差 0.29,说明这两条候选其实咬得很紧。
到这里,这一章的链条闭合了: 文字 → 标记 → 一叠向量 → 一串数 → 长度归一 → 一个可以排序的距离。
6. 384 是模型定死的,所以两头必须用同一个模型
384 不是你能调的参数
它是模型出厂时就定死的。 你换一个模型,这个数就变:
| 模型 | 一串数有多少个 |
|---|---|
all-MiniLM-L6-v2(这本书主要用的) | 384 |
all-mpnet-base-v2(这本书第 9 章后半段悄悄换成的) | 768 —— 正好是前者的两倍8 |
书没有一处说过这件事。 它只是印出 384,像在报告一个观察结果。
后果:入库和查询必须用同一个模型
入库时: 文档 ──(模型 A)──► 一串 384 个数 ──► 存进库
查询时: 问题 ──(模型 B)──► 一串 768 个数 ──► 拿去比
结果:根本比不了——长度都不一样。
图说:就算两个模型碰巧都是 384 维,也一样不能混用:
两个模型各自把「意思」摆在不同的位置上,同一个 384 维空间里的坐标含义完全不同。
而书里有一处正好犯了这个错: 有个配方建库时调用嵌入模型不传模型名字,
用的是库的默认模型;而同一本书其他地方一律指定 all-MiniLM-L6-v29。
书从没有警告过这件事。
判断(我们的,不是书里的): 这是这本书里最容易被照抄进生产的一个坑。 因为它不报错 —— 只要两边的维度碰巧一样,程序会正常跑完,只是检索结果全是乱七八糟的东西。 如果错,会错在: 如果那个默认模型恰好就是
all-MiniLM-L6-v2,那这一处就没有问题。 判据是运行时打印一下维度:书里那个配方从没打印过。
7. 三种「远近」,方向还不一样
这一节是这一章最该记住的东西。 前面第 01 章那三个数(0.9408 / 1.3505 / 2.0506)、 本章第 ⑦ 步那两个数(1.0279 / 1.3194),都要在这里才说得清。
三种量各是什么
第一种叫 L2 距离:把两串数看成空间里的两个点,量这两点之间有多远。 越小越近,完全相同是 0,而且没有上限 —— 两个点可以要多远有多远。
第二种叫内积:把 两串数对应位置相乘,再全部加起来。越大越像。 它同时受两件事影响:方向像不像,以及各自有多长。
第三种叫余弦相似度(常常简称余弦):只看方向、不看长度。 范围是 −1 到 1 —— 1 是方向完全一致,0 是毫不相干,负数是方向相反。
① L2 距离 越小越近 0 ────────► 没有上限
② 内积 越大越像 受方向和长度双重影响
③ 余弦相似度 越大越像 −1 ──── 0 ──── 1
图说:三种量的方向不一样——①是「越小越好」,②③是「越大越好」。
这就是第 01 章那三个数最容易读反的原因。
用书里的真数字看每一种
| 量法 | 书里哪一处 | 数字 | 怎么读 |
|---|---|---|---|
| L2 距离 | 第 01 章配方 11 | 0.9408 / 1.3505 / 2.0506 | 第一名是最小的那个;三者拉得挺开 |
| L2 距离 | 本章第 ⑦ 步 | 1.0279 / 1.3194 | 同上,但两者只差 0.29,咬得很紧 |
| 余弦相似度 | 第 9 章配方 91 | 0.3682 / 0.0452 / −0.0133 | 满分是 1,第一名只拿到三分之一多一点;第三名是负的,方向已经相反了,照样进了前三 |
| 余弦相似度 | 第 9 章配方 95 | 0.6190 | 这是全书最高的一个余弦分,也才 0.62 |
最后一行那两个数值得记住: 在真实语料上,0.6 就已经算「很像」了。 如果你期待相关文档能拿到 0.9,那是对这个量的误解。
三者的关系
这三种量不是三件事,是同一件事的三种切法:
给 a 和 b 都除以自己的长度(也就是第 ⑥ 步那个动作)之后:
内积 = 余弦相似度 ← 长度都是 1,乘出来的就只剩方向
L2 距离² = 2 − 2 × 余弦 ← 所以「L2 越小」和「余弦越大」排出来的名次完全一样
图说:归一化之后,三种量排出来的名次是同一个。这就是下一节的全部内容。
8. 归一化真正的用途 —— 书在这里说错了
书说了什么
真实的文本里总有一些不该有的杂东西:错字、大小写不统一、扫描识别错的字符 —— 这类东西行话叫噪声。
书有一节的标题就叫「用预归一化做抗噪声的嵌入」,正文说:真实数据有这些毛病, 而归一化是一种在它们进入下游之前稳住嵌入的技术10。
它的真正用途
归一化和抗错字没有任何关系。 它只做一件事:把向量的长度统一成 1。
而这件事换来的是上一节最后那个等式:
长度都是 1 之后,内积就等于余弦相似度。
这就是它全部的实用价值,而且价值很大:
想按「意思像不像」排序 → 想用余弦相似度
│
└─ 可是向量库里最快、最基础的两种索引,一种算 L2 距离,一种算内积,
**没有一种直接算余弦**
│
└─ 那就先把所有向量归一化 → 从此内积就是余弦 → 用内积索引即可
图说:归一化是一步「预处理换算力」的交易:入库时每条多做一次除法,
换来查询时可以用最快的索引直接得到余弦。
书自己在第 9 章把它用对了一次: 那个配方在嵌入时打开 normalize_embeddings=True,
然后建的是内积索引,注释里明写「scores are cosine similarities」11。
同一件事,第 4 章讲错、第 9 章用对,而书没有把两处联系起来。
书自己印出来的那三行,说明的恰好是相反的事
回头看第 ⑥ 步那三行数字。三条句子的区别是:一条正常、一条多了两个感叹号和几个空格、 一条全大写。它们归一化之后的向量几乎一模一样,只差在小数点后第三位。
判断(我们的,不是书里的): 这三条几乎一样,功劳不在归一化,在模型本身。 理由是算术上的:归一化只改长度、不改方向,所以「三条的方向几乎一致」这件事 在归一化之前就已经成立了 —— 否则除以一个正数也变不出来。 书拿这个输出当「抗噪声」的证据,实际上它证明的是「这个模型本来就不怕这点噪声」。 如果错,会错在: 如果这三条在归一化之前长度差得很多,而书想说的「稳住」指的是长度而非方向, 那么这个说法在某种狭义上成立。判据是原文:它 说的是抗错字、抗大小写、抗识别失真, 那是方向层面的事,不是长度层面的事。
9. 另起一处:块切得越碎,块与块之间越不像
这是本章唯一一处需要另起的走查,因为它落在失效路径上。
书的最后一个配方想做一件事:把一篇短文切成小块,两两算相似度,超过 0.7 的连一条边, 最后画出一张「哪些块彼此相关」的图。
走一遍
输入: 一段讲 RAG 的短文
切块: 每块最多 80 个字符、重叠 20 ← 注意这个 80
切出: 7 块,其中第 2 块和第 3 块还共享着「them into chunks,」这几个词
两两算相似度,阈值 0.7
输出:
Graph edges based on similarity >= 0.7:
(下面什么都没有)
图说:一条边都没连出来。连相邻且共享文字的第 2、3 块都没过线。
而书照样把它当成功案例收了尾。
为什么会这样
因为 80 个字符太短了。 一块只有半句话,它的「意思」很稀薄 —— 两块各自讲半件事,方向自然对不上。
这条经验可以直接带走:
块越大 → 每块的意思越完整 → 块与块之间越容易相似
块越小 → 每块的意思越单薄 → 相似度整体下滑
图说:所以「阈值定在 0.7」这种写法,在小块上根本不成立——
阈值必须跟着块大小一起调,而书里两个用阈值的配方都是写死的。
这一条同时回答了第 05 章的一个问题: 相关性阈值该定多少? 没有一个普适的数 —— 它取决于你的块有多大、模型是哪个、语料是什么。
10. 作者的判断与证据
书里给了证据的:
| 说法 | 证据 |
|---|---|
| 一句话可以变成固定长度的一串数 | 配方 41 的输出:Embedding dimension: 384 |
| 中间要经过「每个标记一串数 → 求平均」 | 配方 42 手写的那段代码,是全书唯一一次露出内部 |
| 归一化之后长度是 1 | 配方 50 的输出:三条全是 norm=1.00 |
| 有了向量就能排出名次 | 配方 43 的两个距离:1.0279 / 1.3194 |
| 归一化 + 内积 = 余弦 | 第 9 章配方 91 的代码注释与那三个余弦分 |
作者只是断言、没有给证据的:
| 说法 | 缺什么 |
|---|---|
| 「预归一化能抗错字、抗大小写、抗识别失真」 | 这是错的,而且它自己印出 来的那三行恰好说明了相反的事 |
| 那个块相似图配方「能用来做需要跨块串联的多步问答」 | 输出是零条边,一个用途都演示不了 |
| 「只嵌摘要」那个配方说检索到摘要后「可以再展开成全文」 | 代码没有实现:那三个文档根本没有元数据,检索到摘要之后回不到全文 |
| 有个配方标题写「用元数据做更好的过滤」 | 代码里一次都没过滤,只是把标签打印出来 |
11. 边界与局限
- 书从没解释过 384 是什么、从哪来、换模型会不会变。
- 书从没警告过「入库和查询必须用同一个模型」,而它自己有一处正好犯了这个错。
- 三种量法书一种都没讲。 L2、内积、余弦在书里各出现过,但从没有一处说明它们的方向和关系。
allow_dangerous_deserialization=True这个参数在这一章第一次出现,零解释 (它是什么,第 05 章讲)。- 有一个配方的正文里混进了另一个配方的介绍 —— 讲的是「先用轻量模型召回、 再用在线服务重排」,和它所在的那一节毫无关系,后面也没有对应的代码。像是删了某个配方后的残留。
- 多语种、图文混合的嵌入一个字没有(书里那张选型表提了一句「图文混用的场景换别的模型」,再无下文)。
12. 可带走的
全章那条走查,一行写完:
三句话 → 切成标记、补齐长度 → 每个标记各一串数 →
按掩码求平均得 tensor([-0.2326, 0.1575, 0.2014, -0.0072, -0.3706])。
(换输入)走完整流水线得 384 个数、前几个是 [-0.0455, -0.0481, …] →
(换输入)除以自己的长度后 norm=1.00 → (换输入)拿去比远近得距离 1.0279 / 1.3194。
- 给一串数,不是给一个号 —— 号码之间的距离不携带意思;
- 「意思近 ⇒ 数也近」是训练逼出来的,不是天生的 —— 拿海量「该像的一对」拉近、其余推远;
- 所以它有失效范围:没见过的文本类型、被洗成词串的正文、训得少的语言;
- 中间三步:切成标记 → 每标记一串数 → 按掩码求平均;掩码是为了不把补齐用的占位符平均进去;
- 384 是模型定死的,换模型会变(另一个常用模型是 768,正好两倍);
- 入库和查询必须用同一个模型 —— 弄错了不报错,只是结果全是乱的;
- 三种量法方向不同: L2 距离越小越近(没有上限)、内积越大越像、余弦范围 −1 到 1;
- 真实语料上 0.6 的余弦就算很像了,别期待 0.9;
- 归一化的用途是让内积等于余弦,不是抗错字 —— 书这里说错了,而它第 9 章又用对了;
- 块越小,块与块之间的相似度整体越低 —— 所以固定阈值必须跟着块大小一起调。
13. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 关键词搜索的死角 | CHAPTER 5 Vector Stores for Semantic Retrieval | text/12-fm-introduction.txt:7(搜「struggles when queries are phrased differently」) |
| 手写 mean pooling(全书唯一一次露出内部) | CHAPTER 4 Embedding Strategies for Vector Retrieval | text/11-fm-introduction.txt:233(搜「def mean_pooling(model_output, attention_mask)」) · text/11-fm-introduction.txt:245(搜「padding=True, truncation=True」) · text/11-fm-introduction.txt:273(搜「Document 1 vector (first 5 dims)」) |
| 384 维 | 同上 | text/11-fm-introduction.txt:150(搜「Embedding dimension: 384」) · text/11-fm-introduction.txt:154(搜「-0.04551849886775017」) |
| 裸索引比距离 | 同上 | text/11-fm-introduction.txt:409(搜「distance: 1.0279」) · text/11-fm-introduction.txt:411(搜「distance: 1.3194」) |
| 归一化被说成「抗噪声」 | 同上 | text/11-fm-introduction.txt:1195(搜「typo errors, inconsistent casing」) · text/11-fm-introduction.txt:1197(搜「Pre-normalization is a technique that stabilizes embeddings」) |
| 归一化之后 norm = 1.00 | 同上 | text/11-fm-introduction.txt:1314(搜「Sentence 1 (norm=1.00)」) |
| 块相似图:零条边 | 同上 | text/11-fm-introduction.txt:1400(搜「chunk_size=80, chunk_overlap=20」) · text/11-fm-introduction.txt:1462(搜「Graph edges based on similarity >= 0.7」) |
| 建库时不传模型名 | CHAPTER 1 Foundation of Retrieval-augmented Generation | text/08-fm-introduction.txt:959(搜「embeddings = HuggingFaceEmbeddings()」) |
| 归一化 + 内积 = 余弦(用对的那一次) | CHAPTER 9 Effective Search for RAG Systems | text/16-fm-introduction.txt:146(搜「normalize_embeddings=True」) · text/16-fm-introduction.txt:170(搜「scores are cosine similarities」) |
| 真实语料上的余弦分 | 同上 | text/16-fm-introduction.txt:196(搜「score=0.3682」) · text/16-fm-introduction.txt:200(搜「score=-0.0133」) · text/16-fm-introduction.txt:900(搜「0.6190」) |
| 第 9 章后半段换了嵌入模型 | 同上 | text/16-fm-introduction.txt:829(搜「all-mpnet-base-v2」) |