Lecture 7 · 提示增强(一):检索增强生成 · Prompt Augmentation: Retrievers

Prompt Engineering · Lecture 07。个人学习讲义与配套实验,按主题整理并持续修订。

课程源码 ZIP ↓

个人整理的学习笔记,非官方讲义;课程来源与整理说明见课程介绍。配套示例未逐一运行验证。

本篇目录

对应 Maps to:CMU 17-630 Prompt Engineering(Prof. Travis Breaux),第 5 周的第一个主题 · Prompt Augmentation: Retrievers。原课 Schedule 对这一讲的说明(意译):检索增强生成的架构,包括分块、向量化,以及检索前、检索后和模块化的检索处理技术。原课作业 3(RAG)要求在问答和文本摘要两种任务上评估内容导入策略(分块等)和检索器。 依据说明:原课的 slides 和阅读清单不公开。Schedule 点名的几个部分——架构(§2)、分块(§3)、向量化与检索(§4)、检索前 / 检索后处理(§5)、模块化 RAG(§2、§7)——各占一节;§6(生成这一侧)、§7(什么时候检索)和 §8(评估)是讲义补充的,对应作业 3 的”评估”要求。材料来自 Lewis et al.(2020,RAG)、Gao et al.(2023,RAG 综述)、Robertson & Zaragoza(2009,BM25)、Karpukhin et al.(2020,DPR)、Thakur et al.(2021,BEIR)、Sciavolino et al.(2021)、Cormack et al.(2009,RRF)、Nogueira & Cho(2019)、Khattab & Zaharia(2020,ColBERT)、Gao et al.(2023,HyDE)、Ma et al.(2023)、Liu et al.(2024,Lost in the Middle)、Mallen et al.(2023)、Asai et al.(2024,Self-RAG)、Zou et al.(2024,PoisonedRAG)、Xie et al.(2024)、Rashkin et al.(2023)与 Gao et al.(2023,ALCE)等;引用时按原文的说法转述结论,具体数字以原文为准。全部实验是本讲义自己设计的,不代表课堂上讲了什么。 配套代码 Code:code/lecture07/(numpy + PyTorch CPU;1 个模块 + 6 个脚本;不需要 API key;检索部分不需要训练,HyDE 和”什么时候检索”用到第 5 讲的长尾模型缓存;每个脚本都在 5 秒以内) 预计阅读时间:4 小时(含运行代码)


0. 本讲导航 · How to read this lecture

0.1 学习目标 Learning goals

学完这一讲,你应该能够:

  1. 说清楚为什么要检索(长尾、过时、私有、可溯源),以及 RAG 和”把知识微调进模型”的取舍。
  2. 画出一个 RAG 系统的流水线(导入:分块 → 编码 → 建索引;查询:检索前 → 检索 → 检索后;生成:拼提示 → 回答 → 引用),并解释”朴素 / 高级 / 模块化 RAG”的区别。
  3. 为一个文档库选择分块方式(大小、重叠、边界、标题前缀),并解释问答和摘要对检索的不同要求。
  4. 从零实现 BM25 和一个稠密检索器,说出它们各自的典型错误;用 RRF 或在开发集上调权重来融合它们。
  5. 使用查询改写、多查询、HyDE 和重排,并解释 bi-encoder 与 cross-encoder 的取舍。
  6. 用检索指标(R@k、MRR)和端到端指标(问答准确率、摘要覆盖率、归因)评估 RAG;理解”检索到了 ≠ 用上了”。
  7. 设计”什么时候检索、资料和记忆冲突时听谁的”的策略,并知道 RAG 引入的新风险(投毒)。

0.2 先修与本讲范围 Prerequisites & scope

默认你已经会 Assumed本讲只给直觉,后面再展开 Previewed only
第 1 讲:TF-IDF、PPMI-SVD 词向量、句向量、bi-encoder vs cross-encoder(§4)把检索当作一个工具来调用(第 8 讲)
第 5 讲:长尾知识、置信度与 AUROC、“给它一条退路”多跳检索与任务分解(第 9 讲)
前置讲 P1:token、按 token 分块;P2:成本与延迟LLM 判官做评估(第 10 讲);间接提示注入与防御(第 12 讲)
前置讲 P3:开发集 / 测试集、区间智能体的记忆检索(第 14 讲)

0.3 本讲地图 Map

§1  为什么要检索:长尾、过时、私有、可溯源;RAG vs 微调;前提是模型会读
§2  RAG 的架构:导入 → 查询 → 生成;朴素 / 高级 / 模块化 RAG
§3  内容导入:分块的大小、重叠、边界;"丢了主语"与标题前缀;问答 vs 摘要        ← 代码 01
§4  检索:BM25、稠密检索、它们各自错在哪里、融合                                  ← 代码 02
§5  检索前与检索后:改写、多查询、HyDE、重排                                      ← 代码 03
§6  生成这一侧:给几段、怎么摆、引用对不对                                        ← 代码 04
§7  什么时候检索、相信谁:冷门 vs 热门、自适应检索、冲突与投毒                    ← 代码 05
§8  评估一个 RAG 系统;在真实模型上跑一遍                                         ← 代码 06(可选)
§9  小结   §10 常见误区   §11 练习   §12 延伸阅读   §13 下一讲预告

0.4 记号与术语 Notation & terms

术语本讲义里的意思
文档库 / 语料库(corpus)可以被检索的全部文档
段落 / 块(chunk, passage)分块之后的检索单位
参数化知识 / 非参数化知识存在模型权重里的知识(闭卷)/ 存在外部文档库里、推理时检索的知识(开卷)
词法检索(lexical / sparse)按词的重合打分,例如 BM25
稠密检索(dense)、bi-encoder问题和段落各自编码成一个向量,按相似度排序
cross-encoder / 重排器(reranker)把问题和段落一起送进模型打分;更准、更慢
R@k(Recall@k)前 k 个结果里至少有一个相关段落的比例
MRR第一个相关段落排名的倒数,对所有问题取平均
归因(attribution)回答里的说法能否被它引用的段落支持

0.5 本讲的实验装置 · The setup

