【RAG技术实战】-- 18种RAG技术测评(part1-11种方法对比)
【RAG技术实战】-- 18种RAG技术测评(part1-11种方法对比) 首发Tableau 这几天一直想做一个工作测试所有rag的效果,但是鉴于不同框架实现可能导致
【RAG技术实战】-- 18种RAG技术测评(part1-11种方法对比)
首发Tableau
这几天一直想做一个工作测试所有rag的效果,但是鉴于不同框架实现可能导致效果评测有差异。这两天偶然发现一个满自身项目研究分许的宝藏项目。
https://github.com/FareedKhan-dev/all-rag-techniquesgithub.com/FareedKhan-dev/all-rag-techniques
作者从一个简单的RAG方法开始,然后测试更高级的技术,如CRAG、Fusion、HyDE等等!
为了保持简单,作者没有使用LangChain或FAISS。而是只使用基础库以Jupyter notebook风格编写所有技术,以保持简单和易于学习,是非常不错的学习资料,这里简单学习整理,也记录下来分享给大家。
代码库组织如下:
对于响应生成和验证,作者使用LLaMA-3.2-3B Instruct来测试一个小型LLM在RAG任务中的表现。
对于嵌入,作者将使用TaylorAI/gte-tiny模型。
测试查询是一个复杂的查询,将在整个文档中使用它,其正确答案是:
(结论) 效果最好的技术!
与其在最后提供结论,不如在开头就写出来。在对我们的测试查询测试了18种不同的RAG技术之后。
自适应RAG
是明显的赢家,得分最高,达到0.86。
通过智能地对查询进行分类并为每种问题类型选择最合适的检索策略,自适应RAG表现优于其他方法。能够在事实性、分析性、观点性和上下文性策略之间动态切换,使其能够以显著的准确性处理多样化的信息需求。
虽然层次索引(0.84)、Fusion(0.83)和CRAG(0.824)等技术也表现出色,但自适应RAG的灵活性使其在实际应用中具有优势。
环境安装
简单RAG
让我们从最简单的RAG开始。首先,我们将可视化它是如何工作的,然后我们将测试和评估它。
简单RAG工作流程(由Fareed Khan 创建)
如图所示,简单RAG管道的工作流程如下:
从PDF中提取文本。将文本分割成更小的块。将这些块转换为数值嵌入。基于查询搜索最相关的块。使用检索到的块生成响应。将响应与正确答案进行比较以评估准确性。
首先,让我们加载文档,获取文本,并将其分割成可管理的块:
这段代码使用extract_text_from_pdf从我们的PDF文件中提取所有文本。然后,chunk_text将那个大块文本分成更小的、重叠的片段,每个片段大约1000个字符长。
接下来,我们需要将这些文本块转换为数值表示(嵌入):
这里,create_embeddings接收我们的文本块列表,并使用我们的嵌入模型为每个块生成一个数值嵌入。这些嵌入捕获了文本的含义。
现在我们可以执行语义搜索,找到与我们测试查询最相关的块:
然后,semantic_search将查询嵌入与块嵌入进行比较,返回最相似的块。
有了我们的相关块,让我们生成一个响应:
这段代码将检索到的块格式化为大语言模型(LLM)的提示。generate_response函数将这个提示发送给LLM,后者仅基于提供的上下文生成答案。
最后,让我们看看我们的简单RAG表现如何:
嗯…简单rag的响应低于平均水平
让我们继续下一种方法。
👉[CSDN大礼包🎁:全网最全《LLM大模型入门+进阶学习资源包》免费分享(安全链接,放心点击)]()👈
语义分块
在我们的简单RAG方法中,我们只是将文本切成固定大小的块。这很粗糙!它可能会把一个句子切成两半,或者将不相关的句子组合在一起。
语义分块旨在更智能。它不是固定大小,而是试图基于含义来分割文本,将语义相关的句子组合在一起。
语义分块工作流程(由Fareed Khan 创建)
这个想法是,如果句子在讨论类似的事情,它们应该在同一个块中。我们将使用相同的嵌入模型来确定句子之间的相似程度。
这段代码将我们的extracted_text分成单独的句子。然后为每个单独的句子创建嵌入。
现在,我们将计算连续句子之间的相似度:
这个cosine_similarity函数(我们之前定义的)告诉我们两个嵌入有多相似。得分1表示它们非常相似,0表示它们完全不同。我们为每对相邻句子计算这个分数。
语义分块是决定在哪里将文本分成块。我们将使用"断点"方法。我们在这里使用百分位数方法,寻找相似度的大幅下降:
compute_breakpoints函数使用"百分位数"方法,识别句子之间相似度显著下降的点。这些就是我们的块边界。
现在我们可以创建我们的语义块:
split_into_chunks接收我们的句子列表和我们找到的断点,并将句子分组成块。
评估器给出的分数仅为0.2
虽然语义分块在理论上听起来不错,但在这里并没有帮助我们。事实上,与简单的固定大小分块相比,我们的分数反而下降了!
这表明仅仅改变分块策略并不能保证成功。我们需要在方法上更加复杂。让我们在下一节尝试其他方法。
上下文增强检索
我们看到语义分块虽然原则上是个好主意,但实际上并没有改善我们的结果。
一个问题是,即使是语义定义的块也可能过于集中。它们可能缺少来自周围文本的关键上下文。
上下文增强工作流程(由Fareed Khan 创建)
上下文增强检索通过不仅获取最佳匹配块,还获取其邻近块来解决这个问题。
让我们看看这是如何工作的。我们需要一个新函数context_enriched_search来处理检索:
这个函数的核心逻辑与我们之前的搜索类似,但不是只返回单个最佳块,而是获取其周围的一个"窗口"的块。context_size控制我们在两侧包含多少个块。
让我们在我们的RAG管道中使用它。我们将跳过文本提取和分块步骤,因为这些与简单RAG中的相同。
我们将使用固定大小的块,就像在简单RAG部分中那样,我们保持chunk_size = 1000和overlap = 200。
这次,我们得到了0.6的评估分数!
这比简单RAG(0.5)和语义分块(0.1)都有了显著的改进。
通过包含邻近块,我们给了LLM更多的上下文来工作,它产生了更好的答案。
我们还不完美,但我们肯定在朝着正确的方向前进。这表明上下文对于检索有多重要。
上下文块标题
我们已经看到通过添加邻近块来增加上下文有帮助。但如果块本身的内容缺少重要信息呢?
通常,文档有一个清晰的结构标题、标题、副标题,它们提供了关键的上下文。上下文块标题(CCH)利用了这种结构。
上下文块标题(由Fareed Khan 创建)
这个想法很简单:在我们创建嵌入之前,我们先在每个块前面添加一个描述性标题。这个标题就像一个迷你摘要,给检索系统(和LLM)提供更多信息。
generate_chunk_header函数将分析每个文本块并生成一个简洁、有意义的标题来总结其内容。这有助于高效地组织和检索相关信息。
我们遍历我们的块,为标题和文本都获取嵌入,并将所有内容存储在一起。这给了检索系统两种方式来将块与查询匹配。
由于semantic_search已经可以处理嵌入,我们只需要确保我们的标题和文本块都被正确嵌入。这样,当我们执行搜索时,模型可以同时考虑高级摘要(标题)和详细内容(块文本)来找到最相关的信息。
现在,让我们修改我们的检索步骤,不仅返回匹配的块,还返回它们的标题以获得更好的上下文并生成响应。
这次,我们的评估分数是0.5!
通过添加那些上下文标题,我们让系统有了更好的机会找到正确的信息,也让LLM有了更好的机会生成完整和准确的答案。
这显示了在数据进入检索系统之前增强数据本身的力量。我们没有改变核心RAG管道,但我们让数据本身变得更有信息量。
文档增强
我们已经看到在我们的块周围添加上下文(通过邻近块或标题)可以有所帮助。现在,让我们尝试一种不同类型的增强:从我们的文本块生成问题。
这个想法是,这些问题可以作为替代的"查询",可能比原始文本块本身更好地匹配用户的意图。
文档增强工作流程(由Fareed Khan 创建)
我们在分块和嵌入创建之间添加这一步。我们可以简单地使用generate_questions函数来实现这一点。它接收一个text_chunk并返回可以使用它生成的一些问题。
这里重要的变化是我们如何处理搜索结果。我们现在在向量存储中有两种类型的项目:原始文本块和生成的问题。这段代码将它们分开,这样我们就可以看到哪种类型的内容最匹配查询。
最后的步骤,生成上下文然后进行评估:
生成问题并将它们添加到我们的可搜索索引中,使我们的性能又有了提升。
看起来有时候,一个问题比原始文本块更好地表达了信息需求。
查询转换
到目前为止,我们一直专注于改进RAG系统使用的数据。但是查询本身呢?
通常,用户提问的方式并不是搜索我们知识库的最佳方式。查询转换旨在解决这个问题。我们将探索三种不同的方法:
查询重写:
使查询更具体和详细。
后退提示:
创建一个更广泛、更一般的查询以检索背景上下文。
子查询分解:
将复杂查询分解为多个更简单的子查询。
查询转换工作流程(由Fareed Khan 创建)
让我们看看这些转换的实际效果。我们将使用我们的标准测试查询:
decompose_query将原始查询分解成几个更小、更集中的问题。这个想法是,这些子查询加在一起,可能比任何单个查询都能更好地覆盖原始查询的意图。
现在,要看看这些转换如何影响我们的RAG系统,让我们使用一个结合了所有先前方法的函数:
evaluate_transformations函数通过不同的查询转换技术(重写、后退和分解)运行原始查询,然后比较它们的输出。
这帮助我们看到哪种方法检索到最相关的信息以获得更好的响应。
评估分数为0.5。
这表明我们的查询转换技术并没有始终优于更简单的方法。
虽然查询转换可以很强大,但它们不是灵丹妙药。有时,原始查询已经很好地形成了,试图"改进"它实际上可能会让事情变得更糟。
重排序器
我们已经尝试改进数据(通过分块策略)和查询(通过转换)。现在,让我们关注检索过程本身。简单的相似度搜索经常返回相关和不相关结果的混合。
重排序是第二次传递,重新排序最初检索的结果,将最好的结果放在顶部。
rerank_with_llm函数接收初始检索的块,并使用LLM基于相关性重新排序。这有助于确保最有用的信息出现在前面。
重排序后,一个最终函数,让我们称之为generate_final_response,将重新排序的块格式化成提示,并发送给LLM以生成最终响应。
它接收一个query、一个vector_store(我们已经创建好的)和一个reranking_method。我们使用"llm"进行基于LLM的重排序。该函数进行初始检索,调用rerank_with_llm重新排序结果,然后生成响应。
rerank_with_keywords在notebook中定义了但我们这里不使用它。
让我们运行这个看看是否能改善我们的结果:
我们的评估分数现在约为0.7!
重排序给了我们一个明显的改进。通过使用LLM直接对每个检索文档的相关性进行评分,我们能够优先考虑最好的信息来生成响应。
这是一个强大的技术,可以显著提高RAG系统的质量。
RSE
我们一直在关注单个块,但有时最好的信息分布在多个连续的块中。相关段落提取(RSE)解决了这个问题。
RSE不是仅仅获取前k个块,而是试图识别和提取整个相关文本段落。
让我们看看如何在我们现有的管道中实现这一点,我们使用已经为RSE定义好的函数。我们添加一个rag_with_rse函数的调用,它接收一个pdf_path和query并返回响应。 我们组合几个函数调用来执行RSE。
这一行做了很多工作!它
处理文档(提取文本、分块、创建嵌入,所有这些都在rag_with_rse内部处理)。基于与查询的相关性和位置计算"块值"。使用一个巧妙的算法来找到最佳的连续段落块。将这些段落组合成上下文。基于该上下文生成响应。
现在,评估:
而且…我们达到了约0.8的分数!
通过关注相关文本的连续段落,RSE为LLM提供了更连贯和完整的上下文,从而产生更准确和全面的响应。
这表明我们如何选择和呈现信息给LLM与我们选择什么信息一样重要。
上下文压缩
我们一直在添加越来越多的上下文,邻近块、生成的问题、整个段落。但有时候,少即是多。
LLMs有一个有限的上下文窗口,用不相关的信息填充它可能会影响性能。
上下文压缩(由Fareed Khan 创建)
上下文压缩是关于选择性的。我们检索大量上下文,但然后我们压缩它,只保留与查询直接相关的部分。
这里的关键区别是在生成之前的**“上下文压缩”**步骤。我们不是改变我们检索的内容,而是在传递给LLM之前对其进行细化。
我们在这里使用一个函数调用rag_with_compression,它接收query和其他参数并实现上下文压缩。在内部,它使用LLM分析检索到的块并只提取与query直接相关的句子或段落。
让我们看看它的实际效果:
rag_with_compression提供了不同的压缩类型选项:
“selective”
只保留直接相关的句子。
“summary”
创建一个专注于查询的简短摘要。
“extraction”
仅
提取包含答案的句子(非常严格!)。
现在,要运行压缩我们使用这段代码:
这给我们带来了约0.75的分数。
上下文压缩是一个强大的技术,因为它平衡了广度(初始检索获得广泛的信息)和重点(压缩去除噪音)。
通过只给LLM最相关的信息,我们通常能得到更简洁和准确的答案。
反馈循环
我们目前看到的所有技术都是"静态的",它们不会从错误中学习。反馈循环改变了这一点。
这个想法很简单:
用户对RAG系统的响应提供反馈(例如,好/坏,相关/不相关)。系统存储这个反馈。未来的检索使用这个反馈来改进。
反馈循环(由Fareed Khan 创建)
我们可以使用函数调用full_rag_workflow来实现反馈循环。这是函数定义。
这个full_rag_workflow函数做了几件事:
加载现有反馈:
它检查feedback_data.json文件并加载任何之前的反馈。
运行RAG管道:
这部分与我们之前做的类似。
请求反馈:
它提示用户对响应的相关性和质量进行评分。
存储反馈:
它将反馈保存到feedback_data.json文件中。
这个反馈实际上如何用于改进检索的魔法发生在像fine_tune_index、adjust_relevance_scores这样的函数中(为了简洁起见没有显示)。但关键的想法是好的反馈可以提高某些文档的相关性,而坏的反馈可以降低它。
让我们运行一个简化版本,假设我们没有任何现有的反馈:
我们看到分数约为0.7!
这不是一个巨大的跳跃,这是预料之中的。反馈循环随着时间的推移改进系统,通过重复的交互。这一节只是演示了这个机制。
真正的力量来自于积累反馈并使用它来改进检索过程。这使得RAG系统变得自适应并个性化以适应它收到的查询类型。
自适应RAG
我们已经探索了各种改进RAG的方法:更好的分块、添加上下文、转换查询、重排序,甚至是整合反馈。
自适应RAG工作流程(由Fareed Khan 创建)
但如果最佳技术取决于所问问题的类型呢?这就是自适应RAG的想法。
我们在这里使用四种不同的策略:
事实策略:
专注于检索精确的事实和数据。
分析策略:
旨在全面覆盖一个主题,探索不同方面。
观点策略:
试图收集对主观问题的不同观点。
上下文策略:
整合用户特定的上下文来定制检索。
让我们看看这是如何工作的。我们将使用一个名为**rag_with_adaptive_retrieval**的函数来处理整个过程:
它首先使用一个名为classify_query的函数对查询进行分类,该函数与其他辅助函数一起定义。
基于识别的类型,它选择并执行适当的专门检索策略(factual_retrieval_strategy、analytical_retrieval_strategy、opinion_retrieval_strategy或contextual_retrieval_strategy)。 最后,它使用generate_response来使用检索到的文档生成响应。
该函数返回一个包含结果的字典,包括query、query type、retrieved documents和生成的response。
让我们使用这个函数并评估它:
这次我们达到了约0.856的分数。
通过根据特定类型的查询调整我们的检索策略,我们可以获得显著优于一刀切方法的结果。这突出了理解用户意图并相应地调整RAG系统的重要性。
自适应RAG不是一个固定的程序,它是一个框架,让我们能够根据查询选择最佳策略。
读者福利:如果大家对大模型感兴趣,这套大模型学习资料一定对你有用
对于0基础小白入门:
如果你是零基础小白,想快速入门大模型是可以考虑的。
一方面是学习时间相对较短,学习内容更全面更集中。 二方面是可以根据这些资料规划好学习计划和方向。
包括:大模型学习线路汇总、学习阶段,大模型实战案例,大模型学习视频,人工智能、机器学习、大模型书籍PDF。带你从零基础系统性的学好大模型!
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓
👉AI大模型学习路线汇总👈
大模型学习路线图,整体分为7个大的阶段:(全套教程文末领取哈) 第一阶段: 从大模型系统设计入手,讲解大模型的主要方法;
第二阶段: 在通过大模型提示词工程从Prompts角度入手更好发挥模型的作用;
第三阶段: 大模型平台应用开发借助阿里云PAI平台构建电商领域虚拟试衣系统;
第四阶段: 大模型知识库应用开发以LangChain框架为例,构建物流行业咨询智能问答系统;
第五阶段: 大模型微调开发借助以大健康、新零售、新媒体领域构建适合当前领域大模型;
第六阶段: 以SD多模态大模型为主,搭建了文生图小程序案例;
第七阶段: 以大模型平台应用与开发为主,通过星火大模型,文心大模型等成熟大模型构建大模型行业应用。
👉大模型实战案例👈
光学理论是没用的,要学会跟着一起做,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
👉大模型视频和PDF合集👈
观看零基础学习书籍和视频,看书籍和视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
👉学会后的收获:👈
• 基于大模型全栈工程实现(前端、后端、产品经理、设计、数据分析等),通过这门课可获得不同能力;
• 能够利用大模型解决相关实际项目需求: 大数据时代,越来越多的企业和机构需要处理海量数据,利用大模型技术可以更好地处理这些数据,提高数据分析和决策的准确性。因此,掌握大模型应用开发技能,可以让程序员更好地应对实际项目需求;
• 基于大模型和企业数据AI应用开发,实现大模型理论、掌握GPU算力、硬件、LangChain开发框架和项目实战技能, 学会Fine-tuning垂直训练大模型(数据准备、数据蒸馏、大模型部署)一站式掌握;
• 能够完成时下热门大模型垂直领域模型训练能力,提高程序员的编码能力: 大模型应用开发需要掌握机器学习算法、深度学习框架等技术,这些技术的掌握可以提高程序员的编码能力和分析能力,让程序员更加熟练地编写高质量的代码。
👉获取方式:
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