ChatGPT原理与应用开发
上QQ阅读APP看书,第一时间看更新

1.3 ChatGPT基础

1.3.1 最强表示架构Transformer设计与演变

接下来出场的是Transformer,它是一个基于注意力机制的编码器-解码器(encoder-decoder)架构,刚开始主要应用在NLP领域,后来横跨到语音和图像领域,并最终统一几乎所有模态(文本、图像、语音)的架构。Transformer来自谷歌公司在2017年发表的一篇论文“Attention Is All You Need”,其最重要的核心就是提出来的自注意力(self-attention)机制。简单来说,就是在语言模型构建过程中,把注意力放在那些重要的Token上。

Transformer简单来说,就是先把输入映射到编码器(encoder),这里大家可以把编码器想象成前面介绍的RNN,解码器(decoder)也可以想象成RNN。这样,左边负责编码,右边则负责解码。这里不同的是,左边因为我们是知道数据的,所以在建模时可以同时利用当前Token的历史(后面的)Token和未来(前面的)Token;但在解码时,因为是一个个Token输出来的,所以只能根据历史Token以及编码器的Token表示进行建模,而不能利用未来Token。

Transformer的这种架构从更普遍的角度来看,其实是Seq2Seq(sequence to sequence)架构,简单来说就是序列到序列的架构:输入是一个文本序列,输出是另一个文本序列。翻译就是一个很好的例子,如图1-6所示。

图1-6 Seq2Seq架构示意图(摘自GitHub的“google/seq2seq”项目)

刚刚已经讲了,编码器和解码器可以采用RNN,编码器这一侧的每个Token都可以输出一个向量表示,而这些所有Token的输出向量都可以在处理后作为整句话的表示。说到这里,整句话又怎么表示呢?前面曾提到,对于RNN这种结构,可以把最后一个Token的输出作为整个句子的表示。当然,很符合直觉的,你也可以取每个词向量的平均值。除了平均值,也可以求和、取最大值等,我们就不更深入讨论了。现在重点来了,看解码的过程,仔细看,其实解码器在生成每一个Token时都用到了编码器中每一个Token的信息,以及已经生成的那些Token的信息。前面这种关注编码器中每个Token的信息的机制就是注意力(attention)机制。直观的解释,就是当生成单词“power”时,“力量”两个字会被赋予更多权重(注意力),其他情况也类似。

好了,现在让我们带着之前的记忆,看一下Transformer的整体结构,如图1-7所示。

图1-7 Transformer的整体结构(摘自论文“Attention Is All You Need”)

在图1-7中,左边是编码器,一共有N个;右边是解码器,也有N个。为简单起见,我们可以假设N=1,如此一来,图1-7的左边就是一个编码器,右边则是一个解码器。也可以把它们想象成一个RNN,这样有助于从宏观上把握。现在,我们回到现实,Transformer用到的东西其实和RNN并没有关系,这一点通过图1-7也可以很明显地看出来。Transformer主要用了两个模块:多头注意力(multi-head attention)和前馈(feedforward)网络。

对于多头注意力,我们不妨回顾一下Seq2Seq架构的注意力机制,它是解码器中的Token和编码器中每一个Token的重要性权重。多头注意力中用到了自注意力(self-attention),自注意力和刚刚讲的注意力非常类似,只不过自注意力是自己的每一个Token之间的重要性权重。简单来说,就是“一句话到底哪里重要”。自注意力机制可以说是Transformer的精髓,无论是ChatGPT还是其他非文本的大语言模型,都用到了它,它可以说是真正地“一统江湖”。多头(multi-head)简单来说,就是把刚刚的这种自己注意自己重复多次,每个头注意到的信息不一样,这样就可以捕获到更多信息。比如我们前面提到过的一句话——“人工智能让世界变得更美好”,有的头“人工智能”注意到“世界”,有的头“人工智能”注意到“美好”……这样看起来更加符合直觉。