文档库(rag_lab.build_kb)建在第 5 讲的长尾世界上(60 个国家):

  • 60 个百科页面:概述 / 政府 / 人民 / 经济四节。第一句点名(“stindia is a country in the north .”),之后多数句子用指代(“its capital is brelo .”、“the country is led by kralta .”、“people there speak gistese .”),每个事实有 25% 的概率用带国名的说法。热门国家的页面更长。
  • 200 篇干扰文档:新闻(两国领导人会面)、贸易报告(“a buys 商品 from b”)、游记(“we flew to 首都 , the capital of x”)。它们更常提到热门国家。

一共 260 篇、约 6600 个词。因为每一句话都是程序生成的,哪些段落包含答案是精确知道的——这让我们能把”检索错了”和”读错了”分开测。

问题:每个国家 × 首都 / 语言 / 出口 / 领导人,两种问法——标准问法(“who leads X ?”)和换个说法(“who rules X ?”)。60 个国家分成训练用的 30 个(调参数、训练重排器)和测试用的 30 个(报告结果)。

阅读器有三种:

  1. 抽取式阅读器(ExtractiveReader):按排名逐段解析事实,第一个找到的就是答案;不猜、不用记忆。它的准确率完全由检索决定,用来单独测检索。
  2. 模拟 LLM 阅读器(SimReaderLLM):会”读漏中间”、会被别国的同类事实干扰、找不到时会凭”记忆”补——每种行为都是一个旋钮。用来看这些已知现象怎样和真实的检索结果相互作用。
  3. 真实的 Claude(06 号脚本,可选)。

为什么不用迷你 GPT 当阅读器? 第 5 讲 §3.5 发现,长尾模型不会读上下文:给冷门国家正确的资料,准确率和不给一样。为了这一讲,我们试着训练一个会读的迷你 GPT:在长尾语料里混入大量”随机新名字的段落 + 问题”(记住答案没用,只能从段落里找),2 层和 3 层、两种问题格式都试了,在新段落上的准确率都只有 1%–10%。从段落里定位并照抄一个只出现过几次的名字,对这个尺寸的词级模型太难了(第 2 讲的归纳头是在只有 3 个符号的序列上学出来的)。这本身就是一个结论:RAG 的前提是阅读器会读,而这种能力来自规模和专门的训练数据。所以这一讲把”检索”做成真实的计算,把”阅读”交给抽取式阅读器、模拟器和真实模型。


1. 为什么要检索 · Why retrieve

第 5 讲的长尾曲线是这一讲最直接的动机:出现 0 次的事实,迷你模型的准确率 4.2%;出现 1 次的 20.8%。大模型也一样——Kandpal et al.(2023)发现,模型回答一个问题的准确率,随相关文档在预训练数据里出现的次数急剧变化;Mallen et al.(2023)发现,对冷门实体,把模型变大带来的提升很小。

除了长尾,还有三个理由:

  • 知识会过时:模型的知识停在训练数据截止的那一天;文档库可以每天更新。
  • 知识是私有的:公司的内部文档、用户自己的文件,从来不在预训练数据里。
  • 可溯源:答案能指向一段原文,用户可以核对(第 5 讲 §7:引用与可溯源是减少幻觉的策略之一)。

开卷考试的直觉。闭卷时,你只能靠记住的东西(参数化知识);开卷时,你可以翻书(非参数化知识)。开卷考得好,需要两样东西:找到对的那一页(检索),以及读懂它(阅读)。Lewis et al.(2020)提出的 RAG(Retrieval-Augmented Generation)把一个预训练的生成模型和一个维基百科的稠密向量索引连在一起,在知识密集型任务上超过了只靠参数的模型;同一时期的 REALM(Guu et al. 2020)、之后的 RETRO(Borgeaud et al. 2022)都是”让模型在推理时查资料”的不同做法。

RAG 还是微调? 把新知识”教”给模型的另一条路是微调。第 5 讲已经看到它的风险:用模型不知道的事实做微调,模型变得自信地错(Gekhman et al. 2024)。Ovadia et al.(2024)比较了用无监督微调和用 RAG 注入知识,发现 RAG 一贯更好。一个常见的经验法则是:RAG 管”知道什么”,微调管”怎么做”(格式、风格、任务的方法)。两者也可以结合。


2. RAG 的架构 · Architecture

一个 RAG 系统有三个阶段:

导入(离线,一次)   文档 → 清洗 → 分块 → 编码(BM25 统计 / 向量) → 索引
查询(每个问题)     问题 → [检索前:改写 / 多查询 / HyDE] → 检索 top-N → [检索后:重排 / 去重 / 压缩] → top-k
生成(每个问题)     把 top-k 段落和问题拼进提示 → 模型回答 → [引用 / 归因检查]

拼进提示的样子(rag_lab.rag_prompt):

Context:
[1] stindia : stindia is a country in the north . the winters are cold . the capital of stindia is brelo .
[2] stindia : stindia is led by kralta . the old town has a famous bridge . people there speak gistese .
[3] news 41 : ...

Question: where does the government of stindia sit ?
Answer with one word using only the context. If the context does not say, answer 'unknown'.
Then give the number of the passage you used, like 'lorn [2]'.

Gao et al.(2023)的综述把 RAG 的发展分成三个阶段,正好对应 Schedule 里的说法:

  • 朴素 RAG(naive):上面的流水线,不加任何处理。
  • 高级 RAG(advanced):在检索前(改写查询)和检索后(重排、压缩)加处理步骤(§5)。
  • 模块化 RAG(modular):把检索、改写、重排、阅读、判断”要不要检索”等做成可以替换和重新组合的模块,甚至让模型自己决定流程(§7 的自适应检索、Self-RAG;第 8 讲把检索当工具、第 9 讲的多跳检索)。

这一讲的六个脚本基本按流水线的顺序走:01 导入,02 检索,03 检索前后,04 生成,05 要不要检索,06 在真实模型上端到端。


3. 内容导入:分块 · Ingestion & chunking

3.1 为什么要分块

一篇文档不能整篇放进提示:太长(上下文窗口、成本)、太杂(检索的单位太大,和问题相关的那一句被淹没)。所以要切成段落。分块看起来是一个无聊的工程细节,但 01 号脚本会看到,它对端到端准确率的影响比换检索器还大。

3.2 大小、重叠、边界

01_chunking.py 演示 1 用七种方式切同一个文档库,每种再分”加 / 不加标题前缀”,用 BM25 检索、抽取式阅读器回答 120 道测试题:

