Completions 是指GPT模型接收一个输入字符串,然后自动生成一个完成的输出字符串。这种功能通常用于生成文本,例如自动生成文章、电子邮件回复或聊天记录等。用户可以指定输入字符串的前缀,然后让模型生成可能的后缀。这个过程是自动的,不需要实时的交互,因此 completions 功能通常被认为是一种非交互式的应用。
Doc:https://platform.openai.com/docs/api-reference/completions/create
- 接口使用方法 {#title-0} ====================
POST https://api.openai.com/v1/completions
我们需要向该接口发送 POST 请求,并传递该任务相关参数即可完成 Completions 任务的调用。
import os
import openai
import requests
import json
import re
def get_completions(prompt, echo=False, n=1, top_p=0.9, max_len=200):
request_url = 'https://api.openai.com/v1/completions'
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + open('openai_api_key').read(),
}
# data 需要序列化为 json 字符串
data = json.dumps({
"model": "text-davinci-003",
# 是否在生成的文本包含输入的 prompt
'echo': echo,
'prompt': prompt,
# 生成的最大token数量
'max_tokens': max_len,
# 每一个候选token的概率
'top_p': top_p,
# 对于每个 prompt 生成候选的数量
'n': n,
'temperature': 0,
})
response = requests.post(request_url, headers=headers, data=data)
response = json.loads(response.text)
generate_text = []
for text in response['choices']:
# 去除控制字符
text = re.sub(r'[\x01-\x1F]', ' ', text['text'])
# 去除多余空格
text = ' '.join(text.split())
generate_text.append(text)
return generate_text
if __name__ == '__main__':
result = get_completions('请随机生成一副对联')
print(result)
result = get_completions('帮我随机生成一篇自我介绍')
print(result)
生成结果:
['上联:满山红叶秋色浓 下联:江水碧波夕阳红']
['大家好,我叫XXX,来自XXX,是一名XXX专业的大学生。我有着丰富的学习经历,曾参加过多次社会实践,积累了丰富的实践经验。我喜欢阅读,喜欢探索新领域,乐于分享自己的知识和经验。我乐观开朗,']
- 模型参数详解 {#title-1} ====================
以下较为简单的模型参数:
- prompt 是送入模型的内容,模型根据对该内容的理解生成后续的内容
- suffix 在模型生成的补全文本之前增加的前缀
- max_tokens 生成的最大 token 数量,如果模型输出时碰到结束词或者最大 token 数量将会停止生成
- n 可以指定生成并返回多少条候选结果
- stop 它告诉模型在何时停止生成文本,可以是一个单词、一个短语、一个特殊字符,例如:句号
- echo 表示是否把 prompt 文本添加到返回的 Completions 前面
- logprobs 用于分析模型的输出行为
- model 指的是模型的名字,其中在 Completion 任务中可用的模型如下:
其中,直观生成文本效果比较好的是:text-davinci-003 模型,文档里也提到该模型要比 curie、babbage、 ada 模型性能和效果要更好好。
top_p 参数 是影响到每一个 token 产生时,模型要考虑那些 Token。例如:top_p 设置为 0.8,当前时间步候选的词的概率分布为:
A 0.4
B 0.35
C 0.2
D 0.05
E 0.04
此时,前两个 Token A + B 的总概率为 0.75,再加上 C 的概率,恰好大于等于设置的 top_p 阈值。所以,这一时间步我们只考虑 A、B、C 作为候选词,模型会从这 3 个 Token 中随机选择一个作为当前时间步的输出。这种采样方法也叫做 nucleus sampling,top_p 中的 p 指的是 probability。
从这里,可以看到较大的 top_p 值可以增加每一个时间步候选 Token 数量,从而增加生成文本的多样性。该值默认为 1。
temperature 参数 会影响到模型预测 Token 的概率分布,从而使得能够将一些原来不可能作为候选的 Token 纳入到候选序列中,默认值为 1 不使用温度参数。它是如何影响到概率分布的呢?先看下 temperature 参数是如何参与到 Token 概率分布的 SoftMax 公式 中:
公式中 \(x_{i}\) 表示模型对预测当前时间步为 i 的 logits。加上 Temperature 之后,对每个 logits 值产生了影响,此时 softmax 结果肯定会受到影响。该参数在当前场景下的取值范围为 [0, 2],我们可以通过一个实验来看看该值是如何影响到候选 Token 的概率分布,下面给出一个计算代码,重点关注计算结果:
import torch
import torch.nn as nn
# 固定随机数种子
torch.manual_seed(66)
# 当前时间步预测每一个Token的logits
logits = torch.randn(1, 3)
print(logits)
# 前时间步预测每一个Token的概率分布
probas = torch.softmax(logits, dim=-1)
print('temperature=1的概率分布:', probas)
print('#' * 60)
print()
# temperature=[0, 1)
def test01():
# 加入温度参数
T_logits = logits / 0.2
print(T_logits)
# 前时间步预测每一个Token的概率分布
probas = torch.softmax(T_logits, dim=-1)
print('temperature=0.2的概率分布:', probas)
print('-' * 60)
T_logits = logits / 0.8
print(T_logits)
# 前时间步预测每一个Token的概率分布
probas = torch.softmax(T_logits, dim=-1)
print('temperature=0.8的概率分布:', probas)
# temperature=(1, 2]
def test02():
# 加入温度参数
T_logits = logits / 1.2
print(T_logits)
# 前时间步预测每一个Token的概率分布
probas = torch.softmax(T_logits, dim=-1)
print('temperature=1.2的概率分布:', probas)
print('-' * 60)
T_logits = logits / 1.8
print(T_logits)
# 前时间步预测每一个Token的概率分布
probas = torch.softmax(T_logits, dim=-1)
print('temperature=1.8的概率分布:', probas)
if __name__ == '__main__':
test01()
print('\n')
test02()
程序输出结果:
tensor([[ 1.8289, -0.2198, 0.3424]])
temperature=1的概率分布: tensor([[0.7380, 0.0951, 0.1669]])
############################################################
tensor([[ 9.1447, -1.0989, 1.7122]])
temperature=0.2的概率分布: tensor([[9.9937e-01, 3.5563e-05, 5.9133e-04]])
------------------------------------------------------------
tensor([[ 2.2862, -0.2747, 0.4281]])
temperature=0.8的概率分布: tensor([[0.8109, 0.0626, 0.1265]])
tensor([[ 1.5241, -0.1831, 0.2854]])
temperature=1.2的概率分布: tensor([[0.6798, 0.1233, 0.1970]])
------------------------------------------------------------
tensor([[ 1.0161, -0.1221, 0.1902]])
temperature=1.8的概率分布: tensor([[0.5687, 0.1822, 0.2490]])
我们可以将 Temperature 的值分为三部分来考虑:
- Temperature=1,表示模型只考虑正常的模型输出得到的 Token 的概率分布
- Temperature=[0, 1) 我们会发现温度参数值越低,则原来概率较大的 Token 概率会变得更大,概率较小的 Token 的概率会变得越小,这就使得生成文本时更多的考虑原来概率较大的词作为输出。使得模生成文本的多样性收到了限制,生成的文本更单一。简单来说,原来较大的概率变得更大,原来较小的概率变得更小。
- Temperature=(1, 2] 我们会发现,原来概率较大的 Token 的概率值会变得更小一些,原来概率较小的 Token 的概率会变得越大,此时你会发现原来较小概率的 Token 就有更多的机会被作为候选,使得模型生成的文本更具有多样性。简单来说,原来较大的概率变得较小,原来较小的概率变得较大,更加平均。
如果结合 top_p 参数,这个效果更加明显。大于 1 的温度值会使得概率分布更加平均,更多的 Token 会被纳入到 top_p 中,从而增加了生成文本的多样性。
注意一点的是,我前面实现并未考虑温度值等于0时的计算,当设置温度值为0时,我们可以使用 1e-9 等等非常小的常数来代替,避免出现除0异常。此时,模型输出变得单一,并且更多的考虑概率最高的 Token.
presence_penalty 和 frequency_penalty 两个参数也是和文本多样性有关的参数。这两个参数都在考虑某个 Token 是否出现在之前生成的序列中,如果出现了则进行惩罚。文档中给出的惩罚计算公式如下:
从计算公式可以看到:
- presence_penalty 只要当前 Token 在之前的序列中出现,则对该 Token 的 logits 进行惩罚
- frequency_penalty 则也考虑了当前 Token 在之前出现的次数,出现的次数越多则惩罚越重
从这里,我们也可以看到,这两个参数设置的目的是为了让后续的序列生成时尽可能避免重复,增加生成内容的多样性。
logit_bias 参数默认值为 null 表示不使用 logit 偏置,它也能够像前面的 temperature、presence_penalty、frequency_penalty 等一样改变 Token 的概率分布。在模型生成概率分布之前添加到模型的输出中。具体效果会因模型而异,但介于-1到1之间的值应该会减少或增加选中单词的概率;而像-100或100这样的值则会禁止或排他性地选择相关单词。举个例子,可以传递{"50256": -100}来防止模型生成特定的单词。由于某个 Token 值加上 -100 之后将会变得非常小,计算得到的其概率值将会非常非常小,即:被选中作为候选的可能性非常小。
best_of 参数会控制生成候选自动补全的数量,n 参数则指定从 best_of 中选择多少个结果返回。需要注意的是,best_of 必须大于 n。