前馈网络主要引入非线性变换,帮助模型学习更复杂的语言特征和模式。另外,有个地方要特别注意,解码器的淡黄色模块内有一个遮盖多头注意力(masked multi-head attention),它和多头注意力的区别就是遮盖(mask)了未来Token。以本小节开头提到的翻译为例,当给定“Knowledge”生成下一个Token时,模型当然不知道下一个Token就是“is”。还记得前面讲过的学习(训练)过程吗?下一个Token是“is”,这是训练数据里的,模型输出什么要看Token最大概率是不是在“is”这个Token上,如果不在,参数就得更新。

实际上,大多数NLP任务并不是Seq2Seq架构的,最常见的任务主要包括如下几种:句子分类、Token分类(也叫序列标注)、相似匹配和文本生成,前三种应用得最为广泛。这时候,编码器和解码器就可以拆开用了。左边的编码器在把句子表示成一个向量时,可以利用上下文信息,也就是说,可以把它看作双向的;右边的解码器不能看到未来Token,一般只利用上文信息,是单向的。虽然它们都可以用来完成刚才提到的几种任务,但从效果上来说,编码器更适合非生成类任务,解码器则更适合生成类任务。在NLP领域,一般也会把它们分别叫作自然语言理解(natural language understanding,NLU)任务和自然语言生成(natural language generation,NLG)任务。上面提到的这些任务,后面都会进一步介绍,这里大致了解一下即可。

我们首先介绍NLU任务。句子分类是指给定一个句子,输出一个类别。因为句子可以表示为一个向量,所以经过张量运算后,自然可以映射到每个类别的概率分布。这和前面提到过的语言模型的做法没有本质上的区别,只不过语言模型的类别是整个词表大小,而分类的类别则要看具体的任务,有二分类、多分类、多标签分类等。Token分类是指给定一个句子,给其中的每个Token输出一个类别。这和语言模型就更像了,只不过把下一个Token换成了对应的类别,比如命名实体抽取就是把句子中的实体(人名、地名、作品等你所关注的词,一般是名词)提取出来。如果以地名(location,LOC)举例的话,对应的类别是这样的:B-LOC(begin of LOC)表示实体开始、I-LOC(inside of LOC)表示实体中间。举个例子:“中国的首都是北京”。注意此时的Token是字,每个Token对应的类别为“B-LOC、I-LOC、O、O、O、O、B-LOC、I-LOC”,O表示Other。对于分类任务,类别一般也叫作标签。相似匹配一般指给定两个句子,输出它们是否相似,其实可以将其看作特殊的分类任务。

接下来介绍NLG任务。除文本续写外,其他常见的NLG任务还有文本摘要、机器翻译、文本改写、文本纠错等。这里Seq2Seq架构就比较常见了,体现了一种先理解再输出的思路。而纯生成类任务,比如写诗、写歌词、写小说,则几乎是纯解码器架构。此类任务稍微麻烦的是如何做自动评测,文本摘要、机器翻译、文本改写、文本纠错等任务一般都会提供参考答案(reference),可以评估模型输出和参考答案之间的重叠程度或相似程度,但纯生成类任务就有点麻烦,这个好不好有时候其实很难衡量。不过,针对有具体目标的任务(如任务型聊天机器人的回复生成),还可以设计一些诸如“是否完成任务”“是否达到目标”的评测方法。但对于没有具体目标的任务(比如闲聊),评测起来就见仁见智了,很多时候还得靠人工进行评测。