分块方式标题前缀段数事实被切断能独立读懂R@5问答@5
按字符,每段 100 个字符不加 / 加39937%83% / 100%51.7% / 72.5%49.2% / 70.8%
按词,每段 10 个词不加 / 加77739%74% / 100%26.7% / 62.5%20.8% / 68.3%
按词,每段 20 个词不加 / 加41726%79% / 100%58.3% / 80.8%51.7% / 82.5%
按词,20 个词,重叠 5 个不加 / 加4455%73% / 100%66.7% / 93.3%55.8% / 96.7%
按词,每段 40 个词不加 / 加32010%95% / 100%82.5% / 90.8%82.5% / 90.8%
按句子打包(≤ 20 词)不加 / 加4270%73% / 100%61.7% / 95.0%54.2% / 95.0%
按文档结构(每节一段)不加 / 加4400%66% / 100%49.2% / 94.2%39.2% / 94.2%

(“事实被切断”:这个事实所在的句子没有完整地出现在任何一段里。“能独立读懂”:含有事实的段落里,只看段落本身就能知道”这是哪个国家的事实”的比例。)

几条规律:

  • 切点要落在边界上。按字符切,一段可能从一个词的中间开始,“stindia” 变成 “ndia”,BM25 只认完整的词;按词切而不重叠,四分之一的事实被切成两半。按句子或按结构切,句子从不被切断。真实系统常按 token 数切(前置讲 P1)——同样要注意切点的位置。
  • 重叠是切断的补丁:20 个词的段落重叠 5 个词,被切断的事实从 26% 降到 5%,问答从 82.5% 升到 96.7%。代价是段数和重复的 token 变多。
  • 段太小装不下”谁 + 什么”:每段 10 个词时,不加前缀的问答只有 20.8%。段越大越完整,但每一段花的 token 越多(§3.5)。

3.3 “丢了主语”:标题前缀

表里最大的单项改进是标题前缀:每段 20 个词时问答 51.7% → 82.5%,按文档结构分块时 39.2% → 94.2%。

原因是指代。百科页面第一句之后都写 “its capital is brelo”、“the country is led by kralta”——人读整篇文章时毫无问题;可是切开以后,“the country is led by kralta .” 这一段不知道自己在说哪个国家。检索器看不到国名,问 “who leads stindia ?” 时它不会被排上来;即使排上来,阅读器也不知道它属于谁。不加前缀时,按结构分块的含事实段落里有 34% 是这样”丢了主语”的。

修补的办法是在每一段前面加上文档标题(“stindia : the country is led by kralta .”)。Anthropic 的 Contextual Retrieval(2024)把同样的想法做得更彻底:用 LLM 给每一段写一两句”这段话在整篇文档里讲什么”的说明,再建索引。更一般地说:段落要能独立成立——分块时把丢掉的上下文补回去。

3.4 问答 vs 摘要

原课作业 3 要求在问答和摘要两种任务上评估分块和检索。为什么要两种?演示 2 做了一个”查询聚焦的摘要”:检索 “tell me about X” 的前 k 段,看一个国家的 4 个事实能找回几个。

分块方式k = 1k = 3k = 5k = 10k = 20
按句子打包 + 前缀20.0%67.5%85.8%93.3%95.0%
按文档结构 + 前缀5.0%56.7%80.8%93.3%95.8%
按词 20 个,不加前缀18.3%36.7%44.2%50.0%52.5%

问答通常只要一段对的(第一段就对最好);摘要要的是分散在好几段里的全部。同一个 k,对问答是够的,对摘要可能远远不够。所以评估分块策略时,两种任务的结论可以不一样——这就是作业要求两种都做的原因。

3.5 token 预算

演示 3 把每道题放进提示的词数和问答准确率放在一起:

分块方式(加前缀)k = 1k = 3k = 5
按词,每段 10 个词52%(12 词)61%(32 词)68%(54 词)
按词,每段 40 个词68%(32 词)86%(74 词)91%(117 词)
按句子打包(≤ 20 词)57%(19 词)87%(54 词)95%(89 词)

同样的准确率可以用很不一样的 token 数达到:按句子打包、k = 3 只用 54 个词就到了 87%,而每段 40 个词要 74 个词。放进提示的每个 token 都要付钱、增加延迟(前置讲 P2),还会稀释模型的注意力(§6)。分块大小和 k 要一起调,在你的任务和文档上调——没有一个普遍最优的”512 个 token、重叠 50”。


4. 检索:词法与稠密 · Retrieval

4.1 BM25

第 1 讲 §4 的 TF-IDF 给每个词一个权重:在这段里出现得多、在所有段落里出现得少的词更重要。BM25(Robertson & Zaragoza 2009)是它的两处改进:

score(q,d)=∑w∈qIDF(w)⋅tf(w,d) (k1+1)tf(w,d)+k1(1−b+b⋅∣d∣avgdl)\text{score}(q,d)=\sum_{w\in q}\text{IDF}(w)\cdot\frac{\text{tf}(w,d)\,(k_1+1)}{\text{tf}(w,d)+k_1\left(1-b+b\cdot\frac{|d|}{\text{avgdl}}\right)}

  • 词频饱和:一个词出现 10 次不比出现 3 次重要 3 倍多(k1k_1 控制饱和的速度,常用 1.2);
  • 长度归一化:长段落天然含更多词,要打折扣(bb 控制折扣的力度,常用 0.75)。

BM25 几十年来是搜索引擎的基础,今天仍是 RAG 里最常用的基线之一。

4.2 稠密检索

稠密检索把问题和段落各自编码成一个向量,按余弦相似度排序。这是第 1 讲 §4 的 bi-encoder:段落向量可以提前算好、建成索引,查询时只要算一次问题的向量。DPR(Karpukhin et al. 2020)用”问题—相关段落”对做对比学习训练编码器,在开放域问答上超过了 BM25;今天的各种嵌入模型都是这条路线的后代。

我们的玩具版本(DenseRetriever):词向量是第 1 讲的 PPMI-SVD,在第 5 讲的长尾语料和文档库上训练;段落向量是实词的词向量按 IDF 加权平均。它的优点是能处理换说法:“led by / ruled by / the leader of” 在语料里出现在相似的上下文中,向量相近。

