Transformer


参考资料:

https://zhouyifan.net/2022/11/12/20220925-Transformer/

https://zhuanlan.zhihu.com/p/505105707

Transformer的诞生

总结:机器翻译领域,过去都是用RNN或基于RNN的架构来处理。但是RNN本轮的输入状态取决于上一轮的输出状态,这使RNN的计算必须串行执行。因此,RNN的训练通常比较缓慢。

RNN的发展历史

RNN的循环机制使得上一时间步产生的结果, 能够作为当下时间步输入的一部分,能够很好利用序列之间的关系, 因此针对自然界具有连续性的输入序列, 如人类的语言, 语音等进行很好的处理, 广泛应用于NLP领域的各项任务, 如文本分类, 情感分析, 意图识别, 机器翻译等。

传统RNN的缺陷

这种简单的RNN架构仅适用于输入和输出等长的任务。然而,大多数情况下,机器翻译的输出和输入都不是等长的。因此,人们使用了一种新的架构。前半部分的RNN只有输入,后半部分的RNN只有输出(上一轮的输出会当作下一轮的输入以补充信息)。

传统encoder-decoder的缺陷

但是这种架构存在不足:编码器和解码器之间只通过一个隐状态来传递信息。在处理较长的文章时,这种架构的表现不够理想。
为此,有人提出了基于注意力的架构。这种架构依然使用了编码器和解码器,只不过解码器的输入是编码器的状态的加权和,而不再是一个简单的中间状态。每一个输出对每一个输入的权重叫做注意力,注意力的大小取决于输出和输入的相关关系。这种架构优化了编码器和解码器之间的信息交流方式,在处理长文章时更加有效。

即便如此,因为依赖上一轮的输出状态,还是只能串行,所以RNN的训练通常比较慢。

Transformer的设计动机

  1. 提升训练的并行度
  2. 规避RNN的使用,完全使用注意力机制来捕捉序列之间的依赖关系

注意力机制

注意力计算的例子

“注意力“这个名字取得非常不易于理解。这个机制应该叫做“全局信息查询”。

做一次“注意力”计算,其实就跟去数据库了做了一次查询一样。假设,我们现在有这样一个以人名为key(键),以年龄为value(值)的数据库:

{
    张三: 18,
    张三: 20,
    李四: 22,
    张伟: 19
}

现在,我们有一个query(查询),问所有叫“张三”的人的年龄平均值是多少。让我们写程序的话,我们会把字符串“张三”和所有key做比较,找出所有“张三”的value,把这些年龄值相加,取一个平均数。这个平均数是(18+20)/2=19。

但是,很多时候,我们的查询并不是那么明确。比如,我们可能想查询一下所有姓张的人的年龄平均值。这次,我们不是去比较key == 张三,而是比较key[0] == 张。这个平均数应该是(18+20+19)/3=19。

或许,我们的查询会更模糊一点,模糊到无法用简单的判断语句来完成。因此,最通用的方法是,把query和key各建模成一个向量。之后,对query和key之间算一个相似度(比如向量内积),以这个相似度为权重,算value的加权和。这样,不管多么抽象的查询,我们都可以把query, key建模成向量,用向量相似度代替查询的判断语句,用加权和代替直接取值再求平均值。“注意力”,其实指的就是这里的权重。

把这种新方法套入刚刚那个例子里。我们先把所有key建模成向量,可能可以得到这样的一个新数据库:

{
    [1, 2, 0]: 18, # 张三
    [1, 2, 0]: 20, # 张三 
    [0, 0, 2]: 22, # 李四
    [1, 4, 0]: 19 # 张伟 
}

假设key[0]==1表示姓张。我们的查询“所有姓张的人的年龄平均值”就可以表示成向量[1, 0, 0]。用这个query和所有key算出的权重是:

dot([1, 0, 0], [1, 2, 0]) = 1
dot([1, 0, 0], [1, 2, 0]) = 1
dot([1, 0, 0], [0, 0, 2]) = 0
dot([1, 0, 0], [1, 4, 0]) = 1

之后,我们该用这些权重算平均值了。注意,算平均值时,权重的和应该是1。因此,我们可以用softmax把这些权重归一化一下,再算value的加权和。