Transformer基于Seq2Seq架构,可以同时处理NLU和NLG任务,而且这种自注意力机制的特征提取能力(表示能力)很强。其结果就是NLP取得了阶段性的突破,深度学习开始进入微调模型时代,大概的做法就是,拿着一个开源的预训练模型,在自己的数据上微调一下,让它能够完成特定的任务。这个开源的预训练模型往往就是一个语言模型,在大量语料中,使用我们前面所讲的语言模型的训练方法训练而来。偏NLU领域的第一个成果是谷歌公司的BERT,相信不少人即便不是这个行业的也大概听过。BERT就是使用了Transformer的编码器(没有使用解码器),有12个Block(图1-7左侧的淡黄色模块,每一个Block也可以叫作一层)和1亿多个参数。BERT不预测下一个Token,而是随机地把15%的Token盖住(其中80%用[MASK]替换,10%保持不变,10%随机替换为其他Token),然后利用其他没盖住的Token来预测盖住位置的Token。这其实和根据上文信息预测下一个Token是类似的,所不同的是它可以利用下文信息。偏NLG领域的第一个成果是OpenAI的GPT,GPT就是使用了Transformer的解码器(没有使用编码器),参数和BERT差不多。BERT和GPT都发布于2018年,然后分别走上了不同的道路。

1.3.2 生成语言模型GPT进化与逆袭

GPT,就是ChatGPT中的那个GPT,中文叫作生成式预训练Transformer。生成式的意思就是类似于语言模型那样,一个Token一个Token地生成文本,也就是上面提到的解码器的原理。预训练刚刚也提过了,就是在大量语料中训练语言模型。GPT模型从GPT-1到GPT-4,一共经历了5个版本,中间的ChatGPT是3.5版。GPT-1、GPT-2和GPT-3都是有论文发表的,接下来分别介绍它们的基本思想。ChatGPT没有论文发表,不过它的姐妹版本InstructGPT有论文发表,我们放在1.3.3节介绍。GPT-4也没有论文发表,只有技术报告,不过里面并没有技术细节。因此,我们对GPT-4不做介绍,读者可以将其看作能力更强的ChatGPT升级版。

GPT-1和BERT一样,用的是下游任务微调范式,也就是在不同下游任务数据上微调预训练模型,如图1-8所示。

图1-8 GPT-1基本结构和下游任务微调范式(摘自GPT-1论文“Improving Language Understanding by Generative Pre-Training”)

关于图1-8左边的GPT-1基本结构,我们在前面已经介绍过了,用的是Transformer的解码器,不过这里因为没有编码器,所以不需要有和编码器交互的多头注意力模块。现在重点看看图1-8的右边,这是GPT-1在各种下游任务上的处理流程。简单来说,就是针对不同的任务构造不同的输入序列,然后丢给GPT-1获取Token或句子的Embedding表示,再通过Linear+Softmax输出结果。Linear是一种最基础的网络结构,也就是线性映射,这里用于维度转换,转为输出需要的大小。Softmax主要用来把输出映射到概率分布(概率和为1)。这种拼接输入的方法在当时非常流行,紧跟其后的BERT也使用类似的方式,并引领了一个时代,直至ChatGPT的出现让我们进入大语言模型时代(不过,针对很多传统NLP任务BERT依然具备优势)。统一的处理方法能够减小不同任务对模型的适配难度。因此不管什么任务,都想方设法将其变成一个序列就行,比如在图1-8中,相似匹配就是把两句话直接拼接起来,预测它们是否相似(输出标签为1或0)。

GPT-1的这篇论文还有几个点在当时看起来可能没什么感觉,现在回看却有点意思。第一,预训练模型中的每一层(图1-8中的淡黄色模块)都包含用于解决目标任务的有用功能,多层(意味着模型更深)有更多能力;第二,随着参数的增加,零样本获得更好的性能。简单总结就是,模型大了不仅能学到更多知识,有助于解决下游任务,还表现出了零样本能力。这里的零样本(zero-shot)是指直接给模型输入任务,让它输出任务结果。与此类似的还有少样本(few-shot)和单样本(one-shot),即给模型提供一些(或一个)示例,然后给出任务,让它输出任务结果。

有了上面的结论,你是不是想看看更多层(更多参数)的表现如何?于是半年后,GPT-2来了,参数量从GPT-1的1.1亿增加到了15亿,增长了十几倍。更有意思的是,GPT-1的博客文章“Improving language understanding with unsupervised learning”中有一个“未来工作列表”,排在第一位的就是扩大规模,还有两个分别是提升微调,以及更好地理解为什么生成式预训练能提升NLU能力。