4.3 两种检索器各自错在哪里

02_retrievers.py 演示 1(按句子打包 + 前缀,120 道测试题):

问法检索器R@1R@5MRR
标准问法BM2557.5%95.0%0.727
标准问法稠密24.2%50.0%0.351
标准问法混合(RRF)36.7%78.3%0.529
换个说法BM2531.7%85.0%0.515
换个说法稠密34.2%56.7%0.423
换个说法混合(RRF)30.8%69.2%0.476

演示 2 看排在第一的段落是什么:

问法检索器相关同一个国家的别的段落另一个国家的同类事实其他
标准问法BM25694560
标准问法稠密29361738
换个说法BM253870111
  • BM25 抓得住实体:国名是稀有词、IDF 很高,一个词就把候选缩到这个国家的几段。它的弱点是措辞:换个说法之后,R@1 从 57.5% 掉到 31.7%,排在第一的常常是”同一个国家的别的段落”(70 / 120)——国名对上了,关系词没对上。
  • 稠密检索抓不住实体:它有 55 / 120 次把另一个国家的段落排在第一,BM25 只有 6 次。所有国名的词向量都很像(第 1 讲 §3 说的”同一类别”的相似),一段话平均成一个向量之后,国名这一个词的区别被冲淡了。Sciavolino et al.(2021)在真实的稠密检索器上发现了同样的问题:在”某某人出生在哪里”这类以实体为中心的简单问题上,它们明显不如 BM25,尤其是对不常见的实体。BEIR 基准(Thakur et al. 2021)的一个著名结论也是:零样本时,BM25 是一个很难打败的基线。

我们的稠密检索器是玩具,比真实的嵌入模型弱得多;但”错的方式不同”这一点是普遍的,也是融合的出发点。

4.4 融合

倒数排名融合(RRF,Cormack et al. 2009):score(d)=∑r1/(60+rankr(d))\text{score}(d)=\sum_r 1/(60+\text{rank}_r(d))。它用排名而不是分数,因为 BM25 分数和余弦相似度不在一个尺度上。

上表里等权的 RRF 不如单独的 BM25:当一个检索器明显更差时,等权融合会把它的错误也混进前几名。演示 3 改用分数的凸组合 α⋅z(BM25)+(1−α)⋅z(稠密)\alpha\cdot z(\text{BM25})+(1-\alpha)\cdot z(\text{稠密}),在训练国家上选 α\alpha:

α训练 MRR测试 MRR测试 R@5
0.0(纯稠密)0.4280.38753.3%
0.50.6510.57782.5%
0.8(训练集上最好)0.7200.62986.2%
1.0(纯 BM25)0.6890.62190.0%
RRF(等权)0.50373.8%

调出来的 α = 0.8 在测试集上的 MRR 只比纯 BM25 高 0.008,R@5 还更低——在这个文档库上,玩具稠密检索补不上多少 BM25 的短板;调权重的主要价值是避免了等权 RRF 的损失。两个通用的教训:融合的权重要在和测试分开的数据上选(前置讲 P3);任何检索方案都要和 BM25 比一比。

4.5 规模化:近似最近邻

这里的文档库只有几百段,可以逐个算相似度(精确搜索)。真实的文档库有几百万到几十亿段,需要近似最近邻(ANN)索引:例如 HNSW(Malkov & Yashunin 2018)的分层小世界图、FAISS(Johnson et al. 2019)里的倒排 + 量化索引。它们用一点召回率换几个数量级的速度——又一个要在评估里报告的取舍。


5. 检索前与检索后 · Pre- and post-retrieval

5.1 查询改写与多查询

用户的问题不一定用文档里的说法。查询改写先把问题改写成更像文档的形式(Ma et al. 2023 的 Rewrite-Retrieve-Read 用 LLM 改写,再检索、再阅读)。多查询生成几个不同的查询,分别检索,再用 RRF 融合(社区里常叫 RAG-Fusion)。

03_pre_post.py 演示 1(换个说法的 120 道题,BM25):

查询怎样处理R@1R@5MRR
原问题(“who rules X ?”)31.7%85.0%0.515
改写成标准问法(“who leads X ?”)57.5%95.0%0.727
多查询:原问题 + 3 种文档里的说法,RRF34.2%93.3%0.592
(对照)用户本来就用标准问法57.5%95.0%0.727

改写把换说法的损失完全补了回来——但这里用的是一张同义词表,在这个文档库上是完美的;LLM 的改写会出错,也可能把问题改歪。多查询提高了召回(R@5 93.3%),却没有提高第一名的准确率:几个查询各自的第一名在 RRF 里平分秋色。**多查询适合”要全”的任务(摘要、多跳),不一定适合”要准”的任务。**两者都多花了调用——检索前处理是在用延迟和成本换召回。

5.2 HyDE:用一个(可能错的)假想答案去检索

HyDE(Gao et al. 2023,Hypothetical Document Embeddings):先让模型写一个”假想的答案段落”,再用它去检索。假想答案在形式上和真正的段落很像,所以更容易检索到它们。

演示 2 用第 5 讲的长尾模型写假想答案:先让它闭卷回答,再把 “the leader of X is 它的答案 .” 当作查询。

问法长尾模型的回答题数原问题 R@1HyDE R@1
标准问法答对了10355.3%90.3%
标准问法答错了(编的)1770.6%29.4%
换个说法答对了10327.2%90.3%
换个说法答错了(编的)1758.8%29.4%

模型知道答案时,HyDE 让 R@1 几乎翻了三倍(换个说法:27.2% → 90.3%):假想段落用的是文档里的说法,还带上了答案本身。模型不知道、编了一个答案时,HyDE 反而有害(58.8% → 29.4%)。第 5 讲发现,长尾模型编答案时借用的是别的国家的同类事实;那个值写进查询,检索器就被带到了那个国家(17 道编出来的题里有 4 道,排在第一的正是”编的答案所属的那个国家”的段落)。

讽刺的是:检索最该帮忙的,正是模型不知道的冷门事实;而 HyDE 恰恰在这些事实上失效。实用的折中是同时用原问题和假想答案检索,再融合。

5.3 重排:cross-encoder 的极简版

