什么是 GPT?通过图形化的方式来理解 Transformer 中的注意力机制 [译]

什么是 GPT?通过图形化的方式来理解 Transformer 中的注意力机制 [译]

在线字幕版

回顾嵌入技术(Embeddings)

在上一章中,我们开始探讨 Transformer 的内部运作机制。Transformer 是大语言模型中关键的技术组成部分,也被广泛应用于现代 AI 领域的诸多工具中。它首次亮相是在 2017 年一篇广为人知的论文《Attention is All You Need》中,本章我们将深入探讨这种注意力机制,以及可视化展示它如何处理数据。

在此,我想快速回顾一些重要的背景信息。我们正在研究的模型的目标是读取一段文本并预测下一个词。输入文本被分割成我们称之为 Tokens 的小部分,它们通常是完整的单词或单词的一部分。但为了让我们在这个视频中的例子更加简单易懂,让我们假设 Token 总是完整的单词。

Transformer 的第一步是将每个 Token 与一个高维向量关联,这就是我们所说的嵌入向量。我希望你能理解的关键概念是,如何理解在所有可能的嵌入向量所构成的高维空间中,不同的方向能够代表不同的语义含义。在上一章中,我们给出了一个例子,说明了方向如何对应性别,即在这个空间中添加一定的变化可以从一个男性名词的嵌入转到对应的女性名词的嵌入。这只是一个例子,你可以设想,在这样一个复杂的空间中,有无数的方向,每一个都可能代表着词义的不同方面。Transformer 的目标是逐步调整这些嵌入,使之不仅仅编码单词本身,而是包含更丰富、更深层次的上下文含义。

需要提前说明的是,很多人对于注意力机制 —— Transformer 的关键部分,感到非常困惑,因此,请不要着急,需要一些时间来消化理解。

引入案例:为何关注注意力机制

在我们深入到计算细节和矩阵运算之前,有必要先了解一些我们期望注意力机制能实现的行为示例。

考虑一下这几个短语:美国真鼹鼠(mole)、一摩尔(mole)二氧化碳,对肿瘤(mole)进行活检。我们都明白,在不同的上下文环境下,"mole"这个词会有不同的意思。然而,在 Transformer 的第一步中,文本被拆分,每个 Token 都被关联到一个向量,这时,"mole"这个词对应的向量在所有情况下都是相同的,因为初始的 Token 嵌入向量本质上是一个不参考上下文的查找表。直到 Transformer 的下一步,周围的嵌入才有机会向这个 Token 传递信息。你可以想象,嵌入空间里有多个不同的方向,这些方向分别编码了"mole"这个词的多种不同含义。如果 Token 经过了良好的训练,注意力模块就可以计算出需要根据上下文在通用嵌入向量中添加什么内容,使其指向其中一个特定的方向。

再来看一个例子,比如单词"Tower"的嵌入向量,这可能是个非常通用、不特定的方向,与许多大型、高大的名词关联。如果"Tower"前面是"埃菲尔",你可能希望更新这个向量,使其更具体地指向埃菲尔铁塔的方向,可能与巴黎、法国或者钢铁制品相关的向量有关。如果前面还有"微型"这个词,那么这个向量应该进一步更新,使其不再与大型、高大的事物相关。

更进一步讲,注意力模块不仅可以精确一个词的含义,还能将一个嵌入向量中的信息传递到另一个嵌入向量中,即使这两个嵌入向量相距很远,信息也可能比单一单词要丰富得多。如我们在上一章中看到的,所有向量通过网络流动,包括经过许多不同的注意力模块后,预测下一个 Token 的计算过程完全取决于序列中的最后一个向量。例如,你输入的文字是一整部悬疑小说,到了接近尾声的部分,写着"所以,凶手是"。

