写在餐巾纸上的 GPT-3 架构
图|Daniel Dugas
“Multi-Head Attention 多头注意力”草绘图
▽
题记
"We offer no explanation as to why these architectures seem to work; we attribute their success, as all else, to divine benevolence" - Noam Shazeer (second author of the transformer paper, now CEO of Character AI)
附录:餐巾纸草图解析GPT-3架构
关于GPT-3 ,有很多精彩的帖子,
展示了它的功能(如:https://www.gwern.net/GPT-3),
思考了它的后果(如:https://leogao.dev/2020/05/29/GPT-3-A-Brief-Summary/;https://maraoz.com/2020/07/18/openai-gpt3/),
展示了它的工作原理(如http://jalammar.github.io/illustrated-gpt2/)。
▩输入/输出(In / Out )
为了进一步理解GPT架构,我们首先需要知道:GPT 的输入和输出是什么?
就是这样简单!你看到的所有令人印象深刻的 GPT 对话、故事和例子都是用这个简单的输入输出方案得出的:给它一个输入序列——得到这个序列的下一个词。如:
Not all heroes wear -> capes (如上图的输出就是capes)
根据需要重复多次,直到最终生成一个长文本。
实际情况,准确的说,我们需要从两个方面来对上面的示例做一下纠正。
1.每次输入序列实际上固定为2048个词(对于GPT-3而言),短序列仍然可以作为输入传递:只需用“空”值填充所有额外的位置。
2. GPT 输出不仅仅是一个单次猜测,它是一个序列的猜测(长度为 2048)(其中每个可能的词的概率):序列中的每个“下一个”位置猜测一次。但是在生成文本的任务时,我们通常只看对序列最后一个单词的猜测。
就是这样!序列进,序列出。(sequence in,sequence out)
▩编码(Encoding)
但是等一下,GPT 实际上并不能直接理解文字。作为一种机器学习算法,通常操作对象是数值向量。那么我们如何将单词转换为向量呢?
第一步是有一个所有单词的词汇表,这使我们能够为每个单词赋予一个值。Aardvark 为 0,aaron 为 1,依此类推。(GPT 的词汇量为 50257 个单词)。
因此,我们可以将每个单词变成一个大小为 50257 的one-hot编码向量,其中只有索引 i 处的维度(单词的值)为 1,其他所有维度均为 0。
当然,对序列中的每个单词都这样做,之后:
这会产生一个 2048 x 50257 的 1 和 0 矩阵。
注意:为了提高效率,GPT-3 实际上使用字节级 Byte Pair Encoding (BPE) 标记化(tokenization)。这意味着词汇表中的“单词”不是完整的单词,而是文本中经常出现的字符组(对于字节级 BPE,字节)。使用 GPT-3 字节级 BPE 分词器,“Not all heroes wear capes”被拆分为 tokens“Not”“all”“heroes”“wear”“cap”“es”,其 在词汇表中的ID 分别为 3673、477、10281、 5806、1451、274。这是对该主题(https://huggingface.co/transformers/tokenizer_summary.html)的一个很好的介绍,以及一个 github 实现(https://github.com/huggingface/tokenizers),有兴趣的话您可以自己尝试。
2022年加注:OpenAI 现在有一个 tokenizer 工具(https://beta.openai.com/tokenizer),允许你输入一些文本并查看它是如何被分解成tokens的。
▩嵌入(Enbedding)
50257 是一个相当大的向量,且大部分都是零。有很多向量空间浪费。
我们学会了用一个嵌入函数来解决这个问题:一个神经网络,它接受一个 50257 长度的 1 和 0 向量,并输出一个 n 长度的数值向量。在这里,我们试图将词义信息存储(或投射)到更小的维度空间。
例如,如果嵌入维度为 2,则就像将每个单词存储在 2D 空间中的特定坐标处。
另一种直观的思考方式是,每个维度都是一个虚构的属性,例如“softness”或“shmlorbness”,给定每个属性的值,我们可以准确地知道是哪个词的意思。
当然,嵌入维度通常大于 2:GPT 使用 12288 作为嵌入维度。
在工程实践中,每个单词 one-hot 向量都乘以学习到的嵌入网络权重,并最终成为一个 12288 维的嵌入向量。(笔者注:初始权重是随机值)
以算术术语表示的话,就是我们将 2048 x 50257 序列编码矩阵与 50257 x 12288 嵌入权重矩阵(学习)相乘,最后得到一个 2048 x 12288 序列嵌入矩阵。
从现在开始,我将把二维矩阵画成小块,并在旁边写上尺寸。适用时,我将矩阵行分开,以明确每行对应于序列中的一个词。
另请注意,由于矩阵乘法的工作原理,嵌入函数(也称为嵌入权重矩阵)分别应用于每个词编码(也称为序列编码矩阵中的一行)。换句话说,结果与将每个词编码向量分别传递给嵌入函数,并在最后组装连接得到的所有结果是一样的。这意味着:到目前为止,在这个过程中,没有信息流过序列,也没有关于tokens的绝对或相对位置的信息。
▩位置编码(Positional Encoding)
为了对序列中当前token的位置进行编码,作者采用token的位置(标量 i,在 [0-2047] 中)并将其传递给 12288 个正弦函数,每个函数具有不同的频率。
我并不完全清楚为什么这样做的确切原因。作者将其解释为产生许多对模型有用的相对位置编码。对于其他可能的心智模型来分析这种选择:考虑信号通常表示为周期样本之和的方式(参见傅里叶变换或 SIREN 网络架构),或者语言自然呈现各种长度的循环的可能性(例如,诗歌)。
对于每个token,结果是一个 12288 的数值向量。与嵌入一样,我们将这些向量组合成一个具有 2048 行的矩阵,其中每行是序列中token的 12288 列位置编码。
最后,这个与序列嵌入矩阵具有相同形状的序列位置编码矩阵可以简单地相加。
▩注意力(Attention)(简化版)
简单来说,attention的目的就是:对于序列中的每一个输出,预测要关注哪些输入token,关注多少。在这里,想象一个由 3 个token组成的序列,每个token都用 512 个值的嵌入表示。
该模型学习了 3 个线性投影,所有这些都应用于序列嵌入。换句话说,学习了 3 个权重矩阵,它们将我们的序列嵌入转换为三个独立的 3x64 矩阵,每个矩阵用于不同的任务。
前两个矩阵(“查询”和“键”)相乘 (QKT),产生一个 3x3 矩阵。该矩阵(通过 softmax 归一化)表示每个token对彼此的重要性。
注意:此 (QK T ) 是 GPT 中唯一跨序列中的单词进行操作的操作。也是矩阵行交互的唯一操作。
第三个矩阵(“值”)与这个重要性矩阵相乘,对于每个token,产生由其各自token的重要性加权的所有其他tokens值的混合。
例如,如果重要性矩阵只有 1 和 0(每个token只有一个重要的其他token),结果就像根据哪个token最重要来选择值矩阵中的行。
我希望这会有所帮助,即使不能直观地理解注意力过程,至少也能帮助理解所使用的确切代数。(译者注:作为一个工程外行,完全像看魔术 :-))
▩多头注意力(Multi-Head Attention)
现在,在作者提出的 GPT 模型中,使用了多头注意力。所有这些意味着上述过程重复多次(GPT-3 中的 96 次),每次都有不同的学习查询、键、值投影权重。
每个注意力头的结果(单个 2048 x 128 矩阵)连接在一起,产生一个 2048 x 12288 矩阵,然后乘以一个线性投影(不改变矩阵形状),以获得良好的度量。
注意:论文中提到 GPT-3 使用稀疏注意力(https://openai.com/blog/sparse-transformer/),可以提高计算效率。老实说,我还没有花时间去了解它是如何实现的。随你去吧,祝你好运!
'Splitting hairs':上图暗示每个头都有单独的权重矩阵。然而,在实践中,注意力模型的实现可能会对所有头部使用一个大的组合权重张量,进行一次矩阵乘法,然后将其拆分为每个头部的 a、k、v 矩阵。不用担心:理论上,它也不应该影响模型输出,因为代数运算是相同的。(感谢 Kamilė Lukošiūtė 提出这一点)
▩前馈(Feed Forward)
前馈块是一个带有 1 个隐藏层的老式多层感知器。获取输入,乘以学习的权重,添加学习的偏差,再做一次,得到结果。
此处,输入和输出形状相同 ( 2048 x 12288 ),但隐藏层的大小为 4*12288。
需要明确的是:我也将这个操作画成一个圆圈,但与架构中的其他学习投影(嵌入、查询/键/值投影)不同,这个“圆圈”实际上由两个投影组成(学习权重矩阵乘以输入)连续地,在每个之后添加学习的偏差,最后是 ReLU。
▩添加与规范(Add & Norm)
在 Multi-Head attention 和 Feed Forward 块之后,块的输入被添加到它的输出,结果被归一化。这在深度学习模型中很常见(自 ResNet 以来)。
注意:在我的任何草图中都没有反映出这样一个事实,即自 GPT-2 以来,“层归一化被移动到每个子块的输入,类似于预激活残差网络,并且在最终激活之后添加了一个额外的层归一化自注意力块”
▩解码(Decoding)
终于快结束了!通过 GPT-3 的所有 96 层注意力/神经网络机制后,输入已被处理为 2048 x 12288 矩阵。对于序列中的 2048 个输出位置中的每一个,该矩阵应该包含一个 12288 向量的信息,其中包含关于应该出现哪个单词的信息。但是我们如何提取这些信息呢?
如果你还记得在嵌入部分,我们学习了一个映射,它将给定的(一个词的one-hot编码)转换为 12288 向量嵌入。事实证明,我们只需反转此映射即可将输出的 12288 向量嵌入转换回 50257 词汇表里的词编码。这个想法是,如果我们把所有这些精力都花在学习从单词到数值的良好映射上,我们不妨重新使用它!
当然,这样做不会像我们开始时那样给我们 1 和 0,但这是一件好事:在快速 softmax 之后,我们可以将结果值视为每个词的概率。
此外,GPT 论文中提到了参数 top-k,它将输出中可能采样的单词数量限制为 k 个最有可能预测的单词。例如,top-k 参数为 1 时,我们总是选择最有可能的词。
▩完整架构(Full Architecture)
你已经掌握了它:一些矩阵乘法,一些代数,我们自己拥有一个最先进的自然语言处理怪物。我已将所有部件绘制到一个示意图中,单击它可以查看完整尺寸的版本。
(https://dugas.ch/artificial_curiosity/img/GPT_architecture/fullarch.png)
图中包含可学习权重的操作以红色突出显示。
参考
-Daniel Dugas:The GPT-3 Architecture, on a Napkin
END
扫码加群,
立变AI🍚!
AI范儿读者群
那些prompt了我的,
是否也prompt了你...