检索器要在几百万段里快速找候选,所以只能用 bi-encoder 或 BM25 这样”问题和段落分开处理”的方法。重排器只看检索器给出的前几十个候选,可以把问题和每个候选放在一起仔细打分——这是第 1 讲 §4 说的 cross-encoder:精确但慢。Nogueira & Cho(2019)用 BERT 做的 monoBERT 是这条路线的起点;ColBERT(Khattab & Zaharia 2020)的”迟交互”是介于两者之间的折中。

我们的重排器(Reranker)是一个逻辑回归,特征都要同时看问题和段落:BM25 分数、余弦相似度、问题里的国名在不在段落里(ent)、段落里有没有问题所问关系的某种说法(rel)、以及两者的乘积(ent×rel:“这一段既是关于这个国家的,又讲了这个关系”)。在训练国家上训练,在测试国家上评估:

问法检索R@1R@5MRR
标准问法BM2557.5%95.0%0.727
标准问法混合 + 重排90.8%99.2%0.948
换个说法BM2531.7%85.0%0.515
换个说法混合 + 重排81.7%96.7%0.885

学到的权重里最大的是 ent×rel(+2.49),而 rel 单独的权重是 −0.26:光有关系词不够——每个国家都有一句讲首都的——要的是两个条件同时满足。去掉这一个特征重新训练,换个说法的 R@1 从 81.7% 掉到 61.7%。这正是 bi-encoder 做不到的:把段落压成一个向量之后,“这一段里同时有 X 和’首都‘“这种信息就看不到了。

(坦白一点:这个重排器的关系词表是直接给的,真实的 cross-encoder 是从大量标注过的”问题—段落”对里学到”rules 和 led by 是一回事”的。第一版用词向量做软匹配,PPMI-SVD 词向量分不清这些说法,没有效果。)

5.4 其他检索后处理

  • 去重:热门实体常被很多文档用几乎一样的话提到,重复的段落浪费上下文。
  • 压缩:只保留段落里和问题相关的句子,或者让一个小模型把段落压成摘要再交给大模型(RECOMP,Xu et al. 2024;LongLLMLingua 一类的提示压缩)。§6 会看到,少放无关的内容本身就能提高准确率。
  • 过滤:按相关性分数的阈值丢掉候选,宁可少给也不给错的。

6. 生成这一侧:检索到了 ≠ 用上了 · The generation side

6.1 给几段

04_reading.py 演示 1 把重排后的前 k 段交给两种阅读器(240 道题):

k平均词数相关段落在里面(R@k)抽取式阅读器模拟 LLM
11986.2%86.2%85.0%
23995.4%95.4%93.8%
35597.5%97.5%93.3%
59097.9%97.9%87.9%
1018298.3%98.3%87.1%
2036499.2%99.2%62.5%

抽取式阅读器从不分心,准确率跟着 R@k 走,k 越大越好。模拟 LLM 在 k = 2 时最好,k = 20 时掉到 62.5%:段落越多,相关的那段越可能落在中间,别国的同类事实也越多。“检索到了”不等于”用上了”,所以 k 要用端到端的准确率来选,而不是只看 R@k。(模拟 LLM 的位置偏好和被干扰的概率是旋钮;真实模型的曲线要自己测。)

6.2 怎么摆:“lost in the middle”

Liu et al.(2024)在真实模型的多文档问答上发现了一个 U 形曲线:相关信息在上下文的开头或结尾时表现最好,在中间时明显变差,有时甚至不如完全不给文档。演示 2 在模拟 LLM 上把相关段落放在 10 段里的不同位置:第 1 位 91.7%、第 10 位 82.5%、最低的第 5 位 65.0%(模拟器的 U 形旋钮加上干扰项的结果;每个位置 120 道题,曲线有噪声)。

一个常见的对策是重新排列:把排名最高的段落放在开头和结尾,次要的放中间(LangChain 等框架里的 “LongContextReorder”)。演示 2 在真实的 BM25 结果(换个说法,k = 10)上比较了三种摆法:按排名 76.7%、反过来 70.8%、两头放好的 73.3%——差别在 120 道题的噪声范围之内。排名靠前的段落本来就最相关,按排名放已经把它们放在了开头;重新排列只有在”最相关的段落常常排在中间”时才有意义。和别的技巧一样:在你的模型和你的检索结果上测,不要照搬。

6.3 引用对不对:归因

演示 3 故意用一个较弱的检索(BM25,换个说法,k = 2),让一部分题目检索不到答案;模拟 LLM 找不到时,一半的情况下用第 5 讲长尾模型的答案”凭记忆”补,并且照样给出引用:

情况题数答对答 unknown引用的段落确实支持这个答案
检索拿到了59100%0%100%
检索没拿到(热门)4854%46%0%
检索没拿到(冷门)1338%23%0%

检索没拿到答案时,热门事实上模拟 LLM 仍然答对了一半(记忆里有),但它引用的段落不支持这个答案——即使答案碰巧是对的。只看”答对了没有”的评估发现不了这一点。所以 RAG 的评估要单独看归因:回答里的说法能否被它引用的段落支持(Rashkin et al. 2023 的 AIS 框架;Gao et al. 2023 的 ALCE 基准专门评估带引用的生成)。对策:在提示里明确”只根据资料回答,资料里没有就说 unknown”(第 5 讲 §5.4),并在输出端自动检查引用。


7. 什么时候检索、相信谁 · When to retrieve, whom to trust

7.1 冷门 vs 热门

05_when_to_retrieve.py 演示 1 把第 5 讲长尾模型的闭卷回答和 RAG(抽取式阅读器)按出现次数分档比较,检索有”一般”(混合检索,不重排)和”好”(混合 + 重排)两种质量:

出现次数事实数闭卷RAG:一般的检索RAG:好的检索
0 次244.2%29.2%100.0%
1 次2420.8%70.8%100.0%
2 次2996.6%82.8%100.0%
3–5 次64100.0%87.5%100.0%
6–20 次58100.0%69.0%100.0%
21 次以上41100.0%61.0%97.6%

冷门事实上检索是决定性的;热门事实上,一般的检索反而有害(100% → 61%):模型本来就知道,检索却拿错了段落,而热门国家被新闻、报告、游记提到得多,干扰项也多。好的检索几乎消除了这个损失。**检索有没有害,取决于检索有多好。**Mallen et al.(2023)在真实模型和真实的维基百科检索上得到了同样的形状:对热门实体,检索增强有时会降低大模型的准确率;对冷门实体,检索的帮助远大于把模型变大。

