作者:Daniel Dugas
原文:The GPT-3 Architecture, on a Napkin
译者:IvyLee
译文始发于【数据实战派】公众号:《从编码到输出,手绘图逐层拆解 GPT-3 结构》
关于 GPT-3 有很多精彩的文章,展示了它可以做什么(GPT-3 创意小说),思考它的影响(为什么 GPT-3 很重要、OpenAI 的 GPT-3 可能是继比特币以来的最大突破),图解其工作原理等。但是学习了这些内容之后,仍然需要再查找阅读一些论文和博客,才能确信自己真正了解了 GPT-3 的结构。
因此,本文的主要目的就是,帮助他人对 GPT-3 体系结构建立尽可能详细的了解。
如果不想阅读全文,可以直接点击查看完整结构示意图
。
原始图表
首先,原始的 Transformer 和 GPT 论文提供了以下图表:
这两个图表还不错,但是还不足以使我完全理解 GPT-3 的框架。如果你也像我一样,就来一起深入研究一下吧!
输入与输出
首先,我们需要知道:GPT 的输入和输出是什么?
输入是 N 个单词(也称为 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 个 Token(对于 GPT-3 来说)。仍然可以传递短序列作为输入:只需用“空”值填充其他额外的位置。
GPT 输出不仅是单个预测,而是一个多预测值(每个可能单词的概率)构成的序列(长度为 2048),每组预测值对应输入序列中的每个单词的“下一个”位置。但是在生成文本时,我们通常只查看序列中最后一个单词的预测。
如上图所示,以序列输入,以序列输出。
编码(Encoding)
但是请稍等,GPT 实际上并不能理解单词含义。作为一种机器学习算法,它是对数字向量进行运算的。那么如何将单词转换为向量呢?
第一步是将所有单词整理为一个词汇表,这使我们能够为每个单词赋予一个值。Aardvark 是 0,aaron 是 1,依此类推。(GPT 的词汇表包含 50257 个单词)
最后,我们可以将每个单词转换为 50257 长度的独热编码(one-hot)向量,其中仅索引 i 处的维(单词的值)为 1,其他维均为 0。
对序列中的每个单词都执行此操作
结果是一个 2048 x 50257 的 1/0 矩阵。
注意:为了提高效率,GPT-3 实际上使用的是字节级(byte-level)字节对编码(Byte Pair Encoding,BPE)进行分词(tokenization),这意味着词汇表中的“单词”不是完整的单词,而是经常出现在文本中的字符组(对于字节级 BPE 来说就是字节组)。使用 GPT-3 字节级 BPE 分词器,将“Not all heroes wear capes”分成 Token:“Not”、“all”、“heroes”、“wear”、“cap”、“es”,在词汇表中对应的 ID 为 3673、477、10281、5806、1451 和 274。这篇文章详细介绍了这一过程,还有对应的 GitHub 实现,可以自己尝试一下。
嵌入(Embedding)
对于一个向量来说,50257 相当大了,并且其中大部分都用 0 填充,这样会浪费很多空间。
为了解决这个问题,我们学习了一个嵌入(Embedding)函数:一个神经网络,以 50257 长度的 1/0 向量为输入,输出一个长度为 n 的数字向量。尝试将单词含义的信息存储(或投影)到较小的空间中。
例如,如果嵌入维数为 2,就类似于将每个单词存储在二维空间中的特定坐标处。
另一种直观的思考方式是,将每个维度都当做虚构的属性,比如“柔软度”或者“亮度”等,为每个属性赋予一个值,我们就可以准确知道哪个词是什么意思。
当然,嵌入维度通常会大于 2:GPT 使用的是 12288 维。
在实践中,每个单词的 one-hot 向量都与学习的嵌入网络权重相乘,最终成为 12288 维嵌入向量。
用代数术语来说,我们将 2048 x 50257 序列编码矩阵与 50257 x 12288 嵌入权重矩阵(已学习)相乘,最后得到 2048 x 12288 序列嵌入矩阵。
从现在开始,我会将二维矩阵绘制为小方块,并在旁边标注维度。在合适的时候,也会将矩阵行分开,使每行明确对应序列中的单词。
还要注意的是,由于矩阵乘法的工作原理,嵌入函数(即嵌入权重矩阵)分别应用于每个单词编码(即序列编码矩阵中的行)。也就是说,这一结果和将每个单词编码向量分别传递给嵌入函数并在最后将所有结果拼接在一起,是一样的。这意味着:在此过程中,没有信息流过整个序列,也没有关于 Token 的绝对或相对位置信息。
位置编码
为了对当前 Token 在序列中的位置进行编码,作者将 Token 的位置(标量 i,取值范围 [0-2047])作为参数传递给 12288 个频率不同的正弦函数。
这种做法为什么会有效,我还没有完全理解。作者的解释是,生成很多相对位置(relative-position)编码,这对模型很有用。用其他可能的心智模式来分析这一选择:考虑到信号经常表示为周期性样本之和的方式(参见傅立叶变换或 SIREN 网络结构),或者语言自然地呈现不同长度循环的可能性(例如诗歌)。
这一过程的结果是,每个 Token 对应一个 12288 维的数字向量。和嵌入操作一样,我们将这些向量组合成具有 2048 行的单一矩阵,其中每一行是序列中每个 Token 的 12288 列位置编码。
最后,与序列嵌入矩阵相同形状的序列位置编码矩阵可以直接添加到嵌入矩阵中。
注意力(简化版)
简单来说,注意力(Attention)机制的目的是:对于序列中的每个输出,预测需要关注的输入 Token 是哪些,以及有多少。这里,想象一个由 3 个 Token 组成的序列,每个 Token 都由 512 个值的嵌入表示。
该模型学习 3 个线性投影,所有这些投影都应用于序列嵌入。也可以说是学习了 3 个权重矩阵,这些矩阵将我们的序列嵌入转换为 3 个单独的 3x64 矩阵,每个矩阵分别用于不同的任务。
前两个矩阵(“查询 Query”和“键 Key”)相乘(QKT),得出一个 3x3 矩阵。该矩阵(通过 softmax 归一化)表示每个 Token 相对于其他 Token 的重要性。
注意:(QKŤ)这一操作,是 GPT 中唯一跨单词的操作,也就是矩阵中涉及到行交互的唯一操作。
第三个矩阵(“值 Value”)与这个重要性矩阵相乘,从而为每个 Token 生成所有其他 Token 值的混合(按各个 Token 的重要性加权)。
例如,如果重要性矩阵只有 1 和 0(每个 Token 只有一个重要的其他 Token),结果就像在 Value 矩阵中根据最重要的 Token 选择行。
希望这些内容能有助于对注意力机制的理解,至少直观展示了这一过程使用的代数原理。
多头注意力(Multi-Head Attention)
作者提出的 GPT 模型中使用了多头注意力。这仅仅意味着,上述过程被重复了很多次(GPT-3 中为 96 次),每次过程都有不同的学习查询、键、值投影权重。
每个注意力头的结果(单个 2048 x 128 矩阵)被拼接在一起,生成一个 2048 x 12288 矩阵,然后将其乘以一个线性投影(不会改变矩阵形状),以达到良好的度量。
注意:论文中提到 GPT-3 使用稀疏注意力,这使得计算效率更高。老实说,我没有花时间确切理解这是如何实现的,如果你想要弄懂的话,需要自己加油了!
前馈(Feed Forward)
前馈模块是我们熟知的多层感知器(Multi-Later Perceptron,MLP),具有一个隐含层。获取输入,乘以学习的权重,添加学习的偏差,重复该过程,获得结果。
此处,输入和输出形状都相同(2048 x 12288),但是隐藏层的大小为 4 x 12288。
需要明确一点:我同样将这一操作绘制为了一个圆形整体,但是与体系结构中其他的学习投影(嵌入、查询/键/值投影)不同,这一个“圆”实际上由一行的两个投影(学习加权矩阵乘以输入)构成,后续再加上学习偏差,最后经过 ReLU 激活。
相加和归一化
在“多头注意力”和“前馈”模块之后,将模块的输入添加到输出中,然后对结果进行归一化。这在深度学习模型中很常见(自从 ResNet 之后)。
注意:我的所有草图都没有反映出以下事实:自 GPT-2 起,“层归一化(Layer Normalization,LN)已移至每个子模块的输入,类似于激活前的残差网络(Residual Networks,ResNets),并且在最终的自注意力(self-attention)模块之后添加了额外的层归一化”
解码(Decoding)
马上就要完成了!通过所有 96 层 GPT-3 的注意力/神经网络机制后,输入已处理为一个 2048 x 12288 矩阵。对于序列中 2048 个输出位置,该矩阵都应该对应包含一个 12288 维向量,其中包含了可能的单词信息。那么,要如何将这些信息提取出来呢?
回想“嵌入”部分,我们学习了一种映射,该映射将给定单词(的独热编码)转换为了一个 12288 维向量嵌入。实际上,我们可以反转此映射,将输出的 12288 维向量嵌入转换回 50257 维单词编码。这一思路就是,既然已经花费了大量精力学习从单词到数字的良好映射,不妨重新利用!
当然,这样操作不会得到与最开始相同的 1/0 值,但这是一件好事:在快速 softmax 之后,我们可以将结果值视为每个单词的概率。
此外,GPT 论文还提到了参数 top-k,该参数将输出中要采样的可能单词数量限制为 k 个最可能的值。例如,当 top-k 参数为 1 时,选择的就是最有可能的单词。
完整结构
几次矩阵乘法,一些代数运算,我们自己就拥有了最先进的自然语言处理能力。我已将所有模块绘制到一个原型图中,点击下图可打开新页面查看完整版本。包含可学习权重的操作用红色进行了突出显示。