softmax([1, 1, 0, 1]) = [1/3, 1/3, 0, 1/3]
dot([1/3, 1/3, 0, 1/3], [18, 20, 22, 19]) = 19

这样,我们就用向量运算代替了判断语句,完成了数据库的全局信息查询。那三个1/3,就是query对每个key的注意力。

Scaled Dot-Product Attention

Transformer里的注意力就叫Scaled Dot-Product Attention

我们刚刚完成的计算差不多就是Transformer里的注意力,这种计算在论文里叫做放缩点乘注意力(Scaled Dot-Product Attention)。它的公式是:

K就是key向量的数组,也就是:

K = [[1, 2, 0], [1, 2, 0], [0, 0, 2], [1, 4, 0]] 

V是value向量数组

刚才的例子只做了一次查询,操作写为:

其中,query q就是[1, 0, 0]了。

实际上,我们可以一次做多组query。把所有
打包成矩阵Q,就得到了公式

就是query和key向量的长度。由于query和key要做点乘,这两种向量的长度必须一致。value向量的长度倒是可以不一致,
论文里把value向量的长度叫做

为什么要用一个和成比例的项来放缩呢?这是因为,softmax在绝对值较大的区域梯度较小,梯度下降的速度比较慢。因此,我们要让被softmax的点乘数值尽可能小。而一般在较大时,也就是向量较长时,点乘的数值会比较大。除以一个和相关的量能够防止点乘的值过大。

计算相似度不止点乘这一个方法,另一种常用的注意力函数叫做加性注意力,它用一个单层神经网络来计算两个向量的相似度。相比之下,点乘注意力算起来快一些。出于性能上的考量,论文使用了点乘注意力。

自注意力机制

自注意力也是一种注意力机制,只不过它计算序列中某个词和其他所有词的权重。
这个查询query,不是来自外部(数据库的例子),而是从序列本身出发,所以叫self。

如何产生 b1 这个向量?需要衡量输入向量之间的关联度 α。

计算 attention 的模组算出α(关联度大小),有很多方法,比如 Dot-product 方法和 Additive 方法。

下文主要是讲 Dot-product 方法,这是最常用的方法,也是 Transformer 中用的方法。

都是权重矩阵

这个过程要重复进行,在实际中,每个向量向量和自己、和其他向量都要做一次。

得出所有的之后,这些数需要通过激活函数比如 Soft-max 函数,输出激活后的 α’,也就是我们需要的注意力权重。这里的激活函数使用的是 Soft-max,目的是做 Normalization。至于为什么使用 soft-max 并没有定论,也可以使用别的激活函数,比如 ReLU。

最后还需要一个v来抽取信息。

上面计算b的过程,可以并行计算。

多头注意力机制

一种 relevance 还不够,需要多种 relevance。也即是需要多个 q,k,v

其中h是”头“数,是另一个参数矩阵

Transformer 模型架构

残差连接

Transformer使用了和ResNet类似的残差连接,即设模块本身的映射为,则模块输出为
。和ResNet不同,Transformer使用的归一化方法是LayerNorm。
另外要注意的是,残差连接有一个要求:输入x和输出F(x)+x的维度必须等长。在Transformer中,包括所有词嵌入在内的向量长度都是

前馈网络

架构图中的前馈网络(Feed Forward)其实就是一个全连接网络。具体来说,这个子网络由两个线性层组成,中间用ReLU作为激活函数。

整体架构与掩码多头注意力

Decoder部分的Outputs例子

Decoder部分还有个输入是Outputs,要分为训练和推理阶段来理解。

1. 训练阶段

任务目标
将“I love you” 翻译为中文 “我爱你”

输入与输出

原始目标序列(Ground Truth)

[, 我, 爱, 你, ]

Decoder输入(右移后)​:

[, 我, 爱, 你]

Decoder输出(预测目标)​:

[我, 爱, 你, ]

处理过程

​输入右移:将目标序列的起始符 放在最前面,并移除最后一个词 ,得到右移后的输入 [, 我, 爱, 你]。

​掩码机制:Decoder的自注意力层通过掩码矩阵(Mask)遮盖未来词,确保预测第 t 个词时只能看到前 t-1 个词。

​并行计算:所有位置的词同时处理,但掩码确保模型无法“偷看”未来的词。

意义