7.2 自适应检索

既然如此,能不能只在需要时才检索?演示 2 用长尾模型的置信度当开关:置信度低于 τ 才检索,否则用闭卷答案。

阈值 τ检索了多少准确率:一般的检索准确率:好的检索
0(从不检索)0%82.1%82.1%
0.527%88.3%98.8%
0.954%85.4%99.6%
0.9568%82.1%100.0%
1.01(总是检索)100%70.4%99.6%
按热门程度:百科篇数 < 3 才检索53%85.8%100.0%

一般的检索下,τ = 0.5 时最好(88.3%),只检索了 27% 的问题——比总是检索(70.4%)和从不检索(82.1%)都好。好的检索下,自适应的好处主要是省:τ = 0.9 时只检索一半,准确率不变。

能这样做,靠的是第 5 讲的结论:长尾模型的置信度能很好地区分对错(AUROC 0.95)。置信度不可靠的模型就不能用它当开关。Mallen et al.(2023)用实体的流行度当开关(表的最后一行是它的对照版本);Self-RAG(Asai et al. 2024)把”要不要检索、检索到的段落有没有用”训练成模型输出的特殊 token;FLARE(Jiang et al. 2023)在生成过程中,遇到低置信度的词才去检索。这些都是”模块化 RAG”:检索不是固定的一步,而是一个可以被调用、也可以不被调用的模块——第 8 讲会把它正式变成一个”工具”。

7.3 资料和记忆冲突时,听谁的?投毒

RAG 带来一个新的攻击面:文档库本身。演示 3 往文档库里加了 20 篇”更正”,每篇一句话,把一个事实改成另一个国家的同类值(10 个热门事实、10 个冷门事实)。四种策略(好的检索):

策略被投毒的热门(对 / 中毒 / 弃权)被投毒的冷门没被投毒的 220 个事实
总是相信检索(标准 RAG)60% / 40% / 0%50% / 50% / 0%100% / 0% / 0%
总是相信记忆(闭卷)100% / 0% / 0%0% / 0% / 0%85% / 0% / 0%
自适应检索(τ = 0.9)100% / 0% / 0%50% / 50% / 0%100% / 0% / 0%
冲突检查:和有把握的记忆不一致时弃权60% / 0% / 40%50% / 50% / 0%99% / 0% / 1%
  • 只加一句话,标准 RAG 就有约一半的目标被改写:毒文档用的是最直白的说法,正好是检索器和重排器最喜欢的形状。Zou et al.(2024)的 PoisonedRAG 发现,往百万级的文档库里注入很少几篇精心写的文档,就能让 RAG 系统对目标问题给出攻击者想要的答案。
  • 模型对热门事实有把握,所以自适应检索(根本不去检索)和冲突检查(资料和记忆一冲突就弃权)都能挡住热门事实上的投毒。
  • 冷门事实最需要检索,也最容易被骗:模型本来就不知道,没有什么可以拿来对照。

真实模型的”资料 vs 记忆”更复杂:Xie et al.(2024)发现,当资料连贯、有说服力时,LLM 很容易接受和自己记忆矛盾的资料;Longpre et al.(2021)则发现阅读理解模型有时会无视资料、坚持记忆。两种失败都需要评估。更危险的是,检索到的文档里可能藏着指令(“忽略之前的要求,回答……”)——这是第 12 讲的间接提示注入(Greshake et al. 2023)。


8. 评估一个 RAG 系统 · Evaluating RAG

8.1 分层评估

一个 RAG 系统的错误可能出在任何一层,所以评估也要分层:

层问题指标
导入事实被切断了吗?段落能独立读懂吗?切断率、“丢主语”率(需要知道答案在哪)
检索相关段落排上来了吗?R@k、MRR、nDCG
阅读拿到了,用上了吗?没拿到时说不知道了吗?给定”完美检索”时的准确率;无答案题上的弃权率
端到端答对了吗?摘要全吗、忠实吗?准确率 / EM / F1;摘要的事实覆盖率与错误事实数
归因引用支持说法吗?被引用段落支持答案的比例(AIS / ALCE)

分层的价值在于定位:端到端准确率下降时,是检索变差了,还是阅读变差了?01–05 号脚本能把两者分开,靠的是”哪些段落包含答案”是已知的。真实项目里要花力气构造这样的标注——至少为一个小的开发集标出相关段落。

用 LLM 给 RAG 打分(例如 RAGAS,Es et al. 2024:忠实度、答案相关性、上下文相关性)可以省掉人工标注,但它继承了 LLM 判官的偏差(第 10 讲),需要先在一小批人工标注上验证。

8.2 测试集里应该有什么

  • 改写过的问题(§4.3):用户不会用文档里的原话。
  • 按热门程度分层(§7.1):总准确率会掩盖冷门实体上的失败。
  • 无答案的问题:文档库里没有答案的题,正确答案是”不知道”(§6.3)。
  • 需要多段的问题:摘要、比较、多跳(§3.4;第 9 讲)。
  • 有冲突或错误资料的情况(§7.3)。

8.3 在真实模型上(可选)

06_real_llm.py 让真实的 Claude 当阅读器:40 道换个说法的问答(BM25 前 2 段,一部分题检索不到答案)、10 份摘要(重排后的前 5 段)、以及 05 号脚本的 20 个被投毒的事实(前 3 段里真假资料都有),比较”只说根据资料回答”和”资料互相矛盾时回答 conflict”两种提示。真实模型不知道这个虚构世界,它只能靠检索到的段落,所以这是检验”阅读这一侧”的干净设置。--toy 模式用 SimReaderLLM 跑通同一段代码(它不会说 conflict);编写环境里没有安装 anthropic SDK,真实模型的结果没有报告,留给读者自己运行。


