链载Ai

标题: RAG优化策略总结 [打印本页]

作者: 链载Ai    时间: 4 小时前
标题: RAG优化策略总结

一、背景

目前LLM虽然已经具备了强大的能力,但是在某些情况下,它们仍可能无法提供准确的答案。目前 LLM 面临的主要问题有:

  1. 信息偏差/幻觉:LLM 有时会产生与客观事实不符的信息,导致用户接收到的信息不准确。
  2. 知识更新滞后性:LLM 基于静态的数据集训练,这可能导致模型的知识更新滞后,无法及时反映最新的信息动态。
  3. 领域专业知识能力欠缺:LLM通常基于公开数据集训练,在处理特定领域的专业知识时,效果不太理想。

对于 Transformer架构类型的大模型来说,想要提高LLM生成内容的准确性,一般只需要 3 个步骤:

  1. 提供更准确的内容:提供准确性更高的内容,会让 LLM 能识别到关联的内容, 生成的内容准确性更高。
  2. 让重要的内容更靠前:GPT 模型的注意力机制会让传递 Prompt中更靠前的内容权重更高,越靠后权重越低。
  3. 不传递不相关内容:缩短每个块的大小,尽可能让每个块只包含关联的内容,缩小不相关内容的比例。

二、RAG流程简介

RAG 是一个完整的系统,其工作流程可以简单地分为数据预处理、检索、增强和生成四个阶段:

  1. 数据处理阶段:对原始数据进行清洗和处理,然后将处理后的数据转化为检索模型可以使用的格式,最后存储在对应的数据库中。
  2. 检索阶段:将用户的问题输入到检索系统中,从数据库中检索相关信息。
  3. 增强阶段:对检索到的信息进行处理和增强,以便生成模型可以更好地理解和使用。
  4. 生成阶段:将增强后的信息输入到生成模型中,生成模型根据这些信息生成答案。

一个完整的RAG应用开发流程,涉及到文档加载器、向量数据库、检索器、Prompt、记忆、输出解析器、大语言模型、多个功能模块,如下所示:

通常RAG的优化策略主要分为:查询转换、路由、问题构建、索引、检索和生成六个方面进行,如下与所示:

三、RAG优化

3.1 查询转换

3.1.1 查询重写和融合策略

如果直接使用原始问题进行检索,可以因为用户的表述偏差导致检索不到相关的文档。多查询重写策略的核心思想是利用大语言模型(LLM)对原始问题进行扩展、分解或抽象,生成多个语义相关但视角不同的子查询,从而提高检索系统对用户意图的覆盖能力。这种方法能有效解决单一查询可能存在的表述偏差或信息不全问题。整体流程如下所示:

一个简易的prompt如下所示:

你的任务是为给定的用户问题生成3-5个语义等价但表述差异化的查询变体,目的是帮助用户克服基于距离的相似性搜索的一些局限性,以便从向量数据库中检索相关文档。
以下是原始问题:
<question>
{{question}}
</question>
请生成3-5个语义与原始问题等价,但表述不同的查询变体,用换行符分隔这些替代问题。
请在<查询变体>标签内写下你的答案。

由于需要转换问题一般较小,以及生成子问题时对 LLM 的能力要求并不高,在实际的 LLM 应用开发中,通常使用参数较小的本地模型+针对性优化的 prompt 即可完成任务,并将temperature设置为0,确保生成的文本更加有确定性。

调用样例如下:

在多查询重写策略中,每个子问题都会检索出相应的文档片段。针对如何合并这些文档的问题,便延伸出多查询结果融合策略。主要思想对其检索结果进行重新排序(即 reranking)后输出 Top K 个结果,最后再将这 Top K 个结果喂给 LLM 并生成最终答案。通常使用的算法是RRF(Reciprocal Rank Fusion),即倒排序排名算法。公式如下:

该算法会对全集 D 进行二重遍历,外层遍历文档全集 D,内层遍历文档子集,在做内层遍历的时候,我们会累计当前文档在其所在子集中的位置并取倒数作为其权重。也就是说如果该子文档在每个子问题检索位置越靠前,则权重越高。RFF的代码实现如下所示:

defrrf(results: list[list], k: int =60)-> list[tuple]:
"""倒数排名融合RRF算法,用于将多个结果生成单一、统一的排名"""

# 1.初始化一个字典,用于存储每一个唯一文档的得分
fused_scores = {}

# 2.遍历每个查询对应的文档列表
fordocsinresults:
# 3.内层遍历文档列表得到每一个文档
forrank, docinenumerate(docs):
# 4.将文档使用langchain提供的dump工具转换成字符串
doc_str = dumps(doc)
# 5.检测该字符串是否存在得分,如果不存在则赋值为0
ifdoc_strnotinfused_scores:
fused_scores[doc_str] =0
# 6.计算多结果得分,排名越小越靠前,k为控制权重的参数
fused_scores[doc_str] +=1/ (rank + k)

# 7.提取得分并进行排序
reranked_results = [
(loads(doc), score)
fordoc, scoreinsorted(fused_scores.items(), key=lambdax: x[1], reverse=True)
]

returnreranked_results

3.1.2 问题分解策略

当提问的原始问题非常复杂时,无论是使用原始问题进行检索,亦或者生成多个相关联的问题进行检索,往往都很难在向量数据库中找到关联性高的文档,导致 RAG 效果偏差。造成这个问题的原因有几种:

  1. 复杂问题由多个问题按顺序步骤组成,执行相似性搜索时,向量数据库存储的都是基础文档数据,往往相似度低,但是这些数据在现实世界又可能存在很大的关联(文本嵌入模型的限制,一条向量不可能无损记录段落信息)。
  2. 问题复杂度高或者涉及到数学问题,导致 LLM 没法一次性完成答案的生成,一次性传递大量的相关性文档,极大压缩了大语言模型生成内容上下文长度的限制

问题分解策略就是将一个复杂问题分解成多个子问题或者子步骤。问题分解后的子问题跟原始问题是“父子”关系,而查询重写跟原始问题则是“兄弟”关系。

问题分解策略一共有两种分别是串行模式和并行模式。

两种模式的流程如下所示:

串行模式
并行模式

一个简单问题分解的prompt如下所示:

你的任务是针对输入的问题生成多个相关的子问题或子查询,将输入问题分解成一组可以独立回答的子问题或子任务。
以下是输入的问题:
<question>
{{question}}
</question>
请生成3-5个与该问题相关的搜索查询,并使用换行符进行分割。生成的子问题/子查询应具有明确的主题和可独立回答的特点。
请在<子问题>标签内写下生成的子问题/子查询。

调用样例如下:

3.1.3 问题回退策略

问题回退策略和问题分解策略相反,当用户问题非常具体时,可能无法检索的对应文档,就需要将问题进行抽象。比如“李开复在2000年是在哪个公司工作?”,重新抽象成“李开复的工作经历是什么?”。处理流程如下:

下面是一个执行样例:

  1. 原始问题:如果理想气体的温度增加 2 倍,体积增加 8 倍,压力 P 会如何变化?






欢迎光临 链载Ai (https://www.lianzai.com/) Powered by Discuz! X3.5