GPT-2 采用了 Decoder Only 的结构,在介绍它之前,不妨先看一下最原始的 Encoder-Decoder 结构。在 论文中介绍的便是 Encoder-Decoder 结构,最初用作机器翻译任务。它的结构图示如下:
要理解这个结构的意义,得结合它的应用——机器翻译。对于翻译任务,给出原语言的文本,第一步便是理解和提取原语言文本中蕴含的信息。然后借助提取得到的信息,生成目标语言的翻译。在生成目标语言的过程中,也要注意前文与后文的关联,遵循目标语言潜在的规则。
图片来源:https://jalammar.github.io/illustrated-gpt2/,采用:
下面是 Decoder 模块的注意力可视化,可以看见 Decoder 输出的 Token 能够利用源语言 Token 序列的信息,这便是 encoder-decode 注意力实现的。同时输出的目标语言 Token 只能利用之前输出的 Token(彩色连线),而不能利用后面的 Token(灰色连线),这便是 masked 注意力实现的。
图片来源:
了解了 Encoder-Decoder 结构后,可以发现该结构和机器翻译任务极为契合,但这也说明这种结构可能不适合其他任务。
GPT-2 采用了一种新的结构,在整个模型中只存在 Decoder 模块,称为 Decoder Only 结构。
由于没有 Encoder,Decoder 模块的 encoder-decode 注意力就没有意义了,因此它也被移除了。可以回看本文 Encoder-Decoder 结构的图示,其中把 Decoder 的 Multi-Head Attention 和它的 Add&Norm 删掉,便是 GPT-2 的 Decoder 结构了(其实也可以看作把 Encoder 的 Multi-Head Attention 换成 Masked Multi-Head Attention).
以下便是 GPT-2 的总体结构了:
通过对比 GPT-2 的源码和上面的结构图,可以更加深入理解它的模型结构。
https://github.com/openai/gpt-2/blob/master/src/model.py
GPT-2 采用了一种非常简单的绝对位置编码方式,即直接从 0 开始顺序给每个 Token 编号。
用一个示例来运行,可以看见位置编码的结果如下:
最终的嵌入向量由词嵌入向量和位置嵌入向量相加得到:
下面便是 GPT-2 的核心,即 Decoder 块的实现:
不过发现 Layer Norm 的位置似乎不一样,在代码实现中,Layer Norm 是在 Attention 和 MLP 之前的。
Decoder 块要堆叠多层,对应的便是代码的这个循环:
最后一个 Decoder 的输出向量便是预测下一个 Token 需要用到的信息,首先把它还原成二维张量:
接下来用 Text Embed 矩阵把它从 Embed 空间还原到 Token 空间,输出一个预测概率的张量:
其中每一行代表一个单词的预测,即模型预测每个词是词汇表中每个其他词的概率。
首先需要输入一段 Token 序列,可以是一段待补全的句子如 robot must obey ...
,如果想要从零生成,也可以直接输入一个特别的起始 Token <s>
来开始运行。获得输入的 Token 序列后,显然它是不满足 Context 长度的,因此在序列后加上 <pad>
填充到目标长度。该 Token 序列经过词汇表编码,送入模型进行推理,最后会得到一个预测结果的概率分布,下面就需要依据概率分布选取预测的结果了。
Top-k 和 temperature 是两种在 GPT 中常用的策略,用于控制模型生成文本的随机性和多样性。这两种策略在模型生成文本时,会影响模型选择下一个词的方式。
Top-k
在生成模型中,每个词的生成都是基于一个概率分布的。模型会为每个可能的下一个词分配一个概率,然后从这个分布中抽取一个词。抽取过程如下:
模型会计算出每个可能的下一个词的概率。
然后,模型会选择概率最高的 k 个词。
最后,模型会从这 k 个词中随机抽取一个。
Temperature
Temperature 抽样的公式可以用 softmax 函数表示。假设模型的原始输出是一个向量 z,其中 z_i 是第 i 个词的原始输出。那么,经过 temperature 抽样后,第 i 个词被选中的概率 p_i 可以用下面的公式计算:
其中,T 是 temperature 参数,sum_j 是对所有可能的词求和。这个公式说明:
当 T 越小,高概率的词会变得更有可能被抽到,低概率的词会变得更不可能被抽到,模型更加固定。
当 T 越大,高概率和低概率的词之间的差距会变得更小,模型生成的文本会更加随机和多样。
GPT-2 是一种自回归语言模型,它预测下一个 Token 时需要基于它以前的 Token 以及当前的输入,即:
因此,它的运行过程实际上是一个循环的过程。经过 3.1 的一次推理流程后,用 3.2 的方式选取预测的 Token,然后将预测 Token 尾接到输入,再进行推理。循环直到达到需要的长度为止。