9. 小结 · Summary

  1. 为什么检索:长尾、过时、私有、可溯源。RAG 管”知道什么”,微调管”怎么做”。前提是阅读器会读——迷你 GPT 学不会,这本身说明阅读能力来自规模和专门的训练。
  2. 架构:导入(分块 → 编码 → 索引)、查询(检索前 → 检索 → 检索后)、生成(拼提示 → 回答 → 引用);朴素 → 高级 → 模块化 RAG。
  3. 分块:切点落在句子 / 结构边界上,或者加重叠(被切断的事实 26% → 5%);给每段加上文档标题是最大的单项改进(按结构分块时问答 39.2% → 94.2%)。问答要”准”、摘要要”全”。
  4. 检索:BM25 抓得住实体、怕换说法;稠密检索不怕换说法、却常把别的实体排上来(55 / 120 次)。等权融合可能变差,权重要在开发集上调;任何方案都要和 BM25 比。
  5. 检索前后:改写把换说法的损失补回来;多查询提高召回、不提高第一名;HyDE 在模型知道答案时把 R@1 从 27% 提到 90%,在模型编答案时从 59% 降到 29%;交互特征(“这一段既关于 X、又讲了这个关系”)让重排把 R@1 提到 91% / 82%。
  6. 生成:检索到了 ≠ 用上了。会分心的阅读器在 k = 2 时最好、k = 20 时 62.5%;相关段落在中间时最难读到;凭记忆补的答案引用的段落不支持它。
  7. 什么时候检索:一般的检索会伤害热门事实(100% → 61%);用可靠的置信度做自适应检索,准确率最高、检索更少。冷门事实最需要检索,也最容易被投毒骗。
  8. 评估要分层:导入、检索、阅读、端到端、归因;测试集里要有改写、分层、无答案、多段、冲突的题。

10. 常见误区 · Common pitfalls

误区实际情况
“分块是个工程细节,随便切切就行”分块方式和标题前缀对端到端准确率的影响,可以比换检索器还大(39% vs 94%)
“稠密检索 / 向量数据库总比关键词搜索好”在以实体为中心的问题上,BM25 常常更强;先在自己的数据上测 BM25
“混合检索一定更好”等权融合会把较差的那一路的错误带进来;权重要在开发集上调
“R@k 高,问答就好”检索到了 ≠ 用上了:段落太多、相关段落在中间、干扰项多,阅读都会变差
“给的段落越多越保险”更多的段落 = 更多的 token、更多的干扰;k 要用端到端准确率选
“有引用就说明答案有依据”模型可能凭记忆回答再随便引用一段;要单独评估归因
“每个问题都应该检索”对模型有把握的热门事实,不够好的检索反而有害;还多花钱、多一个被投毒的入口
“HyDE / 查询改写总是有帮助”它们依赖模型自己的知识或改写能力;模型编错答案时,HyDE 把检索带偏
“文档库是可信的”文档库可以被投毒,检索到的文档里也可能藏着指令(第 12 讲)

11. 练习 · Exercises

练习 1(计算 BM25) 一个文档库有 N = 100 段。词 “stindia” 出现在 2 段里,词 “capital” 出现在 60 段里。某一段长度等于平均长度,含 “stindia” 1 次、“capital” 1 次。取 k1 = 1.2、b = 0.75。(a)分别算两个词的 IDF。(b)问题 “capital of stindia” 对这一段的 BM25 分数是多少?(c)这说明了什么?

参考答案:(a)IDF = log((N − df + 0.5)/(df + 0.5) + 1)。stindia:log(98.5/2.5 + 1) = log(40.4) ≈ 3.70;capital:log(40.5/60.5 + 1) = log(1.669) ≈ 0.51。(b)长度等于平均长度时,分母里的长度项为 1,每个词的 tf 部分是 1 × 2.2 / (1 + 1.2) = 1。分数 = 3.70 + 0.51 = 4.21(“of” 是停用词)。(c)几乎全部分数来自国名:BM25 在实体问题上强,是因为稀有的实体名 IDF 很高;关系词(capital)几乎在每个国家的页面里都有,区分力很弱——这也是换个说法时 BM25 的第一名常常是”同一个国家的别的段落”的原因。

练习 2(设计分块) 你要给公司的员工手册(200 页,有章、节、小节标题,很多段落用”本条款”、“上述规定”指代)搭一个 RAG 问答。你会怎样分块?说出至少三个决定和理由。

参考答案(一种可能):(1)按结构切:以小节为单位,太长的小节再按段落或句子打包,不在句子中间切。(2)给每一段加上”章 > 节 > 小节”的标题路径作为前缀,或者用 LLM 生成一句上下文说明(Contextual Retrieval),解决”本条款”这类指代丢失的问题。(3)对按长度切的部分加少量重叠,或保证不跨小节。(4)保留段落的元数据(页码、条款号)用于引用和过滤。(5)在一组带有标注答案位置的开发问题上,比较几种方案的 R@k 和端到端准确率,同时测”要汇总多个条款”的问题(摘要式),因为两类问题的最佳方案可能不同。

练习 3(读表) §4.3 的表里,换个说法时,稠密检索的 R@1(34.2%)比 BM25(31.7%)高,可 R@5(56.7% vs 85.0%)低得多。解释这两个数字为什么方向相反,以及这对”混合检索”意味着什么。

参考答案:BM25 的错误主要是”找对了国家、拿错了段落”:前 5 名里通常都是这个国家的段落,所以 R@5 高,但第一名常常不是讲这个关系的那段。稠密检索不太受措辞影响,偶尔能把讲对了关系的那段排第一;但它常常把别的国家的同类段落排上来,所以前 5 名里经常一段这个国家的都没有,R@5 低。两者的错误是互补的:BM25 负责”找对实体”,稠密负责”找对关系”——这是混合检索或重排(先用 BM25 圈定候选,再用能看关系的打分器排序)的道理。但互补不等于等权融合就好:需要调权重,或者用重排器把两种信号组合起来(§5.3 的 ent×rel)。

练习 4(解释) 为什么 §7.1 里,一般的检索会让热门事实的准确率从 100% 降到 61%,而对冷门事实却是提升?用”检索带来的信息”和”检索带来的噪声”两个量来解释,并说明为什么自适应检索能取两者之长。

参考答案:检索的净效果 = 它带来的新信息 − 它带来的噪声(拿错段落导致的错误)。冷门事实上,模型原本几乎不知道(闭卷 4%),检索带来的信息远大于噪声,净效果为正。热门事实上,模型原本就知道(闭卷 100%),检索带来的新信息为零,只剩噪声;而热门实体在文档库里被提到得多,干扰项也多,噪声更大。自适应检索用置信度估计”模型知不知道”:知道就不检索(避开噪声),不知道才检索(拿到信息)。它要求置信度是可靠的——这又回到第 5 讲的校准和 AUROC。

