目录
Task3:RAG
在大模型应用开发(1.0)中,介绍了RAG是什么以及在Langchain框架中是如何实现的。下面主要对于细节的补充和实践
Step1:什么是RAG
1.1:为什么需要RAG
在研究RAG之前,先来研究一下为什么需要RAG,原因主要是下面三点:
1.大模型预训练知识的局限性:对于大模型预训练的数据是从网络上公开的资源,对于一些实时的或者非公开的资源,大模型无法获取,当然也就没有这方面的知识;
2.数据安全性:为了能够使大模型具有某方面的能力,需要将数据纳入训练,对于企业来说,数据的泄露是致命的;大模型预训练的时候使用的是公开的数据,RAG允许企业内部的数据内部自己使用;
3.大模型幻觉:大模型在它不擅长的领域中,会出现胡说八道的情况。
1.2:RAG的步骤
RAG主要包含以下三个步骤:
1.构建知识库
2.索引:将文档库划分为较短的chunk,构成向量索引;
3.检索与召回:计算问题和chunks的相似度,检索出相应的chunks;
4.增强和生成:将检索到的chunks作为背景信息,增强计算问题;输入到大模型中生成问题的答案。
解释一下这个图:
1.右上角的Documents就是相关问答,切割成为了Chunks Vectors,Embedding后成为检索知识库
2.左上角的用户,输入query,立马和知识库中的相应知识进行结合(combine)
3.输入到大模型中,得到左下角的输出
类似的图还有:
Step2:RAG技术细节
由于RAG 分为文档向量数据化构建知识库、检索、增强、生成四步,那么每一步是怎么实现的呢?(以生成单元测试项目为基础来回答以下四个问题)
2.1:怎么构建知识库?
答案:基于Langchain框架,以PDF类型文本构建知识库为例,主要包含数据加载、数据清洗、文档分割、搭建知识库;如下所示:
## 加载数据
from langchain.document_loaders.pdf import PyMuPDFLoader
pdf_file_path = "../data_base/knowledge_db/pumkin_book/pumpkin_book.pdf"
# 绝对地址不可以的原因是:绝对地址应该从根目录开始,而不是llm-universe开始
# 创建一个 PyMuPDFLoader Class 实例,输入为待加载的 pdf 文档路径
loader = PyMuPDFLoader(pdf_file_path)
# 调用 PyMuPDFLoader Class 的函数 load 对 pdf 文件进行加载
pdf_pages = loader.load()
# print("pdf_pages",pdf_pages)
print("这个pdf一共有{}页".format(len(pdf_pages)))
## 数据清洗
import re
pattern1 = re.compile(r'[^\u4e00-\u9fff](\n)[^\u4e00-\u9fff]', re.DOTALL)
pattern2 = re.compile(r'→_→.*?←_←',re.DOTALL)
for i in range(len(pdf_pages)):
pdf_pages[i].page_content = re.sub(pattern1, lambda match: match.group(0).replace('\n', ''), pdf_pages[i].page_content)
pdf_pages[i].page_content = re.sub(pattern2, '', pdf_pages[i].page_content)
##re.sub(pattern, repl, string):这是Python re 模块中的一个方法,用于替换 string 中所有匹配 pattern 的子字符串为 repl。
pdf_pages[i].page_content = pdf_pages[i].page_content.replace('•', '')
pdf_pages[i].page_content = pdf_pages[i].page_content.replace(' ', '')
pdf_pages[i].page_content = pdf_pages[i].page_content.replace('.', '')
pdf_pages[i].page_content = pdf_pages[i].page_content.replace('\n', '')
pdf_content = ''
for i in range(13,len(pdf_pages)):
pdf_content = pdf_content + pdf_pages[i].page_content
## 文档分割
'''
* RecursiveCharacterTextSplitter 递归字符文本分割
RecursiveCharacterTextSplitter 将按不同的字符递归地分割(按照这个优先级["\n\n", "\n", " ", ""]),这样就能尽量把所有和语义相关的内容尽可能长时间地保留在同一位置
RecursiveCharacterTextSplitter需要关注的是4个参数:
* separators - 分隔符字符串数组
* chunk_size - 每个文档的字符数量限制
* chunk_overlap - 两份文档重叠区域的长度
* length_function - 长度计算函数
'''
#导入文本分割器
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 知识库中单段文本长度
CHUNK_SIZE = 500
# 知识库中相邻文本重合长度
OVERLAP_SIZE = 50
# 使用递归字符文本分割器
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=CHUNK_SIZE,
chunk_overlap=OVERLAP_SIZE
)
# text_splitter.split_text(pdf_page.page_content[0:1000
text_pdf_splitter = text_splitter.split_text(pdf_content)
print(f"切分后的文件数量:{len(text_pdf_splitter)}")
##搭建知识库
import importlib
import zhipuai_embedding
importlib.reload(zhipuai_embedding)
from zhipuai_embedding import ZhipuAIEmbeddings
embedding = ZhipuAIEmbeddings()
# 定义持久化路径
persist_directory = '../data_base/vector_db/chroma'
!rm -rf '../data_base/vector_db/chroma'
from langchain.vectorstores.chroma import Chroma
from langchain.schema import Document
documents = [Document(page_content=text, metadata={}) for text in text_pdf_splitter]
vectordb = Chroma.from_documents(
documents=documents,
embedding=embedding,
persist_directory=persist_directory # 允许我们将persist_directory目录保存到磁盘上
)
vectordb.persist()
print(f"向量库中存储的数量:{vectordb._collection.count()}")
2.2:怎么检索?
2.3:怎么增强?
2.4:怎么输入到大模型中?
Step3:开源的RAG框架
注意:只是说明有这个概念的存在,具体有什么框架,网络搜索一下就出来了。
Step4:效果展示:
效果1:大模型能够识别出周志华的西瓜书,如果不加知识库就无法得到满意的回答;