赞
踩
检索增强生成(Retrieval Augmented Generation),简称 RAG,已经成为当前最火热的LLM应用方案。经历今年年初那一波大模型潮,想必大家对大模型的能力有了一定的了解,但是当我们将大模型应用于实际业务场景时会发现,通用的基础大模型基本无法满足我们的实际业务需求,主要有以下几方面原因:
而RAG是解决上述问题的一套有效方案。
一句话总结:RAG(中文为检索增强生成) = 检索技术 + LLM 提示。例如,我们向 LLM 提问一个问题(answer),RAG 从各种数据源检索相关的信息,并将检索到的信息和问题(answer)注入到 LLM 提示中,LLM 最后给出答案。
RAG 是2023年基于 LLM 的系统中最受欢迎的架构。许多产品基于 RAG 构建,从基于 web 搜索引擎和 LLM 的问答服务到使用私有数据的chat应用程序。
在这个过程中,有两个主要步骤:语义搜索和生成输出。在语义搜索步骤中,我们希望从我们的知识库中找到与我们要回答的查询最相关的部分内容。然后,在生成步骤中,我们将使用这些内容来生成响应。
本文的目的是参考 LlamaIndex实现,来系统讲解关键的高级 RAG 技术,以方便大家深入研究。 问题在于,大多数教程只会针对个别技术进行详细讲解,而不是整体全面地系统化归纳总结。 另一件事是,LlamaIndex 和 LangChian 都是了不起的开源项目,他们的开发速度非常快,以至于他们的文档已经比2016年的机器学习教科书还要厚。
下面这张图片展示了大概的工作流程。基本上,主要思路就是利用LLM来生成多个查询,期望能够通过这些查询让问题的各个方面在上下文中显现出来。之后你可以使用生成的查询进行向量搜索(如本系列之前的部分所述),并且基于其在结果集中的显示方式来对内容进行重新排序。
可以用下面提示词生成额外问题:
You are a helpful assistant that generates multiple search queries based on a single input query.
Generate multiple search queries related to: {USER_INPUT}
OUTPUT (4 queries):
如上所述,LLM能够生成覆盖原问题多个方面的查询。这样可以帮助我们在数据库中找到包含各个相关方面的信息,从而潜在地提高我们从RAG应用得到的结果。
RAG的架构如图中所示,简单来讲,RAG就是通过检索获取相关的知识并将其融入Prompt,让大模型能够参考相应的知识从而给出合理回答。因此,可以将RAG的核心理解为“检索+生成”,前者主要是利用向量数据库的高效存储和检索能力,召回目标知识;后者则是利用大模型和Prompt工程,将召回的知识合理利用,生成目标答案。
RAG架构
完整的RAG应用流程主要包含两个阶段:
下面我们详细介绍一下各环节的技术细节和注意事项:
数据准备一般是一个离线的过程,主要是将私域数据向量化后构建索引并存入数据库的过程。主要包括:数据提取、文本分割、向量化、数据入库等环节。
数据准备
向量化是一个将文本数据转化为向量矩阵的过程,该过程会直接影响到后续检索的效果。目前常见的embedding模型如表中所示,这些embedding模型基本能满足大部分需求,但对于特殊场景(例如涉及一些罕见专有词或字等)或者想进一步优化效果,则可以选择开源Embedding模型微调或直接训练适合自己场景的Embedding模型。
数据向量化后构建索引,并写入数据库的过程可以概述为数据入库过程,适用于RAG场景的数据库包括:FAISS、Chromadb、ES、milvus等。一般可以根据业务场景、硬件、性能需求等多因素综合考虑,选择合适的数据库。
在应用阶段,我们根据用户的提问,通过高效的检索方法,召回与提问最相关的知识,并融入Prompt;大模型参考当前提问和相关知识,生成相应的答案。关键环节包括:数据检索、注入Prompt等。
数据检索
常见的数据检索方法包括:相似性检索、全文检索等,根据检索效果,一般可以选择多种检索方式融合,提升召回率。
LLM生成
Prompt作为大模型的直接输入,是影响模型输出准确率的关键因素之一。在RAG场景中,Prompt一般包括任务描述、背景知识(检索得到)、任务指令(一般是用户提问)等,根据任务场景和大模型性能,也可以在Prompt中适当加入其他指令优化大模型的输出。一个简单知识问答场景的Prompt如下所示:
【任务描述】
假如你是一个专业的客服机器人,请参考【背景知识】,回
【背景知识】
{content} // 数据检索得到的相关文本
【问题】
石头扫地机器人P10的续航时间是多久?
Prompt的设计只有方法、没有语法,比较依赖于个人经验,在实际应用过程中,往往需要根据大模型的实际输出进行针对性的Prompt调优。
本文 RAG 管道从一个文本文档语料库开始,直接跳过如何通过数据加载器从Youtube等数据源获取步骤。
标准的 RAG 流程简介:将文本分块,然后使用一些 Transformer Encoder 模型将这些块嵌入到向量中,将所有向量放入索引中,最后创建一个 LLM 提示,告诉模型根据我们在搜索步骤中找到的上下文回答用户的查询。
在运行时,我们使用同一编码器模型对用户的查询进行向量化,然后搜索该查询向量的索引,找到 top-k 个结果,从我们的数据库中检索相应的文本块,并将它们作为上下文输入到 LLM 提示中。
提示与下边内容类似:
def question_answering(context, query):
prompt = f"""
Give the answer to the user query delimited by triple backticks ```{query}```\
using the information given in context delimited by triple backticks ```{context}```.\
If there is no relevant information in the provided context, try to answer yourself,
but tell user that you did not have any relevant context to base your answer on.
Be concise and output the answer of size less than 80 tokens.
"""
response = get_completion(instruction, prompt, model="gpt-3.5-turbo")
answer = response.choices[0].message["content"]
return answer
现在我们深入讲解高级 RAG 技术。包括所涉及的核心步骤和算法的方案,但是省略了一些逻辑循环和复杂的多步代理行为,以保持方案的可读性。
上图中绿色部分是我们接下来详细探讨的核心 RAG 技术。一张图并不能全部展示所有的高级 RAG 技术,比如我们这里省略了上文扩展技术。
首先我们需要为文档内容创建向量索引,然后在运行时搜索与查询向量余弦距离最近的向量索引,这样就可以找到与查询内容最接近语义的文档。
1.1 分块 (Chunking)
Transformer 模型具有固定的输入序列长度,即使输入上下文窗口很大,一个句子或几个句子的向量也比几页文本的向量更能代表其语义含义,因此对数据进行分块—— 将初始文档拆分为一定大小的块,而不会失去其含义。有许多文本拆分器实现能够完成此任务。
1.2 向量化 (Vectorisation)
2.1 向量存储索引
RAG 管道的关键部分是搜索索引,它存储了我们在上一步中获得的向量化内容。最原始的实现是使用平面索引 — 查询向量和所有块向量之间的暴力计算距离。
取决于你的索引选择、数据和搜索需求,还可以存储元数据,并使用元数据过滤器来按照日期或来源等条件进行信息检索。
2.2 分层索引
在大型数据库的情况下,一个有效的方法是创建两个索引——一个由摘要组成,另一个由文档块组成,然后分两步进行搜索,首先通过摘要过滤掉相关文档,然后只在这个相关组内搜索。
2.3 假设性问题和 HyDE
另一种方法是让 LLM 为每个块生成一个问题,并将这些问题嵌入到向量中,在运行时对这个问题向量的索引执行查询搜索(将块向量替换为索引中的问题向量),然后在检索后路由到原始文本块并将它们作为 LLM 获取答案的上下文发送。
这种方法提高了搜索质量,因为与实际块相比,查询和假设问题之间的语义相似性更高。
2.4 内容增强
这里的内容是将相关的上下文组合起来供 LLM 推理,以检索较小的块以获得更好的搜索质量。
有两种选择:一种是围绕较小的检索块的句子扩展上下文,另一种是递归地将文档拆分为多个较大的父块,其中包含较小的子块。
在此方案中,文档中的每个句子都是单独嵌入的,这为上下文余弦距离搜索提供了极大的查询准确性。
为了在获取最相关的单个句子后更好地推理找到的上下文,我们将上下文窗口扩展为检索到的句子前后的 k 个句子,然后将这个扩展的上下文发送到 LLM。
绿色部分是在索引中搜索时发现的句子嵌入,整个黑色 + 绿色段落被送到 LLM 以扩大其上下文,同时根据提供的查询进行推理。
这里的思路与语句窗口检索器非常相似——搜索更精细的信息片段,然后在在LLM 进行推理之前扩展上下文窗口。文档被拆分为较小的子块,这些子块和较大的父块有引用关系。
首先在检索过程中获取较小的块,然后如果前 k 个检索到的块中有超过 n 个块链接到同一个父节点(较大的块),我们将这个父节点替换成给 LLM 的上下文——工作原理类似于自动将一些检索到的块合并到一个更大的父块中,因此得名。请注意,搜索仅在子节点索引中执行。
2.5 融合检索或混合搜索
混合或融合搜索通常能提供更优秀的检索结果,因为它结合了两种互补的搜索算法——既考虑了查询和存储文档之间的语义相似性,也考虑了关键词匹配。
我们使用上述任何算法获得了检索结果,现在是时候通过过滤、重排或一些转换来完善它们了。
这是将检索到的上下文提供给 LLM 以获得结果答案之前的最后一步。
现在,我们将探索更高级的 RAG 技术,比如查询转换和路由。这些技术涉及到大语言模型的使用,代表了一种更复杂的逻辑思维——在 RAG 流程中融合了 LLM 的推理能力。
查询转换是一系列技术,使用 LLM 作为推理引擎来修改用户输入以提高检索质量。有很多技术实现可供选择。
对于复杂的查询,大语言模型能够将其拆分为多个子查询。 比如,
我们不太可能直接在语料库找到它们的比较,所以将这个问题分解为两个更简单、具体的合理的子查询:
关于构建一个可以多次用于单个查询的完美 RAG 系统的下一件工作是聊天逻辑,就像在 LLM 之前时代的经典聊天机器人中一样考虑到对话上下文。
这是支持后续问题、代词指代或与上一个对话上下文相关的任意用户命令所必需的。它是通过查询压缩技术解决的,将聊天上下文与用户查询一起考虑在内。
查询路由是 LLM 驱动的决策步骤,决定在给定用户查询的情况下下一步该做什么——选项通常是总结、对某些数据索引执行搜索或尝试许多不同的路由,然后将它们的输出综合到一个答案中。
查询路由器还用于选择数据存储位置来处理用户查询。这些数据存储位置可能是多样的,比如传统的向量存储、图形数据库或关系型数据库,或者是不同层级的索引系统。在处理多文档存储时,通常会用到摘要索引和文档块向量索引这两种不同的索引。
定义查询路由器包括设置它可以做出的选择。
选择特定路由的过程是通过大语言模型调用来实现的,其结果按照预定义的格式返回,以路由查询指定的索引。如果是涉及到关联操作,这些查询还可能被发送到子链或其他智能体,如下面的多文档智能体方案所展示的那样。
智能体( Langchain 和 LlamaIndex 均支持)几乎从第一个 LLM API 发布开始就已经存在——这个思路是为一个具备推理能力的 LLM 提供一套工具和一个要完成的任务。这些工具可能包括一些确定性功能,如任何代码函数或外部 API,甚至是其他智能体——这种 LLM 链接思想是 LangChain 得名的地方。
每个文档智能体都有两个工具:向量存储索引和摘要索引,它根据路由查询决定使用哪一个。对于顶级智能体来说,所有文档智能体都是其工具。
该方案展示了一种高级 RAG 架构,其中每个智能体都做路由许多决策。这种方法的好处是能够比较不同的解决方案或实体在不同的文档及其摘要中描述,以及经典的单个文档摘要和 QA 机制——这基本上涵盖了最常见的与文档集合聊天的用例。
这种复杂配置的缺点可以通过图片发现 —— 由于需要在智能体内部的大语言模型之间进行多次往返迭代,其运行速度较慢。顺便一提,LLM 调用通常是 RAG 管道中耗时最长的操作,而搜索则是出于设计考虑而优化了速度。因此,对于大型的多文档存储,我建议考虑对此方案进行简化,以便实现扩展。
这是任何 RAG 管道的最后一步——根据我们检索的所有上下文和初始用户查询生成答案。
最简单的方法是将所有获取的上下文(高于某个相关性阈值)与查询一起连接并提供给 LLM。但是,与往常一样,还有其他更复杂的选项,涉及多个 LLM 调用,以优化检索到的上下文并生成更好的答案。
响应合成的主要方法有:
和其他软件世界的架构决策一样,RAG融合也有权衡取舍,你需要清楚它们,以便为你的具体情境做出最好的决定。不过首先,我们先列一下RAG融合的优缺点。
有了以上的介绍,让我们来讨论一下在什么情况下最有可能通过实现RAG融合得到好的效果。如果你的应用程序的内容主要基于常见概念,那么你更有可能从使用RAG融合中获益良多。
如果您拥有的内容包含大量内部行话或与知名品牌重复的词语,则您可能需要调整RAG融合提示以获得良好的效果,或者最好避免使用。以下是一个例子来说明这一点。如今,所有的知名LLMs都是基于“注意力就是你所需要”的论文中首次引入的transformer架构,这是一种根据语境中其他单词对生成下一个token的重要性的度量方法。现在,如果我在该论文的基础上构建一个RAG应用并添加RAG融合功能,其工作方式可能是这样的:(绿色文字表示对RAG融合有贡献的LLM生成的查询)
由于在这个语境下理解“注意力”的含义依赖于上下文,LLM对这个含义产生了误解,并生成了一些与之完全无关的搜索查询。这可能导致您的应用得出糟糕的结果。现在,我们来看一下通过更改系统提示,将其改为“您是一个有用的助手,可以根据单一输入查询为试图解释transformer架构概念的应用生成多个搜索查询”,是如何在这个特定例子中解决这个问题的。
根据您的具体情况,微调提示可能有用也可能没用。您也可以试试以下技巧,然后才放弃:
正如您所见,在这种情况下(像HyDe、TF-IDF、BM25或混合搜索等许多其他方法一样),不清楚这种方法是否会胜过针对您具体用例的基本语义搜索功能。但是,就像人们常说的那样,“如果你不衡量,你就无法改进”。因此,我的建议是:一旦构建了基本的RAG应用程序,就要立即创建一个评估过程。在这一过程中,你会有大量的想法去优化你的提示或搜索功能,而每一种改变所带来的附带影响是不清楚的。有时候,提升某一组查询的效果会以牺牲另一组查询为代价。在这种情况下,最好的办法就是把它当作另一种机器学习问题,看看数据告诉你什么。
这种方法主要是对 Transformer 编码器 和 LLM 进行微调。其中,编码器影响嵌入质量,从而影响上下文检索质量。LLM 负责最好地使用提供的上下文来回答用户查询。
如今的一大优势是可以使用像 GPT-4 这样的高端 LLM 来生成高质量的数据集。但是必须清楚,使用小型合成数据集进微调基础模型,可能会降低基础模型的通用能力。
如果不完全信任基础编码器,可以使用交叉编码器对检索到的结果进行重排。这个过程是这样的:你把查询和每个前 k 个检索到的文本块一起送入交叉编码器,中间用 SEP (分隔符) Token 分隔,并对它进行微调,使其对相关的文本块输出 1,对不相关的输出 0。
LLM 微调
RAG 管道评估的 ragas 框架显示,忠实度指标增加了 5%,这意味着微调后的 GPT 3.5-turbo 模型比原始模型更好地利用了提供的上下文来生成答案。
作为一名热心肠的互联网老兵,我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。
但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的 AI大模型资料
包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。