NO.52.tip: Transformer
背景
Transformer 是深度学习领域最重要的进展之一。基于一种称为注意力(attention)的处理概念,Transformer 能使网络对不同的输入赋予不同的权重,而这些加权系数本身又取决于输入值,从而捕捉到与序列数据和其他形式数据相关的强大归纳偏差。
这类模型之所以叫 Transformer,是因为它们能将某个表示空间中的一组向量变换成某个新空间中具有相同维度的相应向量的集合。变换旨在使新空间具有更丰富的内部表示,该表示更适合解决下游任务。对于 Transformer 而言,其输入可以是非结构化的向量集、有序序列或其他更一般的表示形式,从而使 Transformer 具有广泛的适用性。
源与流
Transformer 处理
Transformer 的输入数据是一组 维向量 ,其中 。我们将这些数据向量称为 token,一个 token 可能对应句子中的一个词、图像中的一个小块或者蛋白质中的一个氨基酸。token 中的元素 称为特征。稍后我们将了解如何为自然语言和图像构建这些 token 向量。Transformer 的一个强大特性是,我们无须设计新的神经网络架构来处理不同类型的数据,而只需要将数据变量组合成一组联合 token 即可。
在清楚地了解 Transformer 的工作原理之前,我们必须准确地使用符号。我们将按照标准惯例,将数据向量组合成一个 维的矩阵 ,其中第 行是 token 向量 ,。请注意,该矩阵代表一组输入 token,而在大多数应用中,我们需要一个包含多组 token 的数据集,例如每个词都是由一个 token 表示的独立文本段落。
Transformer 的基本构件是一个函数,用于将数据矩阵作为输入并创建一个具有相同维度的变换矩阵 作为输出。我们可以写下该函数,形式如下。
然后,我们可以连续叠加多个 Transformer 层来构建能够学习丰富内部表示的深度网络。每个 Transformer 层都有自己的权重和偏置,可以通过梯度下降法使用适当的损失函数进行学习。
Transformer 层本身包含两个阶段。在实现注意力机制的第一阶段,将数据矩阵各列中不同 token 向量的相应特征混合在一起;第二阶段则独立作用于每一行,并对每个 token 向量中的特征进行变换。下面让我们先来看看注意力机制。
注意力系数
假设嵌入空间中有一组输入 token ,我们希望将它们映射到另一组具有相同数量 token 但位于一个新嵌入空间的 ,这一新的嵌入空间能捕捉到更丰富的语义结构。考虑一个特定的输出向量 。 的值不仅取决于相应的输入向量 ,还取决于集合中所有其他的输入向量。凭借注意力,对于那些在确定 的修正表示中发挥特别重要作用的输入向量 来说,这种依赖性应该更强。实现这一目标的简单方法是将每个输出向量 定义为输入向量 与加权系数 的线性组合:
其中, 称为注意力权重(attention weight),又称注意力系数或权重系数。对于对输出 影响较小的输入 token,注意力系数应接近于零;而对于对输出 影响最大的输入 token,注意力系数应最大。因此,我们限制注意力系数为非负值,以避免出现一个注意力系数变大为正值,而另一个注意力系数补偿变小为负值的情况。我们还希望确保如果一个输出对某一特定输入给予更多注意力,则应该以减少对其他输入的注意力为代价,因此我们限制注意力系数的总和为 1。加权系数必须满足以下两个约束条件:
这意味着每个注意力系数都在 的范围内,因此这些注意力系数定义了一个“单位分解”。在 且当 时 的特殊情况下,由于 ,输入向量在变换后保持不变。一般来说,输出向量 是输入向量的混合体,其中一些输入向量的权重系数高于其他输入向量。
请注意,我们为每个输出向量 设置了一组不同的系数,而约束条件[式(3)和式(4)]分别适用于每个 值。这些系数取决于输入数据,后续我们将展示如何计算它们。
自注意力
接下来的问题是如何确定权重系数 。在展开详细讨论之前,我们不妨先了解一下信息检索领域的一些术语。以通过线上电影流媒体服务选择观看哪部电影为例。一种方法是将每部电影与一系列属性关联起来,这些属性描述了电影的类型(喜剧、动作片等)、主演姓名、片长等。用户可以通过目录搜索找到自己喜欢看的电影。我们可以将每部电影的属性编码成一个称为键(key)的向量,从而将该过程自动化。相应的电影文件本身叫作值(value)。同样,用户也可以为所需属性提供自己的个人值向量,我们称之为查询(query)。然后,线上电影流媒体服务可以将查询向量与所有键向量作比较,从而找出最佳匹配,并以值文件的形式将相应的电影发送给用户。这可以视为硬注意力(hard attention)的一种形式,其会返回一个单一值向量。对于 Transformer,我们可以将其泛化到软注意力(soft attention)中,即使用连续变量来度量查询和键之间的匹配程度,然后使用这些变量来权衡值向量对输出的影响。这也将确保 Transformer 函数是可微分的,从而可以通过梯度下降法进行训练。
参照信息检索的方法,我们可以将每个输入 视为一个值向量,用来创建输出 token。我们还可以直接使用 作为输入 token 的键向量。这就好比用电影本身来概括电影的特点。最后,我们可以使用 作为输出 的查询向量,然后将其与每个键向量作比较。要想知道 代表的 token 与 代表的 token 有多大的相似度,我们需要计算出这些向量的相似度。度量向量相似度的一种简单方法是求它们的点积 。为了施加约束条件[式(3)和式(4)],使用 softmax 函数变换点积以定义权重系数 :
(请注意,在这种情况下,softmax 函数不存在概率解释,它只是用来对注意力权重进行适当的归一化。)
因此,总的来说,每个输入向量 都是通过形式为式(2)的输入向量线性组合而转换为相应的输出向量 的,其中应用于输入向量 的权重系数 由 softmax 函数给出[式(5)],该 softmax 函数是根据输入 的查询向量 与输入 相关的键向量 之间的点积 进行定义的。请注意,如果所有输入向量都是正交的,那么每个输出向量都等于相应的输入向量,从而在 的情况下,。
我们可以使用数据矩阵 和类似的 输出矩阵 (其行由 给出)将式(2)用矩阵符号写出:
其中, 是一个算子,作用是取矩阵 中每个元素的指数,然后将每一行独立归一化,使它们的和为 1。自此,为了清晰易懂,我们将讨论的重点放在矩阵符号上。
这个过程叫作自注意力(self-attention),因为我们使用相同的序列来确定查询、键和值。在后续内容中,我们还会遇到这种注意力的变体。此外,由于查询向量和键向量之间的相似度度量是由点积给出的,因此这个过程又叫作点积自注意力(dot-product self-attention)。
网络参数
目前,由于没有可调整的参数,从输入向量 到输出向量 的变换是固定的,且不具备从数据中学习的能力。此外,token 向量 的每个特征值在确定注意力系数时发挥相同的作用,而我们希望网络在确定 token 相似度时能够灵活地将注意力更多地集中在某些特征上。通过定义由原始向量(形式如下)的线性变换给出的修正特征向量,我们可以解决这两个问题。
其中 是可学习权重参数的 矩阵,类似于标准神经网络中的“层”,从而得到一个修正后的变换,其形式为
虽然这种方法更灵活,但它有一个特性,即如下矩阵是对称的。
而我们希望注意力机制支持明显的不对称。例如,我们可能会认为“斧头”与“工具”的关联性很强,因为每一把斧头都是一个工具;而“工具”与“斧头”的关联性很弱,因为除了斧头,还有许多其他种类的工具。虽然 softmax 函数意味着得到的注意力权重矩阵本身并不对称,但我们可以通过允许查询和键具有独立参数来创建一个更加灵活的模型。此外,式(8)使用相同的参数矩阵 来定义值向量和注意力系数,这似乎又是一个不受欢迎的限制。
通过定义单独的查询矩阵、键矩阵和值矩阵,并让每个矩阵都有自己独立的线性变换,我们可以克服这些限制:
其中,权重矩阵 、 和 表示在训练最终的 Transformer 架构时将学习到的参数。矩阵 的维度为 ,其中 是键向量的长度。矩阵 的维度 必须与矩阵 的维度相同,因为只有这样才能在查询向量和键向量之间形成点积。为此,可以设置 。同样, 是一个维度为 的矩阵,其中 是输出向量的长度。如果设置 ,使输出表示和输入表示具有相同的维度,则有助于加入残差连接,我们稍后将对此进行讨论。此外,如果每个层的维度相同,则多个 Transformer 层可以叠加在一起。通过对式(6)进行泛化,我们可以得到
其中矩阵 的维度为 ,且矩阵 的维度为 。
分别通过式(10)和式(11)对输入 进行变换,得到查询矩阵 和键矩阵 ,然后将它们相乘
输出矩阵 中突出显示位置的条目分别由 和 矩阵中突出显示位置的行和列的点积得出
实际上,我们还可以在这些线性变换中加入偏置参数。不过,偏置参数也可以包含在权重矩阵中,就像我们对标准神经网络所做的那样,方法是在数据矩阵 中增加一列 1,然后在权重矩阵中增加额外一行参数来表示偏置。自此,我们将把偏置参数视为隐参数,以免混淆符号。
与传统的神经网络相比,信号路径的激活值之间存在乘法关系。标准的神经网络会将激活值乘以固定权重,而这里的激活值则被乘以依赖数据的注意力系数。这意味着如果选择特定的输入向量,其中一个注意力系数接近于零,那么产生的信号路径将忽略相应的输入信号,因此不会对网络输出产生任何影响。相比之下,如果一个标准的神经网络学会了忽略某个特定的输入或隐藏单元变量,那么该神经网络就会对所有的输入向量都这样做。
缩放点积自注意力
我们还可以对自注意力层进行最后一次改进。回想一下,当输入量较大时,softmax 函数的梯度会呈指数减小,就像 tanh 函数或逻辑斯蒂 sigmoid 函数一样。为了避免出现这种情况,我们可以在应用 softmax 函数之前重新缩放查询向量和键向量的乘积。为了得到合适的缩放结果,请注意,如果查询向量和键向量的元素都是均值为 0 且方差为单位方差的独立随机数,则点积的方差就是 。因此,我们使用 的平方根所给出的标准差对 Softmax 的参数进行归一化处理,从而注意力层的输出形式为
这就是所谓的缩放点积自注意力(scaled dot-product self-attention),也是自注意力神经网络层的最终形式。这一层的算法过程见算法 1。
此处“mat mul”表示矩阵乘法,而“scale”指的是使用 对 Softmax 的参数进行归一化处理。该结构构成了一个单一注意力“头”
算法 1:缩放点积自注意力
Input: Set of tokens X ∈ R^{N×D} : {x_1, ..., x_N}
Weight matrices {W^(q), W^(k)} ∈ R^{D×D_k} and W^(v) ∈ R^{D×D_v}
Output: Attention(Q,K,V) ∈ R^{N×D} : {y_1, ..., y_N}
Q = X W^(q) // 计算查询矩阵 Q ∈ R^{N×D_k}
K = X W^(k) // 计算键矩阵 K ∈ R^{N×D_k}
V = X W^(v) // 计算值矩阵 V ∈ R^{N×D}
return Attention(Q,K,V) = Softmax[ QK^T / sqrt(D_k) ] V
多头注意力
注意力层允许输出向量关注输入向量的依赖数据模式,称作注意力头(attention head)。然而,可能同时存在多种相关的注意力模式。例如,在自然语言中,有些注意力模式可能与时态有关,而另一些注意力模式则可能与词汇有关。使用单一注意力头会导致这些效应的平均化。对此,我们可以并行使用多个注意力头。这些注意力头由结构相同的单个注意力头的副本组成,具有独立的可学习参数,用于控制查询矩阵、键矩阵和值矩阵的计算。这类似于在卷积网络的每一层中使用多个不同的过滤器。
假设我们有 个注意力头,以 进行编号,形式为
其中 由式(14)给出,使用如下方法为每个注意力头定义单独的查询矩阵、键矩阵和值矩阵:
首先将各个注意力头合并成一个单一矩阵,然后使用矩阵 对结果进行线性变换,从而得到一个合并输出,形式为
多头注意力网络的算法过程见算法 2。
每个注意力头都包含图 12.6 所示的结构,并有自己的键、查询和值参数。将各个注意力头的输出合并起来,然后线性投射回输入数据维度
算法 2:多头注意力
Input: Set of tokens X ∈ R^{N×D} : {x_1, ..., x_N}
Query weight matrices {W_1^(q), ..., W_H^(q)} ∈ R^{D×D}
Key weight matrices {W_1^(k), ..., W_H^(k)} ∈ R^{D×D}
Value weight matrices {W_1^(v), ..., W_H^(v)} ∈ R^{D×D_v}
Output weight matrix W^(o) ∈ R^{HD_v×D}
Output: Y ∈ R^{N×D} : {y_1, ..., y_N}
// 为每个注意力头计算自注意力(参见算法 1)
for h = 1, ..., H do
Q_h = X W_h^(q), K_h = X W_h^(k), V_h = X W_h^(v)
H_h = Attention(Q_h, K_h, V_h) // H_h ∈ R^{N×D_v}
end for
H = Concat[H_1, ..., H_N] // 合并注意力头
return Y(X) = H W^(o)
每个矩阵 的维度为 ,因此合并矩阵的维度为 。在使用维度为 的线性矩阵 进行变换后,最终得到维度为 的输出矩阵 ,它与原始输入矩阵 的维度相同。矩阵 的元素是在训练阶段与查询矩阵、键矩阵和值矩阵一起学习的。通常情况下, 选择为与 相等,从而使得到的合并矩阵的维度为 。图 12.8 展示了多头注意力层的信息流。
请注意,前文给出的多头注意力表述与研究文献中使用的表述相同,我们在每个注意力头的 矩阵与输出矩阵 的连续乘法中包含了一些冗余。在消除这些冗余后,多头自注意力层就可以写成每个注意力头的贡献之和。
Transformer 层
多头自注意力构成了 Transformer 网络的核心架构元素。我们知道,神经网络从深度结构中获益匪浅,因此我们希望将多个自注意力层堆叠在一起。为了提高训练效率,我们可以引入绕过多头结构的残差连接。为此,我们要求输出维度与输入维度相同。然后进行层归一化(layer normalization)(Ba, Kiros, and Hinton, 2016),从而提高训练效率。由此产生的变换可以写成
其中 由式(19)定义。有时,层归一化会被预归一化取代,即层归一化在多头注意力之前而不是之后进行,因为这可以带来更有效的优化。在这种情况下,我们得出
在所有情况下, 的维度都与输入矩阵 的维度相同。
我们已经看到,注意力机制创建了值向量的线性组合,然后用以产生输出向量。此外,这些值向量是输入向量的线性函数,因此我们可以看到,注意力层的输出受限于输入的线性组合。非线性通过注意力权重引入,因此输出将通过 softmax 函数非线性地依赖于输入,但输出向量仍然受限于输入向量所跨的子空间,这限制了注意力层的表达能力。我们可以使用一个标准的非线性神经网络(有 个输入和 个输出)对每一层的输出进行后处理,从而提高 Transformer 的灵活性。例如,这可能包括一个具有 ReLU 隐藏单元的两层全连接网络。当我们这样做时,需要保持 Transformer 处理不同长度序列的能力。为此,我们需要对每个输出向量(与 的行相对应)应用相同的共享网络。同样,也可以通过使用残差连接来改进该神经网络层。它还包括层归一化,从而 Transformer 层的最终输出形式为
这就产生了 Transformer 层的整体架构,算法过程见算法 3。同样,我们也可以使用预归一化,在这种情况下,Transformer 层的最终输出形式为
此处“MLP”代表多层感知机,“add & norm”则代表残差连接和随后的层归一化
算法 3:Transformer 层
Input: Set of tokens X ∈ R^{N×D} : {x_1, ..., x_N}
Multi-head self-attention layer parameters
Feed-forward network parameters
Output: X̃ ∈ R^{N×D} : {x̃_1, ..., x̃_N}
Z = LayerNorm[ Y(X) + X ] // Y(X) 由算法 2 给出
X̃ = LayerNorm[ MLP[Z] + Z ] // 共享网络
return X̃
位置编码
在 Transformer 架构中,矩阵 、 和 由输入的所有 token 共用。同样,紧随其后的神经网络(前馈神经网络)也由输入的所有 token 共用。因此,Transformer 具有这样的特性,即对输入 token(也就是 的行)进行排序会导致输出矩阵 的行也被排序。换句话说,Transformer 在输入排列方面是等变化的(equivariant)。网络架构中的参数共享有利于 Transformer 的大规模并行处理,也使网络能够像学习短距离依赖关系一样有效地学习远距离依赖关系。然而,当我们考虑序列数据(如自然语言中的词)时,不依赖 token 顺序就成了一个主要的限制,因为 Transformer 学习到的表示法将与输入 token 的顺序无关。句子 “The food was bad, not good at all” 和 “The food was good, not bad at all” 包含相同的 token,但由于 token 的排序不同,这两个句子的含义也截然不同。显然,token 顺序对于包括自然语言处理在内的大多数顺序处理任务至关重要,因此我们需要找到一种方法来将有关 token 顺序的信息注入网络。
由于想要保留精心构建的注意力层的强大特性,因此我们希望在数据中对 token 顺序进行编码,而不是在网络架构中表示 token 顺序。为此,我们将构建一个与每个输入位置 相关的位置编码向量 ,然后将其与相关输入 token 的嵌入向量 结合起来。一种显而易见的方法是将这些向量合并起来,但这会增加输入空间的维度,进而增加所有后续注意力空间的维度,从而显著增加计算成本。相反,我们可以简单地将位置向量加到 token 向量上,从而得出
这就要求位置编码向量的维度与 token 嵌入向量的维度相同。
初看起来,在 token 向量上添加位置信息会破坏输入向量,使得网络的任务变得更加难以完成。不过,从两个随机选择的不相关向量在高维空间中几乎是正交的这一现象中,我们可以了解到这一方法为什么能够很好地发挥作用,这表明网络能够相对独立地处理 token 的身份信息和位置信息。另外,请注意,由于 Transformer 中的层与层之间都有残差连接,因此位置信息从一层转到下一层的过程中不会丢失。此外,Transformer 中的线性处理层能使合并表示法与加法表示法具有相似的特性。
接下来的任务是构建嵌入向量 。一种简单的方法是为每个位置关联一个整数。但这样做的问题是,数值的大小会无限制地增加,因此可能会严重破坏嵌入向量。此外,对于比训练时所用输入序列更长的新输入序列,它可能无法很好地泛化,因为这些输入序列涉及的编码值将超出训练时所用编码值的范围。我们也可以为序列中的每个 token 分配一个介于 0 和 1 之间的数字,从而保持表示法的有界性。然而,这种表示法对于特定位置并不是唯一的,因为它取决于整个序列的长度。
理想的位置编码应该为每个位置提供唯一的表示,其应该是有界的,能泛化为较长的序列,且应该有一致的方法来表示任意两个输入向量之间的步数,而不管它们的绝对位置如何,因为 token 的相对位置往往比绝对位置更重要。
位置编码的方法有很多(Dufter, Schmitt, and Schütze, 2021)。在此,我们介绍一种由 Vaswani et al. (2017) 引入的基于正弦函数的技术。对于给定的位置 ,相关位置的编码向量具有组件 ,其计算公式为
嵌入向量 的元素由一系列波长逐渐增大的正弦和余弦函数给出。
这种编码的特点是,向量 的元素都位于 范围内。这不禁让人联想到二进制数的表示方法,最低阶位高频率地交替出现,而随后的位则以逐渐降低的频率交替出现:
1: 0 0 0 1
2: 0 0 1 0
3: 0 0 1 1
4: 0 1 0 0
5: 0 1 0 1
6: 0 1 1 0
7: 0 1 1 1
8: 1 0 0 0
9: 1 0 0 1
不过,对于式(25)来说,向量元素是连续变量而非二进制变量。
式(25)给出的正弦表示法有一个很好的特性,即对于任何固定偏移 ,位置 处的编码可以表示为位置 处编码的线性组合,其中系数不取决于绝对位置,而只取决于 值。因此,网络应该能够学会关注相对位置。请注意,这一特性要求编码同时使用正弦和余弦函数。
另一种常用的位置表示方法是使用习得的位置编码。这是通过在每个 token 位置设置一个权重向量来实现的,该权重向量可在训练过程中与模型的其他参数共同学习,从而避免使用人工表示法。由于 token 位置之间不共享参数,因此 token 在排列时不再是不变的,而这正是位置编码的目的。不过,这种方法并不符合前述对较长的输入序列进行泛化的标准,因为训练过程中没有出现过的位置编码未经训练。因此,当输入长度在训练和推理过程中都相对固定时,这种方法通常最适用。