怎么知道自己这套东西是好是坏 — 全书最大的洞
这一章讲三件事: 这本书唯一一次用数字衡量自己,量出来是多少; 那个数字为什么不作数;以及一套 RAG 到底该分几层量、每层量什么。 它在全书链条里的位置: 前十一章是一张完整的施工图,这一章是验收环节 —— 而在原书里,这个环节是空的。 这一章大半的内容是我们从别处补的,每一处都标了来源。
1. 顶层全景
前十一章:怎么做
加载 → 切块 → 嵌入 → 入库 → 检索 → 生成 → 拼链 → 循环
这一章:做得对不对
┌──────────────────────────────────────────────┐
│ 检索层:找回来的那几条,对不对、全不全? │
│ ↓ 找错了,后面全白搭 │
│ 生成层:话说得对不对、有没有编、答没答到点上? │
└──────────────────────────────────────────────┘
而这本书在这里只有一个数:**0.50**,出自两道题。
它还有一张 27 行的调参表,**每一行都是 1.00**。
图说:两个数字并排放着就已经能看出问题 —— 两道题的准确率是五成, 而换一个判分方法,二十七种不同的设置全部满分。同一套系统,不可能同时是这两个样子。
一句话链条: 前十一章每一步都有取舍 → 取舍要靠数字来定 → 而这本书只量过一次 → 那一次的判分方法看的是字母不是意思 → 于是它所有的「最优参数」都没有依据。
2. 主走查:全书唯一一次量化评测
这一节是本章的主走查。这一章每个机制,在这条线上都占一步 —— 第 6 节还会拿同一条走查,换一把尺子再量一遍。
书在第 5 章的倒数第二个配方里做了一次评测1。整本书只有这一次。
它的评测集:两道题
题 1: 问题「What is RAG?」 该出现的关键词「RAG」
题 2: 问题「What is the purpose of vector stores?」
该出现的关键词「vector stores」
图说:注意第二列不是标准答案,是**「该出现的那个关键词」**——
这个区别是这一节后面所有问题的根源。
六步,每步的实际内容
| 步 | 这一步做了什么 | 具体的数或字串 |
|---|---|---|
| ① | 取出题 1,计时开始 | — |
| ② | 在库里检索,取前 3 条 | 三条的开头一模一样:Retrieval Augmented Generation (RAG) is an archite... |
| ③ | 把三条拼成一整段,看那个该出现的关键词在不在里面 | RAG 在 → 判对 |
| ④ | 计时结束 | 0.137 秒 |
| ⑤ | 题 2 同样走一遍 | 三条也一模一样:mitigates this by augmenting the generation proce... → vector stores 不在 → 判错;耗时 0.017 秒 |
| ⑥ | 汇总 | Accuracy@3 = 0.50 · 平均耗时 0.077 秒 |
书对这个 0.50 不置一词。 它把这个数印出来,然后进入下一个配方。
第 ② 步和第 ⑤ 步里藏着另一件事
两道题的「前三条」,各自都是同一块文字的三份副本。
题 1 的 top-3: A、A、A
题 2 的 top-3: B、B、B
图说:书印出来的是每条的前 50 个字符,三条完全相同。
**这不是三条证据,是一条证据被数了三遍。**
第 05 章讲过这个病:同一个落盘目录被反复运行,数据一遍遍累积进去,而灌库这一步不去重。 账单在这里 —— 这次评测的库,已经是脏的。
后果: 名义上取了 3 条,实际只拿到 1 条不同的内容。 「取前 3 条」这个设置在这里等于「取前 1 条」,而评测报告不会告诉你这件事。
3. 判据坏在哪:它量的是「有没有出现这几个字母」
回到第 ③ 步那一行判断。
它做的事: 把 top-3 拼成一整段文字 → 看「RAG」这三个字母在不在里面
于是——
检索回来的哪怕是「RAG 有三大缺点,千万别用」 → 判对
检索回来的哪怕是「本书由 RAG 出版社出版」 → 判对
检索回来的是正确答案但换了说法(「检索增强生成」)→ **判错**
图说:它量的是**字符串包含**,不是**答对没有**。
这两件事在英文里长得很像,因为答案里通常真的会出现那个词。
**但「通常会出现」不等于「出现了就对」。**
题 2 判错,其实是个好消息
题 2 问「向量库是干什么用的」,该出现的关键词是 vector stores。
检索回来的三条讲的是「RAG 缓解了……」——确实不对题,判错是对的。
所以这个判据不是完全没用:它在这一题上给出了正确的判定。
问题是它给出正确判定靠的是运气 —— 那三条里恰好没有出现 vector stores 这两个词。
判断(我们的,不是书里的): 我们认为这种判据的问题不在「太宽松」, 而在它和「答对没有」这件事只是相关,不是同一件事 —— 所以它两个方向都会错:该对的判错(换了说法)、该错的判对(词出现在无关的句子里)。 一个两个方向都会错的判据,拿它的分数去比较不同设置,得到的排序没有意义。 如果错,会错在: 如果评测集里每道题那个该出现的关键词都足够罕见、 而且只可能出现在正确答案里,那么这个判据的错误率会很低、够用。 判据是把每道题那个关键词在全库里数一遍出现次数 —— 这个统计书里没有,我们也没有跑。
4. 另起一处:27 组设置全部满分的那张调参表
这是本章唯一一处另起的走查,因为它在另一章、用另一份语料。
书在第 6 章做了一次调参:三种块大小 × 三种重叠 × 三种取几条 = 27 组, 每一组建一次库、跑一次评测2。
它的评测集:三道题
题 1: What is Artificial Intelligence?
标准答案「AI is the ability of machines to simulate human intelligence.」
题 2: Explain Deep Learning.
标准答案「Deep Learning uses neural networks with multiple layers.」
题 3: Applications of AI in healthcare.
标准答案「AI assists in disease diagnosis and personalized treatment.」
图说:这一次给的是**完整的句子**,看起来比上一处严谨多了。
而判据只取标准答案的第一个词
那一行代码把标准答案按空格切开,只拿第一个词去比对2。
题 1 的第一个词: **AI**
题 2 的第一个词: **Deep**
题 3 的第一个词: **AI**
图说:也就是说,判对的条件是——
检索回来的文字里出现过「AI」或者「Deep」这两三个字母。
而这个库讲的就是人工智能。**「AI」这两个字母在几乎每一块里都有。**
结果
27 行输出,每一行都是 Accuracy=1.00。 然后书印了一行结论:
「找到的最优参数:块大小 200、重叠 20、取 2 条,准确率 1.00」3。
27 组全是 1.00 → 排序时没有任何一组能超过第一组
→ 「最优」就是**第一个被试到的那一组**
图说:那一行「最优参数」,说的其实是「循环里的第一次」。
**它不含任何信息。**
判断(我们的,不是书里的): 我们认为这张表比上一处那个 0.50 危害更大。 0.50 至少是个会变的数;而这张 27 行全 1.00 的表长得像一份调参报告, 读者很容易直接照抄「块大小 200、重叠 20」这组数去用。 一个永远输出满分的判据,等于没有判据;而它印出来的样子和真的一模一样。 如果错,会错在: 如果作者的本意只是演示「网格搜参的代码怎么写」、 不打算让那组参数被当真,那这是演示,不是结论。 但书写的是「Best Parameters Found」 —— 那是结论的措辞。
顺带一提:这个程序的输出第一行是「文档已存入 ChromaDB」, 而这段代码从头到尾没有碰过 Chroma —— 那一行是从第 5 章另一个配方复制过来的3。
5. 那两个耗时不能比:0.137 秒对 0.017 秒
回到第 ④⑤ 步。 两道题,一道 0.137 秒,一道 0.017 秒,差了八倍。
而这两道题做的是同一件事:同一个库、同样取 3 条。
第一次查询做了什么:
把嵌入模型从磁盘加载进内存 ← 只在第一次发生
算查询的向量
查索引
第二次查询做了什么:
算查询的向量
查索引
图说:那八倍的差距,绝大部分是**加载模型**这一次性开销。
它被算进了第一道题的成绩里。
所以那个「平均 0.077 秒」是一个没有意义的数 —— 它是「一次冷的 + 一次热的」的平均。
正确的量法是把这两种情况分开:
| 量什么 | 怎么量 | 它回答什么问题 |
|---|---|---|
| 冷启动耗时 | 进程刚起来的第一次 | 服务重启后,第一个用户要等多久 |
| 稳态耗时 | 先跑几十次热身,再开始计时 | 正常运行时,用户要等多久 |
两个数都要,但绝不能平均到一起。
6. 换一把尺子,把同一条走查再量一遍
先说这个词:一套系统用来给自己打分的那些量,行话叫指标 —— 上一节那个「准确率」是一个指标,下面要讲的每一个也都是。
这一节把第 2 节那两道题重新量一次,用的是这一行真正在用的那几个指标。 书里一个都没有,下面每一条都标了来源。
补充(不在书里):量一套 RAG 要分两层,因为它有两处独立的死法4:
检索层死了 → 材料本身就不对,后面再强也白搭
生成层死了 → 材料是对的,话说错了
图说:两层的修法完全不同(换切法/换检索 vs 换提示/换模型),
**所以必须分开量。混在一起量出一个总分,你不知道该修哪一头。**
检索层:两个数,一个管干净,一个管全
第一个数管干净:取回的 k 条里,真正相关的占几条。 它叫精确率@k(英文 precision@k)—— 它决定塞进提示的材料里有多少是噪声5。
第二个数管全:库里所有相关的块,这次被找回了几成。 它叫召回率@k(英文 recall@k)—— 第 07 章那个「召回」,说的就是它, 只是那一章量的是「换了索引之后丢了多少」,这里量的是「这次检索漏了多少」5。
| 指标 | 怎么算 | 它管什么 |
|---|---|---|
| 精确率@k | 取回的 k 条里,真正相关的占几条 | 干不干净 |
| 召回率@k | 库里所有相关的块,被找回了几成 | 全不全 |
拿题 1 量一遍:
题 1「What is RAG?」的 top-3 是 A、A、A(同一块的三份副本)
精确率@3 :三条都相关 → **3/3 = 1.00**
但去重之后只有 1 条不同的内容 → **实际只拿到了 1 份材料**
← 这就是为什么光看精确率会被骗
召回率@3 :库里到底有几块讲 RAG 是什么?**书没有印出来,我们不知道。**
所以这个数**算不出来**——而这正是召回率的死穴:
它要求有人事先标好「哪些块是相关的」。
图说:精确率能自己算,召回率必须先有人工标注。
**这也是为什么现实中大家都先量精确率。**
拿题 2 量一遍:
题 2「向量库是干什么用的」的 top-3 是 B、B、B,讲的是「RAG 缓解了……」
精确率@3 :**0/3 = 0.00** ← 三条全不相关
书的判据 :判错 ← **这一次两把尺子结论相同**
图说:结论相同不代表方法相同。
书的判据说的是「那两个词没出现」,精确率说的是「这三条不回答这个问题」。
**换一批数据,这两句话就会分道扬镳。**
还有两个名字出门也会撞见,一句话各自带过5: MRR 只看「第一个相关结果排 第几」,适合「找到一个好答案就够」的场景; nDCG 支持「有多相关」的分级打分(不只是相关/不相关两档),最全面也最重。
生成层:三个数
补充(不在书里)6:
| 指标 | 怎么算 | 它拦什么 |
|---|---|---|
| 忠实度(faithfulness) | 把答案拆成一句句独立的陈述,逐句问「这句能不能从检索到的材料里直接推出来」,能的句数除以总句数 | 胡编 —— 这是 RAG 的核心承诺 |
| 答案切题度 | 从答案反推出一个问题,看它和原问题有多像 | 答非所问(材料对了,话跑偏了) |
| 引用准确度 | 答案里标的出处,是否真的支撑那句话 | 引用造假(第 08 章那一处) |
忠实度这个词第 09 章出现过一次 —— 书给那段动态长度的摘要标的标题就是「忠实的摘要」。 这里给它一个可以算出来的定义:
答案:「RAG 通过把答案落在检索到的资料上来减少胡编。
它还能让模型跑得更快。」
拆成两句独立陈述:
① RAG 通过把答案落在检索到的资料上来减少胡编。 → 材料里有 → **1 分**
② RAG 能让模型跑得更快。 → 材料里没有 → **0 分**
忠实度 = 1 / 2 = **0.50**
图说:上面这段答案和这两个判定,是我们为演示编的,不是书里的。
**但这个算法本身不是编的**——拆句、逐句判、算比例,来源见脚注。
逐句判定那一步,用的正是第 09 章说的那类模型(判断「材料能不能推出这句话」), 或者用一个更大的模型当裁判。
拿裁判模型要小心
补充(不在书里):用模型给模型的答案打分,这条路灵活、便宜、不需要人工标注, 但它有几类固定的偏差 —— 比如偏爱长答案、偏爱自己家族的模型写出来的文风6。 所以裁判模型的分数要定期拿人工抽检去校准,不能一装上就当真。
7. 没有标注数据,怎么起步
这一整节的做法书里没有,是通用工程实践 —— 书连「该有一套评测题」这句话都没说过。 下面每一处指回第 05 / 09 章的地方,指的是我们前面几章记下的债,不是书里的交代。
这是最常见的挡路理由,而它挡不住。
| 步 | 做什么 | 花多少力气 |
|---|---|---|
| ① | 手写 30–50 道题,每题写清「正确答案该长什么样」和「哪几块是相关的」 | 一两个下午 |
| ② | 跑一遍现在的系统,记下每一层的分数 —— 这就是基线 | 一次 |
| ③ | 以后每改一个设置,重跑一遍,和基线比 | 每次几分钟 |
| ④ | 把这一步接进日常构建流程,分数掉了就拦下来 | 一次性配置 |
关键不在题多,在判据不能松到什么都能通过。
❌ 「期望关键词出现在结果里就算对」 ← 第 3 节那个
❌ 「标准答案的第一个词出现就算对」 ← 第 4 节那个
✅ 「哪几块是相关的」事先标好,按精确率算
✅ 「答案里每一句能不能被材料支持」逐句判
图说:前两条的共同点是**判据落在字符串上**;
后两条的共同点是**判据落在「这条材料回不回答这个问题」上**。
这套题的第一笔红利:那几个阈值终于有依据了
第 05 章欠了一笔账 —— 那道检索门槛该定在多少,当时说「这属于评测,第 12 章讲」。 这套题就是答案:
① 拿这 30–50 道题跑一遍,把每次检索的分数都记下来
② 分成两堆:
堆 A = 你标过「这块是相关的」那些块,它们拿到的分数
堆 B = 明显无关的那些块,它们拿到的分数
③ 把这两堆数各自排开,**门槛画在两堆之间那道缝里**
图说:这两堆分数如果**分得开**,缝在哪儿就把门槛定在哪儿;
如果**叠在一起分不开**,说明问题不在门槛——**是嵌入模型或切法不行**,
这时候调门槛只是在两种错法之间挪,怎么挪都不对。
第 09 章那两个分档的数(0.7 和 0.5)该用同一个办法定出来 —— 书没说它们从哪来,只把它们硬写在代码里(第 09 章第 10 节记过这笔账), 而换成我们来定,走的就是上面这三步,只是那两堆换成了 「答对的那些次 」和「答错的那些次」各自的置信度。
这就是为什么阈值必须在评测之后定,而不能照抄别人的数 —— 它取决于你的模型、你的切法、你的语料,三样有一样变了,那道缝就挪了位置。
还有一个立刻能做的自检:把评测题倒过来问一遍。 问一道库里肯定没有答案的题,看系统会不会老实说「我没找到」 —— 第 05 章那道门槛和第 09 章那个置信度,能不能起作用,这一题就能看出来。
8. 作者的判断与证据
书里给了证据的:
| 说法 | 证据 |
|---|---|
| 检索质量可以用「取前 k 条命中没有」来量 | 那两道题,以及 Accuracy@3 = 0.50 这个数 |
| 第一次查询比后面慢 | 0.137 秒对 0.017 秒 —— 数是真的,书没解释原因 |
| 参数会影响检索效果 | 没有证据 —— 27 组全是 1.00,这张表证明不了任何差别 |
作者只是断言、没有给证据的:
| 说法 | 缺什么 |
|---|---|
| 「最优参数:块大小 200、重叠 20、取 2 条」 | 27 组并列第一,这一组只是第一个被试到的 |
| 「块大小的经验值是 300–800 个标记」 | 没有实验支撑;书里没有一个配方跑过这个量级(第 03 章第 3 节算过:它最大的那个块还不到自己推荐下限的四分之三) |
| 「热数据的界线是 180 天」 | 没说这个数从哪来 |
| 「相似度门槛 0.5」「高 / 中 / 低 分在 0.7 和 0.5」 | 三个阈值,没有一个说明来历 |
这四行是同一件事的四次重复:书给了一堆具体的数,而没有一个数有来路。
9. 边界与局限
- 全书只量过一次检索质量,一次都没量过生成质量。
- 两处评测的判据都是字符串包含,一处看整个关键词,一处只看第一个词。
- 两处评测的评测集分别是 2 道题和 3 道题。
- 没有一处提到忠实度、精确率、召回率这些名字。
- 没有一处把评测和前面十一章的任何一个取舍连起来 —— 切多大、取几条、门槛设多少、要不要重排,每一个都该由评测来定,而书全靠给一个经验值。
- 延迟量了但没解释,冷热混在一起平均。
- 那次评测用的库是脏的(同一块出现三次),而书没有察觉。
- 这一章的绝大部分内容是我们补的。 我们补的那些指标本身也有边界: 裁判模型有偏差,而需要人工标注的指标一改数据就过时5。