目录
本文为 datawhale 四月组队学习 RAG 笔记,用于项目学习归纳总结和个人实践记录。本人只有浅薄的python基础,但项目总体以学辅项,适合的人群层次较广泛,本篇笔记也是以小白的层次学习完成的,希望能共同交流,没有接触的同学也可以先码住方便上手~
目前更新至:环境搭建
Task1 LLM简介与环境配置
本章偏技术背景/原理的总结与介绍,主要实践在 小节1.5 的个人实践笔记中~
1.1 大语言模型简介
1.1.1 大语言模型概念
LLM通常指包含数百亿(或更多)参数的语言模型,它们在海量的文本数据上进行训练,从而获得对语言深层次的理解。
拥有
1750 亿
参数的GPT-3
和5400 亿
参数的PaLM
。尽管这些大型语言模型与小型语言模型(例如3.3 亿
参数的BERT
和15 亿
参数的GPT-2
)使用相似的架构和预训练任务,但它们展现出截然不同的能力,尤其在解决复杂任务时表现出了惊人的潜力,这被称为“涌现能力”。
发展历程
20世纪90年代 —— 统计学语言建模
2003年 ——深度学习结合融入语言模型
2018年 ——Transformer架构提出,同时发现随着语言模型规模的扩大,模型展现出了一些惊人的能力,在各种任务中的表现均显著提升。这一发现标志着大型语言模型(LLM)时代的开启。
常用LLM
目前闭源:
- GPT模型(3.5/4)——OpenAI:基本原则是通过语言建模将世界知识压缩到仅解码器 (decoder-only) 的 Transformer 模型中。
- Claude系列 ——Anthropic:在 2024 年 3 月 4 日 更新至 Claude-3。
- PaLM/Gemini系列 ——Google:2024 年 2 月 1 日,Google 将 Bard(之前发布的对话应用) 的底层大模型驱动由 PaLM2 更改为 Gemini,同时也将原先的 Bard 更名为 Gemini。
- 文心一言——百度/星火大模型
开源LLM:
- LLaMA系列 ——Meta (使用了高效的数据并行和流水线并行技术以加速模型的训练和扩展)
- 通义千问 ——阿里巴巴
- GLM系列 ——清华&智谱AI
1.1.2 LLM 能力与特点
超过了随机水平,也就是我们常说的量变引起质变——涌现能力。表现为:
- 上下文学习
- 指令遵循
- 逐步推理
LLM特点
- 巨大的规模
- 预训练和微调
- 上下文感知
- 多语言支持
- 多模态支持
- 高计算资源需求
1.1.3 LLM 应用与影响
- 自然语言处理领域:知识问答、机器翻译
- 信息检索领域:增强信息检索
- 计算机视觉领域:多模态应用
1.2 RAG(检索增强生成)简介
一种模型架构:巧妙整合从庞大知识库中检索到的信息,以此提高大模型回答的准确性与深度。
1.2.1 RAG对大模型的改善:
- RAG通过数据检索,改善LLM幻觉问题,增强其推理能力,拓宽LLM应用场景(提高适应性)
- 实时检索最新数据,保持生成内容时效性
- 链接生成内容与检索到的原始资料,增强内容可追溯性
- 检索特定领域相关数据,提高回答专业性
- 检索整合长文本信息,提高LLM长文本输入的处理能力
1.2.2 RAG工作流程
“ 处理 检索 增强 生成 ”
1.2.3 RAG相较于微调的优势有:
- 无需重新训练
- 对数据要求低
- 基于数据库回答有较好的可解释性和可追溯性
- 专业性更强
1.3 什么是LangChain
1.3.1 Langchain简介
LangChain 框架是一个用于开发各种下游应用的开源工具,可为各种大型语言模型应用提供通用接口,从而简化应用程序的开发流程。
利用 LangChain 框架构建的RAG应用可使用本地数据与用户问题通过一系列流程生成Prompt,输入大模型生成更好的回答(Prompt Engineering)
典型检索-生成环节
加载本地文档 -> 读取文本 -> 文本分割 -> 文本向量化 -> question 向量化 -> 在文本向量中匹配出与问句向量最相似的 top k 个 -> 匹配出的文本作为上下文和问题一起添加到 Prompt 中 -> 提交给 LLM 生成回答
1.3.2 LangChain的6个核心组件
模型输入/输出,数据连接,链(Chains),记忆,代理(Agents)、回调(Callbacks):扩展模型的推理能力
1.4 开发LLM应用的流程
1.4.1 何为大模型开发
以大语言模型为功能核心、通过大语言模型的强大理解能力和生成能力、结合特殊的数据或业务逻辑来提供独特功能的应用
一般通过调用 API 或开源模型来实现核心的理解与生成,通过 Prompt Enginnering 来实现大语言模型的控制
开发要素: Prompt Engineering、数据工程、业务逻辑分解、验证迭代优化
对于以调用、发挥大模型为核心的大模型开发而言:
在整体思路上,用 Prompt Engineering 来替代子模型的训练调优,用一个通用大模型 + 若干子业务 Prompt 来解决任务
在评估思路上,构造小批量验证集,与设计的Prompt结合实现验证集效果,从业务逻辑中收集当下 Prompt 的 Bad Case(不合预期的输出),并将 Bad Case 加入到验证集中,针对性优化 Prompt
1.4.2 大模型开发的一般流程
确定目标
一般应先设定最小化目标,从构建一个 MVP(最小可行性产品)开始,逐步进行完善和优化
↓
设计功能
设计本应用所要提供的功能,以及每一个功能的大体实现逻辑(越清晰、深入的业务逻辑理解往往也能带来更好的 Prompt 效果)。先设计核心功能,然后延展设计核心功能的上下游功能
↓
构建 Prompt Engineering
明确 Prompt 设计的一般原则及技巧,构建出一个来源于实际业务的小型验证集,基于小型验证集设计满足基本要求、具备基本能力的 Prompt
↓
验证迭代
进行实际业务测试,探讨边界情况,找到 Bad Case,据此迭代优化Prompt,直到达到一个较为稳定、可以基本实现目标的 Prompt 版本
↓
前后端搭建:设计产品页面(Gradio 和 Streamlit)
↓
体验优化:长期的用户体验跟踪,记录 Bad Case 与用户负反馈,再针对性进行优化即可
1.5 个人环境搭建笔记
1. 云服务器:以阿里云ECS服务器试用(在创建时选择到期自动释放实例防止续费;最小配置:Ubuntu),并创建实例 (需要重置密码,记住之后连接服务器需要填写)。
2. conda环境(官网下载最新版本即可,具体安装细节可参考anaconda安装配置教程)
conda create -n llm-universe python=3.10 #新建虚拟环境
conda activate llm-universe #激活虚拟环境
3. VScode 及插件
(1) Remote SSH 组件:连接远程服务器
首先,添加服务器的SSH
ssh -p 22 username@ip #实例公网ip
刷新SSH远程资源管理器会出现添加的服务器,可选择 直接连接/新页面连接
在上方依次选择和输入 当前系统-确认-实例密码 即可连接成功
注:此处系统选择似乎问题不大,Windows报错换成Linux就连接成功了,这个选择也是一次性的之后在.ssh配置中会有服务器用户文件
(2) Jupyter Notebook 组件:选择conda创建的 llm-universe python环境
(3)Python组件
4.通用环境配置
(1) 自选位置创建项目文件夹。
(2) 下载依赖包:打开项目文件夹,创建并将以下依赖复制至 requirements.txt
fastapi==0.110.0
gradio==4.20.0
huggingface_hub==0.21.3
ipython==8.22.2
langchain==0.1.11
langchain-community==0.0.29
langchain-core==0.1.36
langchain-openai==0.1.1
nltk==3.8.1
openai==1.13.3
pip==23.3.1
pydantic==2.6.3
python-dotenv==1.0.1
qianfan==0.3.3
Requests==2.31.0
transformers==4.38.2
websocket_client==1.7.0
zhipuai==2.0.1
chromadb==0.4.14
rapidocr-onnxruntime==1.3.15
pymupdf==1.24.0
unstructured==0.12.6
chromadb==0.4.14
markdown==3.6
(3) 在项目conda环境下安装依赖包。(进行以下操作前可能需要先把项目名的 -main 后缀去掉)
cd llm-universe #进入/切换至项目文件目录
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple #通过清华源加速安装依赖包
5. 其他资源下载(这段涉及后续配置先贴原文了,之后搞懂后填坑编辑~)
下载NLTK相关资源
我们在使用开源词向量模型构建开源词向量的时候,需要用到第三方库 nltk 的一些资源。正常情况下,其会自动从互联网上下载,但可能由于网络原因会导致下载中断。当我们使用 nltk 时就会报错。此处我们从国内仓库镜像地址下载相关资源。
cd /root
git clone https://gitee.com/yzy0612/nltk_data.git --branch gh-pages
cd nltk_data
mv packages/* ./
cd tokenizers
unzip punkt.zip
cd ../taggers
unzip averaged_perceptron_tagger.zip
Task2 API 开发应用
2.1 基础概念
2.1.1 Prompt
Prompt指的是在机器学习模型中,尤其是预训练模型如BERT、GPT等,输入数据时加入的特定文本或指令。这些文本或指令的目的是引导模型理解输入的上下文、意图或任务需求,从而生成更加准确、有针对性的输出,大模型给我们的返回结果则被称为 Completion。
Prompt技术的核心是利用少量的提示信息,激发预训练模型内在的知识和能力,使得模型在无需或只需少量额外训练的情况下,就能完成特定的下游任务。这种技术,即我们后面会学习的Prompt Engineering,能够有效利用预训练模型在大量语料上的学习成果,提高模型在特定任务上的表现。
2.1.2 Temperature
Temperature 参数一般取值在 0~1 之间,Temperature越大,LLM回答文本更有创意、多样化,同时也更有可能产生错误内容,发生幻觉现象。
对于不同的问题与应用场景,根据对稳定性和创新性的需求,需要设置不同的 temperature。
2.13.System Prompt
与Prompt定义为单次用户的输入不同,System Prompt是一个“全局”概念,是对模型的一种初始化设置,通常会进行“风格/人设”的整体设定,而user的prompt与模型的能力有关。
在不同的System Prompt设置下(其他基础条件均相同),相同的Prompt可能会产生不同的Completion
from zhipuai import ZhipuAI
client = ZhipuAI(api_key="") # 请填写您自己的APIKey
response = client.chat.completions.create(
model="glm-4", # 填写需要调用的模型名称
messages=[
{"role": "system", "content": "你是一个聪明且富有创造力的小说作家"},
{"role": "user", "content": "请你作为童话故事大王,写一篇短篇童话故事,故事的主题是要永远保持一颗善良的心,要能够激发儿童的学习兴趣和想象力,同时也能够帮助儿童更好地理解和接受故事中所蕴含的道理和价值观。"}
],
stream=True,
)
for chunk in response:
print(chunk.choices[0].delta)
2.2 智谱API的使用
2.2.1 智谱API申请
在智谱AI官网注册即可免费试用100wToken的API , 准备好就可以开始使用了!
Token 是模型用来表示自然语言文本的基本单位,可以直观的理解为“字”或“词”;通常 1 个中文词语、1 个英文单词、1 个数字或 1 个符号计为 1 个token。
2.2.2 API使用
一般由于第一次使用,我们先以SDK同步调用API,首先要安装SDK包。
首先,打开项目文件夹,在路径上输入cmd打开终端(或者直接Win+R 后cd指令到文件夹)
pip install zhipuai #若依赖包中已安装则可跳过
为了保密密钥,我们通常将api与调用文件分开,首先在项目文件夹新建一个.env配置文件,(不要命名!之后加载环境变量只能找到 .env的环境文件
ZHIPUAI_API_KEY = "你的API密钥" #智谱 API 访问密钥配置
接下来,在vscode中打开项目文件夹,创建一个.ipynb (jupyter notebook文件)或.py文件以调用智谱AI,输入下方代码并运行,python环境选用 conda 创建的项目环境
import os
from dotenv import load_dotenv, find_dotenv
# 读取本地/项目的环境变量。
# find_dotenv() 寻找并定位 .env 文件的路径
# load_dotenv() 读取该 .env 文件,并将其中的环境变量加载到当前的运行环境中
# 如果你设置的是全局的环境变量,这行代码则没有任何作用。
_ = load_dotenv(find_dotenv())
from zhipuai import ZhipuAI
client = ZhipuAI(
api_key=os.environ["ZHIPUAI_API_KEY"]
)
def gen_glm_params(prompt):
messages = [{"role": "user", "content": prompt}]
return messages
def get_completion(prompt, model="glm-4", temperature=0.05):
messages = gen_glm_params(prompt)
response = client.chat.completions.create(
model=model,
messages=messages,
temperature=temperature
)
if len(response.choices) > 0:
return response.choices[0].message.content
return "generate answer error"
prompt = '智谱AI,启动!'
print(get_completion(prompt))
成功运行代码我们的API就成功接入啦!现在我们可以正式进行RAG实现的学习了。
2.3 Prompt Engineering
设计高效 Prompt 的两个关键原则:1、编写清晰、具体的指令;2、给予模型充足思考时间。掌握这两点,对创建可靠的语言模型交互尤为重要。
2.3.1 编写清晰、具体的指令
清晰明确地表达需求,提供充足上下文,不要过于繁琐或简陋:
1.使用分隔符:如```,""",< >, ,: 等清晰地表示输入的不同部分,但要注意输入的内容不要与你的预设 Prompt 相冲突,以预防提示词注入(Prompt Rejection)
2.结构化输出:即按照某种格式组织的内容输出,例如 JSON、HTML 等
3.要求模型在回答前先检查是否满足任务中预设的条件,如果不满足,则指出并停止执行后续的完整流程
4.提供示例以“预热”语言模型,让它为新的任务做好准备
2.3.2 给模型时间去思考
通过 Prompt 引导语言模型进行深入思考,可以要求其先列出对问题的各种看法,说明推理依据,然后再得出最终结论。
1.指定完成任务所需的步骤,将一个复杂任务拆分成数个子任务交给大模型来完成
2.指导模型在下结论之前找出一个自己的解法,常应用于判断性问题上,如果让模型直接进行判断,模型可能因“幻觉”问题而给出错误判断,因此可先让模型自己思考问题,再将自己的思考与问题相比对给出正确率更高的回答
Task3 搭建知识库
本章我们的任务是将通过自己的知识库经数据处理,利用Embedded模型构建chroma向量库
3.1 数据处理
3.1.1 数据读取
首先在项目文件夹中建立项目数据库(这里我命名为Database),再创建知识库子目录(knowledge_db)存放外部非结构文件
我们以langchain自带的pdf读取工具至 pdf_pages 获得内容
from langchain.document_loaders.pdf import PyMuPDFLoader
# 创建一个 PyMuPDFLoader Class 实例,输入为待加载的 pdf 文档路径
loader = PyMuPDFLoader("../../data_base/knowledge_db/pumkin_book/pumpkin_book.pdf")
# 调用 PyMuPDFLoader Class 的函数 load 对 pdf 文件进行加载
pdf_pages = loader.load()
pdf_pages 是一个列表(list),其元素是 page 文档,具有两个属性:
page_content
包含该文档的内容。meta_data
为文档相关的描述性数据
3.1.2 数据清洗
我们期望知识库的数据尽量是有序的、优质的、精简的,因此我们要删除低质量的、甚至影响理解的文本数据。
读取的pdf文件不仅将一句话按照原文的分行添加了换行符\n
,也在原本两个符号中间插入了\n
,我们可以使用正则表达式匹配并删除掉\n
。
import re
pattern = re.compile(r'[^\u4e00-\u9fff](\n)[^\u4e00-\u9fff]', re.DOTALL)
pdf_page.page_content = re.sub(pattern, lambda match: match.group(0).replace('\n', ''), pdf_page.page_content)
print(pdf_page.page_content)
进一步分析数据,我们发现数据中还有不少的•
和空格,我们的简单实用replace方法即可。
pdf_page.page_content = pdf_page.page_content.replace('•', '')
pdf_page.page_content = pdf_page.page_content.replace(' ', '')
print(pdf_page.page_content)
3.1.3 文档分割(项目未来主要优化方向)
由于单个文档的长度往往会超过模型支持的上下文,导致检索得到的知识太长超出模型的处理能力,因此,在构建向量知识库的过程中,我们往往需要对文档进行分割,将单个文档按长度或者按固定的规则分割成若干个 chunk,然后将每个 chunk 转化为词向量,存储到向量数据库中。
Langchain 中文本分割器都根据 chunk_size
(块大小)和 chunk_overlap
(块与块之间的重叠大小)进行分割。
#导入文本分割器
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]) #将pdf前1000页以分割设置分割
3.2 搭建向量库
首先我们读取知识库中存放的文件并进行上述数据处理,下列代码涉及文档路径请自行适配。
import os
from dotenv import load_dotenv, find_dotenv
# 读取本地/项目的环境变量。
# find_dotenv()寻找并定位.env文件的路径
# load_dotenv()读取该.env文件,并将其中的环境变量加载到当前的运行环境中
# 如果你设置的是全局的环境变量,这行代码则没有任何作用。
_ = load_dotenv(find_dotenv())
# 获取folder_path下所有文件路径,储存在file_paths里
file_paths = []
folder_path = '../../data_base/knowledge_db'
for root, dirs, files in os.walk(folder_path):
for file in files:
file_path = os.path.join(root, file)
file_paths.append(file_path)
print(file_paths[:3])
# 下载文件并存储到text
texts = []
for loader in loaders: texts.extend(loader.load())
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 切分文档
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, chunk_overlap=50)
获得经处理文档,准备向量知识库的建立
split_docs = text_splitter.split_documents(texts)
之后我们开始建立Langchain集成的向量知识库之一 Chroma:我们选择 Chroma 是因为它轻量级且数据存储在内存中,这使得它非常容易启动和开始使用。
Langchain只提供了OpenAI和百度千帆的Embeddeding,但是我们可以以OpenAI提供的类定义将智谱embeddeding api接入。
可以参考原项目notebook讲解内容实现封装代码,也可以直接使用我们已经封装好的代码 zhipuai_embedding.pyzhipuai_embedding.pyzhipuai_embedding.py,将该代码同样下载到本 Notebook 的同级目录,实践过程中发现embedding模块(建立向量库和智谱嵌入模型的py文件)需要放在项目的子文件夹,直接在项目文件夹中运行会报错,就可以直接导入我们封装的函数。源代码可在原项目/本文资源中下载
from zhipuai_embedding import ZhipuAIEmbeddings
# 定义持久化路径
persist_directory = 'Data_base/vector_db/chroma'
# 定义 Embeddings
embedding = ZhipuAIEmbeddings()
!rm -rf '../../data_base/vector_db/chroma' # 删除旧的数据库文件(如果文件夹中有文件的话),windows电脑请手动删除
from langchain.vectorstores.chroma import Chroma
vectordb = Chroma.from_documents(
documents=split_docs[:20], # 为了速度,只选择前 20 个切分的 doc 进行生成;
embedding=embedding,
persist_directory=persist_directory # 允许我们将persist_directory目录保存到磁盘上
)
在此之后,我们要确保通过运行 vectordb.persist 来持久化向量数据库,以便我们在未来的课程中使用。
vectordb.persist()