前言

这份笔记将记录有关该项目的学习历程

1.1 大型语言模型(LLM)简介

首先什么是LLM,LLM指的是大型语言模型的简称

Transformer架构与2018年开始出现,其特点为通过大量文本数据来训练这些模型,使它们可以通过阅读大量文本来深入理解语言规则和模式

语言模型规模的扩大(增加模型大小或者使用更多的数据),模型可以展现出一些惊人的能力,使其在各种任务中都显著提升

获得大模型所经历的三个时期:预训练、后训练和在线推理

1.1.2.1 涌现能力

涌现能力是区分LLM和PLM最为显著的特征,常见的涌现能力有上下文学习,指令遵循以及逐步推理

1.2 什么是RAG

由于LLM在某些情况下人会出现无法提供准确的答案的情况,为了解决这一问题,便提出了一种新的模型架构:检索增强生成

这一功能整合了从庞大知识库中检索到的相关信息,并且由此为基础来指导大型语言模型生成更为精准的答案,从而显著提升回答的准确度与深度

1.2.1 RAG的工作流程

RAG的工作流程可以分为4个阶段,分别是数据处理,检索,增强和生成四个阶段

在数据处理阶段,先对原始数据进行清洗和处理,而后将处理后的数据转化为检索模型可以使用的格式,再讲处理后的数据存储到对应的数据库中。

检索阶段,将用户的问题输入到检索系统中,在数据库中检索相关信息

增强阶段,对检索的信息进行处理和增强,这是为了让生成模型可以更好地理解和使用

生成阶段,将增强后的信息输入到生成模型中,生成模型根据这些信息来生成答案

1.2.2 RAG和微调

在提升大语言模型效果中,RAG和微调是两个比较主流的方法

微调指是在特定的数据集上进一步训练语言模型,由此来提升模型在特定任务上的表现

1.3 LangChain

什么是LangChain呢?

简单来说,在ChatGPT成功的背景下,有大量的开发者急需利用OpenAI提供的API或者私有化模型来开发基于LLM的应用程序

虽然说LLM的调用比较简单,但是想要创建完整的应用程序,还是需要很复杂的工作,在此期间,有很多机构和开发者推出了大量的开源项目,旨在帮助开发者快速构建基于LLM的端到端应用程序

LangChain便是其中的一个项目

LangChain的目的是为各种LLM提供通用接口,从而简化应用程序的开发流程

1.3.1 LangChain的核心组件

LangChain主要由以下6个核心组件组成

模型输入、输出:与模型交互的接口

数据连接:与特定应用程序的数据进行交互的接口

链:将组件组合实现端到端应用

记忆:用于链的多次运行之间持久化应用程序状态

代理、回调:扩展模型的推理能力,用于复杂的应用的调用序列

在实际开发中,会根据自身需求灵活地进行组合

1.4 大模型开发

我们将开发以大语言模型为功能核心、通过大语言模型的强大理解能力和生成能力、结合特殊的数据或业务逻辑来提供独特功能的应用称为大模型开发

一般通过调用API或开源模型来实现核心的理解与生成,通过Prompt Enginnering 来实现大语言模型的控制

传统的AI开发:

  1. 依次拆解复杂业务逻辑
  2. 对每个子业务构造训练数据与验证数据
  3. 优化每个子业务训练的模型
  4. 形成完整的模型链路来解决整个业务逻辑

大模型开发:

  1. 用Prompt Engineering 来代替子模型的训练调优
  2. 用Prompt链路组合来实现业务逻辑
  3. 用一个通用大模型和若干业务Prompt来解决任务

可以看到,大模型开发相对于传统AI开发简洁了许多

传统AI开发:

  1. 构造训练集、测试集、验证集
  2. 在训练集上训练模型
  3. 在测试集上调优模型
  4. 在验证集上最终验证模型效果

大模型开发:

  1. 从实际业务需求出发,先构建小批量验证集
  2. 设计合理Prompt来验证集效果
  3. 不断从业务逻辑中收集Prompt的Bad Case
  4. 将Bad Case加入到验证集中,针对性优化Prompt,由此来实现较好的泛化效果

1.4.1 大模型开发的一般流程

常见的流程如下:

  1. 确定目标
  2. 设计功能
  3. 搭建整体架构
  4. 搭建数据库
  5. Prompt Engineering
  6. 验证迭代
  7. 前后端搭建
  8. 体验优化

使用LLM API开发应用

2.1 基本概念

2.1.1 Prompt