如果模型要准确地预测下一个词,那么这个序列中的最后一个向量,它最初只是嵌入了单词"是",它必须经过所有的注意力模块的更新,以包含远超过任何单个单词的信息,通过某种方式编码了所有来自完整的上下文窗口中与预测下一个词相关的信息。但为了逐步解析计算过程,我们先看一个更简单的例子。假设输入包含了一个句子,"一个蓬松的蓝色生物在葱郁的森林中游荡"。假设我们此刻关注的只是让形容词调整其对应名词的含义的这种更新方式。我马上要讲的是我们通常所说的单个注意力分支,稍后我们会看到一个注意力模块是由许多不同的分支并行运行组成的。

需要强调的是,每个词的初始嵌入是一个高维向量,只编码了该特定词的含义,不包含任何上下文。实际上,这并不完全正确。它们还编码了词的位置。关于位置如何被编码的细节有很多,但你现在只需要知道,这个向量的条目足以告诉你这个词是什么,以及它在上下文中的位置。让我们用字母 E 来表示这些嵌入。我们的目标是,通过一系列计算,产生一组新的、更为精细的嵌入向量,比如说,这样做可以让名词的嵌入向量捕捉并融合了与它们相对应的形容词的含义。而在深度学习的过程中,我们希望大部分的计算都像矩阵 - 向量的乘积,其中的矩阵充满了可调的权重,模型将根据数据来学习这些权重。

需要明确的是,我构造这个形容词调整名词的例子,只是为了说明你可以设想一个注意力分支可能做的事情。正如深度学习常见的情况,真实的行为更为复杂,因为它涉及到调整和微调海量参数以最小化某种成本函数。

注意力机制的工作原理

逐一审视这一过程中涉及的各种参数矩阵时,设想一个具体的应用场景能帮助我们更好地理解其背后的逻辑。

在这个过程的第一步,你可以想象每个名词,比如"生物",都在问,有没有形容词在我前面?对于"毛茸茸的"和"蓝色的"这两个词,都会回答,对,我是一个形容词,我就在那个位置。这个问题会被编码成另一个向量,也就是一组数字,我们称之为这个词的查询向量。这个查询向量的维度比嵌入向量要小得多,比如说 128 维。计算这个查询就是取一个特定的矩阵,也就是 WQ,并将其与嵌入向量相乘。

简化一下,我们把查询向量记作 Q,然后你看到我把一个矩阵放在一个箭头旁边,就表示通过这个矩阵与箭头起点的向量相乘,可以得到箭头终点的向量。在这种情况下,你将这个矩阵与上下文中的所有嵌入向量相乘,得到的是每个 Token 对应的一个查询向量。这个矩阵由模型的参数组成,意味着它能从数据中学习到真实的行为模式。但在实际应用中,要明确这个矩阵在特定注意力机制中的具体作用是相当复杂的。不过,让我们尝试想象一个理想情况:我们希望这个查询矩阵能将名词的嵌入信息映射到一个较小的空间中的特定方向上,这种映射方式能够捕捉到一种特殊的寻找前置形容词的规律。至于它对其他嵌入向量做什么,我们不知道。也许它同时试图用这些实现一些其他的目标。

现在,我们的注意力集中在名词上。同时,这里还有另一个我们称之为键矩阵的矩阵,同样需要与所有嵌入向量相乘。这会生成一个我们称之为键的向量序列。从概念上讲,你可以把键想象成是潜在的查询回答者。这个键矩阵也充满了可调整的参数,同查询矩阵一样,将嵌入向量映射到同一个较小的维度空间中。当键与查询密切对齐时,你可以将键视为与查询相匹配。

