NOTE
RAG
围绕检索增强生成的动机、索引与检索链路、上下文构建、效果优化和评估展开。
RAG
是什么
- 从外部数据源检索相关信息,并利用这些信息增强模型生成
- 不等于长期记忆系统
- Information Retrieval(信息检索)+ Context Construction(上下文构建)+ LLM Generation(模型生成)
为什么需要
- 数据不够
- 模型的内部知识可能不足或过时。
- 业务知识可能根本没有出现在训练数据中。
- 上下文限制
- 数据量可能超过模型的上下文窗口。
- 即使上下文足够长,模型也未必能有效利用全部信息。
- 输入过多无关内容会增加成本、延迟,甚至影响回答质量。
如何实现RAG
- 整体流程
- 索引阶段
- 原始数据 → 数据处理 → 建立索引 → 存储
- 查询阶段
- Query → Retriever → Context Construction → Generator → Answer
- 索引阶段
- 两个核心组件
- Retriever(检索器)
- 分类
- 基于词项的检索
- 实现
- BM25
- 为什么需要
- TF-IDF的局限
- 词频线性增长:关键词出现越多,分数可能被过度放大
- 文档长度偏差:长文档更容易出现较高的关键词词频
- TF-IDF的局限
- 实现原理
- 词频饱和(TF Saturation)
- 假设用户搜索 GMP。 / 文档 / GMP出现次数 / 简单TF / A / 3 / 3 / B / 30 / 30 / 如果只看TF,B的得分是A的10倍。 / 但一篇文章提到30次GMP,并不意味着它就比提到3次的文章相关10倍。 / 因此,BM25引入词频饱和(TF Saturation): / 词频增加仍然可以提高评分,但增加带来的收益逐渐变小。
- 文档长度归一化(Document Length Normalization)
- 假设: / 文档 / 总词数 / GMP出现次数 / A / 100 / 3 / B / 1,000 / 3 / 两个文档出现关键词的次数相同。 / 但A是一篇主要讨论GMP的短文,B可能是一本包含许多其他主题的技术笔记。 / 如果只看原始词频,它们的得分相同。 / BM25引入文档长度归一化(Document Length Normalization),避免长文档仅仅因为包含更多文本就获得不合理的评分优势。 / 注意,这不意味着短文档一定更相关,而是对文档长度造成的统计偏差进行调整。
- 公式
- 对于一个查询词,BM25的简化评分公式是: / [ / \operatorname{Score}(t,D)=\operatorname{IDF}(t)\times / \frac{f(t,D)(k_1+1)} / {f(t,D)+k_1\left(1-b+b\frac{|D|}{avgdl}\right)} / ] / 看起来比较复杂,但实际上只有三个部分。 / 部分 / 作用 / 核心思想 / IDF / 衡量关键词的区分能力 / 越稀有,分数越高 / TF饱和 / 调整关键词出现次数的影响 / 出现次数增加,收益逐渐递减 / 文档长度归一化 / 调整不同长度文档的得分 / 减少长文档天然具有的优势 / 其中的变量: / - (f(t,D)):关键词在当前文档中出现的次数。 / - (|D|):当前文档长度。 / - (avgdl):整个文档集合的平均文档长度。 / - (k_1):控制词频饱和的速度。 / - (b):控制文档长度归一化的程度。 / 常见配置是: / [ / k_1=1.2,\qquad b=0.75 / ] / 这里最重要的不是记住公式,而是理解 BM25同时考虑词频、词项稀有程度和文档长度。 / 三、完整计算示例 / 继续使用搜索 GMP 的例子。 / 假设整个搜索系统有100篇文档,其中10篇包含GMP,平均文档长度为100个词。 / 为了便于比较,假设 (k_1=1.2)、(b=0.75)。 / 我们找到三篇文档: / 文档 / 总词数 / GMP出现次数 / A / 100 / 3 / B / 1,000 / 3 / C / 100 / 30 / 使用常见的BM25 IDF公式计算,GMP的IDF约为2.264。 / 代入BM25公式,得到: / 文档 / BM25分数(约) / 排名 / C / 4.79 / 1 / A / 3.56 / 2 / B / 1.21 / 3 / 观察一:A与B / A和B都出现了3次GMP。 / 但是B的文档长度是A的10倍。 / 因此: / - A得分:3.56 / - B得分:1.21 / 这体现了文档长度归一化。 / 观察二:A与C / A出现3次GMP,C出现30次GMP。 / C出现关键词的次数是A的10倍。 / 但是: / - A得分:3.56 / - C得分:4.79 / C的分数仅比A高约35%,而不是10倍。 / 这体现了词频饱和。 / 所以BM25并不是认为词频无关紧要,而是认为词频的影响不应该无限线性增长。
- 词频饱和(TF Saturation)
- 是什么
- 基于词项的文档相关性评分算法
- 对传统TF-IDF评分方法的改进
- 为什么需要
- TF-IDF
- 是什么
- 一个词在某篇文档中出现得越频繁,同时在整个文档集合中越少见,这个词就越能代表该文档的内容。
- 结合词频和逆文档频率,衡量文档与查询的相关性
- 实现原理
-
- 问题一:关键词匹配无法区分文档的重要性
- TF
- 假设用户搜索: / AI engineering / 搜索系统可能找到100篇包含这两个词的文章。 / 但模型的上下文窗口有限,不可能把全部文章都塞进去。 / 那么,应该优先选择哪些文章? / 一个直观的想法: / 某个关键词在一篇文章中出现得越频繁,这篇文章与该关键词的相关性可能越高。 / 由此引入第一个指标: / TF(Term Frequency,词频) / 表示某个词在一篇文档中出现的次数。 / 例如: / 文档 / engineering出现次数 / TF / 文档A / 10 / 10 / 文档B / 3 / 3 / 文档C / 1 / 1 / 只考虑TF,文档A的相关性最高。
-
- 问题二:不是所有关键词都同样重要
- IDF
- 作者使用的原始例子是: / Easy-to-follow recipes for Vietnamese food to cook at home / 意思是:简单易学的越南菜家庭烹饪食谱。 / 作者指出,这个查询中: / - Vietnamese、recipes:具有较高的信息量。 / - for、at:经常出现,信息量较低。 / 为什么? / 因为如果一个词几乎在所有文档中都出现,它就很难帮助我们区分不同文档。 / 由此引入第二个指标: / IDF(Inverse Document Frequency,逆文档频率) / 用于衡量一个词在整个文档集合中有多稀有。 / 词项 / 出现情况 / 信息量 / for / 几乎所有文档都有 / 低 / recipes / 部分文档包含 / 中 / Vietnamese / 很少的文档包含 / 高 / 因此: / TF衡量一个词对当前文档的重要程度,IDF衡量这个词在整个文档集合中的区分能力。 / 注意,TF只是相关性的一种启发式指标,并不保证词频越高就越相关。
- 结合两者
- 作者将两个指标组合: / [ / \operatorname{TF\text{-}IDF}(t,D) / =\operatorname{TF}(t,D)\times\operatorname{IDF}(t) / ] / 其中: / 1. TF:词频 / [ / \operatorname{TF}(t,D)=f(t,D) / ] / 即词项 (t) 在文档 (D) 中出现的次数。 / 2. IDF:逆文档频率 / 书中的正式公式是: / [ / \operatorname{IDF}(t)=\log\left(\frac{N}{C(t)}\right) / ] / - (N):文档总数。 / - (C(t)):包含词项 (t) 的文档数量。 / 3. 文档相对于Query的最终分数 / [ / \operatorname{Score}(D,Q)= / \sum_{t\in Q}f(t,D)\operatorname{IDF}(t) / ] / 即把Query中各个词项的TF-IDF分数加起来,得到文档的相关性分数。 / 这里有一个书中的细节:作者在文字示例中将10/5=2直接称为IDF,但正式公式包含对数运算。严格按照该公式,IDF应为 (\log 2),而不是2
-
- 是什么
- BM25
- 是什么
- 根据关键词匹配和相关性评分检索
- 实现
- 基于嵌入的检索
- 实现
- Embedding
- 是什么
- 把文本转换成表示其语义的向量。
- Embedding Model
- 负责生成向量的模型
- 使语义相关的数据具有更高的向量相似度
- 是什么
- Vector Search
- 是什么
- 根据向量之间的相似度寻找相关数据。
- 相似度计算
- Cosine Similarity(余弦相似度)
- 检索算法
- k-NN(精确最近邻)
- 与所有向量计算相似度
- 准确但计算成本高
- 与所有向量计算相似度
- ANN(近似最近邻)
- 利用索引缩小搜索范围
- 以部分准确性换取检索效率
- 常见算法
- LSH
- HNSW
- IVF
- PQ
- Annoy
- k-NN(精确最近邻)
- 是什么
- Embedding
- 是什么
- 是把文本转换成向量,再通过向量相似度检索语义相关的内容。
- 为什么
- 反之亦然
- 词项匹配不能直接代表语义相关
- 实现
- 混合检索
- 是什么
- 结合词项检索与嵌入检索
- 是什么
- 基于词项的检索
- 是什么
- 根据用户Query获取相关信息
- 数据来源
- 文档
- 关系型数据库
- 搜索引擎
- 外部API
- 代码仓库
- 分类
- Generator(生成器)
- 是什么
- 利用检索结果生成回答的模型
- 通常就是LLM本身(不是Agent)
- 是什么
- Indexer(索引器)
- 是什么
- 对外部数据进行处理和组织,建立便于Retriever高效查询的索引
- 为什么需要
- 避免查询时遍历全部原始数据
- 提高检索效率
- 支持不同的检索方式
- 实现流程
- Data Ingestion(数据获取)
- 从外部数据源获取原始数据
- Parsing(解析)
- 将原始数据转换为可索引的内容
- Chunking(分块)
- 将长文档拆分成适合检索的数据块
- Index Building(建立索引)
- 基于词项的索引
- 分词
- 建立倒排索引
- 维护词频和文档统计信息
- 基于嵌入的索引
- Embedding生成文档向量
- 建立向量索引
- HNSW
- IVF
- 基于词项的索引
- Storage(持久化)
- 保存索引
- 保存原始数据及元数据关联
- Data Ingestion(数据获取)
- 索引维护
- Incremental Indexing(增量索引)
- Reindexing(重建索引)
- 是什么
- Retriever(检索器)
- 执行流程详解
- 索引阶段
- 获取外部数据
- 数据清洗与分块
- 建立索引
- 词项检索
- 建立倒排索引
- 嵌入检索
- Embedding生成文档向量
- 建立向量索引
- 词项检索
- 存储索引及原始数据关联
- 查询阶段
- 接收Query
- 查询重写(可选)
- Retriever检索
- 词项检索
- 关键词匹配与相关性评分
- 嵌入检索
- Embedding生成查询向量
- Vector Search查找相似向量
- 混合检索
- 合并两路检索结果
- 词项检索
- Reranking(可选)
- Context Construction
- 筛选、组织检索结果
- Generator
- LLM生成最终回答
- 索引阶段
如何优化RAG
- 目的
- 在有限的上下文窗口中获得更相关、完整的信息
- 索引阶段
- 分块(Chunking)
- 解决什么问题
- 文档过长,需要合理切分
- 主要权衡
- 分块大小
- 语义完整性
- 重叠大小
- 解决什么问题
- 上下文检索(Contextual Retrieval)
- 解决什么问题
- 分块后丢失原始文档的上下文
- 解决方式
- 为分块补充原始文档的背景信息或元数据
- 利用增强后的内容建立索引
- 解决什么问题
- 分块(Chunking)
- 查询阶段
- 重排序(Reranking)
- 解决什么问题
- 初次检索结果排序不够准确
- 解决什么问题
- 查询重写(Query Rewriting)
- 解决什么问题
- 用户查询不完整或存在歧义
- 解决什么问题
- 重排序(Reranking)
如何评估RAG
- 生成质量
- 回答正确性
- 最终回答是否正确
- 事实一致性
- 回答是否与检索内容一致
- 回答正确性
- 检索质量
- Precision(精确率)
- 检索到的结果中有多少真正相关
- Recall(召回率)
- 所有相关结果中有多少被检索到
- 排序质量
- 相关文档是否排在前面
- Precision(精确率)
拖动可平移,滚轮缩放,点击节点可展开或折叠。大型导图默认折叠较深层级,可点击节点继续展开。
讨论
使用 GitHub 账号参与讨论,评论会保存在 GitHub Issues 中。在 GitHub 查看