(给机器学习算法与Python学习加星标,提升AI技能)
GPT-3最近又开始火起一阵,关于GPT-3的各种精彩文章现在也很多,其中不光有展示了它生成结果的,也有对结果一些思考的,还有可视化其工作原理的。
由于GPT-3各种参数应用太强大,以至于深度学习之父Hinton都发推特表示,从GPT-3惊人的性能可以推测,生命、宇宙和万物的答案,只是4万亿个参数而已。
虽然都已经有了这些资料,但文摘菌还是花了很多时间通读了好几篇论文和博客才算是大概明白了 GPT-3的原理。
因此,本文目标非常简单明了:帮你尽可能详尽地了解GPT-3的架构。要是你嫌我说得太啰嗦的话(编者:真的啰嗦),也可以直接跳到最后的整体结构图,直接看整体结构。
论文原图
首先,让我们来看看Transformer和GPT原论文里的图:
看完感觉怎样,如果你像我当初一样,对这两张图讲了啥一脸懵逼的话,那么先别急,就和我一起深入探索一下吧!
输入/输出
在开始前,需要明确一下:GPT的输入和输出分别是啥?
它的输入是一个单词序列(也叫token),输出则是最有可能出现在当前输入序列后面的单词的预测。
就是这么简单!基本上你看到的所有GPT输出的对话、故事和其他内容都是通过这种简单的输入输出方式完成的:输入一个单词序列,获得下一个单词。
Not all heroes wear -> capes
当然,一般我们希望生成的不止是一个单词,这也非常简单。只要这样做就行,当生成一个单词后,将其添加到原来序列里去,然后输入新序列,继续生成下一个单词… 如此往复。
Not all heroes wear capes -> but
Not all heroes wear capes but -> all
Not all heroes wear capes but all -> villans
Not all heroes wear capes but all villans -> do
以上步骤可以无限重复直到获得想要长度的预测文本。但还是需要补充两点细节。
第一,虽然输入序列的长度默认值为2048个字符(GPT-3的设置),但可以输入短序列,只用把剩余位置用空值填充即可(编者:不填充也行,只是如果要并行批处理的话,需要对短的进行填充)。
第二,GPT的输出结果并非单个预测,而是一个预测(包含对每个可能词的概率)序列(长度2048)。整个序列中的每一个位置都依据之前文本而作出预测。但生成文本时,一般只看序列中最后一个单词的预测结果。
如上图所示,序列进,序列出。
编码
但稍等一下,这里还有个问题,GPT-3本身是无法理解单词含义的。作为机器学习算法,它只能对向量进行运算。那么如何将单词变成向量呢?
首先第一步,建立一个含有所有单词的词表,这样就能给每个单词一个索引。Aardvark是0,aaron是1,依此类推。(GPT-3的词表大小是50257个词)。
因此,就可以将每个单词转换为50257维的独热向量,只在索引位置赋值1,其余设为0。比如 The 的索引是 100,那么就给第100的位置设为1就行,其他都是0。
对序列中每一个单词都要进行了这样的处理:
就能获得转化的最终结果:一个由0和1组成的2048 x 50257的矩阵。
补充:为了处理词表效率问题,GPT-3实际上用了字节级的Byte Pair Encoding (BPE)算法来处理单词。这就意味着词表中的“单词”其实并不是完整单词,而是一个个类似子词的字符组(字节级别就是字节组)。用GPT-3来对句子进行处理,会将“Not all heroes wear capes”分成以下几个字节:“Not”,“all”,“heroes”,“wear”,“cap”,“es”,其ID为词库中的3673、477、10281、5806、1451和274。
这里有对BPE等Tokenization相关知识进行详尽介绍,而且还可以用这个github库( github implementation)自己上手试试:
https://huggingface.co/transformers/tokenizer_summary.html
词向量
50257维对于向量其实是很大的,而且因为大部分都是用0填充(非常稀疏),这会浪费很多空间。
为了解决这个问题,我们可以学习一个词向量函数:一个输入是50257长度0和1组成的独热向量,输出是n长度数值向量的神经网络。在这里,其实相当于在将单词信息存储(或投影)到一个较小维度空间中去。
举例来说,如果词向量维度是2,那么就好比是将每个单词都存储在一个2维空间里。
另一种直观的思考方式是将每个维度都看做一种属性:比如“柔软的”或是“金闪闪的”,然后为每个属性赋予一个值,这样就能准确知道一个词到底代表着什么。
当然,词向量维度一般大于2:比如GPT-3用了12288维。
在实践中,每个单词的独热向量都会乘以词向量权重,然后变成一个12288维的词向量。
用数学语言表述的话,就是将2048 x 50257独热向量矩阵与学习过的50257 x 12288词向量矩阵相乘,最后获得一个2048 x 12288的词向量矩阵。
下面,我就只给二维矩阵画成小方块,然后旁边标上尺寸。如果需要,我还会将矩阵分行以明确表示每一行对应于序列中的单词。
另外需要注意的是,词向量矩阵是分别并行地和各个词的独热向量相乘的。这意味着:在整个过程中,各个词之间是没有信息传递的,所以词向量中也就不会有绝对或相对位置信息。
位置编码
因此,为了对序列中的词位置进行编码,首先需要获得词位置(一个取值范围为[0-2047]的标量),并将其传递给12288个频率不同的正弦函数。
我还不太清楚这个操作背后的具体原理。但作者解释说,这样产生了大量的相对位置编码,这对于模型很有帮助。从其他角度来看的话:可以把这个看成如一个信号是由多个周期采样之和组成一样(参考傅立叶变换或SIREN网络结构),或者可能是语言本身会展示出在不同长度之间循环的周期性,如诗歌。(文摘菌:但实际上感觉也没这么复杂,就跟用二进制表示数一样,只是这里是用连续的三角函数,可参考这篇:https://kazemnejad.com/blog/transformer_architecture_positional_encoding/,况且现在很多都直接用学习的位置向量)
之后每次词都能获得一个12288维的位置向量。之后将这些向量组合成一个2048行的单个矩阵,每行都是一个位置向量。
最后,再将位置向量矩阵和之前的词向量简单相加就行。
注意力机制(简化版)
简单来说,注意力机制的目的是:对于序列中的每个输出,预测其需要关注的词以要关注的程度大小。先来想象一个由3个词组成的序列,每个词都由512维向量表示。
首先,注意力机制会学习3个线性投影,都要用于词向量。也就是说,分别学习3个权重矩阵,这些矩阵会将词向量矩阵转换为3个单独的3x64矩阵,每个矩阵分别都有自己的用处。
将前两个矩阵(“queries”和“keys”)相乘,生成3x3矩阵 (QK^T)。该矩阵(通过softmax标准化)后,就能表示每个词相对于其他词的重要性了。
第三个矩阵(“values”)与刚才获得的重要性矩阵相乘,对于每个词,都能获得一个根据重要性来对所有词向量进行加权求和的向量。
例如,如果重要性矩阵只含0和1(每个词都只对应一个重要词)的话,那么其结果就好比根据最重要词从value矩阵中直接挑选对应行。
可能上面内容不能让你对注意力机制有个直观上的了解,但至少也能让你理解它内部底层的数学运算是怎么进行的了。
多头注意力机制
在作者提出的GPT-3模型中,还用了多头注意力机制。所谓多头其实就是上面说到的注意力机制被重复了多次(在GPT-3是96次),每次重复都有着不同的query, key, value转换矩阵。每次attention头的结果(单个2048 x 128矩阵)被拼接起来,最后获得一个2048 x 12288矩阵,然后为了获得更好效果,将其和一个线性投影相乘(不改变形状)。
注意:GPT-3为提高计算效率,用了稀疏注意力机制。说实话,我也还没花时间搞懂它怎么实现的。靠你自己了,祝好!
前馈机制
前馈模块是有1个隐层的经典多层感知器。将输入与学习过的权重相乘,加上偏差,相同过程重复一遍,即获得最后结果了。
这里,输入和输出形状都是相同的(2048 x 12288),但隐层的大小是4 * 12288。
需要声明:我将此操作也画成了一个圆,但这和结构中其他投影操作(词向量, query/key/value 投影)不同,这个“圆”实际上由两个投影组成,中间还有激活函数。
残差连接&标准化
在“多头注意力”和“前馈”模块后,有一个残差连接,模块输入和输出相加,然后对结果进行标准化。这在深度学习模型中现在已经很常见(自ResNet起)了。
注意:我画的图中都没有反映出:自GPT-2,层标准化就都移到每个子块的输入,类似于激活前的残差网络,并且在最后一个自注意力模块后,加一个额外的层标准化
解码
快完啦!在经过全部96层GPT-3的网络后,输入数据已被处理为2048 x 12288的矩阵。这个2048长序列中每个12288维度的向量都包含了当前位置该预测哪个词的信息。但是要怎么提取这些信息呢?
如果你还记得词向量部分内容的话,就好说了,在那里我们学习了一个映射,该映射将给定的单词转换为12288维的词向量。而这里,我们只要将这个过程反过来操作,将12288维的向量转回50257维的词编码就行。这里主要是想复用词向量矩阵,毕竟之前已经花了很大功夫来学习这样一个映射。
当然,这样做不会像开始时那样直接输出简单的0和1,但这也是一个好事:一个简单softmax后,就可以将结果值视成每个单词的概率。
此外,GPT-3论文还提到了参数top-k,该参数将输出中要采样候选数量限制为k个最可能的预测词。例如,当top-k参数为1时,就总是选择最可能的单词,其实就是 Greedy Search。
完整架构
这样差不多完了:一些矩阵运算,一些代数运算,我们就有了当前最先进的自然语言处理模型了。我已将所有组件绘制到一个原理图中了,单击下方链接查看完整版本:
http://dugas.ch/artificial_curiosity/img/GPT_architecture/fullarch.png
操作流程中包含学习过权重的部分已经被标红。