GPT-1发布于2018年6月,GPT-2发布于2019年2月,GPT-2是GPT-1的升级版,主要在两个方面进行进一步研究:首先是扩大规模,然后是零样本。如果说GPT-1是观察到了“规模大、能力强的零样本”这个现象,那么GPT-2就是进一步研究这个现象。其结果自然是,模型越来越大,参数越来越多,能力越来越强。GPT-2进一步验证了GPT-1的想法,下一步要做的就是继续扩大规模。

不过且慢,在此之前,我们不妨看一下GPT-2中的Token生成策略,也就是生成下一个Token的方法。前面介绍过比较优秀的集束搜索,不过它有两个比较明显的问题:第一是生成的内容容易重复,第二是高质量的文本和高概率并不一定相关(有时甚至完全没有关系)。简单来看,这两个问题其实可以归结为一个问题:生成的内容依然确定性太大。人们更希望有“不一样”的内容,而不是完全可预测的内容,比如张爱玲说过,“孤独的人有他们自己的泥沼”,这种独一无二的文字用高概率的词大概率是得不到的。

现在,我们介绍一种基于采样的方法,简单来说,就是根据当前上下文得到的概率分布采样下一个Token。这里可以用一个温度(temperature)参数调整输出的概率分布,参数值越大,分布看起来就越平滑,也就是说,高概率和低概率的差距变小了(对输出不那么确定);当然,这个参数值越小的话,高概率和低概率的差距就会更明显(对输出比较确定);如果这个参数值趋近于0,那就和贪心搜索一样了。请看下面的代码示例。

import numpy as np
 
np.random.seed(42)
logits = np.random.random((2, 4))
logits /= temperature
scores = np.exp(logits)
probs = scores / np.sum(scores, axis=1, keepdims=True)

我们让温度参数分别取0.1和0.9,结果如下。

# temperature=0.1
array([[0.003, 0.873, 0.098, 0.026],
[0.001, 0.001, 0.   , 0.998]])
 
# temperature=0.9
array([[0.176, 0.335, 0.262, 0.226],
[0.196, 0.196, 0.176, 0.432]])

以第一行为例,当温度为0.1时,概率最大值为0.873;当温度为0.9时,概率最大值依然在同样位置(这是必然的),但值变为0.335。而且,你也可以很明显地看出来,当温度为0.9时,4个数字看起来更加接近。

还有一个重复惩罚参数(repetition_penalty),它可以在一定程度上避免生成重复的Token。它和温度参数类似,只不过是将温度放到了“已生成的Token”上。也就是说,如果有Token之前已经生成过了,我们就会在生成下一个Token时对那些已生成的Token的分数进行平滑,让它们的概率不那么大。所以,这个参数值越大,越有可能生成和之前不重复的Token。

除了这些技巧,2018年的一篇论文“Hierarchical Neural Story Generation”另外介绍了一种新的采样方案,它很简单也很有效果,它就是GPT-2里使用到的Top-K采样。简单来说,就是在选择下一个Token时,从Top-K(根据概率从大到小的前K个)个Token里面选。这种采样方案不错,不过还有个小问题,就是Top-K采样其实是一种硬截断,根本不管第K个概率是高还是低。在极端情况下,如果某个词的概率是0.99(剩下的所有词加起来才0.01),K稍微大一点就必然会囊括进来一些概率很低的词。这会导致生成的内容不连贯。

于是,2019年的一篇论文“The Curious Case of Neural Text Degeneration”提出了另一种采样方案——Top-P采样,GPT-2里也有用到这种采样方案。这种采样方案是从累积概率超过P的词里进行选择。这样,对于概率分布比较均匀的情况,可选的词就会多一些(可能几十个词的概率和才会超过P);对于概率分布不均匀的情况,可选的词就会少一些(可能两三个词的概率和就超过了P)。

