Chunk Optimization块优化
Modular RAG and RAG Flow: Part I
模块化 RAG 和 RAG 流程:第 I 部分
一个关于 RAG 的压缩且高级的总结。
在第一部分中,我们将聚焦于模块化 RAG 的概念和组件,包含 6 种模块类型、14 个模块和 40 多个运算符。
介绍
要说过去一年里最火的话题,
检索增强生成(RAG)
这一次,我们把目光完全聚焦在
模块化 RAG
三层架构
模块类型
模块
操作符
通过编排这些不同的操作符,就能衍生出各种各样的
RAG 流程(RAG Flow)
本文的
目标
来自我们 RAG 调研中的图表
什么是模块化 RAG?
RAG 的进步带来了更灵活、更丰富的流程,具体体现在以下几个关键方面:
- RAG 的数据源已经超越了传统的非结构化数据,现在还包括半结构和结构化数据。重点在于如何预处理结构化数据,以提高检索效率,并减少模型对外部知识的依赖。
数据获取能力增强:
- RAG 正在与其他技术深度结合,比如微调、适配器模块和强化学习,这些都能显著增强其检索能力。
技术融合:
- 检索过程已经进化到支持多轮检索增强,既能用检索到的内容指导生成,也能用生成的内容反过来改进检索。此外,通过自主判断和利用 LLM 的能力来判断是否需要检索,大大提高了回答问题的效率。
检索过程自适应:
模块化 RAG 的定义
从上面可以看出,RAG 的发展速度之快,已经超越了传统的
链式高级 RAG
模块化 RAG 提供了一种高度
可扩展
与之前范式的关系
模块化 RAG 以多层模块化的形式组织 RAG 系统。可以说,Advanced RAG 是 RAG 的一种模块化形式,而 Naive RAG 则是 Advanced RAG 的一个特例。这三者之间,是一种继承与发展的关系。
模块化 RAG 的机会
模块化 RAG 的好处显而易见,它为现有的 RAG 相关工作提供了一个全新且全面的视角。通过模块化的组织方式,相关的技术和方法得以清晰地总结。
- 模块化 RAG 的高度可扩展性,为研究人员在全面理解当前发展现状的基础上,
研究视角:
创造了条件。提出新的模块类型、模块和操作符
- RAG 系统的设计和构建变得更加便捷。用户可以根据现有的数据、使用场景、下游任务等需求,灵活地定制自己的 RAG Flow。开发者也可以参考现有的流程构建方法,
应用视角:
,以适应不同的应用场景和领域。定义新的流程和模式
模块化 RAG 的框架
模块类型 — 模块 — 操作符
在这一章,我们将深入探讨这个三层结构,并为 RAG 构建一个技术路线图。受限于篇幅,我们不会陷入技术细节的汪&洋大海,但会提供一些参考文献供大家深入阅读。
1. 索引
索引,简单来说,就是把文本切分成易于管理的块,这是组织系统的关键一步。当然,这一步也面临三个主要挑战:
- 分块方式会影响块的语义信息,可能导致一些重要信息在长文本中丢失或被淹没。
内容表示不完整:
- 随着数据量的增加,检索中的噪音也会增大,导致频繁匹配到错误数据,让检索系统变得脆弱且不可靠。
块相似性搜索不准确:
- 检索到的块,其来源可能五花八门,缺乏清晰的引用路径。这可能会导致我们得到多个来自不同文档的块,它们虽然在语义上相似,但讲的却是完全不同的主题。
引用轨迹不清晰:
块优化
这里有个经典的权衡:块越大,能捕获的上下文就越多,但也会引入更多噪音,处理时间和成本也更高。块越小,噪音少了,但可能无法完整地传达所需的上下文。
- 一个简单的平衡方法是使用重叠的块。通过滑动窗口,可以增强语义的连贯性。但这种方法也有局限,比如对上下文大小的控制不够精确,有截断单词或句子的风险,也缺乏对语义的考虑。
滑动窗口:
- 核心思想是把用于检索的块和用于最终合成的块分开。用较小的块来提高检索精度,再用较大的块来提供更丰富的上下文信息。
从小到大:
具体来说,一种方法是先检索
较小的子块
引用其父ID
周边的文本窗口
- 这有点像“从小到大”概念的变体。先为较大的块生成摘要,然后在摘要上进行检索。之后,再根据检索到的摘要,去回找对应的大块。
摘要:
- 可以给块附加元数据信息,比如
元数据附加:
,甚至这个块能回答的问题。这样,在检索时就能根据这些元数据先过滤一遍,缩小搜索范围。页码、文件名、作者、时间戳、摘要
结构组织
提升信息检索效率的一个有效方法,是为文档建立一个层级结构。通过构建有结构的块,RAG 系统可以更快地检索和处理相关数据。
- 在文档的层级结构中,节点以父子关系排列,块就挂在这些节点下面。每个节点上存储着数据的摘要,这有助于快速遍历数据,辅助 RAG 系统判断需要提取哪些块。这种方法还能在一定程度上缓解由于块提取问题导致的幻觉。
分层索引:
构建结构化索引的方法主要有这么几种:
- 比如,感知文档中的段落和句子边界。
结构感知:
- 利用 PDF、HTML、LaTeX 等格式中自带的固有结构。
内容感知:
- 基于 NLP 技术(比如 NLTK)进行文本的语义识别和分割。
语义感知:
- 利用知识图谱(KG)来构建文档的层级结构,有助于保持内容的一致性。它能清晰地描绘不同概念和实体之间的联系,大大降低产生幻觉的可能性。
知识图谱组织文档:
另一个好处是,它能将信息检索的过程转化为 LLM 能够理解的指令,从而提高知识检索的准确性,让 LLM 生成上下文更连贯的响应,最终提升整个 RAG 系统的效率。
2. 预检索
Naive RAG 的一个主要问题在于,它直接拿用户的原始查询去检索。现实情况是,很多人提的问题本身就模棱两可,表述不清,这直接导致检索效果不佳。
这个阶段的主要挑战包括:
- 问题本身可能很复杂,语言组织得也不够好。
查询措辞不当:
- 在处理专业术语或有多重含义的模糊缩写时,语言模型常常会栽跟头。比如,它可能分不清“LLM”到底是指 大语言模型,还是在法律语境下的 法学硕士。
语言复杂性与歧义性:
查询扩展
既然一个查询不够,那把单个查询扩展成多个查询如何?这样可以丰富查询的内容,弥补其在特定细节上的不足,从而保证生成的答案能更好地命中要点。
- 通过提示工程,让 LLM 帮你扩展出多个查询,然后并行地去执行。查询的扩展可不是随意的,得经过精心设计。两个关键的设计标准是查询的
多重查询:
和多样性
。覆盖度
使用多查询有个潜在的问题:可能会
稀释
- 这个过程好比是问题规划:把原始问题分解成一系列必要的子问题,当把所有子问题的答案组合起来,就能完整地回答原始问题了。从原理上讲,这和添加相关上下文类似。具体来说,可以使用
子查询:
,将复杂问题分解为一系列更简单的子问题。从最少到最多的提示方法
- 另一个查询扩展的思路,是使用 Meta AI 提出的验证链。这种方法让 LLM 对扩展出来的查询进行自我验证,以达到减少幻觉的效果。经过验证的扩展查询,通常可靠性更高。
CoVe(Chain-of-Verification):
查询转换
有时候,我们不用用户的原始查询,而是用一个转换后的查询去检索和生成,效果可能更好。
- 原始查询对 LLM 来说并不总是最优的,尤其在现实场景中。因此,我们可以让 LLM 来重写查询。除了用 LLM,也有专门的轻量级语言模型干这个活儿,比如 RRR(Rewrite-Retrieve-Read)。
重写:
淘宝促销系统中使用的查询重写方法(BEQUE)就是一个很好的例子,它显著提高了长尾查询的召回率,进而带动了 GMV 的增长。
- 核心思路是,在响应查询时,让 LLM 先生成一个假设性的回答(假设文档),然后用这个假设文档的向量去检索,而不是直接用原始查询去向量数据库里搜。它强调的是答案到答案之间的嵌入相似性,而不是问题到答案的。同时,也有与之相反的
HyDE(假设文档嵌入):
,专注于查询到查询的检索。Reverse HyDE
HyDE 和 Reverse HyDE 的核心思想,都是在查询和答案之间架起一座映射的桥梁。
- 使用谷歌 DeepMind 提出的这种方法,可以对原始查询进行抽象,生成一个更高层次的概念性问题(step-back question)。在 RAG 系统中,同时用 step-back 问题和原始查询去检索,把两个结果都作为生成答案的基础。
Step-back Prompting:
查询路由
针对不同的查询,路由到不同的 RAG 管道。这对于一个需要适应多种场景的通用 RAG 系统来说,非常实用。
- 第一步,从查询中提取出关键字(实体);然后,基于这些关键字和块中的元数据进行过滤,从而缩小搜索范围。
元数据路由器/过滤器:
- 另一种路由方法,是充分利用查询的语义信息。当然,也可以把语义和元数据结合起来,采用一种混合路由的方法,效果更佳。
语义路由器:
查询构造
简单说,就是把用户的自然语言查询,转换成另一种查询语言,以便访问其他类型的数据源。常见的方法包括:
- (文本转图查询语言)
Text-to-Cypher
- (文本转SQL)
Text-to-SQL
在很多场景下,结构化查询语言(如 SQL、Cypher)往往会和语义信息、元数据结合起来,用于构造更复杂的查询。
3. 检索
检索过程在 RAG 中扮演着核心角色。强大的预训练语言模型(PLM)能够有效地将查询和文本表示到潜在空间中,从而建立起问题和文档之间的语义相似度,支撑起整个检索过程。
这里有三个主要的考量点:
检索效率
嵌入质量
任务、数据和模型之间的对齐程度
检索器选择
自 ChatGPT 发布以来,嵌入模型的发展可谓是日新月异。Hugging Face 的
MTEB
C-MTEB
在构建 RAG 应用时,对于“该用哪个嵌入模型”这个问题,没有放之四海而皆准的答案。但你会发现,某些特定的嵌入模型在某些特定场景下表现尤为出色。
- 虽然稀疏编码模型看起来有点“复古”,通常基于词频统计之类的统计方法,但它们编码效率高、稳定性好,所以至今仍占有一席之地。常见的代表有
稀疏检索器:
和BM25
。TF-IDF
- 基于神经网络的密集编码模型则更加多样,主要包含几种类型:
密集检索器:
- 基于 BERT 架构的编码器-解码器模型,比如 ColBERT。
- 全面的多任务微调模型,例如 BGE 和 Baichuan-Text-Embedding。
- 基于云 API 的模型,如 OpenAI-Ada-002 和 Cohere Embedding。
- 为大规模数据应用设计的下一代加速编码框架 Dragon+。
- 稀疏和密集这两种嵌入方法捕获的是不同的相关性特征,正好可以互补。例如,可以用稀疏检索模型的结果来训练密集检索模型;PLM 也可以用来学习词汇权重,以增强稀疏检索。研究表明,稀疏检索能增强密集检索的零样本能力,并帮助密集检索器处理包含稀有实体的查询,从而提升整体鲁棒性。
混合/混合检索:
图片来自 IVAN ILIN:高级 RAG 技术图解概述
检索器微调
当你的上下文和预训练模型在嵌入空间中认为“相似”的东西不太一样时,尤其是在医疗、法律这些高度专业化的领域,调整嵌入模型本身就能很好地解决问题。虽然这需要额外的努力,但能显著提升检索效率和领域对齐度。
- 你可以基于特定领域的数据,自己构建一个微调数据集,然后微调模型。这个过程用 LlamaIndex 可以很快完成。
SFT(监督微调):
- 与直接构建微调数据集不同,LSR 巧妙地利用语言模型生成的结果作为监督信号,在 RAG 的进行过程中动态微调嵌入模型。
LSR(LM 监督检索器):
- 受 RLHF 的启发,利用语言模型的反馈,通过强化学习来进一步强化检索器。
RL(强化学习):
- 有时,微调整个检索器成本太高,特别是对于那些基于 API、无法直接微调的检索器。这时,我们可以通过添加一个适配器模块来进行微调。这样做还有一个额外的好处:能更好地对齐特定的下游任务。
适配器:
4. 检索后处理
直接把检索到的整个文档块一股脑塞进 LLM 的上下文环境,并不是最佳选择。对检索到的文档做些后处理,能帮助 LLM 更好地利用上下文信息。
这个阶段的主要挑战包括:
- 和人类一样,LLM 也倾向于只记住长文本的开头和结尾,而忘记中间的内容。
迷失在中间:
- 检索到的噪音文档或事实性矛盾的文档,会严重影响最终的生成质量。
噪音/矛盾块:
- 尽管我们可能检索到大量相关内容,但大模型的上下文窗口长度有限,无法容纳所有这些信息。
上下文窗口限制:
重新排序
在不改变文档块内容和长度的情况下,对它们重新排序,目的是让 LLM 能优先关注到更重要的信息。
- 根据一些预设的规则,计算指标来重新排序。常见的指标包括:
基于规则的重新排序:
- 多样性
- 相关性
- MMR(最大边际相关性):这个方法的初衷是减少冗余、增加结果的多样性,后来也被用在文本摘要中。MMR 基于查询相关性和信息新颖性的组合标准,在最终列表中挑选出最佳短语。
- 利用一个语言模型来重新排序。可选的模型包括:
基于模型的重新排序:
- BERT 系列的编码器-解码器模型,比如 SpanBERT。
- 专门的重新排序模型,例如 Cohere rerank 或 bge-raranker-large。
- 通用的大型语言模型,比如 GPT-4。
压缩与选择
RAG 流程中有一个常见的误区:认为检索到越多相关文档越好,把它们全连起来形成长长的提示词就够了。然而,过多的上下文会引入更多噪音,反而会削弱 LLM 对关键信息的感知,导致“迷失在中间”等问题。一个常见的解决方案是对检索到的内容进行压缩和筛选。
- 通过使用对齐且经过训练的小语言模型(比如 GPT-2 Small 或 LLaMA-7B),可以检测并删除提示词中不重要的 tokens,将其压缩乘人类难以读懂但 LLM 却很了解的形式。这是一种直接而实用的提示压缩方法,无需额外训练 LLM,同时还能平衡语言完整性和压缩率。
(Long)LLMLingua:
- 引入了两种类型的压缩器:
Recomp:
,从检索到的文档中挑选相关句子;抽取式压缩器
,则整合多个文档的信息生成简洁的摘要。这两个压缩器都经过专门训练,目的是在生成的摘要被附加到 LLM 输入之前,能提升其在最终任务上的性能,同时确保摘要本身简洁。如果检索到的文档与输入无关,或者无法提供额外信息,压缩器可以返回空字符串,从而实现选择性增强。抽象式压缩器
- 通过
选择性上下文:
,可以精简输入,从而提高语言模型的推理效率。可以把它理解为一种“停用词清除”策略。在实践中,该方法根据基础语言模型计算出的自信息,来评估词汇单元的信息量。通过保留自信息较高的内容,它为语言模型处理提供了更简洁的文本表示,而不会影响其在各种应用场景下的性能。识别并移除输入上下文中的冗余内容
- 一个比较直观直接的方法。先给文档打上标签,然后根据查询的元数据信息进行过滤。
标签过滤:
- 另一个简单高效的方法:在生成最终答案之前,先让 LLM 自己评估一下检索到的内容。通过 LLM 的自我批判,可以过滤掉相关性差的文档。比如在 Chatlaw 项目中,LLM 会被提示对自己引用的法律条文进行评估。
LLM 批判:
5. 生成
这一步就是利用 LLM,基于用户查询和检索到的上下文信息来生成最终答案。
生成器选择
根据应用场景的不同,LLM 的选择大致可以分为两类:
- 通过调用第三方 API 来使用,比如 OpenAI 的 ChatGPT、GPT-4,或者 Anthropic 的 Claude 等。
基于云 API 的生成器:
- 没有服务器压力,支持高并发,能使用更强大的模型。
优点:
- 数据经过第三方,有隐私泄露的风险;绝大多数情况下无法调整模型。
缺点:
- 在本地部署开源或自研的 LLM,比如 Llama 系列、GLM 等。优缺点正好和云 API 模式相反:更灵活,隐私保护更好,但需要较高的计算资源。
本地部署:
生成器微调
除了直接拿来用,根据具体场景和数据特征进行针对性的微调,往往能取得更好的效果。这也是部署本地模型的最大优势之一。
- 当 LLM 缺乏特定领域的数据时,可以通过微调向模型注入额外知识。微调的另一个好处是能调整模型的输入输出格式,比如让模型适应特定的数据格式,或者按照指示生成特定风格的回复。
SFT(监督微调):
- 通过强化学习,让 LLM 的输出与人类偏好或检索器的偏好对齐。除了人类偏好,理论上也可以与微调模型或检索器的偏好对齐。
RL(强化学习):
- 当你无法使用强大的专有模型或更大的开源模型时,一个简单有效的方法是对更强大的模型(如 GPT-4)进行知识蒸馏。
蒸馏:
- 同时微调生成器和检索器,让它们的偏好对齐。一个典型的方法是 RA-DIT,它使用 KL 散度来对齐检索器和生成器的评分函数。
双重微调(Dual FT):
6. 编排
编排是指用来控制 RAG 流程的模块。与 Naive RAG 的固定流程不同,现在 RAG 需要在关键节点做出决策,并根据结果动态选择下一步的行动。这正是模块化 RAG 的关键特征之一。
调度
Judge 模块负责评估 RAG 流程中的关键节点,判断是否需要检索外部知识库、当前答案是否满足要求,以及是否需要进一步探索。它通常应用在递归、迭代和自适应检索中。主要包含两个操作符:
- 根据预设规则决定下一步。通常,会对生成的答案进行打分,然后根据分数是否达到预设阈值(比如 tokens 的置信度水平)来决定是继续还是停止。
基于规则:
- 让 LLM 自主决定下一步。主要有两种方式:
基于提示:
- 通过提示,让 LLM 基于对话历史进行反思或判断,比如 ReACT 框架。好处是不需要微调模型,但判断的输出格式取决于 LLM 对指令的遵循程度。一个例子是 FLARE。
提示基础:
- 让 LLM 生成特定的 token 来触发特定的动作。这个方法可以追溯到 Toolformer,在 RAG 中的应用有 Self-RAG。
微调基础:
融合
这个概念源自 RAG Fusion。正如我们在“查询扩展”部分提到的,现在的 RAG 流程不再是单一的管道。它通常需要通过多个分支来扩展检索范围或多样性。因此,当扩展到多个分支后,就需要 Fusion 模块来合并多个答案。
- 基于多个分支生成的不同 token 的权重值,综合选择最终的输出。主要采用加权平均的方法。
可能性集成:
- 这是一种将多个搜索结果列表的排名组合起来,生成一个统一排名的技术。实践表明,RRF 产生的结果比在任何单个分支下重新排序得到的块更有效。
RRF(倒数秩融合):
结论
关于 RAG Flow 的更多内容,我们会在即将发布的第二部分中详细展开。
由于这是笔者第一次尝试这种形式的文章,还有许多不成熟之处,欢迎各位批评指正。
-
- 关于宇宙的好的网名有哪些
- 角色扮演 | 1
- 网名