RAG,全称为 Retrieval-Augmented Generation,即检索增强生成。通俗地讲,它是一种将检索过程与文本生成技术深度融合的方案,旨在结合传统检索问答系统与自然语言生成模型的优势,从而显著提升AI系统在回答各类问题时的精确度与可信度。
基础背景
优势
RAG的优势在自然语言处理领域中确实非常突出,这也是它能够占据一席之地的关键原因。
首先,其知识更新的灵活性令人印象深刻。与传统微调方法不同,RAG无需对整个模型进行重新训练。当需要融入新知识时,只需更新知识库即可。以金融领域为例,市场数据和法规政策瞬息万变,RAG系统能够随时将最新的股票行情、政策条文纳入知识库,使模型基于最新信息做出回答,其效率远高于重新训练整个模型。
其次,RAG具备强大的可扩展性。它能够从容应对海量数据的处理需求——随着检索语料库的不断扩大,其性能并不会受到明显影响。这是因为RAG的机制是从海量信息中灵活提取相关内容,而非像某些传统模型那样,在数据量增长时陷入瓶颈。大型电商平台的客服系统就是一个典型案例,尽管商品种类和用户数量持续增长,知识库也在不断膨胀,但RAG系统依然能够高效检索,提供精准的购物建议。
最后,在处理复杂任务和开放领域问题时,RAG同样表现出色。它能够从广泛的知识源中检索信息,为模型提供丰富的上下文,从而更好地理解和应对棘手的任务。无论是多轮对话、长篇幅文档的理解与生成,还是需要跨领域知识的综合性问题,RAG都能通过检索相关数据,为生成准确、全面的回答提供有力支持。例如,当智能写作助手需要撰写一篇关于科技发展趋势的文章时,RAG可以从众多科技文献、新闻报道和行业分析中提取相关信息,并将其融合到生成的文章中,使内容更具深度和广度。
不足
当然,RAG也并非无所不能。
一个显著的问题是它对文档质量的依赖性极高。如果知识库中的文档内容不准确、过时、带有噪声,或者格式杂乱无章,那么检索的精确性和最终答案的质量都会受到直接影响。举个例子,如果一个技术文档知识库中混入了错误的技术参数或过时的技术描述,RAG系统在检索和利用这些文档时,很可能会生成错误的技术解答,从而误导用户。此外,文档的切分粒度也至关重要——如果切分不当,信息可能会变得支离破碎,或者关键信息被分割在不同的块中,导致检索效果大打折扣。
另一个问题是,即使检索到的文档本身信息准确,RAG仍然可能生成不准确的回答。这是因为模型在整合和生成答案的过程中,其自身也存在局限性。例如,当需要对多个信息片段进行综合推理和判断时,模型可能会出现失误,导致生成的答案与实际情况不符。此外,如果检索到的文档与问题之间的相关性判断出现偏差,生成的答案也容易偏离主题,无法满足用户的真实需求。
模块化RAG的优化
索引模块优化
Chunk 优化
首先来谈谈Chunk优化。滑动窗口的大小是一个需要仔细权衡的因素。如果设置得过大,虽然能包含更多的上下文信息,但也容易引入无关内容,增加检索负担和噪声;如果设置得过小,信息则容易变得碎片化,关键信息可能被分割到不同的Chunk中,影响检索准确性。例如,在处理一篇科技文献时,如果滑动窗口过大,可能会将多个不同主题的段落合并到一个Chunk中,导致相关性判断出现偏差;而窗口过小,则可能将一个完整的实验步骤或理论阐述拆散。因此,具体的大小需要根据数据特点和应用场景,通过实验和分析来确定,目标是在信息完整性和检索效率之间找到平衡。
Metadata 增强主要有两种实现方式。一种是直接丰富向量数据库中的metadata字段——添加数据来源、创建时间、作者等信息,这样在检索时可以利用多路检索或混合检索技术,更精确地筛选出符合特定条件的信息。例如,一个新闻资讯类的RAG系统,可以将新闻的发布时间、所属类别等信息作为metadata,当用户查询特定时间段或特定主题的新闻时,就能快速定位到相关的Chunk。另一种方式是将需要的信息直接添加到文本中,这种方法适用于文本被切分后可能出现信息丢失的情况,比如后面使用了缩写或代词来指代前文内容。通过将原始信息补充完整,检索的准确性自然会得到提升。例如,在一篇医学文献中,第一次提到“新冠病毒(COVID-19)”,后面可能只用了“病毒”一词——如果在Chunk划分时,将“新冠病毒”的完整信息附加到后续相关的Chunk中,就能避免因信息缺失而导致的检索误判。
Small to big方法的原理比较直观:检索时首先使用小块Chunk进行匹配,确定大致相关的范围,然后利用这个范围前后的Chunk进行信息增强。这样既能保证检索的准确性,又能获取更丰富的上下文信息。例如,在回答一个关于历史事件的问题时,首先通过与问题相关性较高的关键语句所在的小Chunk,定位到事件的核心内容,再结合前后文Chunk,获取事件的背景、起因、影响等信息,从而为生成完整、准确的回答提供支持。
结构组织优化
结构化存储是将非结构化的数据按照一定的结构组织起来,比如构建树形结构。每个节点可以存储总结信息,通过分层检索快速定位到相关的信息层次,然后获取详细内容。其优点是检索路径清晰,能有效降低时间复杂度,适用于数据具有明显层次结构的场景,例如企业的组织架构信息、知识图谱中的分类信息。企业知识管理系统就是一个很好的例子——将不同部门、不同业务领域的知识按照树状结构存储,员工查询与自己工作相关的知识时,可以先到对应部门或业务领域节点下检索,效率很高。缺点是构建和维护结构的成本较高,需要对数据有深入的理解和规划,而且当数据结构发生变化时,调整起来也比较麻烦。
知识图谱存储则是将数据以图结构的形式存储在数据库中,例如微软提出的GraphRAG就是遵循这一思路。通过图检索,可以利用实体之间的关系快速定位相关信息。例如,在一个电影知识图谱中,通过演员、导演、电影类型等实体之间的关联关系,可以快速检索到某个演员参演的所有电影、某个导演执导的作品,或者特定类型的电影集合。不过,这种存储方式的成本确实较高——数据存储和检索计算的开销都不小。后来虽然出现了light GraphRAG等优化方案,但成本问题尚未完全解决。此外,复杂的知识图谱在数据更新和维护方面也面临挑战,一个实体或关系的变化,可能会影响整个图谱的结构和语义。
话说回来,业界也有一种观点:图数据不一定非要存储在专门的图数据库中。很多公司在这方面已经进行过尝试。目前一个比较流行的做法是,将图数据解析成一条条的Chunk,然后存储在向量数据库中,并通过Meta字段增加一些必要的信息。
多向量存储是一种更为先进的存储方式,以 Jina-ColBERT 为代表。它为每个token生成一个向量,通过MaxSim计算得分。其优势在于逐token编码提供了更细粒度的表征能力,在同一领域中,MRR@10(头部排序能力)和Recall@1k(腰尾部召回能力)都表现优异,并且可解释性也更好。例如,在一个专业文献检索系统中,对于一些特定领域的术语和概念,多向量存储能够更精准地捕捉语义细节,从而提高检索准确性。在跨领域场景下,尤其是在处理长尾查询或文档时,由于词粒度的精细表征,模型对未见过的领域也能表现出更好的性能,从而更好地应对不同领域知识的多样性和复杂性。缺点当然是成本较高——向量生成过程会消耗大量计算资源,存储这些大量向量的空间成本也很高,在资源受限的场景下使用起来会有些吃力。
检索前优化
查询修改
查询修改是提升检索准确性的重要手段。重写和扩写是常见的优化方法。例如,当用户只查询“苹果”两个字时,其意图可能非常模糊——是指水果苹果,还是苹果公司,或是其他含义?通过重写和扩写,可以将查询变为“苹果这种水果的营养价值”或“苹果公司的最新产品”等更具体的表述,从而更精准地定位用户的需求。这样做有助于检索系统更好地理解用户意图,克服单一查询的局限性,从而获得更丰富的检索结果集合。
HyDE方法则是借助LLM的能力,首先根据客户的问题生成一份初步回答,然后对这个回答内容进行embedding,再检索相关的Chunk来修正和完善内容。例如,对于“如何提高英语口语能力”这个问题,LLM可能会生成“多与外教交流、观看英语电影、练习口语对话”等回答,然后将这个回答的文本进行embedding,检索相关的学习资料Chunk,对回答进行补充和修正,最终给出更准确、更详细的信息。
Step back prompting的逻辑比较独特。它首先将客户的query抽象成一个更概念化的问题,得到一个逻辑上的答复后,再根据这个答复的结果辅助生成具体答案。例如,对于“如何解决城市交通拥堵问题”的查询,可以先抽象为“城市交通问题的解决策略”,通过LLM得到“优化交通规划、发展公共交通、限制私家车出行”这类一般性答复,然后再结合具体城市的情况、交通设施等信息,进一步生成针对该城市的详细解决方案。
查询路由
查询路由的作用是根据不同的查询意图,将查询引导至对应的数据库或数据源,从而提高检索效率和准确性。不同的prompt可能涉及不同领域或类型的数据,这些数据可能分散在不同的数据库中。例如,在一个企业级的信息检索系统中,产品销售数据的查询可能需要路由到销售数据库,而员工信息的查询则应该路由到人力资源数据库。通过识别客户意图,能够更好地判断应该查询哪个数据库,避免在无关数据库中浪费时间,这不仅减少了检索时间和资源消耗,也提高了检索结果的相关性和准确性。实现方式通常有两种:基于规则或基于机器学习模型。基于规则的方法是通过预先设定一些关键词或查询模式与数据库的映射关系,当查询符合特定规则时,就将其路由到对应的数据库。机器学习模型则通过对大量查询及其对应正确数据库的学习,自动识别查询意图并做出路由决策——例如,使用分类模型将查询划分到不同领域或主题类别,再根据类别与数据库的对应关系进行路由。
查询扩写
Multi query是指LLM将原始查询重写为多个语义相近的queries,然后并发进行检索。例如,对于查询“人工智能在医疗领域的应用”,LLM可能会生成“人工智能在医学影像诊断中的应用”“人工智能在疾病预测中的应用”“人工智能辅助手术的应用”等多个相似查询,并发地在知识库中检索信息。这种方式能够扩展可能不完整或不明确的初始查询,通过合并多个相关查询的检索结果,得到更全面、更丰富的信息集合。不过,这种做法也可能导致检索到更多文档,其中难免包含冗余和无关信息,需要在后续处理中进行去重和筛选,以避免分散LLM的注意力,确保能够有效利用检索到的信息生成准确答案。
Sub query则是将问题拆分开,分别对每个子问题进行检索和回答,最后将结果整合在一起。例如,对于“介绍一下中国古代四大发明及其对世界文明的影响”这个问题,可以拆解为“中国古代四大发明是什么”和“中国古代四大发明对世界文明的影响分别是什么”两个子问题,分别检索答案后再整合起来形成完整回答。这种方法适用于复杂的、可以分解成相对简单子问题的情况,能够降低检索和回答的难度,提高答案的准确性和逻辑性。与Multi query相比,Sub query更注重问题的分解和分步解决,而Multi query则更侧重于通过多个相似查询来扩展信息获取的范围。
查询构建
Text-to-SQL方法主要用于与数据库交互的场景。它能够根据用户输入的自然语言prompt,将其转换为数据库查询语句,从而精准地查询所需信息。例如,在一个电商数据库中,当用户查询“查询销售额大于100万的商品名称和销售数量”时,Text-to-SQL方法可以将这条自然语言查询转换成对应的SQL语句,比如“SELECT product_name, sales_quantity FROM sales_table WHERE sales_amount > 1000000”,然后在数据库中执行,并返回准确结果。其优势在于能够充分利用数据库的强大功能进行高效的数据检索,特别适合大规模数据的查询和分析。在构建查询语句时,需要对数据库结构和查询语言语法有深入理解,同时要确保转换的准确性和安全性,避免因错误的查询构建导致数据泄露或错误结果。一些工具和框架,比如SQLAlchemy等,可以辅助实现这个功能,提供便捷的接口和函数,将自然语言映射到对应的SQL操作,从而简化查询构建的流程。
检索后处理优化
重排序
重排序在RAG流程中至关重要。向量的embedding本质上是在压缩信息,会丢失大量细节,因此检索结果中很容易混入一些与问题相关性不高的内容。例如,JINA的embedding model虽然能够支持8k的上下文长度,但最终可能被压缩到一个1000多维度的向量中,许多细节和语义信息在这个过程中就丢失了。重排序的目的就是要对检索到的文档重新进行评估和排序,筛选出与客户问题最相关的结果。斯坦福大学的研究人员在2023年发表的一篇论文中发现,随着retrieval documents数量的增加,整个模型回答的准确率呈现出一种碗状趋势——首尾高、中间低,也就是所谓的“middle lost”现象。这意味着,当回答问题所需的内容出现在prompt的前面时,LLM最有可能正确回答。因此,将最关键的信息放在开头位置,能够帮助LLM更好地捕捉重点,生成更准确、更完整的答案。通过重排序,能够提高检索结果的质量,让LLM在生成答案时基于更相关、更优质的信息,从而提升答案的准确性和可靠性。
压缩
在检索后处理中,压缩也是一个重要环节。检索回来的内容可能存在信息过多和重复的问题,这会增加LLM的处理负担,影响生成答案的效率和质量。微软提出的LLM lingua就是一种有效的压缩方法。它使用一个小型语言模型,比如GPT2-small或LLaMA-7B,来检测并移除prompt中不重要的tokens。例如,一篇关于历史事件的长篇检索结果中,可能包含很多描述性的、与核心事件关联不大的语句,LLM lingua可以识别并去掉这些冗余信息,将文本压缩成LLM能够更高效处理的形式。它能够实现高达20倍的压缩率,同时性能损失很小。LongLLMLingua则更进一步,在压缩时考虑输入查询,移除那些一般不重要以及对查询不重要的tokens,使得压缩后的信息更精准地聚焦于回答问题所需的内容。例如,当查询“秦始皇统一六国的时间”时,检索结果中关于秦始皇其他方面的信息,比如宫殿建设,就会被视为对查询不重要的信息而被移除。
需要注意一点:在使用压缩方法时,如果检索结果中包含关键数字等重要信息,需要谨慎处理,避免因压缩而丢失关键数据,从而影响答案的准确性。
选择
LLM critique是检索结果选择中常用的方法。它利用大模型来判断检索回来的内容是否需要用于生成答案,并给出一些评价指标,比如检索内容与query的相关性、检索内容对query中实体的召回率(recall)等。举个例子,在一个问答系统中,对于检索到的多篇关于“太阳系行星”的文档,LLM critique会评估每篇文档与用户查询“太阳系中最大的行星是哪个”的相关性,判断文档中是否提到了行星大小比较等关键信息,以及对于“太阳系行星”这一实体的召回是否完整。根据这些指标,可以筛选出与问题高度相关、信息完整的检索结果,排除那些相关性较低或信息冗余的文档。这样能够确保提供给LLM生成答案的信息是最有价值的,从而提高生成答案的准确性和质量。通过合理的选择,能够让LLM聚焦关键信息,避免被无关或低质量信息干扰,从而生成更符合用户需求的答案。
检索器和生成器优化
检索器
在通常意义上的retriever(即基于vector向量数据库检索文本信息的场景)中,本质上就是embedding model。由于许多embedding model也是基于transformer训练的,因此可以简单地理解为:当训练集合与实际应用场景的数据分布差异过大时,会导致embedding效果不佳。不同的词汇在不同的语境下其含义也可能不同。所以,在必要的情况下需要进行微调,将基座模型的参数微调到特定领域的数据分布上,这样才能更好地检索到相关信息。当然,微调的代价比较大,因此有一个取巧的办法:在下游接一个adapter来进行微调。
生成器
生成器也是如此,需要使用垂直领域的数据进行微调,才能更好地回答问题。具体的微调方法有很多,比如LoRA、QLoRA等。如果采用post-training,强化学习人类偏好对齐也是必不可少的,否则可能会产生意想不到的回答。
除了微调,生成部分还有一个重要的步骤:信息验证。这部分相对比较棘手,很多人进行过探索,但想要完美实现并不容易。
关于Knowledge base的验证:从实践角度来看,只能验证一小部分信息是否合理,或者根据某些knowledge graph的数据判断其是否与数据库一致。但想要进行完整的检查是不太现实的——假设已经能够用程序方法检测到所需的内容是否正确,那么为什么还要通过RAG绕这么大一圈来生成回答呢?直接通过程序,最后格式化成一整段话就可以了。
至于LLM担任裁判(judge):这是最常见的做法。比较有名的工具是RAGAS,它包含各种评价指标,包括前面提到的相关性等。在生成阶段,可以使用的指标更多,比如回答是否与检索回来的内容冲突、回答内容是否与客户提问相关等。但由于这是由LLM来判断的,所以也不能保证100%的正确性。