Top-P采样看起来更优雅一些,两者也可以结合使用。不过在大部分情况下,当我们需要调参数的时候,调一个参数就好,包括前面的温度参数。如果要调多个参数,请确保理解每个参数的作用。最后需要说明的是,任何一种采样方案都不能100%保证每一次生成的效果都很好,也没办法完全避免生成重复的句子,也没有任何一种采样方案在任何场景下都适用。读者在使用时需要根据实际情况多尝试,选出效果最好的配置。不过,建议读者从官方给的默认参数开始尝试。

GPT-3发布于2020年7月,这在当时也是个大新闻,因为它的参数已经达到其他任何模型在当时都望尘莫及的量级——1750亿,是GPT-2的100多倍,没有开源。GPT-3既然有零样本能力,那能不能不微调呢?碰到一个任务就微调,这多麻烦。对于人来说,只要几个例子(少样本)和一些简单的说明,就可以处理任务了。怎么办?GPT-2不是进一步确认了零样本能力吗?继续加大参数量,于是就有了GPT-3。也就是说,各种任务来吧,不调参数,顶多就要几个例子(预计下一步连例子也不要了),GPT-3就能帮你完成它们。其实现在回头看,这篇论文是具有里程碑意义的,因为它从根本上触动了原有的范式,而且是革命性的触动。关于这一点,感兴趣的读者可以进一步阅读笔者的一篇文章《GPT-3 和它的 In-Context Learning》。现在回忆,1750亿的参数量在当时看太大了,而且也太贵了(几百万美元),一般的单位和个人根本负担不起。关于这一点,不光小部分人没意识到,可能是除了OpenAI团队之外的整个世界都没意识到。

请看图1-9,横坐标是样本数量,纵坐标是精准度。图1-9提供了如下信息。

x-shot(x表示zero、one、few)在不同参数规模下差别巨大,大语言模型有超能力。

在大语言模型下,单样本效果明显大幅提升,增加提示词会进一步大幅提升效果。

少样本的边际收益在递减。大概在8样本以下时,提示词作用明显,但从单样本到8样本,提示词的效果提升幅度也在递减。当超过10样本时,提示词基本就没有作用了。

图1-9 x-shot在不同参数规模下的表现(摘自GPT-3论文“Language Models are Few-Shot Learners”)

总而言之,大语言模型具有In-Context(上下文)学习能力,这种能力使得它不需要针对不同任务再进行适应性训练(微调),大语言模型用的就是它自己本身的理解力。这本来应该很让人震惊(甚至有一点惊恐),不过大家可能都先被它的价格和规模震惊到了。接下来,我们再直观地感受一下利用这种In-Context学习能力完成任务的方式,如图1-10所示。

图1-10 使用In-Context学习能力和微调完成任务(摘自GPT-3论文“Language Models are Few-Shot Learners”)

图1-10右边的微调方式需要先根据训练样本更新模型参数,之后再进行预测。图1-10左边的三种方式都利用了大语言模型(large language model,LLM)的In-Context学习能力,不需要更新模型,而且看起来也都不复杂,只需要按照格式把输入构建好,然后传给模型进行预测就可以了。这也是本书写作的初衷之一——人工智能已经平民化,只要有手(可能以后不用手也行),通过使用LLM就可以做出人工智能应用了。不过这里有一点需要说明,为了简便,图1-10中的样本都比较简单,但实际中的样本一般是完整的句子。

最后值得一提的是GPT-3论文中的展望,在GPT-3论文的“局限”小节中,作者提出了GPT-3目前的一些问题,其中有两点需要特别指出,因为它们是下一代InstructGPT(也是ChatGPT的姐妹版)以及更高级版本的方向。

自监督训练(也就是语言模型一般的训练方法)范式已到极限,新的训练方法迫在眉睫。未来的方向包括:从人类那里学习目标函数、强化学习微调或多模态。

不确定少样本是在推理时学习到新的任务,还是识别出来了在训练时学到的任务。最终,甚至不清楚人类从零开始学习与从之前的样本中学习分别学到了什么。准确理解少样本的工作原理是未来的一个方向。