​对齐训练与推理:模拟推理时的逐步生成过程(如生成“爱”时只能看到“, 我”)。

​防止信息泄漏:如果直接输入原始目标序列 [, 我, 爱, 你, ],模型可能直接复制未来词,而非学习生成逻辑。

2. 推理阶段

输入英文 ​​“I love you”​,逐步生成中文翻译。

步骤 Decoder输入 预测输出 已生成序列
1 [\] [我]
2 [\, 我] [我, 爱]
3 [\, 我, 爱] [我, 爱, 你]
4 [\, 我, 爱, 你] \ [我, 爱, 你, \]

如果不右移会发生什么

​训练阶段:
输入原始目标序列 [\, 我, 爱, 你, \],模型在预测“爱”时能看到后面的“你”和 \
结果:模型可能直接复制未来词,而非学习“爱”对应“love”的映射,导致训练无效。

推理阶段:

无影响,因为模型只能依赖已生成的词。

技术细节

​起始符 \:触发生成的信号,类似于“开始生成”的指令。
​终止符 \:标记序列结束,模型需学习何时停止生成。

掩码层

掩码机制实现了两个关键目标:

  1. 防止模型在训练时“偷看”未来答案​(避免信息泄漏)
  2. 允许整个序列的并行计算​(提升训练效率)

掩码注意力机制的数学公式:

掩码矩阵示例(长度为4):

未来位置设置为负无穷(softmax后权重为0),当前位置为0。

这样通过矩阵运行一次性处理整个序列,提升训练效率。

执行流程

输入序列会经过个结构相同的层。每层由多个子层组成。第一个子层是多头注意力层,准确来说,是多头自注意力。这一层可以为每一个输入单词提取出更有意义的表示。之后数据会经过前馈网络子层。最终,输出编码结果

得到了后,要用解码器输出结果了。解码器的输入是当前已经生成的序列,该序列会经过一个掩码(masked)多头自注意力子层。我们先不管这个掩码是什么意思,暂且把它当成普通的多头自注意力层。它的作用和编码器中的一样,用于提取出更有意义的表示。

接下来,数据还会经过一个多头注意力层。这个层比较特别,它的K,V来自,Q来自上一层的输出。为什么会有这样的设计呢?这种设计来自于早期的注意力模型。如下图所示,在早期的注意力模型中,每一个输出单词都会与每一个输入单词求一个注意力,以找到每一个输出单词最相关的某几个输入单词。用注意力公式来表达的话,Q就是输出单词,K, V就是输入单词。

K,V的值是让和K,V各自的权重矩阵相乘后再使用,不是直接使用

嵌入层

和其他大多数序列转换任务一样,Transformer主干结构的输入输出都是词嵌入序列。词嵌入,其实就是一个把one-hot向量转换成有意义的向量的转换矩阵。在Transformer中,解码器的嵌入层和输出线性层是共享权重的——输出线性层表示的线性变换是嵌入层的逆变换,其目的是把网络输出的嵌入再转换回one-hot向量。如果某任务的输入和输出是同一种语言,那么编码器的嵌入层和解码器的嵌入层也可以共享权重。

“论文中写道:“输入输出的嵌入层和softmax前的线性层共享权重”。这个描述不够清楚。如果输入和输出的不是同一种语言,比如输入中文输出英文,那么共享一个词嵌入是没有意义的。”

上面这段话的意思是:​“输入输出的嵌入层和softmax前的线性层共享权重”​​ 这一设计在单语言任务​(如文本生成、摘要)中有效,但在跨语言任务​(如中译英)中确实不适用。

​1. 共享权重的设计背景
​适用场景:当输入和输出使用同一种语言的词汇表时(如英文到英文的文本生成),共享权重是合理的。
​具体实现:
​输入嵌入层(Input Embedding)​:将输入词(如英文单词)映射为向量。
​输出嵌入层(Output Embedding)​:在生成时,将目标词(同样是英文单词)映射为向量。
​Softmax前线性层:将解码器的隐藏状态映射到词汇表空间,生成词的概率分布。
​共享权重:输入嵌入矩阵(Embedding Matrix)与Softmax前线性层的权重矩阵(Projection Matrix)共享。

