admin管理员组文章数量:1487745
基于 InternLM 和 Langchain 搭建云端知识库
本篇文章是书生浦语大模型微调系列第三篇。
环境配置
1.1 InternLM 模型部署
在InternStudio平台中选择 A100(1/4) 的配置,如下图所示镜像选择 Cuda11.7-conda
,如下图所示:
接下来打开刚刚租用服务器的进入开发机
,并且打开其中的终端开始环境配置、模型下载和运行 demo
。
进入开发机后,在页面的左上角可以切换 JupyterLab
、终端
和 VScode
,并在终端输入 bash
命令,进入 conda
环境。
进入 conda
环境之后,使用以下命令从本地一个已有的 pytorch 2.0.1
的环境
bash
conda create --name InternLM --clone=/root/share/conda_envs/internlm-base
然后使用以下命令激活环境
代码语言:javascript代码运行次数:0运行复制conda activate InternLM
代码语言:javascript代码运行次数:0运行复制
并在环境中安装运行 demo 所需要的依赖。
代码语言:javascript代码运行次数:0运行复制# 升级pip
python -m pip install --upgrade pip
pip install modelscope==1.9.5
pip install transformers==4.35.2
pip install streamlit==1.24.0
pip install sentencepiece==0.1.99
pip install accelerate==0.24.1
代码语言:javascript代码运行次数:0运行复制
1.2 模型下载
在本地的 /root/share/temp/model_repos/internlm-chat-7b
目录下已存储有所需的模型文件参数,可以直接拷贝到个人目录的模型保存地址:
mkdir -p /root/data/model/Shanghai_AI_Laboratory
cp -r /root/share/temp/model_repos/internlm-chat-7b /root/data/model/Shanghai_AI_Laboratory/internlm-chat-7b
代码语言:javascript代码运行次数:0运行复制
如果本地拷贝模型参数出现问题,我们也可以使用 modelscope
中的snapshot_download
函数下载模型,第一个参数为模型名称,参数cache_dir
为模型的下载路径。
在 /root
路径下新建目录data
,在目录下新建 download.py
文件并在其中输入以下内容,粘贴代码后记得保存文件,如下图所示。并运行 python /root/data/download.py
执行下载,模型大小为 14 GB,下载模型大概需要 10~20 分钟
import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer
import os
model_dir = snapshot_download('Shanghai_AI_Laboratory/internlm-chat-7b', cache_dir='/root/data/model', revision='v1.0.3')
1.3 LangChain 相关环境配置
在已完成 InternLM 的部署基础上,还需要安装以下依赖包:
代码语言:javascript代码运行次数:0运行复制pip install langchain==0.0.292
pip install gradio==4.4.0
pip install chromadb==0.4.15
pip install sentence-transformers==2.2.2
pip install unstructured==0.10.30
pip install markdown==3.3.7
代码语言:javascript代码运行次数:0运行复制
需要使用到开源词向量模型 Sentence Transformer:(选用这个模型是相对轻量、支持中文且效果较好的)
使用 huggingface 下载可能速度较慢,我们可以使用 huggingface 镜像下载。与使用hugginge face下载相同,只需要填入镜像地址即可。
将 download_hf.py中的代码修改为以下代码:
代码语言:javascript代码运行次数:0运行复制import os
# 设置环境变量
os.environ['HF_ENDPOINT'] = ''
# 下载模型
os.system('huggingface-cli download --resume-download sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 --local-dir /root/data/model/sentence-transformer')
在 \root\data
目录下执行该脚本即可自动开始下载:
python download_hf.py
代码语言:javascript代码运行次数:0运行复制
更多关于镜像使用可以移步至 HF Mirror 查看。
1.4 下载 NLTK 相关资源
使用开源词向量模型构建开源词向量的时候,需要用到第三方库 nltk
的一些资源。
我们在 root
目录下新建一个 nltk_data
目录,接着进入 nltk_data
,新建两个文件夹 taggers
和 tokenizers。
使用 wget
工具(Linux 系统自带)来下载 punkt
资源:
cd tokenizers
wget -O punkt.zip .zip
unzip punkt.zip
以相同的方式下载 Averaged Perceptron Tagger
资源:
cd ..
cd taggers
wget -O averaged_perceptron_tagger.zip .zip
unzip averaged_perceptron_tagger.zip
代码语言:javascript代码运行次数:0运行复制
1.5 下载本项目代码
通过以下目录将仓库 clone 到本地,可以直接在本地运行相关代码:
代码语言:javascript代码运行次数:0运行复制cd /root/data
git clone
代码语言:javascript代码运行次数:0运行复制
通过上述命令,可以将本仓库 clone 到本地 root/data/tutorial
目录下,在之后的过程中可以对照仓库中的脚本来完成自己的代码,也可以直接使用仓库中的脚本。
知识库搭建
2.1 数据收集
选择由上海人工智能实验室开源的一系列大模型工具开源仓库作为语料库来源,包括:
- OpenCompass:面向大模型评测的一站式平台
- IMDeploy:涵盖了 LLM 任务的全套轻量化、部署和服务解决方案的高效推理工具箱
- XTuner:轻量级微调大语言模型的工具库
- InternLM-XComposer:浦语·灵笔,基于书生·浦语大语言模型研发的视觉-语言大模型
- Lagent:一个轻量级、开源的基于大语言模型的智能体(agent)框架
- InternLM:一个开源的轻量级训练框架,旨在支持大模型训练而无需大量的依赖
将上述远程开源仓库 Clone 到本地,可以使用以下命令:
代码语言:javascript代码运行次数:0运行复制# 进入到数据库盘
cd /root/data
# clone 上述开源仓库
git clone .git
git clone .git
git clone .git
git clone .git
git clone .git
git clone .git
为语料处理方便,我们将选用上述仓库中所有的 markdown、txt 文件作为示例语料库。
可以选用其中的代码文件加入到知识库中,但需要针对代码文件格式进行额外处理(因为代码文件对逻辑联系要求较高,且规范性较强,在分割时最好基于代码模块进行分割再加入向量数据库)。
将上述仓库中所有满足条件的文件路径找出来,我们定义一个函数,该函数将递归指定文件夹路径,返回其中所有满足条件(即后缀名为 .md 或者 .txt 的文件)的文件路径:
代码语言:javascript代码运行次数:0运行复制import os
def get_files(dir_path):
# args:dir_path,目标文件夹路径
file_list = []
for filepath, dirnames, filenames in os.walk(dir_path):
# os.walk 函数将递归遍历指定文件夹
for filename in filenames:
# 通过后缀名判断文件类型是否满足要求
if filename.endswith(".md"):
# 如果满足要求,将其绝对路径加入到结果列表
file_list.append(os.path.join(filepath, filename))
elif filename.endswith(".txt"):
file_list.append(os.path.join(filepath, filename))
return file_list
代码语言:javascript代码运行次数:0运行复制
2.2 加载数据
使用 LangChain 提供的 FileLoader 对象来加载目标文件,得到由目标文件解析出的纯文本内容。由于不同类型的文件需要对应不同的 FileLoader,我们判断目标文件类型,并针对性调用对应类型的 FileLoader,调用 FileLoader 对象的 load 方法来得到加载之后的纯文本对象:
代码语言:javascript代码运行次数:0运行复制from tqdm import tqdm
from langchain.document_loaders import UnstructuredFileLoader
from langchain.document_loaders import UnstructuredMarkdownLoader
def get_text(dir_path):
# args:dir_path,目标文件夹路径
# 首先调用上文定义的函数得到目标文件路径列表
file_lst = get_files(dir_path)
# docs 存放加载之后的纯文本对象
docs = []
# 遍历所有目标文件
for one_file in tqdm(file_lst):
file_type = one_file.split('.')[-1]
if file_type == 'md':
loader = UnstructuredMarkdownLoader(one_file)
elif file_type == 'txt':
loader = UnstructuredFileLoader(one_file)
else:
# 如果是不符合条件的文件,直接跳过
continue
docs.extend(loader.load())
return docs
代码语言:javascript代码运行次数:0运行复制
使用上文函数,我们得到的 docs
为一个纯文本对象对应的列表。
2.3 构建向量数据库
引入到 LangChain 框架中构建向量数据库。由纯文本对象构建向量数据库,我们需要先对文本进行分块,接着对文本块进行向量化。
LangChain 提供了多种文本分块工具,此处我们使用字符串递归分割器,并选择分块大小为 500,块重叠长度为 150:
代码语言:javascript代码运行次数:0运行复制from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, chunk_overlap=150)
split_docs = text_splitter.split_documents(docs)
代码语言:javascript代码运行次数:0运行复制
选用开源词向量模型 Sentence Transformer来进行文本向量化。LangChain 提供了直接引入 HuggingFace 开源社区中的模型进行向量化的接口:
代码语言:javascript代码运行次数:0运行复制from langchain.embeddings.huggingface import HuggingFaceEmbeddings
embeddings = HuggingFaceEmbeddings(model_name="/root/data/model/sentence-transformer")
代码语言:javascript代码运行次数:0运行复制
选择 Chroma 作为向量数据库,基于上文分块后的文档以及加载的开源向量化模型,将语料加载到指定路径下的向量数据库:
代码语言:javascript代码运行次数:0运行复制from langchain.vectorstores import Chroma
# 定义持久化路径
persist_directory = 'data_base/vector_db/chroma'
# 加载数据库
vectordb = Chroma.from_documents(
documents=split_docs,
embedding=embeddings,
persist_directory=persist_directory # 允许我们将persist_directory目录保存到磁盘上
)
# 将加载的向量数据库持久化到磁盘上
vectordb.persist()
代码语言:javascript代码运行次数:0运行复制
2.4 整体脚本
将上述代码整合在一起为知识库搭建的脚本,见create_db.py。
在 /root/data
下新建一个 demo
目录,将该脚本和后续脚本均放在该目录下运行。
运行上述脚本,即可在本地构建已持久化的向量数据库,后续直接导入该数据库即可,无需重复构建。
InternLM 接入 LangChain
为便捷构建 LLM 应用,基于本地部署的 InternLM,继承 LangChain 的 LLM 类自定义一个 InternLM LLM 子类,从而实现将 InternLM 接入到 LangChain 框架中。
完成 LangChain 的自定义 LLM 子类之后,可以以完全一致的方式调用 LangChain 的接口,而无需考虑底层模型调用的不一致。
基于本地部署的 InternLM 自定义 LLM 类并不复杂,我们只需从 LangChain.llms.base.LLM 类继承一个子类,并重写构造函数与 _call
函数即可:
from langchain.llms.base import LLM
from typing import Any, List, Optional
from langchain.callbacks.manager import CallbackManagerForLLMRun
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch
class InternLM_LLM(LLM):
# 基于本地 InternLM 自定义 LLM 类
tokenizer : AutoTokenizer = None
model: AutoModelForCausalLM = None
def __init__(self, model_path :str):
# model_path: InternLM 模型路径
# 从本地初始化模型
super().__init__()
print("正在从本地加载模型...")
self.tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
self.model = AutoModelForCausalLM.from_pretrained(model_path, trust_remote_code=True).to(torch.bfloat16).cuda()
self.model = self.model.eval()
print("完成本地模型的加载")
def _call(self, prompt : str, stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any):
# 重写调用函数
response, history = self.model.chat(self.tokenizer, prompt , history=[])
return response
@property
def _llm_type(self) -> str:
return "InternLM"
- 重写了构造函数和
_call
函数:- 对于构造函数,在对象实例化的开始加载本地部署的 InternLM 模型,从而避免每一次调用都需要重新加载模型带来的时间过长;
_call
函数是 LLM 类的核心函数,LangChain 会调用该函数来调用 LLM,在该函数中,我们调用已实例化模型的 chat 方法,从而实现对模型的调用并返回调用结果。
将上述代码封装为 LLM.py,后续将直接从该文件中引入自定义的 LLM 类。
构建检索问答链
LangChain 通过提供检索问答链对象来实现对于 RAG 全流程的封装。
所谓检索问答链,即通过一个对象完成检索增强问答(即RAG)的全流程。我们可以调用一个 LangChain 提供的 RetrievalQA
对象,通过初始化时填入已构建的数据库和自定义 LLM 作为参数,来简便地完成检索增强问答的全流程,LangChain 会自动完成基于用户提问进行检索、获取相关文档、拼接为合适的 Prompt 并交给 LLM 问答的全部流程。
4.1 加载向量数据库
将上文构建的向量数据库导入进来,直接通过 Chroma 以及上文定义的词向量模型来加载已构建的数据库:
代码语言:javascript代码运行次数:0运行复制from langchain.vectorstores import Chroma
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
import os
# 定义 Embeddings
embeddings = HuggingFaceEmbeddings(model_name="/root/data/model/sentence-transformer")
# 向量数据库持久化路径
persist_directory = 'data_base/vector_db/chroma'
# 加载数据库
vectordb = Chroma(
persist_directory=persist_directory,
embedding_function=embeddings
)
vectordb
对象即为我们已构建的向量数据库对象,该对象可以针对用户的 query
进行语义向量检索,得到与用户提问相关的知识片段。
4.2 实例化自定义 LLM 与 Prompt Template
实例化一个基于 InternLM 自定义的 LLM 对象:
代码语言:javascript代码运行次数:0运行复制from LLM import InternLM_LLM
llm = InternLM_LLM(model_path = "/root/data/model/Shanghai_AI_Laboratory/internlm-chat-7b")
llm.predict("你是谁")
构建检索问答链,还需要构建一个 Prompt Template,该 Template 其实基于一个带变量的字符串,在检索之后,LangChain 会将检索到的相关文档片段填入到 Template 的变量中,从而实现带知识的 Prompt 构建。
基于 LangChain 的 Template 基类来实例化这样一个 Template 对象:
代码语言:javascript代码运行次数:0运行复制from langchain.prompts import PromptTemplate
# 我们所构造的 Prompt 模板
template = """使用以下上下文来回答最后的问题。如果你不知道答案,就说你不知道,不要试图编造答案。尽量使答案简明扼要。总是在回答的最后说“谢谢你的提问!”。
{context}
问题: {question}
有用的回答:"""
# 调用 LangChain 的方法来实例化一个 Template 对象,该对象包含了 context 和 question 两个变量,在实际调用时,这两个变量会被检索到的文档片段和用户提问填充
QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context","question"],template=template)
4.3 构建检索问答链
调用 LangChain 提供的检索问答链构造函数,基于我们的自定义 LLM、Prompt Template 和向量知识库来构建一个基于 InternLM 的检索问答链:
代码语言:javascript代码运行次数:0运行复制from langchain.chains import RetrievalQA
qa_chain = RetrievalQA.from_chain_type(llm,retriever=vectordb.as_retriever(),return_source_documents=True,chain_type_kwargs={"prompt":QA_CHAIN_PROMPT})
代码语言:javascript代码运行次数:0运行复制
得到的 qa_chain
对象即可以实现我们的核心功能,即基于 InternLM 模型的专业知识库助手。
Langchain 文档查询链—— RetrievalQA 链
选择了 Langchain 的现有文档处理链中"stuff"链类型。在这种模式下,我们只是将所有内容都放在一个调用中,理想情况下,我们放入的内容应该少于4000个令牌。
除了"stuff"之外,Langchain 文档处理链还有精化(Refine)、Mapreduce 、重排(Mapre-rank)。
链式查询指的是查询向量存储链和语言模型的链。例如我们可以有一个查询向量存储的链,以及一个查询语言模型的链。我们可以将这些链条组合在一起,创建一个检索QA链条 。
RetrievalQA 链是Langchain已经封装好的索引查询问答链。实例化之后,我们可以直接把问题扔给它,而不需要 chain.run()
。简化了很多步骤,获得了比较稳定的查询结果。
为了创建这样的链,我们需要一个检索器。我们可以使用之前设置好的 docsearch,作为检索器,并且我们可以设置返回的文档数量 "k":4
。
from langchain.chains import RetrievalQA
# set up FAISS as a generic retriever
retriever = docsearch.as_retriever(search_type="similarity", search_kwargs={"k":4}) # as_retriever方法是构建检索器
# create the chain to answer questions
rqa = RetrievalQA.from_chain_type(llm=xxxx,
chain_type="stuff",
retriever=retriever,
return_source_documents=True)
代码语言:javascript代码运行次数:0运行复制
当我们查询"OpenAI是什么"时,我们不仅会得到一个答案,还会得到源文档 source_documents
。源文档是返回结果的参考文档,它可以帮助我们理解答案是如何得出的。
如果我们不需要中间步骤和源文档,只需要最终答案,那么我们可以直接请求返回结果。将代码设置为:
代码语言:javascript代码运行次数:0运行复制return_source_documents=False
代码语言:javascript代码运行次数:0运行复制
部署 Web Demo
将上文的代码内容封装为一个返回构建的检索问答链对象的函数,并在启动 Gradio 的第一时间调用该函数得到检索问答链对象,后续直接使用该对象进行问答对话,从而避免重复加载模型:
代码语言:javascript代码运行次数:0运行复制
from langchain.vectorstores import Chroma
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
import os
from LLM import InternLM_LLM
from langchain.prompts import PromptTemplate
from langchain.chains import RetrievalQA
def load_chain():
# 加载问答链
# 定义 Embeddings
embeddings = HuggingFaceEmbeddings(model_name="/root/data/model/sentence-transformer")
# 向量数据库持久化路径
persist_directory = 'data_base/vector_db/chroma'
# 加载数据库
vectordb = Chroma(
persist_directory=persist_directory, # 允许我们将persist_directory目录保存到磁盘上
embedding_function=embeddings
)
# 加载自定义 LLM
llm = InternLM_LLM(model_path = "/root/data/model/Shanghai_AI_Laboratory/internlm-chat-7b")
# 定义一个 Prompt Template
template = """使用以下上下文来回答最后的问题。如果你不知道答案,就说你不知道,不要试图编造答案。尽量使答案简明扼要。总是在回答的最后说“谢谢你的提问!”。
{context}
问题: {question}
有用的回答:"""
QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context","question"],template=template)
# 运行 chain
qa_chain = RetrievalQA.from_chain_type(llm,retriever=vectordb.as_retriever(),return_source_documents=True,chain_type_kwargs={"prompt":QA_CHAIN_PROMPT})
return qa_chain
定义一个类,该类负责加载并存储检索问答链,并响应 Web 界面里调用检索问答链进行回答的动作:
代码语言:javascript代码运行次数:0运行复制class Model_center():
"""
存储检索问答链的对象
"""
def __init__(self):
# 构造函数,加载检索问答链
self.chain = load_chain()
def qa_chain_self_answer(self, question: str, chat_history: list = []):
"""
调用问答链进行回答
"""
if question == None or len(question) < 1:
return "", chat_history
try:
chat_history.append(
(question, self.chain({"query": question})["result"]))
# 将问答结果直接附加到问答历史中,Gradio 会将其展示出来
return "", chat_history
except Exception as e:
return e, chat_history
按照 Gradio 的框架使用方法,实例化一个 Web 界面并将点击动作绑定到上述类的回答方法即可:
代码语言:javascript代码运行次数:0运行复制import gradio as gr
# 实例化核心功能对象
model_center = Model_center()
# 创建一个 Web 界面
block = gr.Blocks()
with block as demo:
with gr.Row(equal_height=True):
with gr.Column(scale=15):
# 展示的页面标题
gr.Markdown("""<h1><center>InternLM</center></h1>
<center>书生浦语</center>
""")
with gr.Row():
with gr.Column(scale=4):
# 创建一个聊天机器人对象
chatbot = gr.Chatbot(height=450, show_copy_button=True)
# 创建一个文本框组件,用于输入 prompt。
msg = gr.Textbox(label="Prompt/问题")
with gr.Row():
# 创建提交按钮。
db_wo_his_btn = gr.Button("Chat")
with gr.Row():
# 创建一个清除按钮,用于清除聊天机器人组件的内容。
clear = gr.ClearButton(
components=[chatbot], value="Clear console")
# 设置按钮的点击事件。当点击时,调用上面定义的 qa_chain_self_answer 函数,并传入用户的消息和聊天历史记录,然后更新文本框和聊天机器人组件。
db_wo_his_btn.click(model_center.qa_chain_self_answer, inputs=[
msg, chatbot], outputs=[msg, chatbot])
gr.Markdown("""提醒:<br>
1. 初始化数据库时间可能较长,请耐心等待。
2. 使用中如果出现异常,将会在文本输入框进行展示,请不要惊慌。<br>
""")
gr.close_all()
# 直接启动
demo.launch()
将上述代码封装为 run_gradio.py 脚本,直接通过 python 命令运行,即可在本地启动知识库助手的 Web Demo,默认会在 7860 端口运行,接下来将服务器端口映射到本地端口即可访问:
今天的分享到此为止,谢谢大家!
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。原始发表:2024-01-05,如有侵权请联系 cloudcommunity@tencent 删除函数脚本模型开源对象本文标签: 基于 InternLM 和 Langchain 搭建云端知识库
版权声明:本文标题:基于 InternLM 和 Langchain 搭建云端知识库 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/shuma/1755000323a3182314.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论