LLM之RAG实战(五)| 高级RAG 01:使用小块检索,小块所属的大块喂给LLM,可以提高RAG性能

       RAG(Retrieval Augmented Generation,检索增强生成)系统从给定的知识库中检索相关信息,从而使其能够生成事实信息、上下文相关信息和特定领域的信息。然而,在有效检索相关信息和生成高质量响应方面,RAG面临着许多挑战。在这一系列的博客文章/视频中,我将介绍先进的RAG技术,旨在优化RAG工作流程,并解决原始RAG系统中的挑战。

       第一种技术被称为从小到大的检索。在基本的RAG管道中,我们嵌入一个大的文本块进行检索,而这个完全相同的文本块用于合成。但有时嵌入/检索大的文本块可能会感觉不太理想。在一个大的文本块中有很多填充文本,导致文本块的语义可能不明显,导致检索更差。如果我们可以基于更小、更有针对性的块进行嵌入/检索,但仍有足够的上下文供LLM合成响应,该怎么办?具体而言,将用于检索的文本块与用于合成的文本块解耦可能是有利的。使用较小的文本块可以提高检索的准确性,而较大的文本块则提供更多的上下文信息。小到大检索背后的概念是在检索过程中使用较小的文本块,然后将检索到的文本所属的较大文本块提供给大语言模型。

主要有两种技术:

  1. 较小的子块引用较大的父块:在检索过程中首先获取较小的块,然后引用父ID,并返回较大的块;

  2. 句子窗口检索:在检索过程中获取一个句子,并返回句子周围的文本窗口。

       在这篇博客文章中,我们将深入研究这两种方法在LlamaIndex中的实现。为什么我不在LangChain中做这件事?因为LangChain的高级RAG已经有很多资源了。我宁愿不重复这种努力。此外,我同时使用LangChain和LlamaIndex。最好了解更多的工具并灵活使用它们。

一、基本RAG回顾

准备工作:

安装相关包

! pip install -U llama_hub llama_index braintrust autoevals pypdf pillow transformers torch torchvision

设置环境变量

import osos.environ["OPENAI_API_KEY"] = "TYPE YOUR API KEY HERE"

下载数据集

!wget --user-agent "Mozilla" "https://arxiv.org/pdf/2307.09288.pdf" -O "llama2.pdf"

导入所需要的包

from pathlib import Pathfrom llama_hub.file.pdf.base import PDFReaderfrom llama_index.response.notebook_utils import display_source_nodefrom llama_index.retrievers import RecursiveRetrieverfrom llama_index.query_engine import RetrieverQueryEnginefrom llama_index import VectorStoreIndex, ServiceContextfrom llama_index.llms import OpenAIimport json

让我们从RAG的基本实现开始,包括4个简单步骤:

步骤1:加载文档

      我们使用PDFReader加载PDF文件,并将文档的每一页合并为一个document对象。

loader = PDFReader()docs0 = loader.load_data(file=Path("llama2.pdf"))doc_text = "\n\n".join([d.get_content() for d in docs0])docs = [Document(text=doc_text)]

步骤2:将文档解析为文本块(节点)

      然后,我们将文档拆分为文本块,这些文本块在LlamaIndex中被称为“节点”,我们将chuck大小定义为1024。默认的节点ID是随机文本字符串,然后我们可以将节点ID格式化为遵循特定的格式。

node_parser = SimpleNodeParser.from_defaults(chunk_size=1024)base_nodes = node_parser.get_nodes_from_documents(docs)for idx, node in enumerate(base_nodes):node.id_ = f"node-{idx}"

步骤3:选择embedding模型和LLM

我们需要定义两个模型:

  • embedding模型用于为每个文本块创建矢量嵌入。在这里,我们Hugging Face中的FlagEmbedding模型;
  • LLM:用户查询相关文本块喂给LLM,让其生成具有相关上下文的答案。

       我们可以在ServiceContext中将这两个模型捆绑在一起,并在以后的索引和查询步骤中使用它们。

embed_model = resolve_embed_model(“local:BAAI/bge-small-en”)llm = OpenAI(model="gpt-3.5-turbo")service_context = ServiceContext.from_defaults(llm=llm, embed_model=embed_model)

步骤4:创建索引、检索器和查询引擎

索引、检索器和查询引擎是基于用户数据或文档进行问答的三个基本组件:

  • 索引是一种数据结构,使我们能够从外部文档中快速检索用户查询的相关信息。矢量存储索引获取文本块/节点,然后创建每个节点的文本的矢量嵌入,以便LLM查询。