​2. 跨语言任务的问题
​词汇表差异:
输入语言(如中文)和输出语言(如英文)的词汇表完全不同,共享权重会导致以下问题:
​维度不匹配:中文词汇表大小(如50,000词)与英文词汇表大小(如30,000词)不同,矩阵维度无法对齐。
​语义不匹配:中文词“苹果”与英文词“apple”虽语义相同,但共享同一嵌入向量会混淆语言特性。
​具体矛盾示例:
中文输入词“猫”的嵌入向量被强制映射到英文词“cat”的概率分布,但两者的词汇ID完全不同,导致模型无法学习有效映射。

由于模型要预测一个单词,输出的线性层后面还有一个常规的softmax操作。

位置编码

现在,Transformer的结构图还剩一个模块没有读——位置编码。无论是RNN还是CNN,都能自然地利用到序列的先后顺序这一信息。然而,Transformer的主干网络并不能利用到序列顺序信息,它本身不知道词的顺序。所以必须额外告诉模型每个词的位置,否则它会把“猫→垫子”和“垫子→猫”当成一样的情况(想象你在读一句话:“猫坐在垫子上”。如果把这句话的词打乱成“垫子坐在猫上”,意思就完全反了。)。。因此,Transformer使用了一种叫做“位置编码”的机制,对编码器和解码器的嵌入输入做了一些修改,以向模型提供序列顺序信息。

具体的做法就是给每个词的位置添加一组数字,使用一组正弦和余弦函数来生成位置编码。通过正弦和余弦的交替,模型可以学习到“相对位置”关系。有的维度关注短距离位置(高频变化),有的维度关注长距离位置(低频变化)。

如果使用1,2,3会导致长序列数值过大(如第1000个词的位置编码是1000),影响数值稳定性,从而影响模型训练。

为什么要用自注意力

自注意力层是一种和循环层和卷积层等效的计算单元。它们的目的都是把一个向量序列映射成另一个向量序列,比如说编码器把x映射成中间表示z
。论文比较了三个指标:每一层的计算复杂度、串行操作的复杂度、最大路径长度。

前两个指标很容易懂,第三个指标最大路径长度需要解释一下。最大路径长度表示数据从某个位置传递到另一个位置的最大长度。比如对边长为n的图像做普通卷积操作,卷积核大小3x3,要做次卷积才能把信息从左上角的像素传播到右下角的像素。设卷积核边长为k,则最大路径长度。如果是空洞卷积的话,像素第一次卷积的感受野是3x3,第二次是5x5,第三次是9x9,以此类推,感受野会指数级增长。这种卷积的最大路径长度是

  1. 可以并行
  2. 计算复杂度会降低(这里指改进后的,让每个元素查询最近的r个元素)
  3. 最大路径长度降低,因为是全局查询,可以在

Q,K,V相关问题

什么是Q,K,V

输入数据(原始输入数据经过词嵌入和嵌入位置编码后)经过三种不同功能的线性变化后得到的矩阵。

Q:当前字词计算与其他字词相关性的矩阵

K:其他字词与Q计算相关性的矩阵

V:对Q-K相关性进行加权求和的矩阵

为什么需要Q,K,V三个不同的矩阵,而不是一个或两个

三个矩阵能保证模型表达能力最大化的同时,不会进一步增大计算量。

如果使用两个矩阵

我们可以理解为任意两个矩阵相同。

若Q和K共享矩阵(Q=K),则查询和匹配依据被强行绑定,模型无法区分“需要找什么”和“能被找到的特征”,导致注意力(注意力就是权重)计算失效

若K和V共享矩阵(K=V),则匹配依据与实际内容被强制一致,模型无法区分“哪些特征用于匹配”和“哪些信息需要传递”。

若Q和V共享矩阵(Q=V),模型无法区分“需要关注什么”(Q)和“实际传递什么”(V)。(从公式角度来说,Q既用于计算权重,又作为被加权的值,导致信息重复压缩。这个和K=V是一样的)

从数学的角度来说,Q,K,V经过线性变化讲输入映射到不同空间,合并矩阵会导致投影空间被压缩,限制模型的表达能力。

如果使用一个矩阵

如果只使用一个矩阵,那么模型退化为普通的前馈网格,无法捕捉长距离依赖和上下文关系。


文章作者: Jason Lin
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 reprint policy. If reproduced, please indicate source Jason Lin !
  目录