以我们的例子来说,你可以想象键矩阵将形容词"毛茸茸的"和"蓝色的" 映射到与单词"生物"生成的查询紧密对齐的向量上。为了衡量每个键与每个查询的匹配程度,你需要计算每一对可能的键 - 查询组合之间的点积。我喜欢将其想象为一个充满各种点的网格,其中较大的点对应着较大的点积,即键与查询对齐的地方。就我们讨论的形容词与名词的例子而言,如果"毛茸茸的"和"蓝色的"生成的键确实与"生物"产生的查询非常吻合,那么这两个点的点积会是一些较大的正数。用机器学习的术语来说,"毛茸茸的"和"蓝色"的嵌入向量会关注"生物"的嵌入向量。相比之下,像"the"这样的词的键与"生物"的查询之间的点积会是一些较小或者负值,这反映出它们之间没有关联。因此,我们面前展开了一个值域横跨负无穷到正无穷的实数网格,这个网格赋予了我们评估每个单词在更新其它单词含义上的相关性得分的能力。

接下来,我们将利用这些分数执行一种操作:按照每个词的相关重要性,沿着每一列计算加权平均值。因此,我们的目标不是让这些数据列的数值范围无限扩展,从负无穷到正无穷。相反,我们希望这些数据列中的每个数值都介于 0 和 1 之间,并且每列的数值总和为 1,正如它们构成一个概率分布那样。如果你从上一章继续阅读,就知道我们接下来要做什么。我们会按列计算 softmax 函数以标准化这些值。

在我们的示意图中,对所有列应用 softmax 函数后,我们会用这些标准化的值填充网格。此时,可以将每一列理解为根据左侧的单词与顶部对应值的相关性赋予的权重。我们将这种网格称为"注意力模式"。如果你看原始的 Transformer 论文,你会发现他们用一种非常简洁的方式描述了所有这些。这里的变量 Q 和 K 分别代表查询向量和键向量的完整数组,这些都是通过将嵌入向量与查询矩阵和键矩阵相乘得到的小型向量。这个在分子上的表达式,是一种简洁的表示方式,可以表示所有可能的键 - 查询对的点积网格。这里有一个我之前没有提到的小细节,那就是为了数值稳定性,我们将所有这些值除以键 - 查询空间维度的平方根。然后,包裹全表达式的 softmax 函数,我们应理解为是按列应用的。

至于那个 V 项,我们稍后再讲。

如何应用掩码技术(Masking)

在此之前,还有一个我之前没有提到的技术细节。在训练过程中,对给定文本进行处理时,模型会通过调整权重来奖励或惩罚预测的准确性,即根据模型对文中下一个词的预测概率的高低。有种做法能显著提高整个训练过程的效率,那就是同时让模型预测该段落中每个初始 Token 子序列之后所有可能出现的下一个 Token。例如,对于我们正在关注的那个短语,它也可能在预测 "生物" 后面应该跟什么单词,以及 "the" 后面应该跟什么单词。这样一来,一个训练样本就能提供更多的学习机会。

在设计注意力模式时,一个基本原则是不允许后出现的词汇影响先出现的词汇,如果不这样做,后面的词汇可能会提前泄露接下来内容的线索。这就要求我们在模型中设置一种机制,确保代表后续 Token 对前面 Token 的影响力能够被有效地削弱到零。直觉上,我们可能会考虑直接将这些影响力设置为零。但这样做会导致一个问题:那些影响力值的列加和不再是一,也就失去了标准化的效果。为了解决这个问题,一个常见的做法是在进行 softmax 归一化操作之前,将这些影响力值设置为负无穷大。这样,经过 softmax 处理后,这些位置的数值会变成零,同时保证了整体的归一化条件不被破坏。这就是所谓的掩蔽过程。

在注意力机制的某些应用场景中,我们不会采用掩码技术。但在我们的 GPT 示例中,无论是在训练阶段还是在运作阶段(比如作为聊天机器人),都会应用这种掩蔽,以防止后面的 Token 对前面的 Token 产生影响。

探索上下文的尺寸限制

值得一提的是,这个注意力模式的大小等于上下文大小的平方。这就解释了上下文大小可能会对大型语言模型构成巨大瓶颈,而且要扩大它的话并非易事。近年来,出于对更大上下文窗口的追求,出现了一些对注意力机制的改进,使其在处理上下文方面更具可扩展性,但在这里,我们还是专注于基础知识的讲解。