base_index = VectorStoreIndex(base_nodes, service_context=service_context)

  • Retriever用于获取和检索给定用户查询的相关信息。
base_retriever = base_index.as_retriever(similarity_top_k=2)

  • 查询引擎建立在索引和检索器之上,提供了一个通用接口来询问有关数据的问题。
query_engine_base = RetrieverQueryEngine.from_args(    base_retriever, service_context=service_context)response = query_engine_base.query(    "Can you tell me about the key concepts for safety finetuning")print(str(response))

二、高级方法1:较小的子块参照较大的父块

       在上一节中,我们使用1024的固定块大小进行检索和合成。在本节中,我们将探讨如何使用较小的子块进行检索,并引用较大的父块进行合成。第一步是创建更小的子块:

步骤1:创建较小的子块

对于块大小为1024的每个文本块,我们创建更小的文本块:

  • 8个128大小的文本块
  • 4个大小为256的文本块
  • 2个512大小的文本块

我们将大小为1024的原始文本块附加到文本块的列表中。

sub_chunk_sizes = [128, 256, 512]sub_node_parsers = [    SimpleNodeParser.from_defaults(chunk_size=c) for c in sub_chunk_sizes]all_nodes = []for base_node in base_nodes:    for n in sub_node_parsers:        sub_nodes = n.get_nodes_from_documents([base_node])        sub_inodes = [            IndexNode.from_text_node(sn, base_node.node_id) for sn in sub_nodes        ]        all_nodes.extend(sub_inodes)    # also add original node to node    original_node = IndexNode.from_text_node(base_node, base_node.node_id)    all_nodes.append(original_node)all_nodes_dict = {n.node_id: n for n in all_nodes}

       当我们查看所有文本块“all_nodes_dict”时,我们可以看到许多较小的块与每个原始文本块相关联,例如“node-0”。事实上,所有较小的块都引用元数据中的较大块,index_id指向较大块的索引id。

步骤2:创建索引、检索器和查询引擎

  • 索引:创建所有文本块的矢量嵌入。
vector_index_chunk = VectorStoreIndex(    all_nodes, service_context=service_context)
  • Retriever:这里的关键是使用RecursiveRetriever遍历节点关系并基于“引用”获取节点。这个检索器将递归地探索从节点到其他检索器/查询引擎的链接。对于任何检索到的节点,如果其中任何节点是IndexNodes,则它将探索链接的检索器/查询引擎并查询该引擎。
vector_retriever_chunk = vector_index_chunk.as_retriever(similarity_top_k=2)retriever_chunk = RecursiveRetriever(    "vector",    retriever_dict={"vector": vector_retriever_chunk},    node_dict=all_nodes_dict,    verbose=True,)

      当我们提出问题并检索最相关的文本块时,它实际上会检索节点id指向父块的文本块,从而检索父块。

  • 现在,通过与以前相同的步骤,我们可以创建一个查询引擎作为通用接口来询问有关数据的问题。
query_engine_chunk = RetrieverQueryEngine.from_args(    retriever_chunk, service_context=service_context)response = query_engine_chunk.query(    "Can you tell me about the key concepts for safety finetuning")print(str(response))

三、高级方法2:语句窗口检索

       为了实现更细粒度的检索,我们可以将文档解析为每个块的一个句子,而不是使用更小的子块。

       在这种情况下,单句将类似于我们在方法1中提到的“子”块概念。句子“窗口”(原句子两侧各5句)将类似于“父”组块概念。换句话说,我们在检索过程中使用单个句子,并将检索到的带有句子窗口的句子传递给LLM。

步骤1:创建句子窗口节点解析器

# create the sentence window node parser w/ default settingsnode_parser = SentenceWindowNodeParser.from_defaults(    window_size=3,    window_metadata_key="window",    original_text_metadata_key="original_text",)sentence_nodes = node_parser.get_nodes_from_documents(docs)sentence_index = VectorStoreIndex(sentence_nodes, service_context=service_context)

步骤2:创建查询引擎

       当我们创建查询引擎时,我们可以使用MetadataReplacementPostProcessor将句子替换为句子窗口,以便将句子窗口发送到LLM。

