大模型技术实践(三)|用LangChain和Llama 2打造心灵疗愈机器人

上期文章我们实现了Llama 2-chat-7B模型的云端部署和推理,本期文章我们将用“LangChain+Llama 2”的架构打造一个定制化的心灵疗愈机器人。有相关知识背景的读者可以直接阅读「实战」部分。


2839f867ef83f2dea1707cc44903fff9.jpeg


01 背景


1.1 微调 vs. 知识库


由于大模型在垂直行业领域的问答效果仍有待提升,因此,领域知识的注入成为了最直接的解决方案之一。知识注入方法可以分为领域微调(Fine-tuning)和外挂知识库(Knowledge Base)两种。


1. 领域微调

微调是通过少量特定用例的增量数据对基础模型进行进一步训练,改变其神经网络中的参数权重。微调适用于任务或域定义明确,且有足够的标记数据的场景,比如风格微调。目前常用的微调方法包括Freeze,P-tuning和LoRA,相关细节会在下期文章中详细介绍。


然而,微调方法的不足之处在于:

▪ 高质量训练数据集的构建,微调训练所需的算力以及微调模型定期更新等开销都不容小觑

▪ 试错成本较高,特定领域数据一般难以覆盖模型已学到的参数,且可能会导致模型其他下游任务的表现下降


2. 外挂知识库

外挂知识库的本质在于不修改基座模型参数,通过提示词工程(Prompt Engineering)将特定知识作为prompt中的context,即召回相关性最高的几个文档,让模型分析这些蕴含知识后,并返回答案。知识库适合要求输出明确且精度高的任务。


相对于微调,知识库的优势在于:

▪ 回答精确度更高,基于相关文档中的最相关特定段落进行语义搜索能消除查询歧义以生成更精确的答案

▪ 适应性更强,用户可以通过轻松更新信息源来调整和适配新的领域

但大模型上下文窗口长度的限制和Prompt的构造等因素带来的潜在精度下降也需要纳入知识库构建的考量。


为了打造特定领域(Domain-specific Knowledge)的知识问答系统,我们需要借助提供了外挂知识库的搜索方案LangChain框架。


1.2 LangChain模块


LangChain是一个由语言模型驱动的用于开发应用程序的框架。LangChain主要的两个能力是:

▪ Data-aware:将不同数据源接入到语言模型中

▪ Agentic:允许语言模型和LangChain环境交互


LangChain的核心模块包括Models,Prompts,Chains,Indexes,Agents等 [1]。对于每一个模块,LangChain都提供了标准化的可拓展接口。


ed2836ae1a4b02373970971284656e19.jpeg

图1:LangChain部分模块 [2]


除了用LLM Wrapper可以接入众多的大模型(如 OpenAI、Cohere、Hugging Face),LangChain同时也通过VectorStore Wrapper接口集成了主流的向量数据库(如 Milvus、Pinecone、Chroma等)来优化语义搜索。


LangChain能接入的数据类型涵盖了文本、PPT、图片、HTML、Pdf等非结构化文件。相较于传统数据库的精确搜索,即完全匹配,向量数据库使用最邻近(Approximate Nearest Neighbor,ANN)算法和相似度度量(如余弦相似度,内积等)来找到和查询问题最相似的向量。


基于本地知识库问答的大致流程如下:

加载文档 -> 文本拆分 -> 根据question/query语义检索匹配文本 -> 构建prompt -> LLM生成回答


这里以Milvus数据库和ChatGPT作为示例:

975b0ea4b3ae4b21989b9f6fb7dcfd60.jpeg

图2:LangChian + Milvus + ChatGPT pipeline [3]

 

02 实战


目前,我们已经拆解完了LangChain+LLM文档问答的大致链路,接下来我们正式进入实战环节。


2.1 环境搭建


a. 安装LangChain

确保Python 版本≥ 3.8.1 且 <4.0。

pip install langchain


b.&nbsp;部署LLama 2

▪&nbsp;关于Llama 2模型的部署,详情可参见上期文章《大模型技术实践(二)|关于Llama 2你需要知道的那些事儿》


▪ UCloud官方的“LLaMA2 模型快速部署”文档:https://docs.ucloud.cn/gpu/practice/LLaMA2?id=llama2-模型快速部署


c.&nbsp;下载Embedding 模型

这里我们选择text2vec-large-chinese [4]这个Embedding模型,下载地址为:

https://huggingface.co/GanymedeNil/text2vec-large-chinese


对于中文的场景,也有其他优秀的开源模型可供选择,如m3e和bge等[5]。


d.&nbsp;下载数据集

心灵鸡汤文本数据集:https://huggingface.co/datasets/soulteary/warm-chicken-soup/


这个数据集是从Google网页上爬取的一些心灵鸡汤引用短文,共包含631条文本

&nbsp;

