
大模型之深入探索RAG流程 原创
前言
在上一章【大模型之初识RAG】中,我们初步了解了RAG的基本概念和原理,并通过代码实践了一个简单的RAG流程。本章我们将基于RAG的基本流程,深入了解文档读取(LOAD)
、文档切分(SPLIT)
、向量化(EMBED)
和 存储(STORE)
的每个环节,并结合代码进行常见场景的实践。
RAG流程回顾
回顾RAG的流程如上所示,具体代码见RAG代码,本章不再赘述。
文档读取(LOAD)
简介:由于我们的知识广泛存在各类文档中,所以文档读取(LOAD
)在RAG系统中承担着 获取数据
的职责。
常见文件:
- 文本文件(.txt)
- CSV(.csv)
- PDF(.pdf)
- Word 文档(.docx)
- Excel 工作簿(.xlsx)
- PPT 演示文稿(.pptx)
- HTML 文件(.html)
- Markdown 文件(.md)
常见的文件处理库:
- langchain_community.document_loaders
- 在langchain_community的API文档中有数十种文件的处理API,除了上述常见的文件之外,还有.srt字幕文件、.ipynb文件、代码片段等支持。
CSV文件加载器
CSVLoader函数说明
说明:
-
file_path
:指定要加载的 CSV 文件的路径
例如:"data/myfile.csv"
-
source_column
(可选):指定哪一列作为数据的主要来源。
例如:"text"
-
metadata_columns
:指定哪些列应作为元数据加载。
例如:["name", "age"]
-
csv_args
(可选):自定义 CSV 加载的行为。
例如:{"delimiter": ";", "header": 0}(指定分隔符为分号,并将第一行作为表头)
示例
示例1:仅加载.csv文件示例:
运行结果:
说明:
- metadata:文档元数据,包括文档来源、行号等,用来进行溯源使用。
- page_content:文档内容,即文本内容。
示例2:加载.csv文件指定列作为元数据:
运行结果:
示例3:使用RAG链测试大模型的知识理解能力
运行结果:
TXT文件加载器
TXTLoader函数说明
说明:
-
file_path
:指定要加载的文本文件的路径。 -
encoding
(可选):指定文本文件的编码格式,一般是"utf-8"
示例
运行结果:
PDF文件加载器
PyMuPDFLoader函数说明
说明:
-
extract_images
:指示是否从 PDF 中提取图像。如果设置为 True,将尝试提取 PDF 中的所有图像。 - 使用前需要安装相关依赖:
pip install PyMuPDF
示例
运行结果:
原始PDF文件如下:
说明:通过对比可以看到
- pagecontent与PDF文件内容一致。
- metadata数据有比较多的字段,例如:format,title,author,subject,keywords,creator,producer,creationDate,modDate等。
EXCEL文件加载器
UnstructuredExcelLoader函数说明
说明:
- 使用前需要安装相关依赖:
pip install unstructured
。
示例
运行结果:
原始文件:
PPT文件加载器
UnstructuredPowerPointLoader函数说明
说明:
- 使用前需要安装相关依赖:
pip install python-pptx
。
示例
运行结果:
原始文件:
说明:
- 通过对比,可以看到PPT中的文字会被提取出来,图片会被清洗掉。
WORD文件加载器
UnstructuredWordDocumentLoader函数说明
说明:
- 使用前需要安装相关依赖:
pip install python-docx
。
示例
运行结果:
原始文件:
MARKDOWN文件加载器
UnstructuredMarkdownLoader函数说明
- 使用前需要安装相关依赖:
pip install markdown
。
示例
运行结果:
原始文件:
HTML文件加载器
UnstructuredHTMLLoader函数说明
说明:
- model可以设置为'elements',表示将HTML元素作为文档进行切分。
示例
运行结果:
原始文件:
补充说明: 如果将上面的mode改为mode="elements",运行如下,但是得到内容对于建立知识库没有太大帮助。
SQL文件加载器
SQLDatabaseLoader函数说明
说明:
-
query
:指定要执行的 SQL 查询。
例如:query = "SELECT * FROM documents"
-
db
: 指定要连接的 SQL 数据库实例。 -
parameters
(可选):可选参数,用于传递 SQL 查询的参数,以便于动态查询。
例如:parameters = {"id": 1}
-
page_content_mapper
(可选):可选的映射函数,用于处理查询结果中的每一行,返回格式化后的内容。 -
metadata_mapper
(可选):可选的映射函数,用于处理查询结果中的每一行,返回元数据字典。
例如:metadata_mapper=lambda row: {"id": row['id'], "created_at": row['created_at']}
-
source_columns
(可选):可选参数,指定要从查询结果中提取的列名。 -
include_rownum_into_metadata
:指示是否将行号包含在元数据中。 -
include_query_into_metadata
:指示是否将执行的 SQL 查询包含在元数据中。 - 使用前需要安装相关依赖:
pip install sqlalchemy
。
示例
运行结果:
原始文件:
以上只是对各类文件加载器的简单梳理,在实际应用中需要进行进一步的封装,例如:
- 封装为class方式
- 以文件后缀名自动判断调用的加载器
- 添加更多回溯信息
- ......
在开源项目QAnything(网易的一款开源RAG系统)中,该项目中的qanything_kernel有着比较详细的封装,可以参考。
文档切分(SPLIT)
按照字符切分
CharacterTextSplitter类说明
说明:
- chunk_size:指定每个文本片段的最大字符长度。
- chunk_overlap:指定相邻文本片段之间的重叠字符数。重叠可以帮助保持上下文的一致性。
示例
运行结果:
递归方式切分
RecursiveCharacterTextSplitter函数说明
说明:
- separators:用于指定切分文本时使用的分隔符列表。可以包括换行符、句号、逗号等。
- keep_separator:指定在切分后是否保留分隔符。如果设置为 True,切分后的文本片段将包含分隔符。
- is_separator_regex:指定 separators 是否为正则表达式。如果设置为 True,则 separators 将被视为正则表达式。
示例
运行结果:
对比CharacterTextSplitter的执行结果,RecursiveCharacterTextSplitter在切分长文本时,可以切分出更小的文本片段,并且可以保留分隔符。
按照Token切分
TokenTextSplitter函数说明
说明:
- model_name(可选):可选参数,用于指定特定的语言模型名称。
- allowed_special:指定被允许的特殊令牌集合。
- disallowed_special:指定不允许的特殊令牌集合。
- 使用前需要安装依赖:
pip install tiktoken
。 - encoding_name:指定用于令牌化文本的编码名称。该选项通过以下代码可以得到常见选项:
-
gpt2
:这是 OpenAI 的GPT-2
模型使用的编码,基于Byte Pair Encoding (BPE)
。它适用于大多数基于 GPT-2 的任务,能够有效处理英语文本。 -
r50k_base
:这是针对OpenAI
的模型(如GPT-3
)的一种编码,使用了50,000
个最常见的子词单位。它是一种更为精简的编码方式,适用于对文本进行更细粒度的切分。 -
p50k_base
:基于BPE
的编码,使用50,000
个子词单位。该编码主要用于处理更复杂的文本生成和理解任务,适合与GPT-3
及相关模型的配合使用。 -
p50k_edit
:这是p50k_base
的一种变体,专门优化用于文本编辑任务。这种编码关注于文本的修改和调整,适合需要对文本进行细微编辑的应用场景。 -
cl100k_base
:这是针对更大规模模型(如GPT-4
)的一种编码,使用了100,000
个子词单位。它能够处理更复杂和多样化的文本,适合高性能的文本生成和理解任务。 -
o200k_base
:这是一个更高级的编码,使用了200,000
个子词单位,旨在处理极为复杂的文本任务。它适合需要处理大量信息或多样化文本的应用,能够提供更高的灵活性和精度。
以上选项内容由ChatGPT辅助生成,如有错误,请及时反馈。
示例
运行结果:
如果将encoding_name设置为'gpt2',则切分结果如下:
通过对比,可以看到o200k_base的效果要优于gpt2。
按照markdown标题切分
示例
运行结果:
对于Markdown文本,使用MarkdownTextSplitter进行切分,切分效果比较好。
向量化(EMBED)
考虑到RAG系统一般都有私有化部署的需求(因为数据涉密),所以对应的向量化的方式有两种:本地化 和 在线化。
本地化
在开源项目QAnything中,其提供了一种使用本地模型的方法,其大致步骤是:
- 在本地保存了一个向量化模型。
- 在调用向量化模型时,使用本地的模型.
由于示例代码的集成度较高,剥离核心代码执行成本较大,所以此处并未实践,只是引用相关的信息。
在线化
使用HuggingFaceEmbeddings
LangChain社区提供了HuggingFaceEmbeddings,该类可以加载HuggingFace的预训练模型,并使用该模型进行向量化。
说明:
- 使用前需要安装依赖
pip install sentence-transformers
运行结果:
使用QianfanEmbeddings
由于Qwen的API有样本为6个的限制,所以此处试用百度千帆大模型的向量化API。
运行结果:
通过对比
- 使用QianfanEmbeddingsEndpoint向量化后,在相似性搜索时有两个内容返回;而使用HuggingFaceEmbeddings向量化后,在相似性搜索时只有第一个内容返回。
- 相似性搜索后的内容,看起来与我们Query查询的问题似乎不太相关,只是模型在向量层面计算是比较相似。
原理探寻
运行结果:
对比如下代码执行结果:
运行结果:
由上可以看到:
- 相似度计算:通过计算两个向量的欧式距离,可以得到两个向量之间的相似度,计算结果越小代表越接近。
- 相似度转换:通过计算两个向量的相关系数,可以得到两个向量之间的相似度,计算结果越大代表越相似。
- 欧式距离计算方法:$d(\mathbf{a}, \mathbf{b}) = \sqrt{\sum_{i=1}^{n} (a_i - b_i)^2}$
- 相关系数计算方法:$r = 1 - \frac{d(\mathbf{query}, \mathbf{doc})}{\sqrt{2}}$
-
similarity_search
即为相似度计算。 -
similarity_search_with_relevance_scores
即为相似度转换,它是smimilarity_search
的扩展,可以返回相似度。
存储(STORE)
如上述示例,我们对数据进行向量化之后,下一步需要存储至向量数据库。目前市面上的向量数据库众多,主流的向量数据库对比如下所示:
向量数据库 | URL | GitHub Star | Language | Cloud |
chroma | chroma | 7.4K | Python | ❌ |
milvus | milvus | 21.5K | Go/Python/C++ | ✅ |
pinecone | pinecone | ❌ | ❌ | ✅ |
qdrant | qdrant | 11.8K | Rust | ✅ |
typesense | typesense | 12.9K | C++ | ❌ |
weaviate | weaviate | 6.9K | Go | ✅ |
本例中,我们主要了解Chroma的使用。
Chroma简介
介绍:Chroma 是一个开源的向量数据库,专为处理和存储高维向量而设计,特别适用于机器学习和深度学习应用。
特性:
- 高效的向量存储:Chroma 提供高效的向量存储和检索功能,能够处理大规模数据集。
- 相似性搜索:支持快速的相似性搜索,允许用户根据查询向量找到最相似的向量。
- 灵活的Embedding支持:可以与多种Embedding模型集成。
- 持久化存储:支持将数据持久化存储,确保在重启后数据不会丢失。
- 开源和社区支持:Chroma 是开源项目,用户可以自由使用和修改,同时也享受社区的支持和贡献。
资料:
- 官网主页:https://www.trychroma.com/
- 官网文档:https://docs.trychroma.com/
- Github主页:https://github.com/chroma-core/
- Github项目:https://github.com/chroma-core/chroma/
Chroma使用的几种方式
初次了解Chroma的使用时,我被五花八门的API搞得云里雾里,一会是Chromadb,一会是langchain_chroma..... 因此,查询了部分资料之后,总结Chroma的使用方式大致是以下三种方式:
- 第一种方式:是使用Chromadb的源生API接口使用,包括常见的增删改查接口:
create_collection()
、collection.add()
、collection.query()
... - 第二种方式:是通过Langchain的组合包使用Chroma,常见的是:
Chroma.from_documents()
、similarity_search()
、similarity_search_by_vector()
... - 第三种方式:是通过Retriever检索器的方式使用Chroma。
通过Chromadb的源生接口使用Chroma
Chroma 可以以多种形式进行使用:
- 第一种:in-memory with ephemeral。短暂模式,数据保存在内存中,程序一旦结束数据就被销毁。
- 第二种:in-memory with persistance。持久化模式,数据保存在sqlite数据库中,程序结束数据仍然存在。
- 第三种:client/server模式。
本次实践中,主要尝试 持久化模式
和 Client/Server模式
。
持久化模式
第一步:安装chroma库
第二步:创建Chromadb实例
第三步:创建或获取被查询集合
第四步:添加数据到集合中
第五步:查询集合中的数据
说明:
- query_texts: 查询的文本
- n_results: 查询结果的数量
运行结果:
Client/Server模式
第一步:启动Chroma服务端
说明:
- --path:指定Chroma服务端存储数据的路径。
- --host:可以指定Chroma服务端监听的IP地址,默认localhost
- --port:可以指定Chroma服务端监听的端口号,默认8000
第二步:创建Chromadb实例
第三步:创建或获取被查询集合
第四步:添加数据到集合中
第五步:查询集合中的数据
运行结果:
对比 本地持久化 和 Client/Server模式,两者的使用过程基本一致,只是在Client实例化时略有不同。
- 本地持久化模型:
chroma_client = chromadb.PersistentClient(path="chroma_db")
- Client/Server模式:首先配置
Settings
连接信息,然后调用chroma_client = Client(settings=setting)
查看chromadb的源码,可以看到其内部实现了DB常见的各类增删改查操作:
备注:实际项目产品开发中,知识库需要通过上述的chromadb实现知识库的管理(增删改查)。
通过Langchain的组合包使用Chroma
除了上述chromadb
的方式,也可以使用Langchain_chroma
的方式使用Chroma,两者的使用场景区别是:
chromadb:
- 自定义应用:当开发者需要构建特定的应用,且对数据库的操作有较高的灵活性需求时,可以使用
chromadb
。 - 数据分析:需要对存储的数据进行复杂查询和分析时,使用底层接口可以更好地满足需求。
- 高性能需求:在需要优化性能的情况下,开发者可以通过底层接口进行更细致的调整。
langchain_chroma:
- 快速原型开发:当开发者希望快速构建原型或 MVP(最小可行产品)时,
langchain_chroma
提供了便捷的接口。 - NLP检索:提供更高层次的抽象,适用于检索类任务,如文档检索、语义搜索等。
具体使用方法:
运行结果:
说明:
- 在langchain_chroma中,通过client_settings参数,可以l连接Chroma服务端。
- 在Chroma中,通过add_documents方法,可以批量入库;不过对于西游记这样的小说来说,入库时间比较长,约5分钟。
西游记可以从夸克网盘:西游记下载
通过Retriever检索器的方式使用Chroma
在实际项目开发中,一般需要将Chroma的向量库作为检索器(Retriever)与模型组成chain链,以实现问答系统的问答能力。
使用方法
在上述Langchain_chroma的代码基础上,增加以下代码:
运行结果:
函数说明:
1、as_retriever: 功能:as_retriever 方法将 Chroma 数据库转换为一个检索器(retriever),使其能够根据给定的查询进行文档检索。这个检索器可以使用不同的搜索类型和参数来优化检索结果。
参数
-
search_type
: 指定检索的类型。常见的选项包括: -
"similarity_score_threshold"
:根据相似度分数进行检索,只有超过指定阈值的结果才会被返回。 -
search_kwargs
: 包含与搜索相关的额外参数。常用参数包括: -
k
: 指定要返回的最相关文档的数量。例如,k=4 表示返回前 4 个相关文档。 -
score_threshold
:设置相似度分数的阈值。只有相似度分数高于该阈值的文档才会被返回。例如,score_threshold=0.1 表示返回分数大于 0.1 的文档。
2、invoke: 功能:invoke 方法用于执行实际的检索操作,根据提供的输入查询返回相关的文档。 参数:
-
input
:要查询的输入文本。
内容小结
- RAG的建库的整体流程为:文档读取(LOAD) -> 文档切分(SPLIT) -> 向量化(EMBED) -> 存储(STORE)
- 在文档读取(LOAD)时:
langchain_community.document_loaders有多个加载器,可以加载多种格式的文件,如PDF、EXCEL、PPT、WORD、MARKDOWN、HTML等。
- 在文档切分(SPLIT)时:
字符级切分、递归方式切分、Token方式切分等,一般情况可以使用递归RecursiveCharacterTextSplitter切分。
使用TokenTextSplitter切分时,注意选择对应的encoding_name,如:'o200k_base'。
对于Markdown文档,可以使用MarkdownTextSplitter。
- 在向量化(EMBED)时:
有在线化和本地化,对于数据敏感有本地化部署的情况下,需要考虑使用本地模型进行向量化的方案。
在线的向量化有HuggingFaceEmbeddings和第三方模型(如:QianfanEmbeddings)
在技术原理层面,计算知识
与查询
相似度一般使用的是欧式距离。
- 在存储(STORE)时:
可供选择的向量数据库有多种,其中较为常用的是Chroma。
Chroma支持多种运行方式:短暂模式、持久化模式和Client/Server模式。
使用chromadb的原生接口,可以进行增删改查等操作,适用于知识的管理。
使用langchain_chroma,可以快速原型开发,适用于检索类任务。
使用retriever,可以将Chroma的向量库作为检索器(Retriever),与模型组成chain链,实现问答系统的问答能力。
本文转载自公众号一起AI技术 作者:热情的Dongming