解析“值”在注意力机制中的角色

通过计算这个模式,模型能够推断哪些词与其他词相关。然后就需要实际更新嵌入向量,让词语可以将信息传递给它们相关的其他词。比如说,你希望 "毛茸茸的" 的嵌入向量能够使得 "生物" 发生改变,从而将它移动到这个 12000 维嵌入空间的另一部分,以更具体地表达一个 "毛茸茸的生物"。

我首先向你展示的是执行这个操作的最简单方法,不过要注意,在多头注意力的情境下,这个方法会有些许调整。这个方法的核心是使用一个第三个矩阵,也就是我们所说的"值矩阵"。你需要将它与某个单词的嵌入相乘,比如"毛茸茸的"。得出的结果我们称之为"值向量",是你要加入到第二个单词的嵌入向量中的元素,例如,在这个情境下,就是要加入到"生物"的嵌入向量中。因此,这个值向量就存在于和嵌入向量一样的,非常高维的空间中。当你用这个值矩阵乘以某个单词的嵌入向量时,可以理解为你在询问:如果这个单词对于调整其他内容的含义具有相关性,那么为了反映这一点,我们需要向那个内容的嵌入中添加什么呢?回到我们的图表,我们先不考虑所有的键和查询,因为一旦计算出注意力模式,这些就不再需要了。

接下来,我们将使用这个值矩阵,将其与每一个嵌入向量相乘,从而生成一系列的值向量。你可以将这些值向量视作在某种程度上与它们相对应的"键"有关。对图表中的每一列来说,你需要将每个值向量与该列中相应的权重相乘。举个例子,对于代表"生物"的嵌入向量,你会主要加入"毛茸茸的"和"蓝色的"这两个值向量的较大比例,而其他的值向量则被减少为零,或者至少接近零。

最后,为了更新这一列与之相关联的嵌入向量,原本这个向量编码了"生物"这一概念的某种基本含义(不考虑具体上下文),你需要将列中所有这些经过重新调整比例的值加总起来,这一步骤产生了一个我们想要引入的变化量,我将其称为 delta-e。接着,你就将这个变化量叠加到原有的嵌入向量上。希望最终得到的是一个更精细的向量,是一个更加细致和含有丰富上下文信息的向量,比如描绘了一个毛绒绒、蓝色的奇妙生物。显然,我们不仅仅对一个嵌入向量进行这种处理,而是将同样的加权求和方法应用于图像中所有的列,由此产生一连串的调整。将这些调整加到相应的嵌入向量上,便形成了一组更为细腻且富含信息的嵌入向量序列。

从宏观角度来看,我们讨论的整个过程构成了所谓的"单头注意力"机制。正如之前所解释的,这一机制是通过三种不同的、充满可调整参数的矩阵来实现的,即"键"、"查询"和"值"。

参数数量的计算方法

接下来,我想继续上一章节我们开始探讨的内容,也就是通过统计 GPT-3 模型的参数数量来进行"计分"。这些键矩阵和查询矩阵每个都有 12,288 列,与嵌入维度匹配,以及 128 行,与较小的键查询空间的维度匹配。这给我们每个矩阵增加了大约 150 万个参数。如果你看看值矩阵,按照我目前为止的描述,它看上去是一个正方形的矩阵,有 12,288 列和 12,288 行,因为它的输入和输出都存在于这个庞大的嵌入空间中。如果这是真的,那就意味着要增加大约 1500 万个参数。当然,你可以这样做,你可以让值矩阵拥有数量级更多的参数,而不是键矩阵和查询矩阵。但在实践中,如果想效率更高,你可以让值矩阵的参数数量与键矩阵和查询矩阵的参数数量相同。特别是在同时运行多个注意力机制的场景下,这一点尤为重要。

