1、自注意力机制
1.1 公式表达
自注意力机制的核心是通过计算不同词之间的相关性,赋予不同权重(注意力分数),最终将这些加权后的信息重新组合成新的向量表示。其数学公式通常写成:
解读各个部分的含义:
-
Q(Query)、K(Key)、V(Value):每个词被分别映射成这三个不同维度的向量。例如,假设输入是一个句子中的词语,每个词先被转换为一个向量:
- Query:表示“当前词需要关注什么信息”。
- Key:表示“其他词能提供什么信息”。
- Value:表示“其他词的具体内容”。
:将Key向量转置,使其可以与Query进行点积计算。
:计算Query与所有Key之间的点积,表示每个Query与其他Key的相关性。除以
是为了防止点积结果过大,避免梯度消失或爆炸(类似标准化操作)。
softmax:将点积结果转化为概率分布(0到1之间),表示每个Value的重要性权重。
最终输出:通过权重分数与Value向量加权求和,得到新的上下文表示。
假设有一句话:“苹果公司今天发布了自己的新产品,售价比预期更便宜。”
目标:计算每个词的自注意力,并生成新的上下文表示。
Step 1:初始化矩阵
假设每个词被编码为一个 d 维的向量(比如,词嵌入编码)。然后,通过三个全连接层生成 Q、K、V 矩阵。例如:
示例词:["苹果"]被嵌入为向量 x1,生成 Q1,K1,V1。
Step 2:计算注意力矩阵
对每个词i,计算其与所有词j之间的“关系分数”:
例如,计算“苹果”与“公司”的分数:
填充成矩阵并计算softmax:
这将生成一个注意力权重矩阵 A,其中每个元素 Ai,j表示词j对词i的重要性。
Step 3:加权求和得到输出
例如,新的“苹果”的表示 是所有词的Value向量按权重求和的结果。
Q、K、V做个类比的话:
- Key:像图书馆的“索引标签”,告诉模型“这个词应该被其他词如何关注”。
- Query:像“问题”,告诉模型“我要找什么信息”。
- Value:像“答案内容”,一旦被选中就会被整合到结果中。
假设你在图书馆找一本关于“苹果公司”的书:
- Key:书脊上的分类标签(比如“科技公司”“科技产品”)。
- Query:你想要的书籍关键词(“苹果公司”“新产品”)。
- Value:书的摘要内容。
你通过扫描标签(Keys)找到匹配的书籍(高分数),然后整合摘要(Values)得到信息。
关于Q,K,V,还可以这样理解:
- V 是我们手头已经有的信息,可以作为一个知识库;
- Q 是我们待查询的东西,我们希望把 V 中和 Q 有关的信息都找出来;
- 而 K 是 V 这个知识库的钥匙 ,V中每个位置的信息对应于一个 K 。对于V中的每个位置的信息而言,如果 Q 和对应钥匙 的匹配程度越高,那么就可以从该条信息中找到和 Q 更多的内容。
举个例子,我们现在希望给四不像找妈妈。以四不像作为 Q ,以[鹿 ,牛 ,羊 ,驴,青蛙 ]
作为 V 和 K ,然后发现四不像和鹿的相似度为1/3、和牛的相似度为1/6,和羊、驴的相似度均为1/4,和青蛙的相似度为0,那么最终的查询结果就是:1/3鹿+1/6牛+1/4羊+1/4驴+0青蛙
。
从上面的描述可以看出,计算注意力的流程可以分解为以下两个步骤:
- 计算 Q 和 K 的相似度
- 根据计算得到的相似度,取出 V 每条信息中和 Q 有关的内容。
1.2 公式推导
1.2.1 注意力的计算
step1:获取q、k、v
首先,需要有一系列的输入,以三个输入a1,a2,a3 为例,我们分别将a1,a2,a3 乘以Wq、Wk、Wv 矩阵得到对应的q、k、v ,如下图所示:
需要注意的是:Wq、Wk、Wv 是随机初始化后,通过大模型训练而获得。
本质上是一些矩阵运算,如下图所示:
需要注意的是:Wq、Wk、Wv 是随机初始化后,通过大模型训练而获得。
Wq、Wk、Wv 就是 Transformer 大模型在预训练阶段时,通过神经网络反向传播来训练出来的权重(注意,这里提到的权重,是指神经网络中的连接权重),是 Transformer 大模型通过百万级的训练语料在 8 块 NVIDIA P100 GPU 上运算 3.5 天后的训练所得(当然,这是2017年 Google 团队首次提出 Transformer时的事情了,今天许多的多模态大模型中的 Transformer 架构在训练时往往采用至少百亿计的语料训练集,同时在几百甚至上千块至少是 A100 GPU 上,训练十几天才会完成,以后这个训练成本只可能是越来越巨大)。
很多人会在这里感觉很迷惑, WQ ,WK,WV 这三个矩阵到底是从何而来的,因为很多介绍 Transformer 的文章在这里都只是一笔带过,仅仅说了一下 WQ ,WK,WV 这三个权重矩阵是随机初始化的,但并没有说明是在模型“*训练阶段*”随机初始化的,这造成了诸多的混淆。因为,不同于“*训练阶段*”,在模型的“*执行阶段*”(模型预测阶段)这三个矩阵是固定的,即 Transformer 神经网络架构中的固定的节点连接权重,是早就被预先训练好的(Pre-Trained)。
step2:计算attention score
得到这些q、k、v 后,分别用q去乘每一个得到一个数值
:
a1,1、a1,2、a1,3是一个数值,我们称为attention score
,其表示的是每个输入的重要程度。这部分的图解公式如下:
step3:通过softmax层
这步就比较简单了,即把上步得到的a1,1、a1,2、a1,3经过一个softmax层:
注意:在softmax层处理之前一般会除以,起到一个归一化的作用,这里简化处理,省略了该步骤。
attention score本质上就是一个概率分布,表示 Q 和 K 之间的相似度。
注意力机制本质上可以认为是求一个离散概率分布的数学期望。定义一个离散的随机变量 X
,X
的所有可能取值为[X1,X2,X3]
,离散分布 P=[p1,p2,p3]
,于是注意力的计算结果就是随机变量 X 的数学期望:
而离散分布 P 就是上文通过 Q 和 K 计算得到的softmax归一化之后的attention 。通过这里的解释,我们也可以更好地理解为什么计算 attention 的时候需要使用 softmax 函数进行归一化,归一化之后的每一个 attention 行向量都是一个离散的概率分布,具有更好的数学意义。
step4:得到输出bi
得到a1,1′、a1,2′、a1,3′后,会让其分别乘v1、v2、v3再相加得到b1,过程如下:
图解公式如下:
上文通过q1分别乘k1T、k2T、k3T最终得到b1 ,同理我们可以通过q2分别乘k1T、k2T、k3T和q3分别乘k1T、k2T、k3T得到b2和b3。如下图所示:
在上述step2、step3和step4中,由于没有介绍和b2和b3的生成过程,因此只给出了有关 b1的图解公式。这里再补充上完整的图解公式,如下:
step2:
step3:
step4:
最后,为让大家理解此过程是并行的,将步骤1到步骤4的过程整合在一起,其中I表示输入的向量,通过下图可以很明显的看出这些矩阵运算是可以并行的,即我们把所有的输入ai拼在一起成为I,将I输入网络进行一系列的矩阵运算。
1.2.2 多头注意力的计算
在原始注意力模型的基础上,自注意力通常会并行计算多个不同的注意力分布(称为“头”),然后将结果组合起来,称为多头注意力(Multi-Head Attention)。
为什么需要多头呢?
因为单个注意力头可能无法捕捉所有类型的语义关系(比如有的关注逻辑关系,有的关注位置关系)。多头允许模型从不同“视角”(不同线性变换后的投影空间)观察问题,类似人类从多个角度观察事物。
例如:某个头专注“苹果公司”与后面的“新产品”关联;另一个头关注“售价”和“预期”的对比。最后组合所有头的输出,综合所有视角的信息。
step1:获取q、k、v
首先第一步和self Attention一模一样:
step2:分裂产生多个q、k、v
以下以两个head为例进行阐述,即将q1分裂成q1,1和q1,2,将q2分裂成q2,1和q2,2,将q3分裂成q3,1和q3,2,如下图所示:
那么这个过程是怎么进行的呢,其实也很简单,只需要分别乘上两个矩阵W1Q和W2Q即可。
同理,我们可以将和k和v采用同样的方法,即都相应的乘以两个矩阵进行分裂,结果如下图所示:
step3:对所有head使用self Attention
我们可以将上述结果分成两个head进行处理,如下图所示:
你会发现head1和head2就是我们前面所说的self Attention里面的元素,这样会从head1和head2得到对应输出,如下图所示:
step4:拼接所有head输出的结果
这一步我们会将上一步不同head输出的结果进行Concat拼接,如下图所示:
step5:Concat后的结果乘上Wo矩阵
这一步会乘上Wo矩阵,其作用主要是融合之前多个head的结果,并使我们的输出和输入时维度保持一致,如下图所示:
2、Transformer 架构
下图是简化的 Transformer 的模型架构示意图,先来大概看一下这张图, Transformer 模型的架构就是一个 seq2seq 架构,由多个 Encoder Decoder 堆叠而成。
Encoder由 Self-attention layer 和 Feed Forward组成, 而 Decoder 则由 Self-Attention、Encoder-Decoder Attention、 Feed Forward 组成。
概括来说,我们输入法语:
je suis étudiant
,经过六个 Encoder 后得到了类似于 Context Vector 的东西,然后将得到的向量放进 Decoder 中,每个 Decoder 会对上一个 Decoder 的输出进行 Self-Attention 处理,同时会把得到的结果与 Encoder 传递过来的 Vector 进行 Encoder-Decoder Attention 处理,将结果放入前馈网络中,这算是一个 Decoder,而把六个 Decoder 叠加起来学习,便可得到最后的结果。这里叠起来的编解码器的数量不是固定的。
2.1 Encoder
下面是 Encoder 的示意图,在这里我们假设输入的句子只有两个词,简单高效的讲解 Encoder 的工作原理。
上图所示,我们输入了两个编码后的向量 x1,x2 ,其中 x1 是对单词 Thinking 的表示,x2 是对 Machines 单词的表示。 通过 Encoder 模块得到了两个向量 r1,r2 ,虽然 r1,r2 也分别代表 Thinking、Machines 单词的信息,但是 r1,r2 是加权后的结果,也就是说 r1 中不仅仅包含 Thinking 单词的信息,而且还有 Machines 单词的信息,只不过 Thinking 单词信息占的比重可能很高,毕竟单词和单词本身的相关性是很高的(这里为了方便理解,举一个例子,具体权重如何分配的是模型学习出来的)。这里用两个词语举例子,如果输入的句子单词很多,可能不同单词之间的相关度就不一样,最后得到的 r 向量分配的权重也就不同。
所以,原始输入的向量 x1 、x2 各自包含各自的信息,而最后得到的 r1,r2 便分别包含了原始输入所有向量的信息,只是各占的比重不同。
怎样才能每个单词的信息按不同权重糅合起来呢 ?没错,Self-Attention 机制。
2.1.1 Self-Attention
Self-Attention 中的细节如下图所示,当单词 Thinking、Machines 进行 Embedding 后,分别与矩阵 WQ,WK,WV 相乘。例如 Thinking 单词 Embedding 后变成 X1 向量,此向量与 WQ 相乘后为 q1 向量,也称为 Queries ,X1 与 WK 相乘得到 k1 向量,以此类推。我们称 q1,k1,v1 向量分别为 Queries、Keys、Values 向量。
输入的每个单词都都可以通过同一个矩阵计算得到对应的 qi,ki,vi 向量,x1 通过下图的过程生成 z1。用 q1 即 Thinking 单词的 Queries, 分别与其它单词的 k1 向量相乘,比如 q1·k1=122, q1·k2=96。然后用得到的值除以根号下 dk ( dk 为上方提及的矩阵的第一个维度 ) 得到 14 和 12,对除法的结果进行 softmax ,于是 Thinking 单词对应的比例是 0.88,而 Machines 对应比率的是 0.12 ,接下来使用 softmax 后得到的比率与对应的 Values 向量相乘,便得到了 v1 和 v2 , v1=0.88∗v1 , v2=0.12∗v2。由于 0.12 占的比例较小,得到的 v2 向量颜色较浅。最后将得到的 v1,v2 加起来便是 z1。
于是 z1 便包含了两个单词的信息,只不过 Thinking 单词的信息占的比重更大一些,而 Machines 单词的信息占的比例较小。
同样 z2 向量可以通过相同的方式计算出来,只不过计算 Score 的时候需要用 q2·k1 , q2·k2。需要使用 q2 去分别乘 和k1和k2。除以 8 并且 Softmax 后可以与 v1,v2 相乘,相加后便得到的 z2 ,z2 包含了两个单词的向量,只不过各占的比重不同。
当我们计算 q1,k1,v1 的时候,我们需要用向量 x1 分别与三个矩阵 WQ,WK,WV 相乘,而计算 q2,k2,v2 的时候又得需要进行三次乘法。这里仅仅有两个词就进行了 6 次运算。如果一个句子中有很多词呢 ?那对性能是不是非常大的消耗 ?这里就需要用到向量化的操作了,如下图所示,不在拿 x1,x2 分别去和三个矩阵相乘,而是将 x1,x2 叠加起来看着一个新的矩阵 X ,再去和三个矩阵相乘。得到三个新的矩阵 Q,K,V, 这三个矩阵的每一行代表着一个词转化后的值。
进行 z 值计算的时候也可以用向量化的思想,最后的矩阵 Z 就是 z1 和 z2 叠加在一起的结果。
其中矩阵 WQ,WK,WV 是学习出来的,我们试图去学习三个矩阵 WQ,WK,WV ,与 Embedding 向量相乘后得到 Query,Key,Value 向量。而期望得到的 Query,Key,Value 向量最契合当前的任务。因为矩阵 WQ,WK,WV 是学习出来的,所以得到的 Query,Key,Value 向量是比较抽象的。在这里, WQ,WK,WV 矩阵的功能相当于抽取特征。这里的命名 Query,Key,Value 也非常有意思, 大家自己想想每个向量的功能就能对应上了。这里应该是借鉴了信息检索的相关思想。
2.1.2 Positional Encoding
循环神经网络每个时间步输入一个单词,通过这样的迭代操作能够捕获输入句子中单词的位置信息。 但是 transformer 模型没有类似于循环神经网络的结构, 所以必须提供每个单词的位置信息给 transformer, 这样才能识别出语言中的顺序关系。
所以,输入进 encoder 中的向量不仅仅是单词的 embedding,而是单词的 embedding 和 位置向量 positional encoding 融合后的结果。
2.1.3 Layer normalization
Normalization 有很多种,但是它们都有一个共同的目的,那就是把输入转化成均值为 0 方差为 1 的数据。我们在把数据送入激活函数之前进行 normalization(归一化),因为我们不希望输入数据落在激活函数的饱和区。
说到 normalization,那就肯定得提到 Batch Normalization。BN 的主要思想就是:在每一层的每一批数据上进行归一化。我们可能会对输入数据进行归一化,但是经过该网络层的作用后,我们的数据已经不再是归一化的了。随着这种情况的发展,数据的偏差越来越大,我的反向传播需要考虑到这些大的偏差,这就迫使我们只能使用较小的学习率来防止梯度消失或者梯度爆炸。
2.1.4 Feed Forward
上文已经讲解了怎么把 x1 转化为 z1, 接下来只需要把学习到的 zi 和原始输入进行融合和 Normalize 后输入到一个 feed forward neural network 中 ,相当于做了一个 skip-connection ,也就是下图中的虚线箭头进行的操作。看下面的图可得知, zi 进入 feed forward neural network 是单独进行的,没有揉合在一起。紧接着,feed forward neural network 的输出和它的输入再进行融合和 Layer Normalize 操作才得到了ri。
到此为止,把 encoder 的一个单元的原理讲清楚了,也就是如何将 xi 转化为 ri。
对输入的数据进行 Embedding 得到 xi
把 Embedding 后的结果融入 Positional 信息后输入到 Encoder 网络中。
输入信息输入到 Attention layer 中来捕获多维度的上下文信息,得到 zi
将注意力学习到的结果和原始输入进行融合后进入 Feed Forward 网络进一步学习。
前馈网络的结果和也会和前馈网络的输入做一个融合,类似于 skip-connection。
这样便得到了一个 Encoder 单元后的结果 ri
刚刚给出的一个 encoder 的设计细节,transformer 是可以有很多 encoder 单元组合起来的,如下图所示是两个 encoder 单元叠加起来:
2.2 Decoder
Decoder 中的模块和 Encoder 中的模块类似,都是 Attention 层、前馈网络层、融合归一化层,不同的是 Decoder 中多了一个 Encoder-Decoder Attention 层。这里先明确一下 Decoder 模块的输入输出和解码过程:
输出:对应 i 位置的输出词的概率分布
输入:Encoder 模块的输出 & 对应 i−1 位置 Decoder 模块的输出。所以中间的 Encoder-Decoder Attention 不是 self-attention,它的 K,V 来自 Encoder 模块,Q 来自上一位置 Decoder 模块的输出
解码:这里要特别注意一下,编码可以并行计算,一次性全部encoding出来,但解码不是一次把所有序列解出来的,而是像rnn一样一个一个解出来的,因为要用上一个位置的输入当作 attention 的 query
输入序列经过 encoder 部分,然后将最上面的 encoder 的输出变换成一组 attention 向量 K 和 V, 这些向量会用于每个 decoder 的 encoder-decoder attention 层,有助于解码器聚焦在输入序列中的合适位置。
重复上面的过程,直到 decoder 完成了输出,每个时间步的输出都在下一个时间步时喂入给最底部的 decoder,同样,在这些 decoder 的输入中也加入了位置编码,来表示每个字的位置。
2.2.1 Mask
上图红色部分为 Transformer 的 Decoder block 结构。
Decoder block 的第一个 Multi-Head Attention 采用了 Masked 操作,因为在翻译的过程中是顺序翻译的,即翻译完第 i 个单词,才可以翻译第 i+1 个单词。通过 Masked 操作可以防止第 i 个单词知道 i+1 个单词之后的信息。下面以 "我有一只猫" 翻译成 "I have a cat" 为例,了解一下 Masked 操作。
下面的描述中使用了类似 Teacher Forcing 的概念。在 Decoder 的时候,是需要根据之前的翻译,求解当前最有可能的翻译,如下图所示。首先根据输入 "<Begin>" 预测出第一个单词为 "I",然后根据输入 "<Begin> I" 预测下一个单词 "have"。
Decoder 可以在训练的过程中使用 Teacher Forcing 并且并行化训练,即将正确的单词序列 (<Begin> I have a cat) 和对应输出 (I have a cat <end>) 传递到 Decoder。那么在预测第 i 个输出时,就要将第 i+1 之后的单词掩盖住,注意 Mask 操作是在 Self-Attention 的 Softmax 之前使用的,下面用 0 1 2 3 4 5 分别表示 "<Begin> I have a cat <end>"。
第一步:是 Decoder 的输入矩阵和 Mask 矩阵,输入矩阵包含 "<Begin> I have a cat" (0, 1, 2, 3, 4) 五个单词的表示向量,Mask 是一个 5×5 的矩阵。在 Mask 可以发现单词 0 只能使用单词 0 的信息,而单词 1 可以使用单词 0, 1 的信息,即只能使用之前的信息。
第二步:接下来的操作和之前的 Self-Attention 一样,通过输入矩阵X计算得到Q,K,V矩阵。然后计算Q和 KT 的乘积 QKT 。
第三步:在得到 QKT 之后需要进行 Softmax,计算 attention score,我们在 Softmax 之前需要使用Mask矩阵遮挡住每一个单词之后的信息,遮挡操作如下:
得到 Mask QKT 之后在 Mask QKT上进行 Softmax,每一行的和都为 1。但是单词 0 在单词 1, 2, 3, 4 上的 attention score 都为 0。
第四步:使用 Mask QKT与矩阵 V相乘,得到输出 Z,则单词 1 的输出向量 Z1 是只包含单词 1 信息的。
第五步:通过上述步骤就可以得到一个 Mask Self-Attention 的输出矩阵 Zi ,然后和 Encoder 类似,通过 Multi-Head Attention 拼接多个输出Zi 然后计算得到第一个 Multi-Head Attention 的输出Z,Z与输入X维度一样。