2.2 文档解析


a.&nbsp;加载数据集

LangChain对于不同格式的数据源内置了不同的解析脚本,最终这些数据都将转换为纯txt文本格式,以实现文本标准化。

from langchain.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter

loader = UnstructuredFileLoader("数据集存放地址") &nbsp;
docs = loader.load()


b.&nbsp;文本切分

文本切分中的chunk_size指定了切分后的文本块的字数,chunk_overlap指定了切分文本块之间的重叠字数。由于鸡汤引用文本总长度较短,且文本内部语义关联度高,所以这里的chunk_size设置为50,chunk_overlap设置为20。

text_splitter = RecursiveCharacterTextSplitter(chunk_size=50,chunk_overlap=20)
docs = text_splitter.split_documents(docs)


c.&nbsp;文本嵌入和向量库

文本切分后,我们需要将文本进行向量化表示,将其映射为低维稠密的向量并存储到然向量数据库中。向量数据库选用了无需注册的FAISS。

from langchain.embeddings.huggingface import HuggingFaceEmbeddings
from langchain.vectorstores import FAISS

# 导入向量模型
import os
embeddings = HuggingFaceEmbeddings(
&nbsp;&nbsp;&nbsp;&nbsp;model_name = "{你的地址}/text2vec-large-chinese",
&nbsp;&nbsp;&nbsp;&nbsp;model_kwargs = {'device': 'cuda'})

# 如果没有本地faiss仓库,先读取doc向量库,再将向量库保存到本地
if os.path.exists("{你的地址}/my_faiss_store.faiss") == False:
&nbsp;&nbsp;&nbsp;&nbsp;vector_store = FAISS.from_documents(docs,embeddings)
&nbsp;&nbsp;&nbsp;&nbsp;vector_store.save_local("{你的地址}/my_faiss_store.faiss")
# 如果faiss仓库已存在,则直接读取
else:
&nbsp;&nbsp;&nbsp;&nbsp;vector_store = FAISS.load_local(
&nbsp;&nbsp;&nbsp;&nbsp;"{你的地址}/my_faiss_store.faiss",
&nbsp;&nbsp;&nbsp;&nbsp;embeddings=embeddings)


2.3 加载模型


import torch
from transformers import AutoTokenizer, AutoModelForCausalLM

# 加载tokenizer
tokenizer = AutoTokenizer.from_pretrained(
&nbsp;&nbsp;&nbsp;&nbsp;'/opt/Llama-2-7b-chat-hf',
&nbsp;&nbsp;&nbsp;&nbsp;trust_remote_code=True)

# 加载模型 Llama 2-chat-7B
base_model = AutoModelForCausalLM.from_pretrained(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"/opt/Llama-2-7b-chat-hf",
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;torch_dtype=torch.float16,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;device_map='auto',
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;trust_remote_code=True
&nbsp;&nbsp;&nbsp;&nbsp;)
llm = base_model.eval()


2.4 语义检索


接下来,我就能根据构建好的向量数据库召回对应文本片段。


a.&nbsp;向量化召回

FAISS默认使用L2(欧式距离),召回的文档按照相似度结果从大到小排序。

query = "面对求职屡屡碰壁的大学生,请说一句话来鼓励他?"
docs = vector_store.similarity_search(query) # 计算相似度,并把相似度高的chunk放在前面
context = [doc.page_content for doc in docs] # 提取chunk的文本内容
print(context)


b.&nbsp;设置提示词模板

以下是Llama 2默认的提示词模板

&nbsp;#qa_template = """Use the following pieces of information to answer the user's question.
&nbsp;#If you don't know the answer, just say that you don't know, don't try to make up an answer.
&nbsp;#Context: {context}
&nbsp;#Question: {question}
&nbsp;#Only return the helpful answer below and nothing else.
&nbsp;#Helpful answer: """


我们可以参考上面的模板,根据场景定制化自己的模板来拼接Query和召回结果

context ="\n".join(context)
prompt = f"基于以上内容:\n{context} \n 请回答:{query} \n 字数限制在30字以内"


2.5 推理示例


我们对LLM的参数进行设置,例如最大令牌(max_new_tokens)、最高k值(top_k)、温度(temperature)和重复惩罚(repetition_penalty)等等。最后,将prompt喂给模型。

# 检查显存占用
nvidia-smi

inputs = tokenizer([f"Human:{prompt}\nAssistant:"], return_tensors="pt")
input_ids = inputs["input_ids"].to('cuda')

