本文为转载,原文链接 Transformer详解
Transformer 是谷歌大脑在 2017 年底发表的论文 attention is all you need 中所提出的 seq2seq 模型。现在已经取得了大范围的应用和扩展,而 BERT 就是从 Transformer 中衍生出来的预训练语言模型
这篇文章分为以下几个部分
- Transformer 直观认识
- Positional Encoding
- Self Attention Mechanism
- 残差连接和 Layer Normalization
- Transformer Encoder 整体结构
- Transformer Decoder 整体结构
- 总结
- 参考文章
0. Transformer直观认识
Transformer 和 LSTM 的最大区别,就是 LSTM 的训练是迭代的、串行的,必须要等当前字处理完,才可以处理下一个字。而 Transformer 的训练时并行的,即所有字是同时训练的,这样就大大增加了计算效率。Transformer 使用了位置嵌入 (Positional Encoding) 来理解语言的顺序,使用自注意力机制(Self Attention Mechanism)和全连接层进行计算,这些后面会讲到
Transformer 模型主要分为两大部分,分别是 Encoder 和 Decoder。Encoder 负责把输入(语言序列)隐射成隐藏层(下图中第 2 步用九宫格代表的部分),然后解码器再把隐藏层映射为自然语言序列。例如下图机器翻译的例子(Decoder 输出的时候,是通过 N 层 Decoder Layer 才输出一个 token,并不是通过一层 Decoder Layer 就输出一个 token)
本篇文章大部分内容在于解释 Encoder 部分,即把自然语言序列映射为隐藏层的数学表达的过程。理解了 Encoder 的结构,再理解 Decoder 就很简单了
上图为 Transformer Encoder Block 结构图,注意:下面的内容标题编号分别对应着图中 1,2,3,4 个方框的序号
1. Positional Encoding
由于 Transformer 模型没有循环神经网络的迭代操作,所以我们必须提供每个字的位置信息给 Transformer,这样它才能识别出语言中的顺序关系
现在定义一个位置嵌入的概念,也就是 Positional Encoding,位置嵌入的维度为 [max_sequence_length, embedding_dimension], 位置嵌入的维度与词向量的维度是相同的,都是 embedding_dimension。max_sequence_length 属于超参数,指的是限定每个句子最长由多少个词构成
注意,我们一般以字为单位训练 Transformer 模型。首先初始化字编码的大小为 [vocab_size, embedding_dimension],vocab_size 为字库中所有字的数量,embedding_dimension 为字向量的维度,对应到 PyTorch 中,其实就是 nn.Embedding(vocab_size, embedding_dimension)
论文中使用了 sin 和 cos 函数的线性变换来提供给模型位置信息:
关于位置编码:让研究人员绞尽脑汁的Transformer位置编码 - PaperWeekly的文章 - 知乎
https://zhuanlan.zhihu.com/p/352898810
这样位置编码的解释:随着维度序号变大,周期变化越来越慢,最终产生一种包含位置信息的纹理。
如果不理解这里为何这么设计,可以看这篇文章 Transformer 中的 Positional Encoding
下图画一下位置嵌入,纵向观察,可见随着embedding dimension序号增大,位置嵌入函数的周期变化越来越平缓
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import math
def get_positional_encoding(max_seq_len, embed_dim):
# 初始化一个positional encoding
# embed_dim: 字嵌入的维度
# max_seq_len: 最大的序列长度
positional_encoding = np.array([
[pos / np.power(10000, 2 * i / embed_dim) for i in range(embed_dim)]
if pos != 0 else np.zeros(embed_dim) for pos in range(max_seq_len)])
positional_encoding[1:, 0::2] = np.sin(positional_encoding[1:, 0::2]) # dim 2i 偶数
positional_encoding[1:, 1::2] = np.cos(positional_encoding[1:, 1::2]) # dim 2i+1 奇数
return positional_encoding
positional_encoding = get_positional_encoding(max_seq_len=100, embed_dim=16)
plt.figure(figsize=(10,10))
sns.heatmap(positional_encoding)
plt.title("Sinusoidal Function")
plt.xlabel("hidden dimension")
plt.ylabel("sequence length")
plt.figure(figsize=(8, 5))
plt.plot(positional_encoding[1:, 1], label="dimension 1")
plt.plot(positional_encoding[1:, 2], label="dimension 2")
plt.plot(positional_encoding[1:, 3], label="dimension 3")
plt.legend()
plt.xlabel("Sequence length")
plt.ylabel("Period of Positional Encoding")
2. Self Attention Mechanism
对于输入的句子,通过WordEmbedding得到该句子中每个字的字向量,同时通过Positional Encoding 得到所有字的位置向量,将其相加(维度相同,可以直接相加),得到该字真正的向量表示。第个字的向量记作
接着我们定义三个矩阵,使用这三个矩阵分别对所有的字向量进行三次线性变换,于是所有的字向量又衍生出三个新的向量 。我们将所有的向量拼成一个大矩阵,记作查询矩阵,将所有的向量拼成一个大矩阵,记作键矩阵 ,将所有的向量拼成一个大矩阵,记作值矩阵 (见下图)
为了获得第一个字的注意力权重,我们需要用第一个字的查询向量乘以键矩阵 K(见下图)
[0, 4, 2]
[1, 0, 2] x [1, 4, 3] = [2, 4, 4]
[1, 0, 1]
之后还需要将得到的值经过softmax,使它们的和为1(见下图)
softmax([2, 4, 4]) = [0.0, 0.5, 0.5]
有了权重之后,将权重其分别乘以对应字的值向量(见下图)
0.0 * [1, 2, 3] = [0.0, 0.0, 0.0]
0.5 * [2, 8, 0] = [1.0, 4.0, 0.0]
0.5 * [2, 6, 3] = [1.0, 3.0, 1.5]
最后将这些权重化后的值向量求和,得到第一个字的输出(见下图)
[0.0, 0.0, 0.0]
+ [1.0, 4.0, 0.0]
+ [1.0, 3.0, 1.5]
-----------------
= [2.0, 7.0, 1.5]
对其它的输入向量也执行相同的操作,即可得到通过self-attention后的所有输出
矩阵计算
上面介绍的方法需要一个循环遍历所有的字,我们可以把上面的向量计算变成矩阵的形式,从而一次性计算出所有时刻的输出。
接下来将相乘,然后除以(这是论文中提到的一个trick),经过softmax以后再乘以V得到输出。
为什么transformer中的attention需要scaled?
论文中解释是:向量的点积结果会很大,将softmax函数push到梯度很小的区域,scaled会缓解这种现象。怎么理解将sotfmax函数push到梯度很小区域?还有为什么scaled是维度的根号,不是其他的数?
transformer中的attention为什么scaled? - LinT的回答 - 知乎
https://www.zhihu.com/question/339723385/answer/782509914
Multi-Head Attention
这篇论文还提出了Multi-Head Attention的概念。其实很简单,前面定义的一组可以让一个词attend to 相关的词,我们可以定义多组,让它们分别关注不同的上下文。
在self-attention需要强调的最后一点是其采用了残差网络 [5]中的short-cut结构,目的当然是解决深度学习中的退化问题,得到的最终结果如下图。
Encoder-Decoder Attention
在解码器中,Transformer block比编码器中多了个encoder-decoder attention。Q来自于解码器的上一个输出。K和V则来自于编码器的输出。
Padding Mask
上述Self Attention的计算过程中,通常使用mini-batch来计算,也就是一次计算多句话。即X的维度是[batch_size, sequence_length],但是一个mini-batch是由多个不等长的句子组成的,我们需要按照这个 mini-batch 中最大的句长对剩余的句子进行补齐,一般用 0 进行填充,这个过程叫做 padding。
需要给无效区域加一个很大的负数偏置
3. 残差链接和Layer Normalization
残差连接
我们在上一步得到了经过 self-attention 加权之后输出,也就是,然后把他们加起来做残差连接
Layer Normalization
Layer Normalization 的作用是把神经网络中隐藏层归一为标准正态分布,也就是 独立同分布,以起到加快训练速度,加速收敛的作用
然后用每一列的每一个元素减去这列的均值,再除以这列的标准差,从而得到归一化后的数值,加 是为了防止分母为 0
4. Transformer Encoder 整体结构
经过上面 3 个步骤,我们已经基本了解了 Encoder 的主要构成部分,下面我们用公式把一个 Encoder block 的计算过程整理一下:
1). 字向量与位置编码
2). 自注意力机制
3). self-attention 残差连接与Layer Normalization
4). 下面进行Encoder block结构图中的第4部分,也就是FeedForward,其实就是两层线性映射并用激活函数激活,比如说 ReLU
5). FeedForward 残差连接与 Layer Normalization
5. Transformer Decoder 整体结构
我们先从 HighLevel 的角度观察一下 Decoder 结构,从下到上依次是:
- Masked Multi-Head Self-Attention
- Multi-Head Encoder-Decoder Attention
- FeedForward Network
和 Encoder 一样,上面三个部分的每一个部分,都有一个残差连接,后接一个 Layer Normalization。Decoder 的中间部件并不复杂,大部分在前面 Encoder 里我们已经介绍过了,但是 Decoder 由于其特殊的功能,因此在训练时会涉及到一些细节。
Masked Self-Attention
具体来说,传统 Seq2Seq 中 Decoder 使用的是 RNN 模型,因此在训练过程中输入时刻的词,模型无论如何也看不到未来时刻的词,因为循环神经网络是时间驱动的,只有当时刻运算结束了,才能看到 时刻的词。而 Transformer Decoder 抛弃了 RNN,改为 Self-Attention,由此就产生了一个问题,在训练过程中,整个 ground truth 都暴露在 Decoder 中,这显然是不对的,我们需要对Decoder 的输入进行一些处理,该处理被称为 Mask
举个例子,Decoder 的 ground truth 为 "
之后再做softmax,就能将-inf变为0,得到的这个矩阵即为每个字之间的权重
Masked Encoder-Decoder Attention
其实这一部分的计算流程和前面 Masked Self-Attention 很相似,结构也一摸一样,唯一不同的是这里的 为 Encoder 的输出, 为Decoder 中 Masked Self-Attention 的输出
6. 总结
到此为止,Transformer 中 95% 的内容已经介绍完了,我们用一张图展示其完整结构。不得不说,Transformer 设计的十分巧夺天工
下面有几个问题,是我从网上找的,感觉看完之后能对 Transformer 有一个更深的理解
Transformer 为什么需要进行 Multi-head Attention?
原论文中说到进行 Multi-head Attention 的原因是将模型分为多个头,形成多个子空间,可以让模型去关注不同方面的信息,最后再将各个方面的信息综合起来。其实直观上也可以想到,如果自己设计这样的一个模型,必然也不会只做一次 attention,多次 attention 综合的结果至少能够起到增强模型的作用,也可以类比 CNN 中同时使用多个卷积核的作用,直观上讲,多头的注意力有助于网络捕捉到更丰富的特征 / 信息
Transformer 相比于 RNN/LSTM,有什么优势?为什么?
- RNN 系列的模型,无法并行计算,因为 T 时刻的计算依赖 T-1 时刻的隐层计算结果,而 T-1 时刻的计算依赖 T-2 时刻的隐层计算结果
- Transformer 的特征抽取能力比 RNN 系列的模型要好
为什么说 Transformer 可以代替 seq2seq?
这里用代替这个词略显不妥当,seq2seq 虽已老,但始终还是有其用武之地,seq2seq 最大的问题在于将 Encoder 端的所有信息压缩到一个固定长度的向量中,并将其作为 Decoder 端首个隐藏状态的输入,来预测 Decoder 端第一个单词 (token) 的隐藏状态。在输入序列比较长的时候,这样做显然会损失 Encoder 端的很多信息,而且这样一股脑的把该固定向量送入 Decoder 端,Decoder 端不能够关注到其想要关注的信息。Transformer 不但对 seq2seq 模型这两点缺点有了实质性的改进 (多头交互式 attention 模块),而且还引入了 self-attention 模块,让源序列和目标序列首先 “自关联” 起来,这样的话,源序列和目标序列自身的 embedding 表示所蕴含的信息更加丰富,而且后续的 FFN 层也增强了模型的表达能力,并且 Transformer 并行计算的能力远远超过了 seq2seq 系列模型
7. 参考文章
- Transformer
- The Illustrated Transformer
- TRANSFORMERS FROM SCRATCH
- Seq2seq pay Attention to Self Attention: Part 2
- Transformer 模型相关疑问以及解答