上面的第一点在1.3.3节就会提到,这里主要说说第二点。当我们给出一些示例(少样本)时,我们还无法精准确定是在推理时“学习”到新任务的处理方法(在这种情况下,没有示例就没有能力;这里的“学习”要打引号,因为它不调整参数),还是在训练时就已经具备了这个能力,示例只是让它“回想”起之前学的东西。这里有点绕,拿人来举例,可能不太恰当,但能大致说明问题。假设当你读到一首诗时,自己也诗兴大发写了一句诗。你说这句诗是因为你读到这首诗时“领悟”到的,还是你本来就有这个积累(记忆),现在只是因为读这首诗而被激发出来?这可能涉及大脑、思维、意识等领域知识,而人类至今也没有弄清楚它们的原理,所以我们现在还不知道答案。

1.3.3 利器强化学习RLHF流程与思想

RLHF(reinforcement learning from human feedback,从人类反馈中强化学习)听起来有点平淡无奇。确实,RLHF的思想非常朴素、简单,但它有着不可忽视的效果。刚刚我们已经提到了,GPT-3论文指出未来要找到新的训练方法,其中就包括从人类那里学习目标函数、强化学习微调、多模态等。时至今日,从InstructGPT到ChatGPT,再到GPT-4,人类正一步一步地实现这些新的训练方法。这里有一点需要提醒,这些方向并不是一开始就清晰地摆在那里的,中间还有非常多的探索和阶段性成果(既有OpenAI自己的研究,也有其他从业人员的研究)。千万不要看到结果觉得平淡无奇,这中间的艰难探索永远值得尊敬。另外,有时候即便知道了方法,要做出来,还要做出效果来,也是非常有难度的。而且本书只能介绍少部分内容,虽然整体结构比较完整,但总体还是比较简单。总的来说,要做出来很有难度,不过我们如果只是用的话,如前所述,有手就行。

好了,言归正传,RLHF被人熟知应该主要源自OpenAI的InstructGPT论文“Training language models to follow instructions with human feedback”,更大范围的熟知就是ChatGPT的发布。因为后者没有论文发表,也没有开源,所以我们也只能“拿InstructGPT的管窥一窥ChatGPT的豹”。当然,如果按照ChatGPT官方页面上的说法,ChatGPT是InstructGPT的姐妹版,那么这个“管”可能还比较粗。如果用简单的语言来描述InstructGPT,其实就是用强化学习的算法微调一个根据人类反馈来加以改进的语言模型,重要的是还调出了效果——规模为130亿的InstructGPT堪比规模为1750亿的GPT-3。

现在我们来看看具体是如何做的,RLHF在其中又起了什么作用,以及如何起作用。InstructGPT的整个流程分为三个步骤,如图1-11所示。

图1-11 InstructGPT流程图(摘自InstructGPT论文“Training language models to follow instructions with human feedback”)

步骤一:SFT(supervised fine-tuning,有监督微调)。顾名思义,SFT是在有监督(有标注)数据上微调训练得到的。这里的有监督数据其实就是输入提示词,输出相应的回复,只不过这里的回复是人工编写的。这个工作要求比一般标注要高,其实算是一种创作。

步骤二:RM(reward model,奖励模型)。具体来说,将一个提示词丢给前一步的SFT,输出若干(4~9个)回复,由标注人员对这些回复进行排序。然后从4~9个回复中每次取两个,因为是有序的,所以可以用来训练RM,让模型学习到好坏评价。这一步非常关键,它就是所谓的人类反馈(human feedback),用于引导下一步模型的更新方向。

步骤三:RL(reinforcement learning,强化学习),使用PPO进行训练。PPO(proximal policy optimization,近端策略优化)是一种强化学习优化方法,它背后的主要思想是避免每次太大的更新,提高训练的稳定性。具体过程如下:首先初始化一个语言模型,然后丢给它一个提示词,生成一个回复,用上一步的RM给这个回复打分,将这个打分回传给模型更新参数。这里的语言模型在强化学习视角下就是一个策略。这一步有个很重要的动作,就是在更新模型时考虑模型每一个Token的输出和SFT输出之间的差异性,要让它们尽量相似。这是为了缓解强化学习可能的过度优化。

