RGA
RAG:全称Retrieval-Augmented Generation,检索增强生成。我们知道本次由ChatGPT掀起的LLM大模型浪潮,其核心就是Generation生成,而 Retrieval-augmented 就是指除了 LLM 本身已经学到的知识之外,通过外挂其他数据源的方式来增强 LLM 的能力,这其中就包括了外部向量数据库、外部知识图谱、文档数据,WEB数据等。
架构
如上图所示,经过Doc Loader,加载各种数据源的数据,经过embedding向量化后存储进向量数据库。这是Retrieval-augmented基础数据处理器。用户通过 QA向LLM提问,会通过QA问题向向量数据库召回相似度较高的上下文,通过Prompt提示词一起发给LLM,LLM通过问题与上下文一起生成答案返回给用户。
我们不经会问,为什么大模型动不动就千亿参数级别,涵盖了PB级的数据,还需要自己外挂数据源。
这里面主要几方面的原因:
数据更新: LLM数据来源截止日期一般都是在2022年,而且它无法实时了解最新的信息。外挂知识库可以提供更新的、实时的信息,确保模型对新兴事实和领域内的最新发展有所了解。
领域专业知识: 尽快训练LLM的数据量很庞大,但是在某些特定领域,如医学、法律或科学,可能需要深入的专业知识。LLM在这些领域可能无法提供高度准确的信息,因此如果能提供这方面的数据,它能工作得好。
定制需求: 对于某些应用场景,用户可能需要LLM在特定方面的专业化,例如公司内部知识库、产品规格等。外挂知识库可以帮助模型更好地服务于特定用户或组织的需求。
避免错误: 在特定领域,LLM可能会生成不准确或误导性的信息。通过使用外挂知识库,可以提高答案的准确性,避免潜在的错误。 在实际应用中,外挂知识库通常与LLM进行集成,通过定制的方式来满足用户或企业的特殊需求,提供更专业、准确和个性化的服务。这种集成可以帮助弥补LLM通用性的不足,使其更好地适应特定的应用场景。
好,我们了解了RAG的基本概念,接下来我们就一起深入技术细节,了解RAG的实现原理。
技术实现(5-tech)
1 数据加载(Document Loaders)
RAG的第一要解决的问题是数据来源的问题,数据有多种来源,各种格式的数据,如csv、html、json、markdown、PDF。所有的这些数据都需要有对应的Document Loaders来进行加工处理,将信息正确提取出来。
以langchain(LLM应用框架)为例,目前langchain社区中已经实现了154种文档加载器 如html:
1 | from langchain_community.document_loaders import UnstructuredHTMLLoader |
更多的文档加载器,可以访问langchain api
langchain–module-langchain_community.document_loaders
可以看到目前langchain社区目前涵盖了国内网诸多网站和平台的数据,如百度云盘、腾讯云文档,甚至包括了区块链信息
2 数据处理(Text Splitters)
数据分割
加载完数据后,我们下一步通常需要将数据进行拆分,尤其是在处理长文本的情况下。如何将文本进行分割处理,听起来很简单,比如我按400个字符,直接切片就好了,但往往这样应用效果不甚理想。
我们通常希望能将将语义相关的文本片段保留在一起。 重点其实就在这个“语义相关”,比如中文,我们希望是句号为分割符,比如一段长代码,我们希望以编程语言特点来分割,比如Python中的def、class
以langchain为例,langchain目前支持HTML、字符、MarkdownHeader和多种代码分割,甚至正在实验中的语义分割。
1、按MarkdownHeader分割
1 | from langchain.text_splitter import MarkdownHeaderTextSplitter |
2、按语义分割
1 | from langchain_experimental.text_splitter import SemanticChunker |
数据信息(metadata)
在进行文本分割的同时,我们还可以给分割的文本添加一下metadata的数据,方便记录该文本段的一些基本信息,如文章来源、作者信息等。
一个是能在进行文本召回时可以作为过滤搜索,另一方面还在作为发给LLM的补充数据,让LLM生成的内容更为丰富
1 | metadatas = [{"document": 1}, {"document": 2}] |
分割参数
在进行文本分割时,我们还需要重点关注两个参数 chunk_size 和 chunk_overlap,这两个参数分别表示分割长度和两段分割文本重合长度。
在实际RAG应用中,chunk_size需要结合向量数据库的来选择合适大小,比如腾讯云的向量数据库,一次只支持单块512token(400左右字符)的大小写入,那chunk_size就应该设设置400多。chunk_overlap的大小建议设置在chunk_size的1/5左右,在召回多段文本时,可以增加数据的丰富度。
实际情况请结合具体项目进行设置和测试验证
3 数据向量化 (Text embedding models)
在进行数据分割后,需要对文本数据段进行向量化,目前主流的中文向量化模型有
模型 | 中文支持 |
---|---|
M3E | 是 |
text2vec | 是 |
OpenAlEmbeddings | 是 |
使用OpenAIEmbeddings向量化处理
1 | from langchain_openai import OpenAIEmbeddings |
目前Langchain支持37种embedding model,这些向量化模型核心功能就将文本向量化,提供给向量数据库进行存储
4 向量数据库 (Vector stores)
数据向量化后,就需要将向量数据存储进向量数据库。目前有很多开源向量数据库,如chromadb、faiss-cpu、lancedb。云服务厂商也陆续推出了向量数据库服务,包括腾讯云、阿里云的向量数据库
lancedb向量数据库使用
1 | from langchain_community.document_loaders import TextLoader |
如以Langchain VertorStore为基础类,实现的支持腾讯云的向量数据库服务 TencentVectorDB
目前Langchain支持47种向量数据库接入,开发者也可以自行实现VertorStore,定义自己的向量数据库。
主要实现以下三个抽象方法
1 |
|
add_texts: 将文本数据向量化,添加进向量数据库
similarity_search: 从向量数据库召回数据
from_texts:类方法,实现将文本数据向量化,添加进向量数据库
5 数据召回(Retrievers)
在讲解完数据加载、数据处理、数据向量化和向量数据库后,我们开始进入数据召回的环节。数据召回是我们向 LLM提问时,需要根据我们提问的问题向向量数据库召回相关的文档数据,并和问题加载进Prompt发送给LLM。
比如下面这段提示词:
1 | template = """Answer the question based only on the following context: |
context就是我们召回的上下文
数据召回的方法有许多种,应用在不同应用场景,当前Langchain主流支持的Retrievers有以下8种
应用场景
这里简述一下不同Retrievers的主要应用场景,大家可以具体问题具体分析,再去查阅一下相关文档
Vectorstore 基础的向量召回方法,根据用户问题直接向向量数据库召回数据
ParentDocument 如果你文档数据有许多不同的小块信息,你可以根据问题检索出小块信息,再根据小块信息去索引出原文档或者更大块的数据,将大块数据作为上下文发送给LLM
Multi Vector 如果你有相关问题的数据集,可以为问题和文档分别存储到不同的向量数据库,在检索时可以根据问题检索出合适文档上下文
Self Query 如果你提出的问题可以通过基于元数据(而不是与文本的相似性)来获取文档,可以使用这种Retrievers,利用LLM的能力,自动生成对应的检索方法,来召回数据
Contextual Compression 如果您发现您检索的文档包含太多不相关的信息,并且分散了LLM的注意力,可以利用上下文压缩的方法,将召回的数据利用LLM进行数据处理
Time-Weighted Vectorstore 如果你的文档数据中包含时间相关的数据,可以考虑用此Retriever
Multi-Query Retriever 用户提出的问题很复杂,需要多个不同的信息来回答,可以使用此Retriever,利用LLM生成多个相关的问题,再分别从向量数据库召回数据
Ensemble 如果您有多种检索方法,并希望尝试将它们组合起来,可以使用此Retriever
Long-Context Reorder 当你需要召回多段上下文数据时,但发现LLM并没有根据你的上下文来回答问题时,可以考虑使用 Retriever对你召回的数据进行重新排序,将相似度较高的排在前面,让LLM能更好的利用上下文来回答问题
数据召回算法
在数据召回中,目前业内有两种较为通用的召回算法
1 相似度匹配算法(Similarity Search with Euclidean Distance)
这是向量数据库自身具备的特点,通过比较向量之间的距离来判断它们的相似度。
1 | docs_with_score = db.max_marginal_relevance_search_with_score(query) |
2 最大边界相关算法(Maximal Marginal Relevance)
采用这个算法,会优化召回的数据段之间的相似程度和多样性,对数据重新打分
1 | def maximal_marginal_relevance( |
我们可以在数据召回实践中,测试不同算法下的效果,来选择合适的算法。
总结
最后,我们对RAG进行一下总结。RAG底层依赖LLM大模型和数据获取、数据存储等相关技术,在RAG技术层面基于底层技术,共实现了数据加载、数据处理、数据向量化、向量数据库和数据召回等五种技术。可以使用这个5种技术,完成RAG应用实现。