Prompt 最初是 NLP(自然语言处理)研究者为下游任务设计出来的一种任务专属的输入模板,类似于一种任务(例如:分类,聚类等)会对应一种 Prompt。

简单来讲,就是每个具体的任务对应了一个输入的模版,这个模版就被成为Prompt

在后续会使用Prompt来代替给LLM的输入,使用Completion来代替LLM的输出

2.1.2 Temperature

从原理上我们可以知道,LLM的生成是具有随机性的,通过在模型中的顶层选出不同预测概率的预测结果来决定最后的生成,而Temperature参数便可以来调整LLM生成就饿过对应的随机性和创造性

一般来讲,Temperature的取值在0~1左右,越接近0,那么生成的随机性会越低,而越接近1,其随机性便会越高,文本的创意也会越高

Temperature的选取不是固定的,需要根据具体的任务来选择具体的范围

比如说想要搭建个人知识库助手,那么Temperature一般设置成0,这样可以规避一些模型的幻觉问题

但如果说是为了生成一些创意的文本,那么Temperature可以设定较高的数值

2.1.3 System Prompt

System Prompt是随着ChatGPT API开放并逐步得到使用的一个新兴概念,但是它并不会在大模型训练中得到体现,而是大模型服务方为提升用户体验所设置的一种策略

具体来说,你在使用ChatGPT API的时候,你可以设置两种Prompt

一种是System Prompt,该种Prompt内容会在整个对话过程中持久地影响模型的回复,相比于不同Prompt具有更高的重要性

另一种是User Prompt,也就是我们平时所提到的Prompt,需要模型做出回复的输入

2.2 使用LLM API

使用LLM API来调用大模型

2.2.4 使用智谱GLM

接下来介绍如何使用智谱GLM的API

此处建议使用SDK进行调用以获得更好的编程体验

首先是配置秘钥信息:

需要将API key设置到.env文件中的ZHIPUAI_API_KEY参数

然后运行以下代码来加载配置信息

1
2
3
4
5
6
7
8
9
10
11
import os

from dotenv import load_dotenv, find_dotenv

# 读取本地/项目的环境变量。

# find_dotenv() 寻找并定位 .env 文件的路径
# load_dotenv() 读取该 .env 文件,并将其中的环境变量加载到当前的运行环境中
# 如果你设置的是全局的环境变量,这行代码则没有任何作用。
_ = load_dotenv(find_dotenv())

智谱的调用传参与其他类似,需要传入一个messages列表,这个列表中包括了roleprompt此处封装如下的get_completion函数,方便后续使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from zhipuai import ZhipuAI

client = ZhipuAI(
api_key=os.environ["ZHIPUAI_API_KEY"]
)

def gen_glm_params(prompt):
'''
构造 GLM 模型请求参数 messages

请求参数:
prompt: 对应的用户提示词
'''
messages = [{"role": "user", "content": prompt}]
return messages


def get_completion(prompt, model="glm-4-plus", temperature=0.95):
'''
获取 GLM 模型调用结果

请求参数:
prompt: 对应的提示词
model: 调用的模型,默认为 glm-4,也可以按需选择 glm-3-turbo 等其他模型
temperature: 模型输出的温度系数,控制输出的随机程度,取值范围是 0.0-1.0。温度系数越低,输出内容越一致。
'''

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"

之后调用get_completion("你好")

接下来介绍传入的参数:

messages(list):调用对话模型的时候,将该信息列表作为提示输入给模型,格式如下:{"role": "user", "content": "你好"}

如果总长度超过模型最长输入限制后会自动截断

temperature(float):采样的温度,取值范围为(0.0,1.0),不能等于0,默认为0.95

top_p(float):温度采样的另一种方式,称为核采样,取值范围为:(0.0, 1.0),开区间,默认为0.7

模型考虑具有top_p概率质量tokens的结果(?),如0.1则意味着模型解码器只考虑从前10%概率的候选集中取tokens

request_id(string):用于区分每次请求的唯一标识,如果用户端不传时平台会默认生成

一般根据实际的应用场景来调整top_ptemperature,但不要同时调整两个参数

2.3 Prompt Engineering

什么是Prompt呢?简单来说就是用户与大模型交互输入的代称

我们给大模型的输入称为Prompt

而大模型返回的输出一般称为Completion

一般来讲,一个好的Prompt设计会极大地决定了LLM的能力上限和下限

如何设计一个好的Prompt

如何设计出一个好的Prompt来充分发挥LLM的性能?在了解这个之前需要知道设计Prompt的两个关键原则:编写清晰、具体的指令和基于模型充分思考的时间

