自从ChatGPT推出以来,短短几个月的时间内,市场上就涌现出了大量基于ChatGPT的产品,很多喜欢动手的同学也都开发、试验了不少自己的项目。我最近也基于ChatGPT的API动手写了几个小Demo,在这个过程中,有一个一直困扰我的点,那就是ChatGPT的输出不是很稳定,有时候并不能完全按照我的期望给出结果。
这里举个具体的例子来说明问题,之前我写过一个程序,想让它像钢铁侠的Jarvis一样,通过对话来帮我创建一个日历项,大家都知道创建日历项时对日期格式有严格要求,我前后反复修改了几十次Prompt,最后才终于能相对稳定地得到YYYY-MM-DD HH:MM这种格式的日期信息。这个过程费时费力,而且我也不确定这种稳定性会不会随着OpenAI升级模型而继续保持。
让ChatGPT的输出精准可控是一个很普遍的挑战,上述控制日期格式的问题只是一个小例子,这样的例子还有很多,比如有些场景下人们希望ChatGPT的输出不要包含某些特定的词语(如敏感话题的讨论),或者增加某些方面内容的输出,有时候人们希望ChatGPT的输出能严格按照某个条件终止,有时候人们希望ChatGPT输出的内容能自然衔接上下文等...
针对上面的这个问题,本文总结了一些小技巧,希望能对大家的动手实践有所帮助。
使用Insert模式自然衔接上下文:
假设我想写一篇文章,第一部分讲自己高中毕业(High school graduation),并在这个标题下给出了这个段落的第一句话,然后让ChatGPT帮我完成这个段落,结果如下:
然后在第二个段落,我想说自己需要搬家(move to San Francisco):
这时,我们就会发现刚才ChatGPT帮我们生成的内容和第二段搬家这个主题的切换很生硬,但这也很正常,因为ChatGPT在帮我们生成内容的时候并不知道我们接下来要写什么。
为了解决这个问题,ChatGPT为我们提供一种叫做Insert的模式,在这个模式下,ChatGPT能在一段文字中间的位置插入内容,并使得插入的内容和前后的上下文都能自然衔接。基于此,我们可以先想好文章大纲,之后再让ChatGPT插入(Insert)内容:
下面再简单介绍一下其背后的工作原理,不感兴趣的同学可跳过。Insert虽然从人类的角度看很正常,但是了解ChatGPT工作原理的同学都知道,ChatGPT的底层工作实际是一个从左向右,不断预测下一个单词的过程,现在要让它的预测下一个单词的时候,不但要考虑之前出现的单词还要考虑之后出现的单词,这实际是一个很大的挑战,ChatGPT是如何做到的呢?2022年OpenAI有一篇论文回答了这个问题:
The key to our approach, described in Section 3, is a transformation applied to a fraction of our dataset, in which we split documents into three pieces at random and move the middle piece to the end:document → (prefix, middle, suffix) → (prefix, suffix, middle)
For inference, we encode the given prefix and suffix and prompt the model with:
◦ Enc(prefix) ◦ ◦ Enc(suffix) ◦
简单说,就是在训练模型的时候,有意把一部分训练数据的顺序打乱,把一部分中间的内容挪到末尾,而在推理的时候,也是和训练时类似,把头、尾的信息作为Prompt给ChatGPT,让它生成中间的内容,感兴趣的同学可以在这里看论文:arxiv.org/pdf/2207.14…
使用Edit模式对输出进行精准控制:
在本文的一开始,我提到为了控制ChatGPT严格按照YYYY-MM-DD HH:MM的格式输出,我前后试验了几十个版本的Prompt才得到一个相对稳定的结果,ChatGPT实际上还提供了一个叫做e****dit的模式,顾名思义,在这个模式下,ChatGPT的工作不是要生成一段文本,而是要对已有的文本进行编辑,下面是来自OpenAI官网的一个例子:
接下来,我们来看看如何利用edit模式,来精准控制ChatGPT的输出,还是上文提到的创建日历项的例子:
这里的Input我直接用了json数据格式,得到的输出也是一个json并且填充上了具体的值,Instruction在截图中无法完全显示,下面是完整内容:
大家可以看到Specification部分基本和大家看到的API文档说明差不多,然后ChatGPT就能够从给出的句子中准确地提取信息,并严格按照给定的格式返回给我了。
Edit模式的工作原理我暂时还没找到论文或者相关文档,如果有了解的小伙伴欢迎私信。
使用logit bias控制关键词出现概率
我们还是用例子来说明问题,小时候学英语,相信大家一定都背过"Once upon a",一般只要出现这几个词,后面一般都会跟着time这个词,但假设我对"Once upon a time"这个说法看得太烦了,想让ChatGPT换个说法应该怎么做呢?
我们只需要在调用OpenAI的API的时候告诉它我们不想看到time这个词就可以了:
openai.Completion.create(
engine="davinci",
max_tokens=50,
temperature=0,
prompt = "Once upon a",
logit_bias={2435:-100, 640:-100},
)
具体地,在logit_bias这个参数这里,2345是"time"的token id,640是“ time"(time前有一个空格)的token id,-100表示绝对不要出现这个词(这里的取值范围为-100 ~100,-100表示绝对不要出现,100表示一定要出现),目前OpenAI的API在这里只接受token id而不接受直接输入单词,一个单词的token id可以通过调用tiktoken API获得。
调用上述函数得到的结果是:
Once upon a midnight dreary, while I pondered, weak and weary.
反过来,我们也可以提升某些关键词的出现概率,比如你想让ChatGPT为你生成一个食谱,你希望食谱中的烹饪方法多出现微波炉,那么你可以提升"微波炉"这个词(token id:27000)的logit_bias的取值:
openai.Completion.create(
engine="davinci-instruct-beta",
prompt="Write a recipe based on these ingredients and instructions:\n\nFrito Pie...",
temperature=0,
max_tokens=120,
top_p=1,
frequency_penalty=0,
logit_bias={27000:5},
presence_penalty=0
)
然后就会得到下面这样的输出:
1. Heat the chili in a microwave or on the stovetop.
...
使用Stop词及时终止ChatGPT的输出
有时候,ChatGPT容易犯话唠的问题,我们需要其适可而止,比如我在《玩转 ChatGPT 和 Self-Instruct:驯化你的个性化聊天机器人 Alpaca》一文中,用下面的模板让ChatGPT帮我生成一个任务列表:
GEN_INSTRUCTION = """Given the following examples, come up with 2 more tasks:
### examples ###
Task 0: {instruction0}
Task 1: {instruction1}
Task 2: {instruction2}
Task 3: {instruction3}
Task 4: {instruction4}
Task 5: {instruction5}
Task 6: {instruction6}
Task 7: {instruction7}
### 2 more tasks ###"""
但偶尔有些时候,ChatGPT会忽略"2 more tasks"的这个要求,而生成过多的tasks,因此我在代码里就指定了用"Task 10"作为Stop词,这样就能确保ChatGPT只会生成两个任务.
再比如在有些问答场景下,你希望ChatGPT只给出答案就好,不要过多解释,你就可以用比如句号或者换行符作为Stop词来实现这一目的:
以上就是精准控制ChatGPT输出的4个小技巧,希望能对大家有帮助。