# llm参数设置
param_config = {
&nbsp;&nbsp;&nbsp;&nbsp;"input_ids":input_ids,
&nbsp;&nbsp;&nbsp;&nbsp;"max_new_tokens":1024,
&nbsp;&nbsp;&nbsp;&nbsp;"do_sample":True,
&nbsp;&nbsp;&nbsp;&nbsp;"top_k":5,
&nbsp;&nbsp;&nbsp;&nbsp;"top_p":0.95,
&nbsp;&nbsp;&nbsp;&nbsp;"temperature":0.1,
&nbsp;&nbsp;&nbsp;&nbsp;"repetition_penalty":1.3
}
result &nbsp;= llm.generate(**param_config)

answer = tokenizer.decode(result[0], skip_special_tokens=True)

print(answer)
# output&nbsp;example

# Q:面对求职屡屡碰壁的大学生,请说一句话来鼓励他?
# A:坚持不懈,机会终将降临


03&nbsp;外挂知识库的问题和优化


3.1 LLM+Embedding-Search的局限


外挂知识库将用户问题和本地知识向量化,比较两者的向量相似度(Vector Similarity)进行召回。然而,这种全量的Embedding-Search在面对多知识点聚合处理的场景下,存在召回精度低的问题。因为知识库的构建是对单个知识点进行索引,而非对不同知识点的排列组合分别索引。


居里夫人的出生年月 -> 单索引
居里夫人、爱因斯坦、奥本海默的出生年月 -> 组合索引

Q: 居里夫人、爱因斯坦和奥本海默三人中谁最早出生?


为了避免召回遗漏,直观的处理方法包括降低相似度阈值(similarity score threshold)和增加召回数量(top_k),但这不免会引入无关的知识点噪声且增加和LLM交互的token开销。


3.2 效果优化方向


意图识别和召回优化

提升问答系统的精度可以从意图识别和召回优化两个角度考虑,且两者都可以用关键词表示,即从直接将用户query和知识点进行embedding转变为对两者提取关键词后再进行匹配。意图识别可以通过关键词提取(Information Extraction, IE)和槽位填充(Slot Filling,SF)实现。


1.&nbsp;关键词提取


a.&nbsp;面向query——槽位填充

利用LLM思维链(Chain-of-Thought,COT)的提示能力来引导用户多轮对话并进行信息总结。针对我们的心灵疗愈机器人的场景,比如用户查询心灵鸡汤的句子,那么就要求用户的提供年龄段,情绪问题和情感需求等信息。语义槽格式如下:

&nbsp; &nbsp;"心灵鸡汤" : {&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"用户年龄段" : ____, # 青年,中年,老年
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"情绪问题" : ____, # 焦虑,失恋
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"情感需求" : ____, # 寻求安慰,寻求激励
&nbsp;&nbsp;&nbsp;&nbsp;}


b.&nbsp;面向知识点——索引入口


对于知识点可以从以下两个方面考虑:

i. 对相同知识点建立多级索引,有助于实现对维度查询。比如对一位奥运冠军的姓名,竞赛项目,年龄,获奖时间等分别建立索引。

ii. 将知识库转化为以关系三元组为核心的知识图谱。三元组的抽取除了传统的命名实体识别(NER)等方法,也可以通过prompt让大模型来进行抽取。


基于关键词的embedding入库和搜索流程如下:

464272ac6a1fbc048615af4340cbdb9a.jpeg


2.&nbsp;多路召回

类似于Bert时代的垂直领域问答系统,我们可以将语义检索和传统的Elasticsearch(ES)关键词搜索并行,对两者进行加权打分投票来获取最终的top_k。


5fc6e6fafa40723f60fe83db5aca389f.jpeg


目前类似于以上优化思路已经落地的有“智海-录问”法律大模型 [6],其基座模型为Baichuan-7B。智海-录问知识增强的完整链路如图3。值得注意的是,智海-录问在知识库中对每一个知识点是以 [key, value] pair 形式存储的。key是知识点的内容简介,用于检索;value是知识点的具体内容,用于模型输入。实现细节请参照其Hugging Face仓库。


&nbsp;

516f59096a239564a94a037b6171aeb1.jpeg

图3:“智海-录问”知识增强链路


其他优化方向

除了Embedding部分,“LangChain+LLM”(图2)链路内的其他组件也有进一步优化的空间:


1.&nbsp;知识库细化

当用户手动选择分区后,分区检索可以明显提高召回的精度。


7b650225b45ac9080e5d919dcb1f963b.jpeg

图4:“智海-录问”的交互界面


2.&nbsp;文本切分方式

由于文本重叠(overlap)的大小没有统一标准,如何保证语义完整和连贯都需要不断测试。


3.&nbsp;提示词的质量

在提示词模板的设计上要增加明确约束条件的指令,减少大模型出现幻觉现象的几率。


4.&nbsp;大模型的选型

选择基座模型还是微调后的模型,以及对中文的支持程度的需求都需要结合下游场景进行判别。

&nbsp;

本期文章带你基于“LangChain+LLM”框架快速搭建了知识增强后的问答机器人——心灵疗愈师,并探讨了提升模型的内容理解和执行能力的潜在优化方向。