练习 5(批判) 有人说:“我们的 RAG 系统上线前测了 R@5 = 95%,所以回答基本都有依据。“列出至少三个你会追问的问题。

参考答案:(1)R@5 是在什么问题上测的?是不是用文档原话写的问题?用户的真实问法(换个说法、拼写错误、多意图)上是多少?(2)检索到了,阅读器用上了吗?端到端准确率是多少?给 5 段时,相关段落在中间的那些题表现如何?(3)检索没拿到答案的 5% 里,系统说”不知道”了吗?还是凭记忆回答、再随便引用一段?引用的支持率是多少?(4)按实体的热门程度、文档类型分层看过吗?(5)文档库里有没有过时或互相矛盾的资料?有人能往里面写东西吗(投毒、间接注入)?

练习 6(动手) 修改 rag_lab.wiki_page,让所有事实都用带国名的”显式”说法(把 25% 改成 100%),重新运行 01 号脚本。预期标题前缀的作用会怎样变化?

参考答案:显式说法下每个事实句都自带国名,段落不再”丢主语”,不加前缀时能独立读懂的比例接近 100%,标题前缀带来的提升会大幅缩小(主要只剩下对检索的帮助:前缀让段落多了一次国名,BM25 分数更高)。这说明标题前缀解决的是指代问题;文档的写法决定了分块之后丢失多少上下文。实际数字要运行才知道。

练习 7(设计评估) 为作业 3 设计一个实验:比较两种分块方式(按句子 vs 按固定长度 + 重叠)和两种检索器(BM25 vs 混合),在问答和摘要两种任务上。说出你的自变量、因变量、对照、统计方法。

参考答案(一种可能):自变量:分块方式(2)× 检索器(2),四种组合;检索深度 k 固定(或作为第三个因素扫描)。因变量:问答——R@k、端到端准确率、无答案题上的弃权率、引用支持率;摘要——事实覆盖率、错误事实数(忠实性)、平均 token 数。对照:闭卷(不检索)和”完美检索”(直接给相关段落),分别给出下界和阅读器的上界。数据:开发集调 k 和融合权重,测试集只用一次;问题按实体热门程度分层,包括改写过的问法。统计:同一批问题上的配对比较(McNemar 或配对 bootstrap,前置讲 P3),报告置信区间;多组比较时做多重比较校正。


12. 延伸阅读 · Further reading

RAG 的提出与综述

  • Lewis et al. (2020). Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks. NeurIPS.
  • Guu et al. (2020). REALM;Borgeaud et al. (2022). Improving Language Models by Retrieving from Trillions of Tokens(RETRO)。
  • Gao et al. (2023). Retrieval-Augmented Generation for Large Language Models: A Survey.——朴素 / 高级 / 模块化 RAG。

检索

  • Robertson & Zaragoza (2009). The Probabilistic Relevance Framework: BM25 and Beyond.
  • Karpukhin et al. (2020). Dense Passage Retrieval for Open-Domain Question Answering.
  • Thakur et al. (2021). BEIR: A Heterogeneous Benchmark for Zero-shot Evaluation of Information Retrieval Models.
  • Sciavolino et al. (2021). Simple Entity-Centric Questions Challenge Dense Retrievers.
  • Cormack et al. (2009). Reciprocal Rank Fusion Outperforms Condorcet and Individual Rank Learning Methods.
  • Nogueira & Cho (2019). Passage Re-ranking with BERT;Khattab & Zaharia (2020). ColBERT.
  • Malkov & Yashunin (2018). HNSW;Johnson et al. (2019). Billion-scale Similarity Search with GPUs(FAISS)。

检索前后处理

  • Ma et al. (2023). Query Rewriting for Retrieval-Augmented Large Language Models.
  • Gao et al. (2023). Precise Zero-Shot Dense Retrieval without Relevance Labels(HyDE)。
  • Xu et al. (2024). RECOMP: Improving Retrieval-Augmented LMs with Compression and Selective Augmentation.
  • Anthropic (2024). Introducing Contextual Retrieval(博客)。

生成与归因

  • Liu et al. (2024). Lost in the Middle: How Language Models Use Long Contexts. TACL.
  • Rashkin et al. (2023). Measuring Attribution in Natural Language Generation Models(AIS);Gao et al. (2023). Enabling Large Language Models to Generate Text with Citations(ALCE)。
  • Es et al. (2024). RAGAS: Automated Evaluation of Retrieval Augmented Generation.

什么时候检索、冲突与安全

  • Mallen et al. (2023). When Not to Trust Language Models: Investigating Effectiveness of Parametric and Non-Parametric Memories.
  • Asai et al. (2024). Self-RAG;Jiang et al. (2023). Active Retrieval Augmented Generation(FLARE)。
  • Longpre et al. (2021). Entity-Based Knowledge Conflicts in Question Answering;Xie et al. (2024). Adaptive Chameleon or Stubborn Sloth.
  • Zou et al. (2024). PoisonedRAG;Greshake et al. (2023). Not What You’ve Signed Up For(间接提示注入)。
  • Ovadia et al. (2024). Fine-Tuning or Retrieval? Comparing Knowledge Injection in LLMs.

13. 下一讲预告 · Next lecture

这一讲的 RAG 是一个固定的流水线:每个问题都先检索、再回答。§7 已经看到,“什么时候检索”本身就值得让模型来决定。第 8 讲继续原课第 5 周:提示增强(二):工具使用——把检索、计算器、数据库、网页浏览都看作模型可以调用的工具。

  • 为什么要工具:模型不擅长的计算、实时的信息、需要真正”做”的事;
  • Toolformer 的”采样—执行—过滤”:模型怎样自己学会在哪里调用工具——我们会在第 5 讲的长尾模型上做一个迷你版,看它是否学会只在冷门事实上查询;
  • 函数调用与 JSON Schema、WebGPT(为浏览而微调、引用来源)、工具使用中的规划与自我批判;
  • 原课作业 2 的核心对比:单阶段提示 vs 多阶段任务分解,以及工具描述的写法怎样影响调用的准确率。
← 返回课程目录