就这样?对,就这样,RLHF都表现在上面了,效果大家都知道了。虽然ChatGPT没有相关论文发表,但我们基本相信它也是基于类似的思路实现的。当然,这里面细节非常多,即便知道了这个思路,也不一定能复现出来。这在深度学习时代很正常,里面的各种小设计、小细节实在太多了。当它们堆积到一定量时,造成的差别是很难一下子弥补的,如果别人不告诉你,那你就只能自己慢慢做实验去逐步验证了。

下面我们强行解释一下RLHF是如何起作用的,以及为什么它现在能成为一个基本的范式。其实,对于将强化学习用在NLP领域一直以来都有研究,正好笔者也由于一些原因一直在关注文本生成,以及强化学习在文本生成方面的研究。这里可能有两个难点:一是训练的稳定性;二是奖励函数的设计。前者有PPO与SFT的差异衡量,得到不小的改进;而对于后者,如果要从客观角度考虑设计一个规则,就不那么容易了。笔者也曾设想过很多类似的方法,比如加入一些语法规则限制,甚至加入类似最省力法则这样的规则。

最省力法则:是由齐夫在Human Behavior and the Principle of Least Effort: An Introduction to Human Ecology一书中提出的。简单来说,就是语言具有惰性,它会朝着使用较少的词语表达尽可能多的语义这个方向演化。

InstructGPT使用人类反馈直接作为“规则”,把这种“规则”给隐式化,当作黑盒。我们只管结果好坏,至于中间有什么规则,有多少种规则,怎么起作用,统统不关心。这是和深度学习类似的思路,相比而言,我们之前的想法可能有些过于想当然了,毕竟语言学本身也有不少争议,认识并没有得到统一,比如语言能力是不是人与生俱来的能力?InstructGPT的做法则更加简单、直接,而且有效。

剩下要解决的就是怎么衡量“好坏”,毕竟最终是要有个结果的,既然要结果,就要有标准。读者不妨思考一下,如果换作你,你会如何设计一些指标来衡量两段输出内容的好坏。这一步看似容易,其实特别难,因为指标的设计会影响到模型的学习方向,最终就会影响到效果。因为这个输出的好坏衡量标准太多了,虽然看起来是对给出的几个结果进行排序(上文的步骤二),但其实这个过程中间隐藏了大量人类的认知,模型训练过程其实就是和步骤二这个衡量过程对齐的过程;所以,如果步骤二指标没设计好,步骤三就会白费力气。尤其是对于InstructGPT这样要完成大量不同任务的设计,衡量就更加不容易。以一个文本摘要任务为例,我们可能最关注的是能否准确概括原文信息,而一个生成任务可能更关注流畅性和前后逻辑一致性。InstructGPT里面有10种任务,分别针对每种任务设计指标,不仅麻烦,而且效果还不一定好,因为这些指标并不一定都是一个方向。还有就是,万一又有了一个新任务,难道要再去设计一套指标,全部重新训练一遍模型吗?

让我们来看看InstructGPT是怎么设计衡量指标的,笔者觉得这是InstructGPT论文最宝贵的地方,也是最值得我们思考和实践的地方。感兴趣的读者可以进一步阅读笔者之前写的一篇专门介绍ChatGPT标注的文章《ChatGPT 标注指南:任务、数据与规范》。首先,InstructGPT用了三大通用指标——有帮助、真实性和无害性,有点类似于阿西莫夫的机器人三定律。也就是说,不管是什么任务,都得朝着这三个方向靠拢。这个想法值得称赞。现在我们看到这个结果了,自然感觉好像没什么,但如果事先不知道要去设计出来,大部分人可能还是很容易陷入被任务影响的境地。其实,OpenAI团队在“In-Context”学习能力上的坚持也是一样的。当别人告诉你那个结果时,你可能觉得好像没有什么,甚至很多研究机构、研究人员都有过这种想法。但在有效果之前,笃信一条罕有人走的路,且一直坚定不移地走下去,这是很不容易的。