具体来说,值映射实际上是两个小矩阵乘积的形式。我还是建议从整体上去理解这个线性映射过程,输入和输出都在这个更大的嵌入空间中,比如将"蓝色"的嵌入向量指向加到名词上以表示"蓝色"的方向。它只是被分成了两个单独的步骤。这里的变化只是它的行数比较少,通常和键查询的空间大小一致。可以理解为,它将较大的嵌入向量映射到了一个更小的空间。虽然这不是通用的术语,但我决定将其称作"值降维矩阵"。而第二个矩阵则是从这个较小的空间映射回原来的嵌入空间,生成了用于实际更新的向量,我将其称为"值升维矩阵",这个命名同样非传统。在大多数论文中,你会看到的描述方式可能和我说的有所不同,我稍后会解释原因。但我个人认为,那种描述方式可能会让概念理解变得更加混乱。

在这里借用一下线性代数的专业术语,我们实际上在做的,就是把整个数据的变换过程限制成一种比较简单的形式。回到参数计数,这四个矩阵的尺寸相同,将他们全部加起来,就得到了大约 630 万个参数,这是一个注意力机制所需要的。

交叉注意力(Cross-attention):扩展视角

顺带一提,为了更准确,到目前为止我们讨论的这部分通常被称为"自我注意力"机制,这是为了将其与其他模型中出现的一个不同版本区分开来,那就是被称为"交叉注意力"的版本。这与我们的 GPT 示例无关,但如果你感兴趣的话,交叉注意力涉及的模型会处理两种不同类型的数据,比如一种语言的文本和正在翻译的另一种语言的文本,或者可能是语音输入和正在进行的转录。交叉注意力机制看起来几乎和自注意力机制一样。唯一的区别是,键和查询映射在交叉注意力机制中会作用于不同的数据集。例如,在进行翻译的模型中,键可能来自一种语言,而查询来自另一种语言,注意力模式可以描述 一种语言的哪些词对应另一种语言的哪些词。在这种情况下,通常不会有遮蔽,因为并不存在后面的词会影响前面的词的概念。

多头注意力(Multi-head Attention):增强模型能力

继续讨论自注意力机制,如果你已经理解了到目前为止的所有内容,那么即使你现在停下,也已经领会了注意力模型的核心要义。我们剩下要讲的就是这个过程需要做多次的原因。在之前的例子中,我们专注于形容词如何改变名词的含义,但实际上,语境对词语含义的影响方式有很多种。比如,如果"他们撞毁了"出现在"车"之前,会对车子的形状和结构产生预设。而且很多时候,这种联系可能并不遵循语法规则。比如,如果"魔法师"和"哈利"出现在同一篇文章中,可能暗示的是哈利·波特,而如果这篇文章中还出现了"女王","萨塞克斯"和"威廉",那么哈利的词嵌入应该更新为指代王子。你能想象的每一种上下文更新方式,键和查询矩阵的参数都会有所不同,用以捕捉不同的注意力模式,而值映射的参数会根据需要添加到嵌入中的信息有所改变。

再次强调,虽然这些映射的真实行为更复杂难懂,但权重设置是为了让模型能够更好地完成预测下一个 Token 的任务。前面所描述的都只是单个注意力头,在 Transformer 中完整的注意力块由多头注意力组成,同时运行多个操作,每个操作都有其独特的键、查询和值映射。例如,GPT-3 在每个块中都使用了 96 个注意力头。考虑到每一个都相当复杂,的确需要花费一些精力理解。也就是说,你有 96 套不同的键和查询矩阵,产生 96 种不同的注意力模式。然后,每个注意力头都有独特的值矩阵用来产生 96 个值向量的序列。所有这些都通过使用对应的注意力模式作为权重进行加总。这意味着,在上下文中的每个位置,每个 Token,所有的头都会产生一个建议的变化,以便添加到该位置的嵌入中。因此,你需要将所有这些建议的变化加在一起,每个头对应一个,然后将结果加入到该位置的原始嵌入中。这个总和就是从多头注意力块输出的一个部分,是精炼后的嵌入之一,它从另一端弹出来。再次强调,这需要考虑的东西很多,所以如果需要一段时间来理解,完全不用担心。