下期文章我们将深入解读目前主流的大模型微调技术,敬请期待~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/102117.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Revit SDK:Selections 选择

前言 Revit 作为一款成熟的商业软件&#xff0c;它将自己的UI选择功能也通过 API 暴露出来。通过 API 可以按照特定的过滤规则来选择相应的元素&#xff0c;能力和UI基本上是等价的。这个 SDK 用四个例子展示了 API 的能力&#xff0c;内容如下。 内容 PickforDeletion 核心…

A133P EC200M模块调试

Linux USB驱动框架&#xff1a; USB 是一种分层总线结构。USB 设备与主机之间的数据传输由 USB 控制器控制。Linux USB 驱动程序架构如下图所示。Linux USB 主机驱动包括三部分&#xff1a;USB 主机控制器驱动、USB 核心和 USB 设备驱动。 模块加载 USB 转串口 option 驱动程序…

记录docker 部署nessus

1、开启容器 docker run -itd --nameramisec_nessus -p 8834:8834 ramisec/nessus 2、登录 &#xff1a;注意是https https://ip8843 3、修改admin密码 #进入容器 docker exec -it ramisec_nessus /bin/bash#列出用户名 /opt/nessus/sbin/nessuscli lsuser#修改密码&a…

JAVA设计模式第十讲:SPI - 业务差异解决方案

JAVA设计模式第十讲&#xff1a;SPI - 业务差异解决方案 我们需要在不修改源代码的情况下&#xff0c;动态为程序提供一系列额外的特性。首先想到的是Spring的AOP技术来构建应用插件&#xff0c;但是在Java自带的插件中&#xff0c;就有完整的实现。SPI&#xff08;Service Pro…

echarts饼图label自定义样式

生成的options {"tooltip": {"trigger": "item","axisPointer": {"type": "shadow"},"backgroundColor": "rgba(9, 24, 48, 0.5)","borderColor": "rgba(255,255,255,0.4)&q…

DQN算法概述及基于Pytorch的DQN迷宫实战代码

一. DQN算法概述 1.1 算法定义 Q-Learing是在一个表格中存储动作对应的奖励值&#xff0c;即状态-价值函数Q(s,a)&#xff0c;这种算法存在很大的局限性。在现实中很多情况下&#xff0c;强化学习任务所面临的状态空间是连续的&#xff0c;存在无穷多个状态&#xff0c;这种情…

CSS笔记(黑马程序员pink老师前端)浮动,清除浮动

浮动可以改变标签的默认排列方式。浮动元素常与标准流的父元素搭配使用. 网页布局第一准则:多个块级元素纵向排列找标准流&#xff0c;多个块级元素横向排列找浮动。 float属性用于创建浮动框&#xff0c;将其移动到一边&#xff0c;直到左边缘或右边缘触及包含块或另一个浮动框…

Xilinx IDDR与ODDR原语的使用

文章目录 ODDR原语1. OPPOSITE_EDGE 模式2. SAME_EDGE 模式 ODDR原语 例化模板&#xff1a; ODDR #(.DDR_CLK_EDGE("OPPOSITE_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE" .INIT(1b0), // Initial value of Q: 1b0 or 1b1.SRTYPE("SYNC…

使用Python操作MySQL数据库

准备 安装Python,打开命令提示符&#xff0c;我已经安装成功了 安装Mysql我也安装成功了 我在用户的86188下利用记事本写了一个.py的python代码&#xff0c;在命令提示符中运行 Python自带的集成式开发环境,在电脑搜索框直接IDEA你也会打开 一&#xff0c;建立连接 使用Python…

colab使用(基础入门)——随手记

挂载到google drive 挂载目录/content/drive from google.colab import drive drive.mount(/content/drive) 图解colab读取Google Drive 文件 - 知乎 下载文件 !curl -L https://dl.fbaipublicfiles.com/imagebind/imagebind_huge.pth -o imagebind_ckpt参数&#xff1a;[-…

Nginx 学习(十)高可用中间件的配置与实现

一 Keepalived热备 1 概述 调度器出现单点故障&#xff0c;如何解决?Keepalived实现了高可用集群Keepalived最初是为LVS设计的&#xff0c;专门监控各服务器节点的状态Keepalived后来加入了VRRP功能&#xff0c;防止单点故障 2 运行原理 Keepalived检测每个服务器节点状…

Pytest系列-快速入门和基础讲解(1)

前言 目前有两种纯测试的测试框架&#xff0c;pytest和unittestunittest应该是广为人知&#xff0c;而且也是老框架了&#xff0c;很多人都用来做自动化&#xff0c;无论是UI还是接口pytest是基于unittest开发的另一款更高级更好用的单元测试框架 单元测试框架介绍 单元测试…