有了刚刚的三大通用指标,接下来就是细化,使其具有可操作性。比如,对于通用指标“有帮助”,InstructGPT给了一些属于“有帮助”行为的示例,如下所示。

用清晰的语言写作。

回答他们想问的问题,即使问错了,也要回答。

对国际性敏感(比如“football”不应该指美式足球,“总统”不一定指美国总统)。

如果指令(instruction)太让人困惑,要求澄清并解释指令为什么让人困惑。

不给出过长或冗长的答案,或重复问题中的信息。

不在给定的内容之外假设无关的额外上下文,除非是关于世界的事实,或是任务的隐含部分。比如,如果要求“礼貌地回复这封电子邮件:{邮件内容}”,则输出不应该假设“我这次不能来,但下周末有空”。但如果要求“给苏格拉底写一封电子邮件”,则可以放心地使用上面的假设。

笔者相信实际上这个列表可能很长,有很多例子会在实际标注过程中被依次添加进去,直到能覆盖绝大多数情况为止,即对于大部分要标注的数据,根据提供的细则很容易就判断出来是否“有帮助”。现在不妨停下来思考一下,如果一开始就奔着这些细则设计奖励规则——只是想想就觉得不太现实。其他两个通用指标也有一些示例,这里不赘述,感兴趣的读者可以阅读上面提到的笔者之前写的那篇文章,以及这篇文章最后所列的参考资料(因为有些文档资料在这篇文章中并没有提及)。

有了细则还没完,接下来要解决的是指标之间的冲突权衡问题。因为这是一个比较任务(比较哪个输出好),当涉及多个指标时,一定会出现A指标的一个结果好于另一个结果,但B指标可能相反的情况。指标越多,情况越复杂(好在只有三个指标)。对此,InstructGPT也给出了指导原则。

对于大部分任务,无害性和真实性比有帮助更加重要。

然而,如果一个输出比另一个输出更有帮助,或者该输出只是稍微不那么真实或无害,又或者该任务似乎不属于“高风险领域”(如贷款申请、医疗、法律咨询等),则更有帮助的输出得分更高。

当选择同样有帮助但以不同方式不真实或有害时,问自己哪个输出更有可能对用户(现实世界中受任务影响最大的人)造成伤害。这个输出应该排名较低。如果在任务中不清楚这一点,则将这些输出标记为并列。

对于边界样例的总体指导原则是,你更愿意从试图帮助你完成此任务的客户助理那里收到哪种输出?这是一种设身处地的原则,把自己假想为任务提出者,然后问自己期望得到哪种输出。

看看这些,你是不是也觉得这一步没那么容易了,它们虽然看起来没那么“技术性”,想要很好地完成却需要优秀的设计能力、宏观把控能力和细节感知能力。笔者更加相信这些细则是自底向上逐步构建起来的,而不是一开始就设想好的。它一定是在实践中不断产生疑惑,然后经过仔细分析权衡,逐步加入一条条规则,最终逐步构建起来的一整套系统方案。笔者觉得这套系统方案可能是比数据还要珍贵的资产,它所产生的壁垒是用时间不断实践堆积出来的。

InstructGPT或ChatGPT相比GPT-3有更强的零样本能力,少样本很多时候已经用不着,但提示词还是需要的,由此催生了一个新的行当——提示工程。不过,据OpenAI的CEO在一次采访中所言,再过几年提示工程也不需要了(可能在生成图片时还需要一些),用户要做的就是直接通过自然语言和人工智能交互。我们无法判断他说的会不会真的实现,但有一点可以肯定,人工智能的门槛必定会进一步降低,再过几年,可能一名初中生都能通过已有的服务创造出不错的人工智能应用。