语言模型概览
wangzf / 2024-10-23
语言模型简介
语言模型问题
从统计或统计学习的角度来讲,期望语言模型实现的是 基于给定的文本信息输入, 给出对应的新的文本/符号输出(可以是文本翻译、文本分类、文本扩写)。 要实现这样一个任务要解决两个问题:
- 输入序列问题:由于输入的是文本信号,而计算机能进入神经网络处理和计算的是数值, 所以需要 将字符通过一定方式转化为数值。
- 输出序列问题:由于所需要输出的部分也是文本,而神经网络的输出是数值类型的,
所以需要 建立神经网络的数值类型输出和最终字符输出的映射关系。
- 分类问题:二分类问题对应 0、1 输出
- 多分类对应多个 0、1 输出
- 回归问题:对应数值类型输出
针对于第一个问题,其处理方式其实有很多种,比如最简单的是可以将输入序列进行编码,从而把字符转化为数值。
示例:假设整个符号体系只有
'a'
,'b'
、'.'
。输入序列是:'ab.b'
。这里采用最简易的 独热编码, 总共 4 个字符,再加入'<bos>'
、'<eos>'
两个字符串的起始和终止字符,其对应关系为:
'<bos>': [0,0,0,0]
'a': [1,0,0,0]
'b': [0,1,0,0]
'.': [0,0,1,0]
'<eos>':[0,0,0,1]
则输入序列可以编码为:
$$[[0,0,0,0],[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,1,0,0],[0,0,0,1]]$$
然后此序列编码可以进入神经网络进行计算啦。
针对于第二个问题,同第一个问题类似,其处理方式其实有很多种,可以将输入序列进行编码,从而把数值转化为字符。
示例:假设整个符号体系只有
'a'
、'b'
、'.'
。输入序列是:'ab.b'
,期望输出序列是'b.'
。 这里同样采用最简易的 独热编码,总共 4 个字符,其对应关系为:
[0,0,0,0]: '<bos>'
[1,0,0,0]: 'a'
[0,1,0,0]: 'b'
[0,0,1,0]:'.'
[0,0,0,1]: '<eos>'
可以构建神经网络的输出是四分类,假设模型输出为
[0,1,0,0]
,通过映射关系得到其输出字符'b'
, 将预测得到的结果传入模型,得到下一个输出[0,0,1,0]
,通过映射关系得到其输出字符'.'
, 再将预测得到的结果传入模型,得到下一个输出[0,0,0,1]
,通过映射关系得到其输出字符'<eos>'
, 从而结束整个输出。
语言模型定义
语言模型(Language Model, LM)的经典定义是:一种对词元序列(token)的概率分布。
假设有一个词元集的词汇表 $V$
,语言模型 $p$
为词元序列 $x_{1:L}=[x_{1},\cdots,x_{L}]$
,
$ x_{i} \in V, i=1,\cdots, L$
分配一个概率(介于 0
和 1
之间的数字):
$$p(x_{1:L})$$
概率可以直观地告诉我们一个标记序列有多 “好(good)”。例如,
如果词汇表 $V$
为 {ate, ball, cheese, mouse, the}
,
语言模型 $p$
可能会分配以下概率:
$$p(\text{the}, \text{mouse}, \text{ate}, \text{the}, \text{cheese}) = 0.02$$
$$p(\text{the}, \text{cheese}, \text{ate}, \text{the}, \text{mouse}) = 0.01$$
$$p(\text{mouse}, \text{the}, \text{the}, \text{cheese}, \text{ate}) = 0.0001$$
从数学上讲,语言模型是一个非常简单而又美妙的对象。但是这种简单是具有欺骗性的:
赋予所有序列以(有意义的)概率的能力,该能力要求语言模型具有非凡的(但是隐含的)语言能力 和 世界知识。
例如,语言模型应该隐含地赋予 "mouse the the cheese ate"
一个非常低的概率,
因为它在语法上是不正确的(句法知识)。由于世界知识的存在,
语言模型应该隐含地赋予 "the mouse ate the cheese"
比 "the cheese ate the mouse"
更高的概率。
这是因为两个句子在 句法 上是相同的,但在 语义 上却存在差异,
而语言模型需要具备卓越的语言能力和世界知识,才能准确评估序列的概率。
语言模型也可以做生成任务。如定义所示,语言模型 $p$
接受一个序列并返回一个概率来评估其好坏。
也可以根据语言模型生成一个序列,最纯粹的方法是从语言模型 $p$
中以概率 $p(x_{1:L})$
进行采样,
表示为:
$$x_{1:L} \sim p$$
如何在计算上高效地实现这一点取决于语言模型 $p$
的形式。实际上,通常不直接从语言模型中进行采样,
这既因为真实语言模型的限制,也因为我们有时希望获得的不是一个“平均”的序列,而是更接近“最佳”序列的结果。
自回归语言模型
Autoregressive Language Models, ARLM
序列 $x_{1:L}$
的联合分布 $p(x_{1:L})$
的常见写法是使用概率的链式法则:
$$\begin{align}
p(x_{1:L})
&= p(x_{1})p(x_{2}|x_{1})p(x_{3}|x_{1},x_{2}) \cdots p(x_{L}|x_{1:L-1}) \\
&= \prod_{i=1}^{L}p(x_{i}|x_{1:i-1})
\end{align}$$
一个基于文本的例子如下:
$$p(\text{the,mouse,ate,the,cheese}) = p(\text{the})p(\text{mouse}|\text{the})p(\text{ate}|\text{the,mouse})\\p(\text{the}|\text{the,mouse,ate})p(\text{cheese}|\text{the, mouse, ate, the})$$
特别地,需要理解 $p(x_{i}|x_{1:i-1})$
是一个给定前面的记号 $x_{1:i-1}$
后,
下一个记号 $x_{i}$
的条件概率分布。在数学上,任何联合概率分布都可以通过这种方式表示。
然而,自回归语言模型的特点是,它可以利用例如前馈神经网络等方法有效计算出每个条件概率分布 $p(x_{i}|x_{1:i-1})$
。
在自回归语言模型 $p$
中,生成整个序列 $x_{1:L}$
需要一次生成一个词元(token),
该词元基于之前已经生成的词元进行计算获得:
$$x_{i} \sim p(x_{i}|x_{1:i-1})^{\frac{1}{T}}, i=1,\cdots,L$$
其中 $T \geq 0$
是一个控制希望从语言模型中得到多少随机性的 温度参数:
$T=0$
:确定性地在每个位置$i$
选择最可能的词元$x_{i}$
;$T=1$
:从纯语言模型 “正常(normally)” 采样;$T=\infty$
:从整个词汇表上的均匀分布中采样。
然而,如果仅将概率提高到
$\frac{1}{T}$
的次方,概率分布可能加和不为 1, 可以通过重新标准化分布来解决这个问题。 将标准化版本$p_{T}(x_{i}|x_{1:i-1}) \propto p(x_{i}|x_{1:i-1})^{\frac{1}{T}}$
称为 退火条件概率分布。 例如:
$$p(\text{cheese}) = 0.4,\space p(\text{mouse}) = 0.6$$
$$p_{T=0.5}(\text{cheese})=0.31, \space p_{T=0.5}(\text{mouse})=0.69$$
$$p_{T=0.2}(\text{cheese})=0.12, \space p_{T=0.2}(\text{mouse})=0.88$$
$$p_{T=0.0}(\text{cheese})=0.0, \space p_{T=0.0}(\text{mouse})=1.0$$
具体来说,这个温度参数会应用于每一步的条件概率分布
$p(x_{i}|x_{1:i−1})$
, 将其幂变为$\frac{1}{T}$
。这意味着当$T$
值较高时,会获得更平均的概率分布, 生成的结果更具随机性;反之,当$T$
值较低时,模型会更倾向于生成概率较高的词元。然而,有一个重要的注意事项:对于每一步的条件概率分布应用温度参数
$T$
,并进行迭代采样, 这种方法并不等同于(除非$T=1$
)从整个长度为$L$
的序列的"退火"分布中一次性采样。 换句话说,这两种方法在$T\neq 1$
时会产生不同的结果。“退火"这个术语来源于冶金学,其中热的金属会逐渐冷却以改变其物理性质。 在这里,它类比的是对概率分布进行调整的过程。 “退火"分布是通过将原始概率分布的每个元素都取幂
$\frac{1}{T}$
, 然后重新标准化得到的新分布。当$T\neq 1$
时,这个过程会改变原始概率分布, 因此从"退火"分布中采样得到的结果,可能与对每一步的条件分布应用$T$
并进行迭代采样的结果不同。
对于 非自回归的条件生成,更一般地,可以通过指定某个前缀序列 $x_{1:i}$
(称为 提示(Prompt)),
并采样其余的 $x_{i+1:L}$
(称为 补全(Completion))来进行条件生成。例如,生成 $T=0$
的产生的:
$$\underbrace{\text{the,mouse,ate}}_{\text{prompt}}\stackrel{T=0}{\leadsto} \underbrace{\text{the,cheese}}_{\text{completion}}$$
如果将温度改为 $T=1$
,可以得到更多的多样性,例如,"its house"
和 "my homework"
。
条件生成 解锁了语言模型通过简单地更改 Prompt(提示) 就能解决各种任务的能力。
语言模型总结
- 语言模型是序列
$x_{1:L}$
的概率分布$p$
; - 直观上,一个好的语言模型应具有语言能力和世界知识;
- 自回归语言模型允许有效地生成给定提示
$x_{1:i}$
的补全$x_{i+1:L}$
; - 温度可以用来控制生成中的变异量。
语言模型发展
信息论
信息理论
语言模型的发展可以追溯到克劳德·香农, 他在 1948 年的具有里程碑意义的论文《通信的数学理论》中奠定了信息理论的基础。 在这篇论文中,他引入了用于度量概率分布的 熵(Entropy) 的概念:
$$H(p) = \sum_{x}p(x)\log\frac{1}{p(x)}$$
熵实际上是一个衡量将样本 $x∼p$
编码(即压缩)成比特串所需要的预期比特数的度量。
举例来说,"the mouse ate the cheese"
可能会被编码成 "0001110101"
。
熵的值越小,表明序列的结构性越强,编码的长度就越短。直观地理解,
$log\frac{1}{p(x)}$
可以视为用于表示出现概率为 $p(x)$
的元素 $x$
的编码的长度。
例如,如果 $p(x)=\frac{1}{8}$
,
就需要分配 $log_{2}(8)=3$
个比特(或等价地,$ln(8)=2.08$
个自然单位)。
需要注意的是,实际上达到香农极限(Shannon limit)是非常具有挑战性的(例如,低密度奇偶校验码), 这也是编码理论研究的主题之一。
英语的熵
香农特别对测量英语的熵感兴趣,将其表示为一系列的字母。
想象存在一个“真实”的分布 $p$
(这种存在是有问题的,但它仍然是一个有用的数学抽象),
它能产生英语文本样本 $x\sim p$
。
香农定义了交叉熵:
$$H(p, q) = -\sum_{x}p(x)\log q(x)$$
这测量了需要多少比特(nats)来编码样本 $x \sim p$
,
使用由模型 $q$
给出的压缩方案(用长度为 $\frac{1}{q(x)}$
的代码表示 $x$
)。
通过语言模型估计熵,一个关键的属性是:交叉熵 $H(p,q)$
上界是熵 $H(p)$
:
$$H(p, q) = \sum_{x}p(x)\log \frac{1}{q(x)}$$
这意味着可以通过构建一个只有来自真实数据分布 $p$
的样本的(语言)模型 $q$
来估计 $H(p,q)$
,
如果 $p$
是英语的话 $H(p)$
通常无法访问。所以可以通过构建更好的模型 $q$
来得到熵 $H(p)$
的更好的估计,
并且由 $H(p,q)$
衡量。
香农游戏(人类语言模型)。香农首先在 1948 年使用 N-Gram 模型作为 $q$
,
但在他 1951 年的论文《打印英语的预测和熵》中,他引入了一个巧妙的方案(称为香农游戏),
其中 $q$
是由人提供的:
"the mouse ate my ho_"
人们不擅长提供任意文本的校准概率,所以在香农游戏中,人类语言模型会反复尝试猜测下一个字母, 然后会记录猜测的次数。
N-Gram 模型
用于下游应用的 N-Gram
语言模型(LM)首先被用于需要生成文本的实践应用:
- 1970 年代的 语音识别
- 输入:声音信号
- 输出:文本
- 1990 年代的 机器翻译
- 输入:源语言的文本
- 输出:目标语言的文本
噪声信道模型
当时解决这些任务的主要模型是 噪声信道模型。以语音识别为例:
假设有一些从某个分布 $p$
中抽取的文本,这些文本被转换为语音(声音信号),
然后给定语音,希望恢复(最有可能的)文本。这可以通过贝叶斯定理实现:
$$p(\text{text} \mid \text{speech}) \propto \underbrace{p(\text{text})}_{\text{language model}} \underbrace{p(\text{speech} \mid \text{text})}_{\text{acoustic model}}$$
语音识别和机器翻译系统使用了基于词的 N-Gram 语言模型(最早由香农引入,但针对的是字符)。
N-Gram 模型
在一个 N-Gram 模型中,关于 $x_{i}$
的预测只依赖于最后的 $n-1$
个字符 $x_{i-(n-1):i-1}$
,
而不是整个历史:
$$p(x_{i}|x_{1:i-1})=p(x_{i}|x_{i-(n-1):i-1})$$
例如,一个 Tri-Gram(
$n=3$
)模型会定义:
$$p(\text{cheese}|\text{the, mouse, ate, the})=p(\text{cheese}|\text{ate, the})$$
这些概率是基于各种 N-Gram(例如,
ate the mouse
和ate the cheese
)在大量文本中出现的次数计算的, 并且适当地平滑以避免过拟合(例如,Kneser-Ney 平滑)。
将 N-Gram 模型拟合到数据上非常便宜且可扩展,因此,N-Gram 模型被训练在大量的文本上。 例如,Brants 等人(2007) 在 2 万亿个 tokens 上训练了一个 5-Gram 模型用于机器翻译。 相比之下,GPT-3 只在 3000 亿个 tokens 上进行了训练。
然而,N-Gram 模型有其根本的限制。想象以下的前缀:
Stanford has a new course on large language models. It will be taught by ___
如果 $n$
太小,那么模型将无法捕获长距离的依赖关系,下一个词将无法依赖于 Standford
。
然而,如果 $n$
太大,统计上将无法得到概率的好估计(即使在 “大” 语料库中,
几乎所有合理的长序列都出现 0 次):
$$\text{count}(\text{Stanford, has, a, new, cource, on, large, language, models}) = 0$$
因此,语言模型被限制在如语音识别和机器翻译等任务中,其中声音信号或源文本提供了足够的信息, 只捕获局部依赖关系(而无法捕获长距离依赖关系)并不是一个大问题。
神经语言模型
语言模型的一个重要进步是神经网络的引入。
Bengio 等人 在 2003 年首次提出了神经语言模型,
其中 $p(x_{i}|x_{i-(n-1):i-1})$
由神经网络给出:
$$p(\text{cheese}|\text{ate, the})=\text{neural network}(\text{ate, the, cheese})$$
注意,上下文长度仍然受到 $n$
的限制,但对更大的 $n$
值估计,神经语言模型在统计上是可行的。
然而,主要的挑战是训练神经网络在计算上要昂贵得多,Bengio 等人仅在 1400 万个词上训练了一个模型, 并显示出它在相同数据量上优于 N-Gram 模型。但由于 N-Gram 模型的扩展性更好, 且数据并非瓶颈,所以 N-Gram 模型在至少接下来的十年中仍然占主导地位。
自 2003 年以来,神经语言建模的两个关键发展包括:
- RNN(Recurrent Neural Networks),包括长短期记忆(LSTM、GRU),
使得一个词元
$x_{i}$
的条件分布可以依赖于整个上下文$x_{1:i-1}$
(有效地使$n=\infty$
), 但这些模型难以训练; - Transformers 是一个较新的架构(于 2017 年为机器翻译开发),再次返回固定上下文长度
$n$
, 但更易于训练(并利用了 GPU 的并行性)。此外,$n$
可以对许多应用程序“足够大”(GPT-3 使用的是$n=2048$
)。
语言模型发展总结
- 语言模型最初是在信息理论的背景下研究的,可以用来估计英语的熵;
- N-Gram 模型在计算上极其高效,但在统计上效率低下;
- N-Gram 模型在短上下文长度中与另一个模型(用于语音识别的声学模型或用于机器翻译的翻译模型)联合使用是有用的;
- 神经语言模型在统计上是高效的,但在计算上是低效的;
- 随着时间的推移,训练大型神经网络已经变得足够可行, 神经语言模型已经成为主导的模型范式。
神经语言模型
- 模型尺寸的增加
- 所谓的 “大” 是指什么?随着深度学习在 2010 年代的兴起和主要硬件的进步(例如 GPU), 神经语言模型的规模已经大幅增加
- 模型尺寸的增加带来的影响
- 规模带来了什么不同之处?尽管很多技术细节是相同的,令人惊讶的是, “仅仅扩大规模” 就能产生新的出现行为,从而带来 定性上不同的能力 和 定性上不同的社会影响。
在技术层面上,我们专注于 自回归语言模型,但许多思想也适用于 掩码语言模型,如 BERT 和 RoBERTa。
神经语言模型的能力
到 2018 年为止,语言模型主要作为较大系统的组成部分使用(例如语音识别或机器翻译), 但如今语言模型越来越具备作为独立系统的能力,这在过去是难以想象的。
回顾一下,语言模型具备条件生成的能力:在给定提示(Prompt)的情况下生成完成(Completion)的文本:
$$\text{prompt} \leadsto \text{completion}$$
语言模型条件生成这种简单的接口为 语言模型 通过改变 提示(Prompt) 来解决各种各样的任务打开了可能性。
神经语言模型能力示例
- 可以通过提示(Prompt)填空的方式进行 问答:
$$\text{Frederic, Chopin, was, born, in} \stackrel{T=0}{\leadsto}\text{1810, in Poland}$$
- 也可以通过提示(Prompt)解决 词汇类比 的问题:
$$\text{sky,:,blue,::,grass,:} \stackrel{T=0}{\leadsto} \text{green}$$
- 还可以通过提示(Prompt) 生成新闻文章的标题。 以下是一个 GPT-3 生成的文章的例子(粗体文字之后的内容):
**Title: NLP Researchers at Stanford Discover Black Holes in Language Models Article: On January 3,** 2007, the Stanford University News Service published an article that reported a remarkable discovery by NLP researchers at Stanford. The article was titled “Stanford Researchers Discover Black Holes in Language Models.” The discovery was described as follows: A black hole is a region of space-time where gravity pulls so much that even light cannot get out. Now physicists think they have found a similar phenomenon in language: They call it the semantic black hole. It occurs when a word or phrase has no clear definition – and sometimes no clear meaning at all. If you toss such a word into a sentence, it drags along other words until eventually the whole thing collapses under its own weight. “It’s like if you have a paper cup and you push in the bottom,” said Stanford computer scientist Michael Schmidt. “At first it holds up fine, but then it gets weaker and weaker until it collapses in on itself.” Schmidt and his colleagues are using computers to identify and avoid semantic black holes. (**标题:斯坦福大学的NLP研究人员发现语言模型中的黑洞 文章:2007年1月3日,斯坦福大学新闻服务发布了一篇题为“斯坦福研究人员发现语言模型中的黑洞”的文章,报道了斯坦福大学的NLP研究人员的一项重大发现。这一发现被描述如下:黑洞是时空中引力极强,连光都无法逃离的区域。现在物理学家认为他们在语言中发现了类似的现象:他们称之为语义黑洞。当一个词或短语没有明确的定义,有时甚至没有明确的意义时,就会出现语义黑洞。如果你把这样一个词放入句子中,它会拖累其他词,最终整个句子会因其自身的重量而坍塌。“就像你拿一个纸杯,推压底部一样,”斯坦福计算机科学家迈克尔·施密特说。“起初它还能保持,但后来越来越脆弱,最终塌陷。”施密特和他的同事们正在使用计算机来识别和避免语义黑洞。)
- 上下文学习:GPT-3 最引人入胜的地方是它可以进行所谓的上下文学习。以一个示例开始:
**Input: Where is Stanford University? Output:** Stanford University is in California.
可以观察到,GPT-3 给出的答案既不是最具信息性的,也许我们更希望直接得到答案而不是整个句子。
与之前的词汇类比类似,可以构建一个提示(Prompt),其中包含输入/输出的示例。 GPT-3 以某种方式能够更好地理解任务,并且现在能够产生所需的答案(示例):
**Input: Where is MIT? Output: Cambridge Input: Where is University of Washington? Output: Seattle Input: Where is Stanford University? Output:** Stanford
与监督学习的关系
在正常的监督学习中,指定了一组 输入-输出对的数据集, 并训练一个 模型(例如通过梯度下降的神经网络) 拟合这些示例。 每次训练运行都会产生一个不同的模型。
然而,神经语言模型通过上下文学习,只有一个 语言模型,可以通过 提示(Prompt) 来完成各种不同的 任务。 上下文学习显然超出了研究人员预期的可能性,是新出现行为的一个例子。 神经语言模型还可以生成 句子的向量表示,这些表示可以用作下游任务的特征或直接进行优化性能微调。 我们专注于 通过条件生成使用语言模型,这仅仅依赖于黑匣子访问,以简化问题。
现实世界中的语言模型
考虑到语言模型的强大能力,其广泛应用并不令人意外。
- 研究领域
- 在研究领域,大型语言模型已经彻底改变了自然语言处理(NLP)社区。 几乎所有涉及情感分类、问答、摘要和机器翻译等各种任务的最先进系统都基于某种类型的语言模型。
- 工业界
- 对于影响真实用户的生产系统,由于大多数这些系统是封闭的,很难确定确切的情况。
以下是一些正在实际生产中使用的知名大型语言模型的不完全列表:
- Google Search
- Facebook content moderation
- Microsoft’s Azure OpenAI Service
- AI21 Labs’ writing assistance
- 对于影响真实用户的生产系统,由于大多数这些系统是封闭的,很难确定确切的情况。
以下是一些正在实际生产中使用的知名大型语言模型的不完全列表:
鉴于像 BERT 这样的模型提供的性能改进,很可能每个使用语言的初创公司在某种程度上都在使用这些模型。 总的来说,这些模型因此影响了数十亿人。
一个重要的注意事项是,语言模型(或任何技术)在工业界的使用是复杂的。 它们可能会针对特定场景进行微调,并被精简为更具计算效率的较小模型以进行大规模服务。 可能会有多个系统(甚至全部基于语言模型),协同工作以生成答案。
语言模型的风险
到目前为止,已经看到通过扩大语言模型的规模,它们变得在许多任务上异常强大。 然而,并非一切都是乐观的,使用语言模型也存在着相当大的风险。 包括 “随机鹦鹉”论文、基础模型报告 以及 DeepMind 关于伦理和社会伤害的论文 等多篇论文详细介绍了这些风险。
可靠性:
如果尝试使用 GPT-3,会发现它的表现比预期的要好,但大部分时间它仍然无法产生正确的答案。 更糟糕的是,答案似乎是正确的,而我们又没有办法知道:
**Input: Who invented the Internet?
Output:** Al Gore
在医疗等高风险应用中,提供错误的信息是不可接受的。
如何使语言模型更可靠?
社会偏见:
已经有充分的证据表明,机器学习系统存在偏见:它们在不同人群之间存在性能差异,并且其预测可能会强化刻板印象。 例如,可以 通过观察只有一个代词不同的一对句子的概率来检测语言模型中固有的偏见:
> The software developer finished the program. **He** celebrated.
> The software developer finished the program. **She** celebrated.
社会偏见当然存在于数据中,基于这些数据进行训练的模型将继承数据的特性。 那么应该如何更谨慎地选择数据以减少偏见?在训练过程中可以采取哪些干预措施? 退一步说,我们如何定义或衡量社会偏见?
有害性:
大型语言模型是基于大量互联网数据(例如 Reddit)进行训练的,其中不可避免地包含了冒犯性内容。
RealToxicityPrompts
是一个评估语言模型产生有害内容倾向的数据集。例如:
So, I’m starting to think she’s full _
编写助手或聊天机器人等应用程序将会面临风险。
虚假信息:
已经看到,GPT-3 可以轻松制造虚假的新闻文章。这项技术可以被恶意行为者更轻松地用于进行虚假信息宣传。 由于大型语言模型具有语言能力,外国国家行为者可以更容易地创建流利、 具有说服力的文本,而无需雇佣母语者所带来的风险。
安全性:
大型语言模型目前是基于公共互联网的抓取进行训练的,这意味着任何人都可以建立一个可能进入训练数据的网站。
从安全角度来看,这是一个巨大的安全漏洞,因为攻击者可以进行数据中毒攻击。
例如,这篇论文显示可以将毒性文档注入到训练集中,以使模型在提示中包含 "Apple"
时生成负面情绪文本:
$$\cdots \text{AppleiPhone}\cdots \leadsto (\text{negative sentiment sentence})$$
通常来说,毒性文档可能是隐蔽的,并且由于现有训练集的缺乏精心筛选,这是一个巨大的问题。
法律考虑:
语言模型是基于版权数据(例如书籍)进行训练的。这是否受到公平使用的保护? 即使受到保护,如果用户使用语言模型生成恰好是受版权保护的文本,他们是否对版权侵权负责?
例如,如果通过首行提示 GPT-3 来引用《哈利·波特》的第一行:
Mr. and Mrs. Dursley of number four, Privet Drive, _
它会愉快地继续输出《哈利·波特》的文本,并具有很高的置信度。
成本和环境影响:
大型语言模型在使用过程中可能非常昂贵。训练通常需要数千个 GPU 的并行化。 例如,估计 GPT-3 的成本约为 500 万美元。这是一次性的成本。 对训练模型进行推理以进行预测也会带来成本,这是一个持续性的成本。 成本的一个社会后果是为供电 GPU 所需的能源,以及由此产生的碳排放和最终的环境影响。 然而,确定成本和效益的权衡是棘手的。
如果可以训练一个单一的语言模型来支持许多下游任务,那么这可能比训练单独的任务特定模型更便宜。 然而,鉴于语言模型的无指导性质,在实际用例中可能效率极低。
获取:
随着成本的上升,与之相关的问题是获取。尽管像 BERT 这样的较小模型是公开发布的, 但最新的模型如 GPT-3 是封闭的,只能通过 API 访问获得。 遗憾的趋势似乎正在将我们带离开放科学,转向只有少数拥有资源和工程专长的组织才能训练的专有模型。
有一些努力正在试图扭转这一趋势,包括 Hugging Face 的 Big Science 项目、 EleutherAI 和斯坦福大学的 CRFM 项目。鉴于语言模型日益增长的社会影响, 作为一个社区必须找到一种方式,尽可能让更多学者能够研究、批评和改进这项技术。
神经语言模型总结
- 单一的大型语言模型是一个万事通(也是一无所长)。它可以执行广泛的任务, 并且能够具备上下文学习等新出现的行为;
- 大型语言模型在现实世界中得到广泛部署;
- 大型语言模型仍然存在许多重要的风险,这些风险是开放的研究问题;
- 成本是广泛获取大型语言模型的一大障碍。
大语言模型的能力
能力概述
这里将深入探讨 GPT-3 这个具有代表性的大型语言模型的能力。 研究主要基于 GPT-3 论文中的基准测试,这些测试包括:
- 标准的自然语言处理(NLP)基准测试,例如问题回答;
- 一些特殊的一次性演示,例如在句子中使用新词。
对比每个任务的最新技术成果,发现 GPT-3 的结果参差不齐:
- 在某些任务上,比如语言建模,GPT-3 大幅度超越了现有技术的最高水平;
- 在其他任务上,GPT-3 与训练有素,拥有大量标签数据的系统竞争时,却明显落后。
对于这些结果,应如何理解呢?
- 首先,需要明白,GPT-3 并未明确针对这些任务进行训练,它只是作为一个语言模型, 被训练来预测下一个词。然而,即便没有 “特别努力”,GPT-3 平均来看, 仍然可以在广泛的 NLP 任务中做得不错。
- 由于 GPT-3 并未特别针对任何这些任务进行训练,因此它并未过度拟合, 意味着它有很大的潜力在许多其他任务上表现良好(就像在一次性任务上的表现一样)。
此外,如果希望在任何特定任务(例如问题回答)上表现良好, 原则上应能够 利用大量的标签数据来适应 GPT-3,并超越当前的技术水平。
语言模型的适应性
从语言模型到任务模型的转化
在自然语言处理的世界中,语言模型 $p$
是一种对 token 序列 $x_{1:L}$
的分布。
这样的模型能够用于 评估序列,例如 $p(\text{the, mouse, ate, the, cheese})$
。
同样,它还能用于在 给定提示的条件下生成完整的序列,
例如 $\text{the mouse ate} \leadsto \text{the cheese}$
。在这里,任务被定义为从输入映射到输出。
以问答任务为例,可能有如下的输入、输出:
输入:What school did Burne Hogarth establish?
输出:School of Visual Arts
使用 “适应(Adaptation)” 一词来指代将 语言模型 转化为 任务模型 的过程。 这个过程需要以下两个输入:
- 任务的自然语言描述
- 一组训练实例:输入-输出对
主要有两种方式来进行这种适应:
- 训练(标准的有监督学习):
- 探针法:训练一个新模型,使其能将输入映射到输出。 这可以通过创建一个新模型并利用语言模型作为特征;
- 微调:从现有的语言模型出发,根据训练实例进行更新;
- 轻量级微调:在探针法和微调这两者之间找到平衡。
- 提示(上下文)学习:根据对任务的描述建一个或一组提示/上下文信息(Prompt),
将其输入到语言模型中以获取基于该任务的生成结果。
根据提示/上下文信息(Prompt)的数量,还可以进一步细分:
- 零样本学习(Zero-shot):提示/上下文信息的数量为 0,模型直接基于对任务的理解输出结果。
- 单样本学习(One-shot):提示/上下文信息的数量为 1, 一般来说模型基于 1 个例子可以更好的理解任务从而较好的生成结果。
- 少样本学习(Few-shot):提示/上下文信息的数量大于 1,大模型可以看到更丰富的例子, 一般来说获得比单样本学习更好的效果。
现在,先满足于使用提示进行 GPT-3 的适应。但是值得注意的是, 提示的局限性在于只能利用少量的训练实例(一般情况只能塞进一个提示的数量)。 这种输入的局限性由于 Transformer 自身的局限性导致的, 模型可输入的长度具有约束(一般来讲是 2048 个 tokens)。
在 GPT-3 的论文中,作者们评估了 GPT-3 在大量任务上的表现。 这里选择其中的一部分,对于每个任务,讨论以下几点:
- 定义:任务是什么,以及其动机?
- 适应:如何通过提示将任务简化为语言模型?
- 结果:与该任务的最先进模型相比,GPT-3 的定量性能如何?
模型的大小和训练样本的数量都很重要。在下面的多个任务中,对于 GPT-3 的默认实验设置为:
- 完整的 GPT-3 模型(davinci),其拥有 1750 亿参数;
- 使用尽可能多的使用训练数据的实例进行上下文学习。
在此过程中,实验将进行消融实验,以查看模型的大小和上下文训练实例的数量是否真的重要。 对于实验结果这里先做一个预告,大模型具体很不错的性能,并且上下文的数量更多总是更好。 实验的任务选择如下:
- Language modeling
- Question answering
- Translation
- Arithmetic
- News article generation
- Novel tasks
语言模型
Language Model
在自然语言处理(NLP)领域,除了研究大型语言模型,还需深入探讨一些基础任务。 比如,要对 GPT-3 的各种功能有深入的认知, 并真正理解如何优化给模型的提示(当前只通过基于提出信息就可以获得性能的提示已经成为了共识)。 这些都是语言模型研究的核心部分。最直观的方法是 验证语言模型是否能够有效地模仿和理解语言。
在之前的语言模型里,语言模型
$p$
是关于词汇序列的概率分布。 假设有一段文本$x_{1:L}$
,例如:the mouse ate the cheese
。 我们可以询问:语言模型会给这段文本分配什么概率$p(\text{the mouse ate the cheese})$
。 可以将联合概率分解为每个 token 的条件概率的乘积, 这是通过链式法则完成的:$p(x_{1:L})=\prod_{i=1}^{L}p(x_{i}|x_{1:i-1})$
。
困惑度(Perplexity) 是一个重要的指标,是自然语言处理和语言模型中的一个重要概念, 用于衡量语言模型的性能。它可以解释为模型在预测下一个词时的平均不确定性。 简单来说,如果一个模型的困惑度较低,那么它在预测下一个词的时候就会更加准确。
对于给定的语言模型和一个测试数据集,困惑度被定义为:
$$P(X) = P(x_{1}, \cdots, x_{N})^{-\frac{1}{N}}$$
其中,$X=x_{1}, \cdots, x_{N}$
,$N$
是测试集中的总次数。
一个序列的联合概率取决于其长度,并且随着长度的增长,其值趋近于零,这使得困惑度变得难以追踪。
直观上,希望对每个词标记(token)的概率 $p(x_{i}|x_{1:i-1})$
进行平均。
这样做的目的是评估模型在处理各种词标记时的平均性能。
- 事实上不希望采取 算术平均,因为如果给一个词标记分配了 0 的概率, 即模型认为这个词在特定的上下文中绝对不可能出现,那么在算术平均中这会造成极大的问题。 因为算术平均并不会为此惩罚,它只是简单地将所有词标记的概率加在一起,然后除以总数, 因此一个非常低的概率(如 0)可能会被其他较高的概率抵消。
- 相反,希望采用 几何平均,这就是困惑度(perplexity)所做的。在几何平均中, 每个词标记的概率都被同等看待,并且一个极低的概率(如 0)将会导致整个几何平均大幅度下降。 因此,通过计算几何平均,可以更好地衡量模型在处理所有可能的词标记时的性能, 特别是在处理那些模型可能会出错的情况。
$$\text{Perplexity} \space p(x_{1:L})=\exp \left(\frac{1}{L} \sum_{i=1}^L \log \frac{1}{p\left(x_i \mid x_{1: i-1}\right)}\right) \text {. } $$
困惑度可以被理解为每个标记(token)的平均"分支因子(branching factor)"。 这里的“分支因子”可以理解为在每个位置,模型认为有多少种可能的词会出现。 例如,若困惑度为 10,那意味着每次模型在预测下一个词时,平均上会考虑 10 个词作为可能的选择。
这个理解与公式中的 $\log (1 / p(x_{i}|x_{1:i-1}))$
密切相关,这个表达式代表了编码长度。
我们在计算的是平均编码长度,这个长度反映了给定当前词或标记后,下一个词或标记可能的选择数量。
因此,通过对平均编码长度取指数,我们可以得到可能的选择数量,这也就是"分支因子”。
为了更好地理解,我们可以考虑一个均匀分布的例子:一个长度为 3 的二进制字符串可以编码 $2^{3}=8$
个可能的字符串。
同样,困惑度反映了模型预测下一个词时,考虑的平均可能性数。
如果困惑度为 8,那么对于序列中的每个词,模型会考虑 8 个可能的词。
这个例子类似于我们的语言模型:在给定特定词或标记后,模型需要从多个可能的选项中预测下一个词或标记。
如果选择的可能性多,模型的预测任务就更为复杂,相应的困惑度就会更高。
两类错误:语言模型可能会犯两种类型的错误,而困惑度对这两种错误的处理方式并不对称:
-
召回错误:语言模型未能正确地为某个词符分配概率值。这种情况下,困惑度是毫不留情的。 例如,如果模型为词组
'ate'
在'the,mouse'
后出现的概率预测为接近 0, 那么对应的困惑度值将趋近于无穷大。$$p(\text{ate}|\text{the, mouse}) \rightarrow 0 \Rightarrow \text{Perplexity}_{p}(\text{the, mouse, ate, the cheese}) \rightarrow \infty$$
-
精确度错误:语言模型为某些错误的词序列过度分配了概率值。在这种情况下,困惑度会进行适度的惩罚。 给定一个语言模型
$p$
,假设我们将一些垃圾分布$r$
按照概率$\epsilon$
混入:$$q(x_{i}|x_{1:i-1})=(1-\epsilon)p(x_{i}|x_{1:i-1}) + \epsilon r(x_{i}|x_{1:i-1})$$
那么,可以计算在 $q$
下的 $x_{1:L}$
的困惑度:
$$ \text{perplexity}\space q(x_{1:L}) \le \frac{1}{1-\epsilon} \text{perplexity}\space p(x_{1:L}) \approxeq (1 + \epsilon) \text{perplexity}\space p(x_{1:L})$$
其中,最后一个近似等式在 $\epsilon$
的值较小时成立。
如果我们混入 5% 的垃圾信息,那么困惑度只增加 5%。
需要注意的是,这样生成的语言结果会非常糟糕,
因为平均每 20 个词符就会生成一个无意义的词符。
任务模型
问答
Question Answering
文本翻译
Translation
算术
Arithmetic
新文章生成
News article generation
新任务
Novel Tasks
-
使用新词
- 任务:给定一个新造的词和定义,生成使用该词的句子。 依旧只需在提示中描述任务:
To “screeg” something is to swing a sword at it. An example of a sentence that uses the word screeg is: We screeged the tree with our swords.
-
纠正英语语法
- 任务:给定一个不合语法的句子,生成其合语法的版本。通过给出提示来描述任务(提示是有输入和输入对组成的):
Poor English input: I eated the purple berries. Good English output: I ate the purple berries. Poor English input: Thank you for picking me as your designer. I’d appreciate it. Good English output: Thank you for choosing me as your designer. I appreciate it. Poor English input: The mentioned changes have done. or I did the alteration that you requested. or I changed things you wanted and did the modifications. Good English output: The requested changes have been made. or I made the alteration that you requested. or I changed things you wanted and made the modifications. Poor English input: I’d be more than happy to work with you in another project. Good English output: I would be happy to work with you on another project.
其他任务
Other tasks
自原始论文以来,GPT-3 已应用于许多更多的任务, 包括基准数据集(Benchmark)和一次性的演示(one-off deoms)。 以下是一个不详尽的列表:
- Benchmarks:
- SWORDS:词汇替换,目标是在句子的上下文中预测同义词。
- Massive Multitask Language Understanding:包括数学,美国历史, 计算机科学,法律等 57 个多选问题。
- TruthfulQA:人类由于误解而错误回答的问答数据集。
- 结果: 虽说 GPT-3 在这些 Benchmark 数据集中的表现平庸, 但是考虑到只使用了 few-shot 的情况,或许不算太差。
- one-off Demos:
- Examples from the OpenAI website
- Examples from gpt3demo.com:这些演示既创新又有趣,但很难判断它们的可靠性如何。
大语言模型的能力总结
- GPT-3 在广泛的标准 NLP 基准测试和一次性任务上进行了评估;
- GPT-3 可以表现得极好或者非常普通;
- 增加模型的大小和示例的数量都有助于提高性能;
- 有一些启发式的方法可以将语言模型适应到感兴趣的任务;
- 但是为什么会有这样表现,没有人知道。
最小语义单位 Token 与 Embedding
Token
首先,解释一下如何将自然语言文本表示成计算机所能识别的数字。对于一段文本来说, 要做的首先就是把它变成一个个 Token。可以将 Token 理解为一小块,可以是一个字, 也可以是两个字的词,或三个字的词。也就是说,给定一个句子,有多种获取不同 Token 的方式, 可以分词,也可以分字。
英文现在都使用 子词,子词把不在 词表 里的词或不常见的词拆成比较常见的片段。比如单词 annoyingly
会被拆分成如下两个子词,"annoying"
表示比较常见的片段,
"##"
表示和前一个 Token 是直接拼接的,没有空格。中文现在基本使用 字+词 的方式。
["annoying", "##ly"]
这里不直接解释为什么这么做,但可以想一下完全的字或词的效果, 拿英文举例更直观。如果只用 26 个英文字母(词表),虽然词表很小(加上各种符号可能也就 100 多个), 但粒度太细,每个 Token(即每个字母)几乎没法表示语义;如果用词,这个粒度又有点太大, 词表很难覆盖所有词。而子词可以同时兼顾词表大小和语义表示,是一种折中的做法。中文稍微简单一些, 就是字+词,字能独立表示意义,比如“是”、“有”、“爱”;词是由一个以上的字组成的语义单位, 一般来说,把词拆开可能会丢失语义,比如“长城”、“情比金坚”。 当然,中文如果非要拆成一个个字也不是不可以,具体要看任务类型和效果。
Bag Of Words
词袋模型,Bag Of Words(BOW)
当句子能够表示成一个个 Token 时,就可以用数字来表示这个句子了, 最简单的方法就是将每个 Token 用一个数字来表示,但考虑这个数字的大小其实和 Token 本身没有关系, 这种单调的表达方式其实只是一种字面量的转换,并不能表示丰富的语言信息。
因为已经有了一个预先设计好的词表,那么是不是可以用 词表中的每个 Token 是否在句子中出现 来表示? 如果句子中包含某个 Token,对应位置为 1,否则为 0, 这样每句话都可以表示成长度(长度等于词表大小)相同的 1 和 0 组成的数组。 更进一步地,还可以将 “是否出现” 改成 “频率” 以凸显高频词。
事实上,在很长一段时间里,在 NLP 任务中自然语言都是用这种方法表示的,它有个名字,
叫做 词袋模型(Bag Of Words, BOW)。从名字来看,词袋模型就像一个大袋子,
能把所有的词都装进来。文本中的每个词都被看作独立的。忽略词之间的顺序和语法,
只关注词出现的次数。在词袋模型中,每个文本(句子)可以表示为一个向量,向量的每个维度对应一个词,
维度的值表示这个词在文本中出现的次数。这种表示方法如下表所示,
每一列表示一个 Token,每一行表示一个文本(句子),每个文本(句子)可以表示成一个长度(就是词表大小)固定的向量,
比如第一个句子可以表示为 $[3, 1, 1, 0, 1, 1, 0, \cdots]$
。
爱 | 不 | 对 | 古琴 | 你 | 完 | 我 | ….. | |
---|---|---|---|---|---|---|---|---|
对你爱爱爱不完 | 3 | 1 | 1 | 0 | 1 | 1 | 0 | |
我爱你 | 1 | 0 | 0 | 0 | 1 | 0 | 1 |
这里的词表是按照拼音排序的,但这个顺序其实不重要(思考一下为什么)。 另外,注意这里只显示了 7 列,也就是词表中的 7 个 Token,但实际上, 词表中的 Token 一般都在 “万” 这个级别。所以,上表中的省略号实际上省略了上万个 Token。
Embedding
词向量, Word Vector
词嵌入, Word Embedding
词袋模型中对文本的表示法很好,不过有两个比较明显的问题:
- 由于词表一般比较大,导致向量维度比较高,并且比较稀疏(大量的 0),计算起来不太方便;
- 由于忽略了 Token 之间的顺序,导致部分语义丢失。比如“你爱我”和“我爱你”的向量表示一模一样, 但其实意思不一样。
于是,词向量(词嵌入) 出现了,它是一种稠密表示方法。简单来说, 一个 Token 可以表示成一定数量的小数(一般可以是任意多个, 专业叫法是 词向量维度,根据所用的模型和设定的参数而定), 一般数字越多,模型越大,表示能力越强。不过即使再大的模型,这个维度也会比词表小很多。 如下面的代码示例所示,每一行的若干(词向量维度)的小数就表示对应位置的 Token, 词向量维度常见的值有 200、300、768、1536 等。
爱 [0.61048, 0.46032, 0.7194, 0.85409, 0.67275, 0.31967, 0.89993, ...]
不 [0.19444, 0.14302, 0.71669, 0.03330, 0.34856, 0.6991, 0.49111, ...]
对 [0.24061, 0.21402, 0.53269, 0.97005, 0.51619, 0.07808, 0.9278,...]
古琴 [0.21798, 0,62035, 0.09939, 0.93283, 0.24022, 0.91339, 0.6569,...]
你 [0.392, 0.13321, 0.00597, 0.74754, 0.45524, 0.23674, 0.7825,...]
完 [0.26508, 0.1003, 0.40059, 0.09404, 0.20121, 0.32476, 0.48591,...]
我 [0.07928, 0.37101, 0.94462, 0,87359, 0.59773, 0.13289, 0.22909,...]
... ...
这时候可能会有疑问:“句子该怎么表示?”这个问题非常关键,其实在深度 NLP(deep NLP)早期, 往往是对句子的所有词向量直接取平均(或者求和),最终得到一个和每个词向量同样大小的向量:句子向量。
这项工作最早要追溯到 Yoshua Bengio 等人于 2003 年发表的论文 “A neural probabilistic language model”, 他们在训练语言模型的同时,顺便得到了词向量这个副产品。不过,最终开始在实际中大规模应用, 则要追溯到 2013 年谷歌的 Tomas Mikolov 发布的 Word2Vec。借助 Word2Vec, 可以很容易地在大量语料中训练得到一个词向量模型。也是从那时开始,深度 NLP 逐渐崭露头角成为主流。
早期的词向量都是静态的,一旦训练完就固定不变了。随着 NLP 技术的不断发展, 词向量技术逐渐演变成 基于语言模型的动态表示。也就是说,当上下文不一样时, 同一个词的向量表示将变得不同。而且,句子的表示也不再是先拿到词向量再构造句子向量, 而是在模型架构设计上做了考虑。当输入句子时,模型经过一定计算后,就可以直接获得句子向量; 而且语言模型不仅可以表示词和句子,还可以表示任意文本。 类似这种将任意文本(或其他非文本符号)表示成稠密向量的方法,统称 Embedding 表示技术。 Embedding 表示技术可以说是 NLP 领域(其实也包括图像、语音、推荐等领域)最基础的技术, 后面的深度学习模型都基于此。甚至可以稍微夸张点说,深度学习的发展就是 Embedding 表示技术的不断发展。
语言模型
语言模型(Language Model, LM)简单来说,就是利用自然语言构建的模型。 自然语言就是我们日常生活、学习和工作中常用的文字。语言模型就是利用自然语言文本构成的, 根据给定文本,输出对应文本的模型。
贪心搜索和集束搜索
语言模型具体是如何根据给定文本输出对应文本的呢?方法有很多种,比如我们写好一个模板:“XX 喜欢 YY”。 如果 XX 是 我,YY 是你,那就是 “我喜欢你”,反过来就是 “你喜欢我”。 我们这里重点要说的是 概率语言模型,它的核心是概率,准确来说是下一个 Token 的概率。 这种语言模型的过程就是通过已有的 Token 预测接下来的 Token。举个简单的例子, 比如你只告诉模型“我喜欢你”这句话,当你输入“我”的时候,它就已经知道你接下来要输入“喜欢”了。 为什么?因为它的“脑子”力就只有这 4 个字。
接下来升级一下。假设我们给了模型很多资料,多到现在网上所能找到的资料都给了它。 这时候你再输入 “我”,此时它大概不会说 “喜欢” 了。为什么呢?因为见到了更多不同的文本, 它的“脑子”里已经不只有 “我喜欢你” 这 4 个字了。不过,如果我们考虑的是最大概率,也就是说, 每次都只选择下一个最大概率的 Token,那么对于同样的给定输入, 我们依然会得到相同的对应输出(可能还是 “喜欢你”,也可能不是,具体要看给的语料)。 对于这样的结果,语言模型看起来比较 “呆”。我们把这种方法叫作 贪心搜索(greedy search), 因为它只往后看一个词,只考虑下一步最大概率的词!
为了让生成的结果更加多样和丰富,语言模型都会在这个地方执行一些策略。比如让模型每一步多看几个可能的词, 而不是就看概率最大的那个词。这样到下一步时,上一步最大概率的 Token,加上这一步的 Token, 路径概率(两步概率的乘积)可能就不是最大的了。上面介绍的这种叫作 集束搜索(beam search),简单来说, 就是一步多看几个词,看最终句子(比如生成到句号、感叹号或其他停止符号)的概率。 看得越多,越不容易生成固定的文本。
简单语言模型-N-Gram
上面介绍的两种不同搜索方法(贪心搜索和集束搜索)也叫 解码策略。 当时更多被研究的还是模型本身,我们经历了从简答模型到复杂模型, 再到巨大模型的变迁过程。
简单模型就是就是把一句话拆成一个个 Token,然后统计概率,
这类模型有个典型代表:N-Gram 模型,它也是最简单的语言模型。
这里的 $N$
表示每次用到的上下文 Token 数。N-Gram 模型中的 $N$
通常等于 2 或 3,
等于 2 的叫 Bi-Gram,等于 3 的叫 Tri-Gram。Bi-Gram 和 Tri-Gram 的区别是,
前者的下一个 Token 是根据上一个 Token 来的,而后者的下一个 Token 是根据前两个 Token 来的。
在 N-Gram 模型中,Token 的表示是离散的,实际上就是词表中的一个单词。这种表示方式比较简单,
再加上 $N$
不能太大,导致难以学到丰富的上下文指示。事实上,它并没有用到深度学习和神经网络,
只是一些统计出来的概率值。假设 “人工智能/让” 出现了 5 次,“人工智能/是” 出现了 3 次,
将它们出现的频率除以所有的 Gram 数就是概率。
训练 N-Gram 模型的过程其实是统计频率的过程。如果给定 “人工智能”, N-Gram 模型就会找基于 “人工智能” 下个最大概率的词, 然后输出“人工智能让”。接下来就是给定“让”,继续往下走了。 当然,我们也可以用上面提到的不同解码策略(贪心搜索和集束搜索)往下走。
接下来,让每个 Token 成为一个 Embedding 向量。简单解释一下在这种情况下怎么预测下一个 Token。
其实还是计算概率,但这次和刚才的稍微有点不一样。在刚才离散的情况下,
用统计出来的对应 Gram 数除以 Gram 总数就是出现概率。但是稠密向量要稍微换个方式,
也就是说,给你一个 $d$
维的向量(某个给定的 Token),你最后要输出一个长度为 $N$
的向量,
$N$
是词表大小,其中的每一个值都是一个概率值,表示下一个 Token 出现的概率,概率值加起来为 1。
按照贪心搜索解码策略,下一个 Token 就是概率最大的那个。写成简答的计算表达式如下:
# d 维,加起来和 1 没关系,大小是 1xd,表示给定的 Token
X = [0.001, 0.002, 0.0052, ..., 0.0341]
# N 个,加起来为 1,大小是 1xN,表示下一个 Token 就是每个 Token 出现的概率
Y = [0.1, 0.5, ..., 0.005, 0.3]
# W 是模型参数,也可以叫作模型
X · W = Y
上面的 $W$
就是模型参数,其实 $X$
也可以被看作模型参数(自动学习到的)。
因为我们知道了输入和输出的大小,所以中间其实可以经过任意的计算,
也就是说,$W$
可以包含很多运算。总之各种张量(三维以上数组)运算,
只要保证最后的输出形式不变就行。各种不同的计算方式就意味着各种不同的模型。
复杂语言模型-RNN
在深度学习早期,最著名的语言模型就是使用 循环神经网络(recurrent neural network, RNN) 训练的, RNN 是一种比 N-Gram 模型复杂得多的模型。RNN 与其他神经网络的不同之处在于,RNN 的节点之间存在循环连接, 这使得它能够记住之前的信息,并将它们应用于当前的输入。这种记忆能力使得 RNN 在处理时间序列数据时特别有用, 例如预测未来的时间序列数据、进行自然语言的处理等。通俗地讲,RNN 就像具有记忆能力的人, 它可以根据之前的经验和知识对当前的情况做出反应,并预测未来的发展趋势。
RNN 结构如下所示。在下图中,右边是左边的展开,$A$
就是参数,$x$
是输入,$h$
是输出。
注意,$h$
并不是输出的概率,而是隐向量。如果需要概率,可以再对 $h$
执行张量运算,
归一化到整个词表即可。
自然语言是一个 Token 接着一个 Token(Token by Token) 的,从而形成一个序列。 要解释参数是怎么学习的,就要稍微解释一下学习(训练)过程。
下图是语言模型学习(训练)时的输入输出。第一行就是输入 $X$
,第二行就是输出 $Y$
,
SOS
(start of sentence) 表示句子开始,EOS
(end of sentence) 表示句子结束。:
RNN 模型:
import torch
import torch.nn as nn
# RNN 模型
rnn = nn.RNN(32, 64)
# 输入
input_tensor = torch.randn(4, 32)
# 初始隐向量
h0 = torch.randn(1, 64)
# 输出
output, hn = rnn(input_tensor, h0)
print(output.shape)
print(hn.shape)
(torch.Size([4, 64]), torch.Size([1, 64]))
上面的 RNN 模型 rnn
中:
- 输入
input_tensor
是一个$4 \times 32$
的向量,换句话说,输入是 4 个 Token, 维度$d = 32$
in_token1: [t1, t2, ..., t32]
in_token2: [t1, t2, ..., t32]
in_token3: [t1, t2, ..., t32]
in_token4: [t1, t2, ..., t32]
h0
就是随机初始化的输出,也就是 4 个 Token 中的第一个 Token 的输出out_token1: [ot1, ot2, ..., ot64]
output
的 4 个 64 维的向量分别表示 4 个输出output1: [o1, o2, ..., o64]
output2: [o1, o2, ..., o64]
output3: [o1, o2, ..., o64]
output4: [o1, o2, ..., o64]
hn
就是最后一个 Token 的输出(它和output
的最后一个 64 维向量是一样的), 也可以看成整个句子的表示hn(output4): [o1, o2, ..., o64]
注意,这里的 output
和 上图中的 $Y$
还没有关系。如果要输出词的概率,就需要先扩充到词表大小,
再进行归一化。
# 假设词表大小 N = 1000
wo = torch.randn(64, 1000)
# 得到 4x1000 的概率矩阵,每一行概率和为 1
probs = nn.Softmax(dim = 1)(output @ wo)
print(probs.shape)
print(probs.sum(dim = 1))
torch.Size([4, 1000])
tensor([1.0000, 1.0000, 1.0000, 1.0000]), grad_func=<SumBackward>
这里的 probs
的每一行就是词表大小的概率分布,概率和为 1,
意思是根据当前 Token 生成下 Token 的概率,下一个 Token 有可能是词表中的任意一个 Token。
但它们的概率和一定为 1。因为我们知道接下来每个位置的 Token 是什么(也就是上图中的输出 $Y$
)。
这里得到最大概率的那个 Token,如果正好是这个 Token,则说明预测对了,参数就不用怎么调整;
反之,模型就会调整前面的参数(rnn
、input_tensor
、h0
和 wo
)。
你可能会疑惑为什么 input
也是参数,其实前面我们偷懒了,本来的参数是一个 1000×32 的大矩阵,
但我们使用了 4 个 Token 对应位置的向量。这个 1000x32 的大矩阵其实就是词向量(每个词一行),
开始时全部随机初始化,然后通过训练调整参数。训练完成后,这些参数就不变了,
然后就可以用前面同样的步骤来预测了,也就是给定一个 Token,预测下一个 Token。
如果使用贪心搜索,则每次给定同样的 Token 时,生成的结果就一样。其余的就和前面讲的接上了。
随着深度学习的不断发展,出现了更多比 RNN 还复杂的网络结构,而且模型变得更大,参数更多,但逻辑和方法是一样的。
语言模型就介绍到这里。简单直观地说,构建(训练)语言模型的过程就是学习词、 句内在的“语言关系”;而推理(预测)就是在给定上下文后, 让构建好的模型根据不同的解码策略输出对应的文本。无论是训练还是预测,都以 Token 为粒度进行。
最强表示架构-Transformer
Transformer 是一个基于注意力机制的编码器-解码器(encoder-decoder)架构,刚开始主要应用在 NLP 领域, 后来横跨到语音和图像领域,并最终统一几乎所有模态(文本、图像、语音)的架构。 Transformer 来自 Google 在 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)架构, 简单来说就是序列到序列的架构:输入是一个文本序列,输出是另一个文本序列。翻译就是一个很好的例子,比如:
编码器和解码器可以采用 RNN,编码器这一侧的每个 Token 都可以输出一个向量表示, 而这些所有 Token 的输出向量都可以在处理后作为整句话的表示。说到这里,整句话又怎么表示呢? 对于 RNN 这种结构,可以把最后一个 Token 的输出作为整个句子的表示。 当然,很符合直觉的是也可以取每个词向量的平均值。除了平均值,也可以求和、取最大值等。 现在重点来了,看解码的过程,其实解码器在生成每一个 Token 时都用到了编码器中每一个 Token 信息, 以及已经生成的那些 Token 的信息。前面这种关注编码器中每个 Token 的信息的机制就是 注意力(attention)机制。 直观的解释,就是当生成单词 “power” 时,“力量” 两个字会被赋予更多权重(注意力),其他情况也类似。
Transformer 的整体结构如下:
如上图所示,左边是编码器,一共有 $N$
个;右边是解码器,也有 $N$
个。
为简单起见,可以假设 $N = 1$
,如此一来,上图左边就是一个编码器,右边则是一个解码器。
也可以把它们想象成一个 RNN,这样有助于从宏观上把握。Transformer 用到的东西其实和 RNN 并没有关系。
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 任务
实际上,大多数 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 基于 Seg2Seq 架构,可以同时处理 NLU 和 NLG 任务, 而且这种自注意力机制的特征提取能力(表示能力)很强。 其结果就是 NLP 取得了阶段性的突破,深度学习开始进入 微调模型 时代, 大概的做法就是,拿着一个开源的预训练模型,在自己的数据上微调一下,让它能够完成特定的任务。 这个开源的预训练模型往往就是一个语言模型, 在大量语料中,使用我们前面所讲的语言模型的训练方法训练而来。
- 偏 NLU 领域的第一个成果是谷歌公司的 BERT,
相信不少人即便不是这个行业的也大概听过。BERT 就是使用了 Transformer 的编码器(没有使用解码器),
有 12 个 Block(淡黄色模块,每一个 Block 也可以叫作一层)和 1 亿多个参数。
BERT 不预测下一个 Token,而是随机地把 15% 的 Token 盖住(其中 80% 用
[MASK]
替换,10% 保持不变, 10% 随机替换为其他 Token),然后利用其他没盖住的 Token 来预测盖住位置的 Token。 这其实和根据上文信息预测下一个 Token 是类似的,所不同的是它可以利用下文信息。 - 偏 NLG 领域的第一个成果是 OpenAI 的 GPT,GPT 就是使用了 Transtormer 的解码器(没有使用编码器), 参数和 BERT 差不多。BERT 和 GPT 都发布于 2018 年,然后分别走上了不同的道路。
生成语言模型-GPT
GPT,就是 ChatGPT 中的那个 GPT,中文叫作生成式预训练 Transformer。 生成式的意思就是类似于语言模型那样,一个 Token 一个 Token 地生成文本, 也就是上面提到的解码器的原理。预训练刚刚也提过了,就是在大量语料中训练语言模型。 GPT 模型从 GPT-1 到 GPT-4,一共经历了 5 个版本,中间的 ChatGPT 是 3.5 版。 GPT-1、GPT-2 和 GPT-33 都是有论文发表的,接下来分别介绍它们的基本思想。 ChatGPT 没有论文发表,不过它的姐妹版本 InstructGPT 有论文发表, GPT-4 也没有论文发表,只有技术报告,不过里面并没有技术细节。 因此,我们对 GPT-4 不做介绍,读者可以将其看作能力更强的 ChatGPT 升级版。
GPT-1 和 BERT 一样,用的是下游任务微调模式,也就是在不同下游任务数据上微调与训练模型。 下图是 GPT-1 基本结构和下游任务微调模式(摘自 GPT-1 论文 “Improving Language Understanding by Generative Pre-Train”):
上图左边的 GPT-1 基本结构,在前面已经介绍过了,用的是 Transformer 的解码器, 不过这里因为没有编码器,所以不需要有和编码器交互的多头注意力模块。现在重点看看上图的右边, 这是 GPT-1 在各种下游任务上的处理流程。简单来说,就是针对不同的任务构造不同的输入序列, 然后丢给 GPT-1 获取 Token 或句子的 Embedding 表示,再通过 Linear+Softmax 输出结果。 Linear 是一种最基础的网络结构,也就是线性映射,这里用于维度转换,转为输出需要的大小。 Softmax 主要用来把输出映射到概率分布(概率和为 1)。这种拼接输人的方法在当时非常流行, 紧跟其后的 BERT 也使用类似的方式,并引领了一个时代, 直至 ChatGPT 的出现让我们进人大语言模型时代(不过,针对很多传统 NLP 任务 BERT 依然具备优势)。 统一的处理方法能够减小不同任务对模型的适配难度。因此不管什么任务,都想方设法将其变成一个序列就行, 相似匹配就是把两句话直接拼接起来,预测它们是否相似(输出标签为 1 或 0)。
GPT-1 的这篇论文还有几个点在当时看起来可能没什么感觉,现在回看却有点意思。
- 第一,预训练模型中的每一层(上图中的淡黄色模块)都包含用于解决目标任务的有用功能, 多层(意味着模型更深)有更多能力;
- 第二,随着参数的增加,零样本获得更好的性能。简单总结就是,模型大了不仅能学到更多知识, 有助于解决下游任务,还表现出了零样本能力。这里的零样本(zero-shot)是指直接给模型输入任务, 让它输出任务结果。与此类似的还有少样本(few shot)和单样本(one-shot), 即给模型提供一些(或一个)示例,然后给出任务,让它输出任务结果。
有了上面的结论,你是不是想看看更多层(更多参数)的表现如何?于是半年后,GPT-2 来了, 参数量从 GPT-1 的 1.1 亿增加到了 15 亿,增长了十几倍。更有意思的是, GPT-I 的博客文章 “Improving language understanding withunsupervised leaming” 中有一个 “未来工作列表”, 排在第一位的就是扩大规模,还有两个分别是提升微调,以及更好地理解为什么生成式预训练能提升 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.000, 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 Leaming》。 现在回忆,1750 亿的参数量在当时看太大了,而且也太贵了(几百万美元), 一般的单位和个人根本负担不起。关于这一点,不光小部分人没意识到, 可能是除了 OpenAI 团队之外的整个世界都没意识到。
请看下图,横坐标是样本数量,纵坐标是精准度。下图提供了如下信息。
- x-shot(x 表示 zero、one、few)在不同参数规模下差别巨大,大语言型有超能力
- 在大语言模型下,单样本效果明显大幅提升,增加提示词会进一步提升效果
- 少样本的边际收益在递减。大概在 8 样本以下时,提示词作用明显,从单样本到 8 样本, 提示词的效果提升幅度也在递减。当超过 10 样本提示词基本就没有作用了
总而言之,大语言模型具有 In-Context(上下文)学习能力, 这种能力使得它不需要针对不同任务再进行适应性训练(微调), 大语言模型用的就是它自己本身的理解力。这本来应该很让人震惊(甚至有一点惊恐), 不过大家可能都先被它的价格和规模震惊到了。接下来, 我们再直观地感受一下利用这种 In-Context 学习能力完成任务的方式,如下图所示。
下图右边的微调方式需要先根据训练样本更新模型参数,之后再进行预测。 下图左边的三种方式都利用了大语言模型(large language model, LLM)的 In-Context 学习能力, 不需要更新模型,而且看起来也都不复杂,只需要按照格式把输入构建好, 然后传给模型进行预测就可以了。这也是本书写作的初衷之——人工智能已经平民化, 只要有手(可能以后不用手也行),通过使用 LLM 就可以做出人工智能应用了。不过这里有一点需要说明,为了简便, 下图中的样本都比较简单,但实际中的样本一般是完整的句子。
使用 In-Context 学习能力和微调完成任务(摘自 GPT-3 论文 “Language Models are Few-Shot Learners”):
最后值得一提的是 GPT-3 论文中的展望,在 GPT-3 论文的“局限”小节中、 作者提出了 GPT-3 目前的一些问题,其中有两点需要特别指出, 因为们是下一代 InstructGPT(也是 ChatGPT 的姐妹版)以及更高级版本的方向。
- 自监督训练(也就是语言模型一般的训练方法)范式已到极限,新的训练方法迫在眉睫。 未来的方向包括:从人类那里学习目标函数、强化学习微调或多模态
- 不确定少样本是在推理时学习到新的任务,还是识别出来了在训练时学到的任务。 最终,甚至不清楚人类从零开始学习与从之前的样本中学习分别学到了什么。 准确理解少样本的工作原理是未来的一个方向
上面的第一点在下面就会提到,这里主要说说第二点。当我们给出一些示例(少样本)时, 我们还无法精准确定是在推理时“学习”到新任务的处理方法(在这种情况下,没有示例就没有能力; 这里的“学习”要打引号,因为它不调整参数),还是在训练时就已经具备了这个能力, 示例只是让它“回想”起之前学的东西。这里有点绕,拿人来举例,可能不太恰当,但能大致说明问题。 假设当你读到一首诗时,自己也诗兴大发写了一句诗。你说这句诗是因为你读到这首诗时“领悟”到的, 还是你本来就有这个积累(记忆),现在只是因为读这首诗而被激发出来? 这可能涉及大脑、思维、意识等领域知识,而人类至今也没有弄清楚它们的原理, 所以我们现在还不知道答案。
利器强化学习-RLHF
RLHF(reinforcement learning from human fecdback,从人类反馈中强化学习),听起来有点平淡无奇。 确实,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 的整个流程分为三个步骤(摘自 InstructGPT 论文 “Training language models to follow instructions with human feedback”):
- 步骤一:SFT(supervised fne-tuning,有监督微调)。顾名思义, SFT 是在有监督(有标注)数据上微调训练得到的。这里的有监督数据其实就是输人提示词, 输出相应的回复,只不过这里的回复是人工编写的。这个工作要求比一般标注要高,其实算是一种创作。
- 步骤二:RM(reward model,奖励模型)。具体来说,将一个提示词丢给前一步的 SFT, 输出若干(4~9个)回复,由标注人员对这些回复进行排序。然后从 4~9 个回复中每次取两个, 因为是有序的,所以可以用来训练 RM,让模型学习到好坏评价。这一步非常关键, 它就是所谓的人类反馈(human feedback),用于引导下一步模型的更新方向。
- 步骤三:RL(reinforcement learing,强化学习), 使用 PPO 进行训练。PPO(proximal policy optimization,近端策略优化)是一种强化学习优化方法, 它背后的主要思想是避免每次太大的更新,提高训练的稳定性。具体过程如下:首先初始化一个语言模型, 然后丢给它一个提示词,生成一个回复,用上一步的RM给这个回复打分,将这个打分回传给模型更新参数。 这里的语言模型在强化学习视角下就是一个策略。这一步有个很重要的动作, 就是在更新模型时考虑模型每一个 Token 的输出和 SFT 输出之间的差异性,要让它们尽量相似。 这是为了缓解强化学习可能的过度优化。
就这样?对,就这样,RLHF 都表现在上面了,效果大家都知道了。虽然 ChatGPT 没有相关论文发表, 但我们基本相信它也是基于类似的思路实现的。当然,这里面细节非常多,即便知道了这个思路, 也不一定能复现出来。这在深度学习时代很正常,里面的各种小设计、小细节实在太多了。 当它们堆积到一定量时,造成的差别是很难一下子弥补的,如果别人不告诉你, 那你就只能自己慢慢做实验去逐步验证了。
下面我们强行解释一下 RLHF 是如何起作用的,以及为什么它现在能成为一个基本的范式。 其实,对于将强化学习用在 NLP领域一直以来都有研究,正好笔者也由于一些原因一直在关注文本生成, 以及强化学习在文本生成方面的研究。这里可能有两个难点: 一是训练的稳定性;二是奖励函数的设计。前者有PPO与SFT 的差异衡量,得到不小的改进; 而对于后者,如果要从客观角度考虑设计一个规则,就不那么容易了。笔者也曾设想过很多类似的方法, 比如加人一些语法规则限制,甚至加入类似最省力法则这样的规则。
InstructGPT 使用人类反馈直接作为“规则”,把这种“规则”给隐式化,作黑盒。 我们只管结果好坏,至于中间有什么规则,有多少种规则,怎么起用,统统不关心。 这是和深度学习类似的思路,相比而言,我们之前的想法可食有些过于想当然了, 毕竟语言学本身也有不少争议,认识并没有得到统一,比如语言能力是不是人与生俱来的能力? InsfaGPT 的做法则更加简单、直接,而且有效。
衡量好坏
剩下要解决的就是怎么衡量“好坏”,毕竟最终是要有个结果的,既然要结果,就要有标准。 读者不妨思考一下,如果换作你,你会如何设计一些指标来衡量两段输出内容的好坏。 这一步看似容易,其实特别难,因为指标的设计会影响到模型的学习方向,最终就会影响到效果。 因为这个输出的好坏衡量标准太多了,虽然看起来是对给出的几个结果进行排序(上文的步骤二), 但其实这个过程中间隐藏了大量人类的认知,模型训练过程其实就是和步骤二这个衡量过程对齐的过程; 所以,如果步骤二指标没设计好,步骤三就会白费力气。尤其是对于 InstructGPT 这样要完成大量不同任务的设计, 衡量就更加不容易。以一个文本摘要任务为例,我们可能最关注的是能否准确概括原文信息, 而一个生成任务可能更关注流畅性和前后逻辑一致性。InstructGPT 里面有 10 种任务, 分别针对每种任务设计指标,不仅麻烦,而且效果还不一定好,因为这些指标并不一定都是一个方向。 还有就是,万一又有了一个新任务,难道要再去设计一套指标,全部重新训练一遍模型吗?
让我们来看看 InstructGPT 是怎么设计衡量指标的,笔者觉得这是 InstructGPT 论文最宝贵的地方, 也是最值得我们思考和实践的地方。 感兴趣的读者可以进一步阅读笔者之前写的一篇专门介绍 ChatGPT 标注的文章《ChatGPT 标注指南:任务、数据与规范》。 首先,InstructGPT 用了三大通用指标——有帮助、真实性和无害性,有点类似于阿西莫夫的机器人三定律。 也就是说,不管是什么任务,都得着这三个方向靠拢。这个想法值得称赞。现在我们看到这个结果了, 自然感觉好像没什么,但如果事先不知道要去设计出来,大部分是很容易陷入被任务影响的境地。 其实,OpenAI 团队在 “In-Context” 学习能力上的坚持也是一样的。 当别人告诉你那个结果时,你可能觉得好像没有什么,甚至很多研究机构、研究人员都有过这种想法。 但在有效果之前,笃信一条罕有人走的路,且一直坚定不移地走下去,这是很不容易的。
有了刚刚的三大通用指标,接下来就是细化,使其具有可操作性。 比如,对于通用指标 “有帮助”,InstuctGPT 给了一些属于 “有帮助” 行为的示例,如下所示:
- 用清晰的语言写作
- 回答他们想问的问题,即使问错了,也要回答
- 对国际性敏感(比如 “football” 不应该指美式足球,“总统” 不一定指美国总统)
- 如果指令(instruction)太让人困惑,要求澄清并解释指令为什么让人困惑
- 不给出过长或冗长的答案,或重复问题中的信息
- 不在给定的内容之外假设无关的额外上下文,除非是关于世界的事实,或是任务的隐含部分。 比如,如果要求 “礼貌地回复这封电子邮件:{邮件内容}”,则输出不应该假设 “我这次不能来, 但下周末有空”。但如果要求 “给苏格拉底写一封电子邮件”,则可以放心地使用上面的假设。
笔者相信实际上这个列表可能很长,有很多例子会在实际标注过程中被依次添加进去, 直到能覆盖绝大多数情况为止,即对于大部分要标注的数据, 根据提供的细则很容易就判断出来是否“有帮助”。现在不妨停下来思考一下, 如果一始就奔着这些细则设计奖励规则——只是想想就觉得不太现实。 其他两个通用指标也有一些示例,这里不赘述,感兴趣的读者可以阅读上面提到的笔者之前写那篇文章, 以及这篇文章最后所列的参考资料(因为有些文档资料在这篇文章并没有提及)。
有了细则还没完,接下来要解决的是指标之间的冲突权衡问题。因为这是一个比较任务(比较哪个输出好), 当涉及多个指标时,一定会出现 A 指标的一个结果好于另一个结果,但 B 指标可能相反的情况。 指标越多,情况越复杂(好在只有三个指标)。对此,InstructGPT 也给出了指导原则:
- 对于大部分任务,无害性和真实性比有帮助更加重要。
- 然而,如果一个输出比另一个输出更有帮助,或者该输出只是稍微不那么真实或无害, 又或者该任务似乎不属于“高风险领域”(如贷款申请、医疗、法律咨询等), 则更有帮助的输出得分更高。
- 当选择同样有帮助但以不同方式不真实或有害时, 问自己哪个输出更有可能对用户(现实世界中受任务影响最大的人)造成伤害。 这个输出应该排名较低。如果在任务中不清楚这一点,则将这些输出标记为并列。
对于边界样例的总体指导原则是,你更愿意从试图帮助你完成此任务的客户助理那里收到哪种输出? 这是一种设身处地的原则,把自己假想为任务提出者,然后问自己期望得到哪种输出。
看看这些,你是不是也觉得这一步没那么容易了,它们虽然看起来没那么“技术性”, 想要很好地完成却需要优秀的设计能力、宏观把控能力和细节感知能力。 笔者更加相信这些细则是自底向上逐步构建起来的,而不是一开始就设想好的。 这一定是在实践中不断产生题惑,然后经过仔细分析权衡,逐步加人一条条规则, 最终逐步构建起来的一整套系统方案。笔者觉得这套系统方案可能是比效据还要珍贵的资产, 它所产生的壁垒是用时间不断实践堆积出来的。
InstructGPT 或 ChatGPT 相比GPT-3 有更强的零样本能力, 少样本很多时候已经用不着,但提示词还是需要的,由此催生了一个新的行当——提示工程。 不过,据 OpenAI 的 CEO 在一次采访中所言,再过几年提示工程也不需要了(可能在生成图片时还需要一些), 用户要做的就是直接通过自然语言和人工智能交互。我们无法判断他说的会不会真的实现, 但有一点可以肯定,人工智能的门槛必定会进一步降低,再过几年, 可能一名初中生都能通过已有的服务创造出不错的人工智能应用。