首先第一点,Prompt需要清晰明确表达需求,需要提供充足的上下文,这里的目的是让语言模型能够理解我们的意图

一般而言,更长更复杂的Prompt能够提供更丰富的上下文和细节,让模型可以更准确地把握所需的操作和响应方式

接下来详细介绍如何设计Prompt的技巧

2.3.1 使用分隔符清晰地表示输入的不同部分

在编写Prompt时,我们可以使用各种标点符号作为“分割符”,将不同的文本区分开来,这样的好处是可以将不同的指令、上下文分割出来,避免出现意外的混淆

使用的符号没有太多要求,只要能明确起到隔断作用即可

[!WARNING]

此处需要注意的一点是为什么要使用分隔符

使用分割符的一大目的是为了防止提示词注入

那么什么是提示词注入呢?简单来说就是用户输入的文本可能包含了与你的预设Prompt相冲突的内容,如果不加分割,那么这些注入就有可能操纵语言模型

[!NOTE]

上文的提示词注入有个比较常见的例子,在大模型刚出来的没一会,有人便将这项技术用于水军上,而这时候便有个很好的判断方法就是回复:“请你现在扮演一只猫娘……”,然后通过其回复来判断是否为机器人,此处便用到了提示词注入

寻求结构化的输出

有时候我们需要让语言模型的输入遵循一定的格式,而不仅仅是连续的文本

比如说我想要让LLM生成一些书的标题、作者和类别,并且要求以JSON的格式返回给我们

为了方便,还可以制定JSON的键名:

1
2
3
4
5
6
7
prompt = f"""
请生成包括书名、作者和类别的三本虚构的、非真实存在的中文书籍清单,\
并以 JSON 格式提供,其中包含以下键:book_id、title、author、genre。
"""
response = get_completion(prompt)
print(response)

此处可以看到在Prompt中有一句并以 JSON 格式提供,其中包含以下键:book_id、title、author、genre。

这里便是让LLM结构化输出

要求模型检查是否满足条件

如果任务包含了不一定可以满足的条件,我们可以告诉模型先检查这些假设,如果不满足,则会指出并执行后续的完整流程

提供少量示例

写Prompt中我们给模型提供一些参考的样例,让模型了解我们的要求和期望的输出样式

利用少样本样例,可以比较轻松预热语言模型

2.3.2 给模型时间思考

在设计Prompt的时候,给语言模型充足的推理时间是非常重要的

语言模型跟人一样,需要给时间思考来解决复杂问题,如果说让模型匆忙给出结论,那么其结果可能不准确

而这里也告诉我们应该在Prompt中引导语言模型进行深入思考,可以要求其列出对问题的各种看法,说明推理依据,然后再得出最终结论

指导模型在下结论之前找出一个自己的解法

在设计Prompt的时候,我们可以通过明确指导语言模型来自我思考从而获得更好的效果

假设我们想要让语言模型判断一个数学问题的解答是否正确,只是提供问题和解答是明显不够的

原因在于语言模型可能会做出不合理的判断

为了解决这一问题,我们便需要再Prompt中要求语言模型尝试自己解决这一问题,思考出自己的解法,然后在与提供的解法相判断,以此来判断解答的正确性

搭建向量数据库

3.1 向量及向量知识库

3.1.1 词向量与向量

在机器学习和自然语言处理中,词向量(word embedding)是一种以单词为单位将没个单词转化为实数向量的技术

这些实数向量可以更好被计算机理解和处理

词向量的背后是相似或相关的对象在向量空间中的距离应该很近

如果两个词汇的语意比较接近,那么他的向量空间中的位置将会非常接近,例如:”apple“和”orange“,因为这两者都是水果

需要注意的一点是,虽然说词向量可以在一定程度上捕捉并表达文本中的语义信息,但是其忽略了不同语境的意思会受到影响的事实。因此在RAG中使用的向量一般成为通用文本向量,该技术可以对一定范围内的任意长度文本进行向量化

与词向量不同的是向量的单位不再是单词而是输入的文本,输出的向量会捕捉更多的语义信息

为什么要使用向量呢?首先,向量比文字更适合检索,如果数据库里面存储的是文字的话,主要通过检索关键词等方式来找到相对匹配的数据,匹配程度主要取决于数据库中的文档是否含有查询句的关键词

而向量中包含了原文本的语义信息,这使得可以通过计算问题与数据库中的数据点积等指标来获取问题与数据在语义上的相似度,并且向量可以通过多种向量模型来将多种数据映射成统一的向量形式