一.提出背景
Transformer最早是Google在2017年的Attention Is All You Need论文中提出,用于解决解决传统的序列到序列(Seq2Seq)模型在处理可变长序列时遇到的问题。(序列到序列:指的是模型的输入是一段序列,模型输出也是序列;比如语音识别中给模型一段中文语音序列,让模型给出中文文字序列)(可变长序列:在自然语言处理、时间序列分析等领域中,数据常常以序列的形式出现。例如,在文本处理中,一个句子可以看作是一个单词序列,不同句子的单词数量可能不同;在时间序列分析中,一段时间内的股票价格数据可以看作是一个价格值序列,不同时间段的数据点数量可能不一样。这些序列的长度根据具体的文本内容或时间范围而有所变化,就是可变长序列。不同的应用场景和任务可能涉及不同长度的序列数据。在机器翻译任务中,源语言句子和目标语言句子的长度可能不同;在语音识别任务中,不同时长的语音片段会被转换为不同长度的音频特征序列。)
传统的序列建模方法在处理长序列均存在难点和挑战:
-
模型输入要求:许多传统的机器学习模型和深度学习模型要求输入数据具有固定的尺寸,以方便进行矩阵运算等操作。对于可变长序列,需要采用特殊的处理方法来使其适应模型的输入要求。例如,在神经网络中,可以通过填充(padding)或截断(truncation)操作将可变长序列转换为固定长度,但这可能会引入额外的计算开销或信息损失,导致计算效率低下。
-
模型设计与计算复杂性:处理可变长序列需要模型能够动态地处理不同长度的输入,这对模型的架构设计提出了挑战。例如,在循环神经网络(RNN)中,需要设计合适的循环机制来处理不同长度的序列,并且在处理长序列时要考虑序列长度对梯度传播的影响,容易出现梯度消失或梯度爆炸的问题,模型难以捕捉到长距离的依赖关系,不能并行计算。对于基于注意力机制的模型(如 Transformer),需要有效地计算不同长度序列中元素之间的注意力分数,以捕捉序列中的长距离依赖关系。(梯度:梯度是损失函数关于模型参数的导数,它表示损失函数在参数空间中的变化率。)(长距离依赖关系指的是序列中相隔较远的元素之间的相互影响。由于梯度消失或梯度爆炸,RNN在捕捉这些长距离依赖关系时会遇到困难,因为这些依赖关系需要通过多层的梯度传播来学习。)(并行计算是指在处理序列数据时,可以同时处理序列中的所有元素。RNN在理论上支持并行计算,但在实际应用中,由于梯度消失或梯度爆炸的问题,这种并行性可能无法有效实现,因为网络的深层层可能无法正确学习到长距离的依赖关系。)
-
计算资源和效率:处理可变长序列可能需要更多的计算资源和时间,尤其是当序列长度差异较大时。例如,在处理长文本序列时,模型需要更多的计算步骤来处理所有的单词,这可能导致训练和推理过程变慢。因此,需要优化算法和模型结构,以提高处理可变长序列的效率。
解决方法
-
数据预处理:如前面提到的填充和截断操作,将可变长序列调整为固定长度。例如,在自然语言处理中,可以在较短的句子末尾添加特殊的填充标记(如
),使其长度与最长句子相同;或者截断过长的句子,保留句子的开头部分。但这种方法需要谨慎使用,以避免对数据信息造成过多破坏。 -
模型架构改进:设计专门用于处理可变长序列的模型架构。例如,Transformer 通过引入多头自注意力机制,能够同时关注序列中的不同位置,而不受序列长度的限制,有效地处理可变长序列。一些基于循环神经网络的改进模型,如长短时记忆网络(LSTM)和门控循环单元(GRU),通过特殊的门控机制来更好地处理长序列,减少梯度消失问题。
-
动态计算和自适应处理:一些模型可以根据输入序列的实际长度进行动态计算,避免对固定长度的依赖。例如,在计算注意力分数时,只计算实际存在的序列元素之间的关系,而不考虑填充部分。在模型训练过程中,可以采用自适应的学习率策略,根据序列长度调整学习率,以提高训练的稳定性和效率。
特点
Transformer 引入了注意力机制,能够同时关注序列中的所有位置,动态地分配权重给不同位置的元素,从而更好地捕捉长距离依赖关系。它不依赖于循环结构或卷积操作,通过多头自注意力机制并行计算序列中元素之间的相关性。在处理文本时,Transformer 可以根据句子中每个单词与其他单词的关系,更全面地理解句子的语义。
此外,Transformer 的架构具有高度的并行性,能够在大规模数据上高效训练,这体现在:
-
transformer在处理序列数据时,可以同时处理序列中的所有元素,归功于“在计算注意力分数时,每个元素都可以与序列中的其他所有元素进行交互,通过计算点积来衡量它们之间的相关性”这一步。
-
Transformer 的整体架构由多个相同结构的编码器和解码器堆叠而成。这种模块化的设计便于在大规模数据训练时进行分布式计算和并行处理。例如,在训练过程中,可以将不同的编码器或解码器分配到不同的计算设备(如多个 GPU)上进行并行计算,进一步提高训练速度。
二.Transformer架构
2.1 Transformer的输入———词嵌入+位置编码
Transformer中单词的输入表示x 由单词Embedding和位置Embedding(Positional Encoding)相加得到。词嵌入(Word Embedding)将每个词转换为向量表示,并添加位置编码(Positional Encoding)以保留序列的顺序信息。
词嵌入
【用0到100的范围来表示你是多么内向/外向(其中0是最内向的,100是最外向的)假设一个叫Jay的人,其内向/外向得分为38/100,则可以用下图表示这个得分:
为了更好的表达数据,我们把范围收缩到-1到1:
考虑到人性复杂,对于一个人的描述只有一条信息显然是不够的,为此,我们添加另一测试的得分作为一个新的第二维度,而这两个维度均可以表现为图上的一个点(或称为从原点到该点的向量)
然后可以说这个向量部分地代表了Jay的人格。当你想要将另外两个人与Jay进行比较时,这种表示法就有用了。而计算两个向量之间相似度得分的常用方法是余弦相似度
小结一下,词嵌入有关键两点
1.将每个词转换为向量表示
2.我们可以很容易地计算出相似向量之间的相互关系。】
词嵌入将原始输入转换为Transformer可以处理的向量格式。词嵌入提供了一种将离散的词汇映射到连续的高维空间的方法,这使得模型能够捕捉到词汇的语义和语法特征,例如相似性或类比关系。词嵌入允许Transformer模型并行处理整个序列,这提高了处理效率。通过使用词嵌入,Transformer模型可以减少参数数量,因为每个词都有一个固定的向量表示,而不是为每个位置的每个词分配独立的参数。
词嵌入可以来自预训练的语言模型,如Word2Vec、GloVe 或 BERT,这些预训练的嵌入可以捕捉丰富的语言特征。具体而言,流程如下
1.每个单词都被嵌入为512维的向量(512是Transformer论文中设定的一个维度,类似编码器/解码器的数量一样,都是可以设置的超参数。顺带提句,训练集中最长句子的长度论文中也设置的512。为方便后续一系列的图示,这里用4个格子代表512维,即虽然你只看到4维,但你要明白实际背后代表着512维)
2.第一个的那个编码器接收的是嵌入向量,之后的编码器接收的是前一个编码器的输出
位置编码
由于 Transformer 本身的架构不包含循环或卷积结构,无法直接获取输入元素的位置信息,而位置信息在序列处理中往往非常重要(如句子中单词的顺序会影响句子的语义)。位置编码通过为每个位置生成一个独特的向量来弥补这一缺失,使得模型能够区分不同位置的元素。(Transformer本身是不能利用单词的顺序信息的,因此需要在输入中添加位置 Embedding,否则Transformer就是一个词袋模型了。)这些向量遵循模型学习的特定模式,有助于模型确定每个词的位置,或序列中不同词之间的距离。
那么位置编码向量到底遵循什么模式?其具体的数学公式如下:
Transformer中单词的输入表示x由单词Embedding和位置Embedding(Positional Encoding)相加得到。
此时,我们可以看出Transformer的一个核心特性
1.输入序列中每个位置的单词都各自单独的路径流入编码器,即各个单词同时流入编码器中,不是排队进入.
2.在自注意力self-attention层中,这些路径两两之间是相互依赖的,而前馈层(feed-forward)则没有这些依赖性,所以这些路径在流经前馈层(feed-forward)时可以并行计算
2.2 编码器
编码器的主要功能是对输入序列进行特征提取,将输入序列转换为一系列包含丰富语义和语法信息的上下文表示向量。它通过堆叠多个编码器层来逐步深入地分析输入序列。每个编码器层包含多头自注意力机制(Multi - Head Self - Attention)和前馈神经网络层(Feed - Forward Neural Network),并配合归一化层(Normalization Layer)和残差连接(Residual Connection)避免梯度递减的问题。
【上下文表示序列:它是一系列向量,每个向量对应输入序列中的一个位置,这些向量不仅包含了该位置元素本身的信息,还融合了整个输入序列的上下文信息(即该位置元素与其他位置元素之间的关系、语义依赖等),为解码器提供了丰富的信息源,以便解码器在生成目标序列时能够基于这些信息做出准确的决策。】
Transformer的编码组件是由6个编码器叠加在一起组成的,解码器同样如此。所有的编码器在结构上是相同的,但是它们之间并没有共享参数。
第一个Encoder block的输入为句子单词的表示向量矩阵,后续Encoder block的输入是前一个Encoder block的输出,最后一个Encoder block输出的矩阵就是编码信息矩阵C(上下文表示序列以及K、Q、V),这一矩阵后续会用到Decoder中。
多头自注意力机制
在编码器中,多头自注意力机制允许每个位置的元素与序列中的其他所有元素进行交互,计算它们之间的相关性得分(注意力分数),从而确定每个元素在整个序列中的重要性权重,使得模型能够聚焦于与当前元素相关的其他元素,更好地理解序列的语义和结构。通过这种方式,编码器能够捕捉输入序列中的长距离依赖关系和复杂的语义结构。
多头机制允许模型从不同的表示子空间关注输入序列,每个头可以学习到不同方面的特征信息,然后将这些信息整合起来,提供更丰富、全面的特征表示。
全连接前馈神经网络层(Feed - Forward Neural Network)
全连接层的每一个结点都与上一层的所有结点相连因而称之为全连接层。由于其全相连的特性,一般全连接层的参数也是最多的。Feed Forward由两个线性变换组成,即两个全连接层组成,第一层的激活函数为Relu,第二层不使用激活函数。全连接层对应的公式如下
X是输入,Feed Forward最终得到的输出矩阵的维度与X一致。这两层网络就是为了将输入的Z映射到更加高维的空间中然后通过非线性函数ReLU进行筛选,筛选完后再变回原来的维度。在每个编码器和解码器中,虽然这个全连接前馈网络结构相同,但是不共享参数。整个前馈网络的输入和输出维度都是d=512.第一个全连接层的输出和第二个全连接层的输入维度为d=2048。
对经过多头自注意力层处理后的序列进行进一步的非线性变换,增强模型的表达能力。它由两个线性变换层和一个激活函数(如 ReLU)组成,能够学习到输入序列的更复杂的非线性特征。前馈神经网络层独立地处理每个位置的向量,不考虑位置之间的相互关系,与多头自注意力层相互补充,共同完成对序列特征的提取和转换。
堆叠编码器输出——上下文表示序列
经过多个编码器层的反复处理,输入序列中的每个元素都被充分地与其他元素进行信息交互和融合,最终形成了上下文表示序列。这个上下文表示序列是一系列向量,每个向量对应输入序列中的一个位置,这些向量不仅包含了该位置元素本身的信息,还融合了整个输入序列的上下文信息(即该位置元素与其他位置元素之间的关系、语义依赖等),为后续的解码器提供了全面的输入序列信息,使得解码器能够基于这些信息更好地理解输入序列的语义和结构,从而在生成目标序列(如翻译任务中的目标语言句子、文本生成任务中的生成文本等)时做出更准确的决策。
归一化层和残差连接
编码器结构中有一个需要注意的细节:每个编码器的每个子层(Self-Attention层和FFN层)都有一个残差连接,然后做了一个:层归一化(layer-normalization)。也就是,在经过多头注意力机制得到矩阵Z之后,并没有直接传入全连接神经网络,而是经过了一步Add&Normalize。
其计算公式如下:
括号里面是Add,括号外面是层归一化。其中X表示Multi-Head Attention或者Feed Forward的输入,MultiHeadAttention(X)和FeedForward(X)表示输出(输出与输入X维度是一样的,所以可以相加)。
-
Add就是在z的基础上加了一个残差块X。将输入和多头注意力层或全连接神经网络的输出相加,再传递给下一层,避免梯度递减的问题。下图就是一个残差块:
X是输入值,F(X)是经过第一层线性变换后并且激活的输出,在第二层线性变化之后,激活之前,F(X)加入了这一层输入值X,然后再进行激活后输出。 -
LayerNorm是在同一个样本中不同神经元之间进行归一化,会将每一层神经元的输入都转成均值方差都一样的,这样可以加快收敛。
展开Add&Normalize过程的一个编码器示意图如下所示:
2.3 解码器(Decoder)
右边是解码器的部分,解码器的核心功能是根据编码器提供的上下文信息以及已生成的部分目标序列(在生成式任务中)来生成完整的目标序列。解码器同样由多个解码器层组成,其结构与编码器层相似,但存在一些关键差异,以适应生成任务的需求。
和Encoder Block一样,Decoder也是由6个decoder堆叠而成的。
解码器的输入也要加上位置编码,来指示每个词的位置。
每个解码器层由三个子层连接结构组成:包含两个Multi-Head Attention层,一个前馈全连接子层。第一个Multi-Head Attention层采用了Masked操作。第二个Multi-Head Attention 层的K,V矩阵使用Encoder的编码信息矩阵C进行计算,而Q使用上一个Decoder block的输出计算。每个子层后都接有Add&Norm操作。
第一层,掩码多头自注意力层。mask表示掩码,它对某些值进行掩盖,使其在参数更新时不产生效果。Transformer模型里面涉及两种mask,分别是padding mask和sequence mask。
【什么是Padding mask呢?因为每个批次输入序列的长度是不一样的,所以我们要对输入序列进行对齐。具体来说,就是在较短的序列后面填充0(但是如果输入的序列太长,则是截断,把多余的直接舍弃)。因为这些填充的位置,其实是没有什么意义的,所以我们的Attention机制不应该把注意力放在这些位置上,所以我们需要进行一些处理。具体的做法:把这些位置的值加上一个非常大的负数(负无穷),这样的话,经过 Softmax 后,这些位置的概率就会接近0。
Sequence Mask是防止解码器在生成过程中 “看到” 未来的信息,确保生成过程是按照从左到右的顺序依次进行的,逐个预测,逐步生成目标序列的每个元素,符合自然语言生成的逻辑。(例如,在生成一个句子时,当生成第i个单词时,不能让模型 “知道” 后续的单词,只能根据已生成的前i-1个单词来进行预测,这样可以保证生成过程的合理性和逻辑性。)也就是对于一个序列,在t时刻,我们的解码输出应该只能依赖于t时刻之前的输出,而不能依赖t之后的输出。因为我们需要想一个办法,把t之后的信息给隐藏起来。具体的做法:产生一个上三角矩阵,上三角的值全为 0。把这个矩阵作用在每个序列上,就可以达到我们的目的。】
Encoder中的Multi-Head Attention也是需要进行mask的,只不过Encoder中只需要padding mask即可,而Decoder中需要padding mask和sequence mask,具体实现就是两个Mask相加。
因此第一层在计算注意力得分时,模型只能关注生成内容的当前位置之前的信息,避免未来信息的泄漏。比如,这里计算输出b2时,就只使用了a1、a2两个位置的注意力得分进行加权。模型需要按照顺序逐步生成序列(a1、a2),因此不能提前获取到未来a3、a4的信息。
再比如翻译I have a cat。【第一步:确定是Decoder的输入矩阵和Mask矩阵,输入矩阵包含"
第二步:接下来的操作和之前的Self-Attention一样,通过输入矩阵X计算得到Q,K,V矩阵。然后计算Q和K转置的乘积Q*(K转置),然后需要使用Mask矩阵遮挡住每一个单词之后的信息,遮挡操作如下:
得到遮挡后的矩阵之后在该矩阵上进行Softmax,计算attention score。每一行的和都为1。但是单词0在单词1,2,3,4上的attention score都为0。使用然后遮挡后的矩阵与矩阵V相乘,得到输出Z。
第三步:通过上述步骤就可以得到一个Mask Self-Attention的输出矩阵Zi,然后和Encoder类似,通过Multi-Head Attention拼接多个输出Zi,然后计算得到第一个Multi-Head Attention的输出Z,Z与输入X维度一样。】
第二层,利用了Encoder的输出结果计算交叉注意力。
与编码器块相比,解码器块在多头自注意力模块和位置FFN之间额外插入了交叉注意力(cross-attention)模块。与Encoder中的Q、K、V全部来自于上一层单元的输出不同,Decoder只有Q来自于上一个Decoder单元的输出,K与V都来自于Encoder最后一层的输出。也就是说,Decoder是要通过当前状态Q与Encoder的输出K算出权重后(计算query与各个key的相似度),最后与Encoder的V加权得到下一层的状态。这样做的好处是每一位单词都可以利用到Encoder所有单词的信息(这些信息无需Mask),将解码器的当前状态与编码器输出的上下文表示向量进行交互。它允许解码器在生成目标序列的每个位置时,关注输入序列中的相关信息,从而更好地利用输入序列提供的上下文来指导目标序列的生成。
堆叠解码器输出——目标序列
目标序列是模型在处理任务时期望生成或预测的序列。它取决于具体的任务类型,在不同的应用场景中有不同的含义。例如,在机器翻译任务中,目标序列是源语言句子对应的翻译后的目标语言句子;在文本生成任务中,目标序列可以是根据给定的主题或前文生成的后续文本内容;在情感分类任务中,目标序列可以是表示输入文本情感倾向的类别标签(如 “积极”“消极”“中性” 等)
2.4 输出(分类头)
解码器的输出是一个float向量,还要根据任务的需求生成最终的输出。例如,在语言生成任务(如文本生成、机器翻译的目标语言生成等)中,输出层将模型学习到的特征转换为目标序列的概率分布,通常是通过一个线性层将解码器输出的向量映射到词汇表大小的维度,然后经过 Softmax 函数得到每个单词的生成概率,最后选择最高概率所对应的单词,作为这个时间步的输出,此步为预测输出单词。在分类任务中,通过一个全连接层将上下文表示向量映射到类别标签空间,然后通过 Softmax 函数得到每个类别的概率分布,从而确定输入序列所属的类别(Transformer 用于分类任务时可以不用解码器)。
不用解码器的原因:
- 解码器的主要功能:解码器主要用于序列到序列的任务,如机器翻译、文本生成等,其作用是根据编码器提供的上下文信息以及已生成的部分目标序列(在生成式任务中)来生成完整的目标序列。在这个过程中,解码器需要关注已生成的序列信息以及与编码器输出的交互,以确保生成的序列在语义和语法上合理且连贯。
- 分类任务的特点:分类任务的目标是将输入数据分配到预定义的类别中,不需要像生成式任务那样逐步生成序列。编码器已经能够很好地捕捉输入数据的特征和语义信息,通过上述方式可以直接利用这些信息进行分类决策,而不需要解码器的复杂序列生成机制。
2.5.训练过程
Transformer的训练过程和深度神经网络模型类似。以中文翻译成英文的翻译任务为例:
-
数据准备:首先,需要准备训练数据集,包括大量中文序列和人工标注的正确英文序列。
-
输入表示:将中文源语言句子和英语句子进行编码,并进行适当的标记和嵌入处理。常见的编码方式是使用词嵌入(Word Embedding)将每个词转换为向量表示,并添加位置编码(Positional Encoding)以保留序列的顺序信息。注意在训练时,Decoder的输入是人工标注为正确的英文序列,而不是Decoder自己生成的英文序列。
-
确定模型架构:构建Transformer模型的架构,包括编码器和解码器层数、注意力机制和前馈神经网络层数等等。
-
训练模型:定义适当的损失函数来衡量模型在训练过程中的性能。对于机器翻译任务,常用的损失函数是交叉熵损失函数,用于比较模型生成的英语句子与正确的英语句子之间的差异。训练过程中,通过反向传播算法和优化器(如Adam优化器)来更新模型的参数(如全连接神经网络的参数、注意力机制中的Wk、Wq、Wv参数),以最小化损失函数。通常会使用小批量(mini-batch)的方式进行训练,即将训练数据划分为多个批次,每个批次包含一定数量的样本。
-
验证和调优:使用验证数据集评估模型的性能,进一步进行超参数调整、模型结构调整等操作,提升模型的性能。
-
推理和应用:训练完成后,使用已训练好的模型将中文句子翻译成英文句子。
注意,Transformer所需的训练数据量通常在数百万到数十亿个tokens(输入向量)之间,因此个人训练Transformer是很困难的,最好直接使用预训练好的模型。
三.自注意力机制Self-Attention
自注意力机制是一种用于处理序列数据的机制,它能够在序列中的每个位置上计算该位置与其他位置之间的关联程度,并根据这些关联程度来加权组合序列中的信息,将对其他位置的理解融入到当前位置中。(自注意力会关注整个输入序列的所有单词,帮助模型对本单词更好地进行编码。在处理过程中,自注意力机制会将对所有相关单词的理解融入到我们正在处理的单词中。)
【举例:假如,我们要翻译下面这个句子:The animal didn’t cross the street because it was too tired.这个句子中的it指的是什么?是指animal还是street?对人来说,这是一个简单的问题,但是算法来说却不那么简单。当模型在处理it时,Self-Attention 机制使其能够将it和animal关联起来。
当模型处理每个词(输入序列中的每个位置)时,Self-Attention机制使得模型不仅能够关注当前位置的词,而且能够关注句子中其他位置的词,从而可以更好地编码这个词。Transformer使用Self-Attention机制将对其他词的理解融入到当前词中。说的直白点就是,你如果要更好的理解句中某个特定单词的含义,你要把它放到整个语境之中去理解,比如通过对上下文的把握。
那上下文哪些词对理解该特定词更重要呢?这个重要程度便用所谓的权重表示(权重来自于该词/向量本身跟其他各个词/向量之间的相似度),权重越大的单词代表与『该词』越相关(某种意义上可以认为是越相似),从而对理解『该词』越重要,所以自注意力机制在编码it时把更多的注意力/权重放在了animal等单词上,然后把该词编码为包括该词在内所有词的加权和。】
自注意力机制更具体的功能如下:
-
序列建模:自注意力可以用于序列数据(例如文本、时间序列、音频等)的建模。它可以捕捉序列中不同位置的依赖关系,从而更好地理解上下文。这对于机器翻译、文本生成、情感分析等任务非常有用。
-
并行计算:自注意力机制可以并行计算所有元素的注意力权重,这意味着模型可以同时考虑序列中的所有元素,而不是一次只考虑一个。相比于RNN和CNN等序列模型,它更容易在GPU和TPU等硬件上进行高效的训练和推理。
-
长距离依赖捕捉:传统的循环神经网络(RNN)在处理长序列时可能面临梯度消失或梯度爆炸的问题。自注意力机制允许模型在序列中的任意两个元素之间直接建立联系,这使得模型能够捕捉到长距离的依赖关系,即使这些元素在序列中相隔很远。
Self-Attention机制基本结构如图
1.输入:
Q(Query,查询向量)、K(Key,键向量)和V(Value,值向量)是自注意力机制的三个主要输入。
2.点积计算(MatMul):
通过计算Query矩阵和Key矩阵之间的点积(即对应元素相乘后求和),来衡量Query与每个Key之间的相似度或匹配程度,这反映了序列中每个元素之间的相似性或匹配度。
3.缩放(Scale):
由于点积操作的结果可能非常大,尤其是在输入维度较高的情况下,这可能导致softmax函数在计算注意力权重时进入饱和区。为了避免这个问题,缩放点积注意力引入了一个缩放因子,通常是输入向量维度dk的平方根。点积结果除以这个缩放因子,可以使得softmax函数的输入保持在一个合理的范围内。
4.可选掩码(Mask):(可无)
在某些情况下,如在解码器(Decoder)中处理序列生成任务时,掩码用于屏蔽未来信息,确保模型在预测当前元素时不会看到未来的信息。
5.SoftMax函数:
缩放后的点积结果即原始得分通过SoftMax函数转换为概率分布,计算每个Key相对于Query的注意力权重。
6.加权求和(MatMul):
最后,使用SoftMax得到的权重对值V进行加权求和,得到最终的注意力输出。这个过程根据注意力权重的大小,将更多的关注放在与Query更匹配的Value上。
计算自注意力需要先理解几个概念:
- Query矩阵(Q):表示当前的关注点或信息需求,用于与Key矩阵进行匹配。
- Key矩阵(K):包含输入序列中各个位置的标识信息,用于被Query矩阵查询匹配。
- Value矩阵(V):存储了与Key矩阵相对应的实际值或信息内容,当Query与某个Key匹配时,相应的Value将被用来计算输出。
3.1 计算自注意力
计算自注意力有两种方式,一种通过向量,一种通过矩阵。
①通过向量
第一步:获取Q、K、V
向Self-Attention输入每个单词的词嵌入向量或者上一个编码器的输出,生成三个向量:查询向量、键向量、值向量。这三个向量的生成方法是把输入的向量分别乘以三个不同的权重矩阵Wq、Wk、Wv,得到Q、K、V。而这些权重矩阵是在模型训练阶段中训练出来的。[对于权重矩阵如何训练出来的,还是标准老套路:先随机初始化,然后在损失函数中表示出来,最后通过反向传播不断优化学习得出,最终目标是最小化模型的预测误差] 最终使得输入序列的每个单词各自创建一个查询向量、一个键向量和一个值向量。
新向量在维度上往往比词嵌入向量更低。后面会提到,transformer通过多头注意力机制multi headed attention,对每个512维的输入向量都设置了8个头,不同的头关注每个输入向量不同的部分,而每个头的维度则是:512/8=64,且再多说一句,也可以设置为2个头,不一定非得设置为8个头。查询向量、键向量、值向量这三个向量的维度在论文中设置的是64,在维度上比词嵌入向量更低,因为词嵌入和编码器的输入/输出向量的维度是512,但也不是必须比编码器输入输出的维数小,这样做主要是为了让后续多头注意力的计算更稳定。
第二步:计算原始注意力得分
即计算序列每个位置对当前位置的打分,这个分决定着编码当前位置时,应该对其他位置上的单词各自给予多少关注度。这个得分通过当前位置单词所对应的查询向量query和所有词的键向量key,依次乘积(点积)得出来。
第三步:进行softmax归一化处理
将得分除以8,即Q,K矩阵的列数,即向量维度(8只是一个参考值,是论文中使用的键向量的维数64的平方根,这会让梯度更稳定,也可以使用其它值)。原始的注意力分数通常通过softmax函数转换为概率分布,确保所有元素的注意力权重之和为1。softmax的作用是使所有单词的分数归一化,得到的分数都是正值且和为1。经过softmax函数处理后的注意力分数,就是注意力权重,代表了在编码当前位置时,序列中每个元素对当前元素的贡献或重要性。显然,正在当下位置上的元素获得最高的softmax分数(毕竟自己跟自己最相似嘛)。
第四步:根据注意力权重,对每个词语的值向量进行加权求和
将每个值向量乘以softmax分数,(这一步是为了关注语义上相关的单词,并弱化不相关的单词),然后对加权值向量求和。得到自注意力层在该位置的输出(关于该单词的输出)。
针对每个单词都进行上述四步的自注意力得分计算。
计算某位置元素自注意力机制输出的过程用公式表示:假定序列有两个单词,计算第一个单词的自注意力机制输出,先是计算该位置单词对应的Q(q1)与各个不同的K(k1、k2)计算相似度,然后除以8继而softmax,最后softmax值乘以值向量v1、v2并加权求和
下图里的b1相当于Z1.dk是Q,K矩阵的列数,即向量维度。
对第一个单词:
对第二个单词:
最终每个词的输出向量Zi都包含了其他词的信息,每个词都不再是孤立的了,而且词与词的相关程度可以通过softmax输出的权重进行分析。如此,所有单词的自注意力计算就完成了,得到的向量就可以传给前馈神经网络。然而实际中,这些计算是以矩阵形式完成的,以便算得更快。
输出的向量是序列的新表示,也称为上下文向量。
② 通过矩阵
第一步:计算查询矩阵、键矩阵和值矩阵
我们将输入词向量合并成输入矩阵(矩阵的每一行代表输入句子中的一个单词,所以整个矩阵就代表整个句子),将其乘以我们训练的权重矩阵:Wq、Wk、Wv「再次提醒:词嵌入向量(512维,用图中的4个格子表示),和q/k/v向量(64维,或图中的3个格子)的大小差异」
第二步:计算原始注意力得分(点积)
第三步:进行softmax归一化处理
得到Q×(K的转置)之后,使用Softmax计算每一个单词对于其他单词的attention系数,公式中的Softmax是对矩阵的每一行进行Softmax,即每一行的和都变为1.
第四步:根据注意力权重,对每个词语的值向量进行加权求和
得到Softmax矩阵之后可以和V相乘,得到最终的输出Z。
上图中Softmax矩阵的第1行表示单词1与其他所有单词的attention系数,最终单词1的输出Z1等于所有单词i的值Vi根据attention系数的比例加在一起得到,如下图所示:
将四个步骤合并为一个公式来计算自注意力层的输出
四.多头自注意力机制(Multi-head self attention,MSA)
多头自注意力机制是一种模型可以像有多个大脑一样思考的方式。每个“头”就像是模型中的一个小思维单元,可以关注文本中不同的方面。这就好像在理解一篇文章时,一个“头”可以专注于理解文章中的人物,另一个“头”可以专注于时间线,还有一个“头”可以专注于地点,然后将它们的想法合并起来,以获得更全面的理解。举一个不一定妥帖的例子:当你浏览网页的时候,你可能在颜色方面更加关注深色的文字,而在字体方面会去注意大的、粗体的文字。这里的颜色和字体就是两个不同的表示子空间。同时关注颜色和字体,可以有效定位到网页中强调的内容。使用多头注意力,也就是综合利用各方面的信息/特征。
多头注意力机制是一种在自注意力机制基础上的扩展,它允许模型同时关注不同的表示子空间,通过将输入X分别传递到多个头,每个头都能独立地学习不同的注意力权重,从而增强模型对输入序列中不同部分的关注能力。传统自注意力机制在编码“Thinking”的时候,虽然最后Z1或多或少包含了其他位置单词的信息,但是它实际编码中把过多的注意力放在“Thinking”单词本身,毕竟自己和自己最相似。
具体过程:
1.对于“多头”注意机制,我们有多个查询/键/值权重矩阵集(Transformer使用8个注意力头,则对于每个编码器/解码器都有8个://的矩阵集合),每一组都是随机初始化,经过训练之后,输入向量可以被映射到不同的子表达空间中,从而产生不同的查询/键/值矩阵。(注:子空间映射——每个头通过其独立的线性变换将输入数据映射到一个新的子空间。这些子空间是原始数据的高维表示,每个头可以学习到不同的特征表示。)
2.按照上面的方法,使用不同的权重矩阵进行8次自注意力计算,就可以得到8个不同的Z矩阵
3.这给我们带来了一点挑战。前馈层没法一下子接收8个矩阵,它需要一个单一的矩阵(最终这个单一矩阵类似输入矩阵X那样,矩阵中每个的行向量对应一个单词,比如矩阵的第一行对应单词Thinking、矩阵的第二行对应单词Machines)
所以我们需要一种方法把这8个矩阵合并成一个矩阵。那该怎么做?其实可以直接把这些矩阵拼接在一起,然后乘以一个附加的权重矩阵WO,得到最终的Z。
在最后一步再合并不同子空间中的Attention信息。这样降低了计算每个head的Attention时每个向量的维度,在某种意义上防止了过拟合;由于Attention在不同子空间中有不同的分布,Multi-head Attention实际上是寻找了序列之间不同角度的关联关系,并在最后拼接这一步骤中,将不同子空间中捕获到的关联关系再综合起来。
Multi-head Attention的本质是,在参数总量保持不变的情况下,将同样的Query,Key,Value映射到原来的高维空间的不同子空间中进行Attention的计算,在最后一步再合并不同子空间中的Attention信息。这样降低了计算每个head的Attention时每个向量的维度,在某种意义上防止了过拟合;由于Attention在不同子空间中有不同的分布,Multi-head Attention实际上是寻找了序列之间不同角度的关联关系,并在最后拼接这一步骤中,将不同子空间中捕获到的关联关系再综合起来。