总的来说,通过并行运行多个不同的头,你就能让模型有能力学习上下文改变含义的多种不同方式。我们计算下来,每个包含 96 个头的参数,各自有四个矩阵的变体,每个多头注意力块最后大约有 6 亿个参数。

输出矩阵的构成与意义

对于那些想深入了解 transformer 的人,这里有个小插曲我必须提一下。你可能还记得我曾经说过,值映射被分解成两个不同的矩阵,我把它们标记为值下降和值上升矩阵。我之前的描述可能会让你觉得在每个注意力头里都会看到这一对矩阵,并且实际上也确实可以这样实现,这种设计是可行的。但是在论文中,以及在实践中的实现方式看起来有些不同。

所有这些每个头的值向上矩阵,都像是被合在一起的一个巨大的矩阵,我们称之为输出矩阵,它与整个多头注意力块相关联。当你看到人们谈论某个 attention head 的值矩阵时,他们通常只指的是这个第一步,也就是我所说的值向下投影到小空间的步骤。对那些好奇的人,我在这里注明了这一点。虽然这是一个可能会让人偏离主要概念的细节,但我还是想提一下,这样当你在其他地方看到这些讨论时,你就会知道它的来龙去脉。

深入探讨:如何通过深化模型结构增强性能

抛开所有的技术细节,我们在上一章的概览中了解到,数据在 Transformer 中的流动并不局限于单个注意力模块。首先,数据还会经过其他被称为多层感知器的操作。我们会在下一章详细介绍这个。然后,数据会反复经过这两种操作的多个副本。这意味着,一个单词在吸收了一些上下文信息后,这个更细致的 embedding 仍有更多的机会受到其周围更为细致环境的影响。你在网络中越深入,每个 embedding 从所有其他 embedding 中获取的含义就越多,这些 embedding 本身也变得越来越复杂,我们希望这能有助于编码关于给定输入的更高级别和更抽象的概念,而不仅仅是描述和语法结构。这些概念可以是情感、语调,是否是一首诗,以及与这个作品相关的基础科学真理等等。

再回到我们的统计,GPT-3 包括 96 个不同的层,因此关键查询和值参数的总数需要乘以 96,这使得总数达到将近 580 亿个,这些参数全部用于各种 attention head。这确实是一个巨大的数字,但它只占网络总计 1750 亿参数的大约三分之一。所以,尽管注意力模型吸引了所有的关注,但大部分的参数其实来自那些位于这些步骤之间的模块。在下一章,我们将更深入地讨论这些模块,以及更多关于训练过程的信息。

尾声:本章总结

注意力机制的成功之处并非在于它所启动的任何特定类型的行为,而在于它极其适合并行运算,这意味着你可以使用 GPU 在短时间内完成大量的计算任务。在过去十年或二十年的深度学习研究中,我们得到了一个重要启示,那就是规模的放大似乎可以带来模型性能的巨大定量提升。因此,适合并行运算的架构具有巨大的优势。

如果你想了解更多关于这个主题的信息,我在视频简介中留下了很多链接。特别是,由 Andrej Karpathy 或 Chris Ola 制作的任何内容都非常值得一看。在这个视频中,我只是想直接介绍现在的注意力机制,但是如果你对我们是如何达到这里的历程以及你可能如何为自己重新创新这个想法感到好奇,我的朋友 Vivek 最近就发布了几个视频,深入讲解了这个动机。此外,来自"The Art of the Problem"频道的 Britt Cruz 有一部非常精彩的视频,介绍了大语言模型的历史。

谢谢。