【ChatGPT原理与应用开发】第三章:句词分类
原文链接:HuggingLLM(ChatGPT原理与应用开发)-课程详情 | Datawhale
此处仅为学习记录和总结
3:句词分类------句子Token都是类别
3.1:句词分类基础
NLU(自然语言理解)
natural language understanding
Query解析:用户输入查询(Query),需要系统给出响应的场景
🤔Query解析(例如:智能对话)的处理过程
-
情感分析
- 细粒度情感分析:对不同实体或属性的情感倾向
例如:商品评论 => 价格 & 快递 & 客服服务...
- 细粒度情感分析:对不同实体或属性的情感倾向
-
意图识别
-
多标签分类:给定输入文本,输出多个标签
例如:地址、时间、价格
-
层级标签分类:给定输入文本,输出从根节点到最终细粒度类别的路径
例如:地址/家庭地址、地址/公司地址
-
-
实体和关系抽取
- 实体:具有特定意义的实词
例如:人名、地名 - 关系抽取:实体和实体之间的关系判断,常用三元组表示
例如:曹雪芹(人名)、红楼梦(作品),编写(二者之间的关系),三元组表示为(曹雪芹,编写,红楼梦)
- 实体:具有特定意义的实词
句词分类
句子级别的分类:给一个句子,给出一个或多个标签
- 情感分析
- 意图识别
- 关系抽取
token级别的分类:给一段文本和一个问题,在文本中找出问题的答案(即:给一个句子,给出对应实体或答案的索引位置)
- 实体抽取
- 阅读理解
🤔token级别分类的例子
句子:中国四大名著之首红楼梦由清代作家曹雪芹编写
标注:token以字进行分割
中
国
四
大
名
著
之
首
红 begin-work
楼 inter-work
梦 inter-work
由
清
代
作
家
曹 begin-person
雪 inter-person
芹 inter-person
编
写
其他字的标注为 others
begin标签表示开始
inter标签表示内部
others标签表示其他非实体
work标签表示作品
person标签表示人名
句子分类,sequence classification
🤔句子分类的步骤
- 将给定句子或文本,变成embedding表示
- 将embedding表示传入神经网络,计算不同标签的概率分布
- 比较标签概率分布与真实标签,将误差回传,修改神经网络参数(训练神经网络)
- 得到训练好的神经网络(模型)
🤔单个标签的句子分类的示例
embedding数组:生成一个均值为0、标准差为1的1 * 32的高斯分布
维度1 => 词表里面只有1个token(即这1个句子就是1个token)
import numpy as np
np.random.seed(0)
# 0:均值,1:标准差,1*32:维度
emd = np.random.normal(0, 1, (1, 32))
句子实行三分类 ,模型参数 W
的维度为32 * 3
W = np.random.random((32, 3))
z = emd @ W
# z的结果
# z == array([[6.93930177, 5.96232449, 3.96168115]])
# z.shape == (1, 3)
标签概率分布 => 归一化z
def norm(z):
exp = np.exp(z)
return exp / np.sum(exp)
y = norm(z)
# y的结果,各个标签的概率求和后为1
# y == array([[0.70059356, 0.26373654, 0.0356699 ]])
# np.sum(y) == 0.9999999999999999
预测标签为0的概率最大,为70.06%
假设真实标签为1,为100%,而该标签对应在模型中的概率为26.37%,则下一次训练时会回传loss,调整参数 W
注意: W
可以是多个数组组成,只要最后矩阵乘法的结果是1 * 3即可
例如:W由3个子矩阵组成,分别是w1、w2和w3
w1 = np.random.random((32, 100))
w2 = np.random.random((100, 32))
w3 = np.random.random((32, 3))
y = norm(norm(norm(emd @ w1) @ w2) @ w3)
# y的结果
y == array([[0.32940147, 0.34281657, 0.32778196]])
🤔多个标签的句子分类的示例
假设有10个标签,需要表示10个标签的概率分布。例如:
def norm(z):
# 指定维度求和
axis = -1
exp = np.exp(z)
return exp / np.expand_dims(np.sum(exp, axis=axis), axis)
np.random.seed(42)
# embedding数组为1 * 32
emd = np.random.normal(0, 1, (1, 32))
# W数组为10 * 32 * 2
W = np.random.random((10, 32, 2))
y = norm(emd @ W)
# y数组为10 * 1 * 2
# 即10个标签最后均输出为1 * 2的矩阵
# y.shape == (10, 1, 2)
y == array([
[[0.66293305, 0.33706695]],
[[0.76852603, 0.23147397]],
[[0.59404023, 0.40595977]],
[[0.04682992, 0.95317008]],
[[0.84782999, 0.15217001]],
[[0.01194495, 0.98805505]],
[[0.96779413, 0.03220587]],
[[0.04782398, 0.95217602]],
[[0.41894957, 0.58105043]],
[[0.43668264, 0.56331736]]
])
输出每一行有两个值,分别表示"不是该标签"和"是该标签"的概率
token分类
🤔embedding数组的对比
假设给定文本的长度是10,维度是32,则:
- 句子分类为 (1, 32)
- token分类为 (1, 10, 32)
即:文本的每个token是一个32维的向量
假设标签有5个,则:
# 生成一个均值为0,标准差为1的1*10*32维度的embedding向量
emd = np.random.normal(0, 1, (1, 10, 32))
# 参数矩阵的维度是32*5
W = np.random.random((32, 5))
z = emd @ W
y = norm(z)
# y的维度,1*10*5
# y.shape == (1, 10, 5)
"""
y == array([[
[0.23850186, 0.04651826, 0.12495322, 0.28764271, 0.30238396],
[0.06401011, 0.3422055 , 0.54911626, 0.01179874, 0.03286939],
[0.18309536, 0.62132479, 0.09037235, 0.06016401, 0.04504349],
[0.01570559, 0.0271437 , 0.20159052, 0.12386611, 0.63169408],
[0.1308541 , 0.06810165, 0.61293236, 0.00692553, 0.18118637],
[0.08011671, 0.04648297, 0.00200392, 0.02913598, 0.84226041],
[0.05143706, 0.09635837, 0.00115594, 0.83118412, 0.01986451],
[0.03721064, 0.14529403, 0.03049475, 0.76177941, 0.02522117],
[0.24154874, 0.28648044, 0.11024747, 0.35380566, 0.0079177 ],
[0.10965428, 0.00432547, 0.08823724, 0.00407713, 0.79370588]
]])
"""
第一个token是0标签的概率是23.85%,是1标签的概率是4.65%,是2标签的概率是12.50%,是3标签的概率是28.76%,是4标签的概率是30.24%,因此标签概率最高的是4标签
而后根据第一个token的真实标签,进行损失回传,调整 W
矩阵的参数
3.2:ChatGPT接口使用
OpenAI的Completion接口
利用大模型的In-Context能力进行零样本或少样本的推理
-
in-context:模型根据输入的文本,可以自动给出对应的结果
-
零样本:直接给模型文本,获得标签或输出
-
少样本:给模型一些类似的样例(输入+输出),再拼上一个新的没有输出的输入,让模型给出输出
import openai
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
openai.api_key = OPENAI_API_KEYdef complete(prompt: str) -> str:
response = openai.Completion.create(
model="text-davinci-003",
prompt=prompt,
temperature=0,
max_tokens=64,
top_p=1.0,
frequency_penalty=0.0,
presence_penalty=0.0
)
ans = response.choices[0].text
return ans
🤔completion的参数说明
- model:指定的openai模型
- prompt:提示词,默认为<|endoftext|>
- temperature:采样温度,默认为1
- 取值范围:[0, 2]
- 值越小,输出更确定
- 值越大,输出更随机(多样化)
- max-tokens:生成的最大token数,默认为16
- 提示词 + 生成的文本,所有的token长度不能超过模型的上下文长度
- top-p:下一个token在累计概率为
top_p
的token中采样,默认为1- 0.8:只选择前80%概率的token进行下一次采样
- frequency_penalty:频次惩罚,默认为0
- 取值范围:[-2, 2]
- 正值:根据新token到目前为止在文本中的现有频率来惩罚新token
- 降低模型重复生成相同内容的可能性
- presence_penalty:存在惩罚,默认为0
- 取值范围:[-2, 2]
- 正值:根据新token到目前为止是否出现在文本中来惩罚
- 增加模型讨论新主题的可能性
🤔零样本示例
prompt只有问题和输出格式
prompt="""The following is a list of companies and the categories they fall into:
Apple, Facebook, Fedex
Apple
Category:
"""
ans = complete(prompt)
ans输出结果
模型返回每个公司所属的类别
ans == """
Technology
Facebook
Category:
Social Media
Fedex
Category:
Logistics and Delivery
"""
🤔少样本示例
prompt给出2个案例,以及需要回答的问题
prompt = """今天真开心。-->正向
心情不太好。-->负向
我们是快乐的年轻人。-->
"""
ans = complete(prompt)
ans输出结果
模型返回第三个句子的情感分类
ans == """
正向
"""
OpenAI的ChatCompletions接口
🤔ChatCompletions的参数说明
- model:指定模型
- messages:会话消息,支持多轮 (即,多条消息)
- 每条消息是1个字典,包含
role
和content
字段 - 例如:
[{"role": "user", "content": "Hello!"}]
- 每条消息是1个字典,包含
- temperature:采样温度
- top-p:采样范围的概率门槛
- stop
- max-tokens
- presence-penalty
- frequency-penalty
通用方法导入
import openai
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
openai.api_key = OPENAI_API_KEY
def ask(content: str) -> str:
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": content}]
)
ans = response.get("choices")[0].get("message").get("content")
return ans
零样本
prompt="""The following is a list of companies and the categories they fall into:
Apple, Facebook, Fedex
Apple
Category:
"""
ans = ask(prompt)
ans == """
Technology/Electronics
Facebook
Category:
Technology/Social Media
Fedex
Category:
Logistics/Shipping
"""
修改输出格式
prompt="""please output the category of the following companies:
Apple, Facebook, Fedex
The output format should be:
<company>
Category:
<category>
"""
ans = ask(prompt)
ans == """
Apple
Category:
Technology
Facebook
Category:
Technology/Social Media
Fedex
Category:
Delivery/Logistics
"""
prompt
🤔提示词工程的写法建议
- 清晰
- 描述具体
- 问题聚焦
- 间接
- 主题相关
3.3:相关任务与应用
文档问答
文档问答:用QA的方法召回一个相关的文档,让模型在这个文档中找出问题的答案
🤔流程
- 召回相关文档(给定问题与一批文档,计算相似度,从中选出相似度最高的那个文档)
- 做阅读理解任务(预测答案在原始文档中的位置索引,即开始和结束的位置)
- 大模型环境下,任务变为:将召回来的文档和问题以提示词的方式提交给大语言模型接口,直接让大模型帮忙得出答案
🤔提示词限制(保证输出结果的准确性)
- 要求根据给定的文本尽量真实地回答问题
- 如果答案未包含在给定文本中,就回复"我不知道"
模型微调
🤔出现分歧输出的调整手段
- 少样本:每次随机从训练数据集里抽几条样本(包括句子和标签),作为提示词的一部分
- 微调:把数据集按指定格式准备好,提交给微调接口,让接口微调一个在给定数据集上学习过的模型
🤔接口微调的步骤
- 准备数据
- 按照接口要求的格式修改数据集,至少包含一段文本和应该类别
- 在openai的接口中,需要有prompt、completion 这2列,例如:
{"prompt":"哈尔滨 东北抗日联军博物馆 ->","completion":" culture"}
- 微调
- 使用微调接口传递数据集
- 服务器自动完成微调,得到新模型ID
- 使用新模型推理
- 把接口的model参数 换成新模型ID
智能对话
智能对话:和用户通过聊天方式进行交互
🤔传统对话机器人的模块
- 自然语言理解模块NLU:对用户输入进行理解
- 对话管理模块DM:对话方向的控制
- 回复生成模块NLG:生成最终要回复的输出文本
🤔智能对话的关键设计
- 对用户输入进行敏感性检查,确认没问题后开始对话
- 存储用户消息,并在每轮对话时将用户历史消息传递给接口