query_engine = sentence_index.as_query_engine(    similarity_top_k=2,    # the target key defaults to `window` to match the node_parser's default    node_postprocessors=[        MetadataReplacementPostProcessor(target_metadata_key="window")    ],)window_response = query_engine.query(    "Can you tell me about the key concepts for safety finetuning")print(window_response)

        语句窗口检索能够回答“你能告诉我安全微调的关键概念吗”的问题:

       在这里,您可以看到检索到的实际句子和句子的窗口,它提供了更多的上下文和细节。

结论

       在这篇博客中,我们探讨了如何使用从小到大的检索来改进RAG,重点是使用LlamaIndex的父子递归检索器和句子窗口检索。在未来的博客文章中,我们将深入探讨其他技巧和窍门。请继续关注这场激动人心的先进RAG技术之旅!

参考文献:

[1] https://towardsdatascience.com/advanced-rag-01-small-to-big-retrieval-172181b396d4

[2] https://colab.research.google.com/github/sophiamyang/demos/blob/main/advanced_rag_small_to_big.ipynb

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/255450.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

redis:六、数据过期删除策略(惰性删除、定期删除)和基于redisson实现的分布式锁(看门狗机制、主从一致性)和面试模板

数据过期删除策略 Redis的过期删除策略:惰性删除 定期删除两种策略进行配合使用 惰性删除 惰性删除:设置该key过期时间后,我们不去管它,当需要该key时,我们在检查其是否过期,如果过期,我们就…

119. 杨辉三角 II

描述 : 给定一个非负索引 rowIndex,返回「杨辉三角」的第 rowIndex 行。 在「杨辉三角」中,每个数是它左上方和右上方的数的和 题目 : LeetCode 119. 杨辉三角 II : 119. 杨辉三角 II 分析 : 这道题用二维数组来做 . 解析 : class Solution {pub…

Jmeter接口测试断言

一、响应断言 对服务器的响应接口进行断言校验,来判断接口测试得到的接口返回值是否正确。 二、添加断言 1、apply to: 通常发出一个请求只触发一个请求,所以勾选“main sampie only”就可以;若发一个请求可以触发多个服务器请…

选择排序、快速排序和插入排序

1. 选择排序 xuanze_sort.c #include<stdio.h> #include<stdlib.h>//选择排序void xuanze_sort(int arr[],int sz){//正着for(int i0;i<sz;i){//外层循环从第一个数据开始依次作为基准数据for(int j i1;j<sz;j){//int j i1 因为第一个数据作为了基准数据&…

如何使用 C++ 开发 Redis 模块

在本文中&#xff0c;我将总结 Tair 在使用 C 开发 Redis 模块时遇到的一些问题&#xff0c;并将其提炼为最佳实践。目的是为 Redis 模块的用户和开发人员提供帮助。其中一些最佳实践也可以应用于 C 编程语言和其他编程语言。 介绍 从 Redis 5.0 开始&#xff0c;支持模块插件…

Unity中URP下的顶点偏移

文章目录 前言一、实现思路二、实现URP下的顶点偏移1、在顶点着色器中使用正弦函数&#xff0c;实现左右摇摆的效果2、在正弦函数的传入参数中&#xff0c;加入一个扰度值&#xff0c;实现不规则的顶点偏移3、修改正弦函数的振幅 A&#xff0c;让我们的偏移程度合适4、修改正弦…

【玩转 TableAgent 数据智能分析】股票交易数据分析+预测

文章目录 一、什么是TableAgent二、TableAgent 的特点三、实践前言四、实践准备4.1 打开官网4.2 注册账号4.3 界面介绍4.4 数据准备 五、确认分析需求六、TableAgent体验七、分析结果解读八、总结&展望 一、什么是TableAgent TableAgent是一款面向企业用户的智能数据分析工…

Redis——多级缓存

JVM进程缓存 为了演示多级缓存&#xff0c;这里先导入一个商品管理的案例&#xff0c;其中包含商品的CRUD功能。将来会给查询商品添加多级缓存。 导入Demo数据 1.安装mysql 后期做数据同步需要用到MySQL的主从功能&#xff0c;所以需要在虚拟机中&#xff0c;利用Docker来运…

C++ Qt 开发:ListWidget列表框组件

Qt 是一个跨平台C图形界面开发库&#xff0c;利用Qt可以快速开发跨平台窗体应用程序&#xff0c;在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置&#xff0c;实现图形化开发极大的方便了开发效率&#xff0c;本章将重点介绍ListWidget列表框组件的常用方法及灵活运用。…

【网络安全】-Linux操作系统基础

文章目录 Linux操作系统目录结构Linux命令格式Linux文件和目录操作命令Linux用户和用户组操作命令Linux查看和操作文件内容命令Linux文件压缩和解压缩命令Linux网络管理命令Linux磁盘管理和系统状态命令Linux安全加固总结 Linux是一个强大的操作系统&#xff0c;广泛用于服务器…

C# WPF上位机开发(进度条操作)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 软件上面如果一个操作比较缓慢&#xff0c;或者说需要很长的时间&#xff0c;那么这个时候最好添加一个进度条&#xff0c;提示一下当前任务的进展…

通过层进行高效学习:探索深度神经网络中的层次稀疏表示

一、介绍 深度学习中的层次稀疏表示是人工智能领域日益重要的研究领域。本文将探讨分层稀疏表示的概念、它们在深度学习中的意义、应用、挑战和未来方向。 最大限度地提高人工智能的效率和性能&#xff1a;深度学习系统中分层稀疏表示的力量。 二、理解层次稀疏表示 分层稀疏表…

【MATLAB】数据拟合第11期-基于粒子群迭代的拟合算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~也可转原文链接获取~ 1 基本定义 基于粒子群迭代的拟合算法是一种优化技术&#xff0c;它基于粒子群优化算法&#xff08;PSO&#xff09;的基本思想。该算法通过群体中个体之间的协作和信息共享来寻找最优解。 在基于粒…

探索拉普拉斯算子:计算机视觉中用于边缘检测和图像分析的关键工具

一、介绍 拉普拉斯算子是 n 维欧几里得空间中的二阶微分算子&#xff0c;表示为 ∇。它是函数梯度的发散度。在图像处理的上下文中&#xff0c;该运算符应用于图像的强度函数&#xff0c;可以将其视为每个像素具有强度值的二维信号。拉普拉斯算子是计算机视觉领域的关键工具&am…

基于VUE3+Layui从头搭建通用后台管理系统(前端篇)十五:基础数据模块相关功能实现

一、本章内容 本章使用已实现的公共组件实现系统管理中的基础数据中的验证码管理、消息管理等功能。 1. 详细课程地址: 待发布 2. 源码下载地址: 待发布 二、界面预览 三、开发视频 3.1 B站视频地址: 基于VUE3+Layui从头搭建通用后台管理系统合集-验证码功能实现 3.2 西瓜…

不做数据采集,不碰行业应用,专注数字孪生PaaS平台,飞渡科技三轮融资成功秘诀

12月15日&#xff0c;飞渡科技在北京举行2023年度投资人媒体见面会&#xff0c;全面分享其产品技术理念与融资之路。北京大兴经开区党委书记、管委会主任常学智、大兴经开区副总经理梁萌、北京和聚百川投资管理有限公司&#xff08;以下简称“和聚百川”&#xff09;投资总监严…

pytorch实现DCP暗通道先验去雾算法及其onnx导出

pytorch实现DCP暗通道先验去雾算法及其onnx导出 简介实现ONNX导出导出测试 简介 最近在做图像去雾&#xff0c;于是在Pytorch上复现了一下dcp算法。暗通道先验去雾算法是大神何恺明2009年发表在CVPR上的一篇论文&#xff0c;还获得了当年的CVPR最佳论文。 实现 具体原理就不…

麻雀规则设计器maquerule

规则设计器 1、应用场景 目前市场上主要的规则引擎中都可以动态解析脚本语言&#xff0c;比如javascript,drools,aviator。这些语言解析在业务上可以两种方式&#xff0c;一种是开发人员直接把相应的脚本写好&#xff0c;跟随程序一起交付&#xff1b; 第二种就是现场的人员可…

云原生之深入解析如何在K8S环境中使用Prometheus来监控CoreDNS指标

一、什么是 Kubernetes CoreDNS&#xff1f; CoreDNS 是 Kubernetes 环境的DNS add-on 组件&#xff0c;它是在控制平面节点中运行的组件之一&#xff0c;使其正常运行和响应是 Kubernetes 集群正常运行的关键。DNS 是每个体系结构中最敏感和最重要的服务之一。应用程序、微服…

USB2.0 Spec

USB System Description A USB system is described by three definitional areas: • USB interconnect • USB devices • USB host USB interconnect The USB interconnect is the manner in which USB devices are connected to and communicate with the host. USB Ho…