Transformer简介
Transformer模型的核心是“self-attention”或“scaled dot-product attention”机制,这是一种能够处理输入序列中的每个元素,并能计算其与序列中其他元素的交互关系的方法。这使得模型能够更好地理解序列中的上下文关系,例如在语句中一个词语的意思可能会受到其它词语的影响。
在具体实现上,Transformer模型由编码器(Encoder)和解码器(Decoder)组成。编码器部分接受一段输入序列,进行一系列的自注意力和全连接网络操作,并输出一段连续的表示。解码器部分接受编码器的输出以及之前解码器的输出,生成新的输出。
Transformer的主要优点包括:
- 并行计算:由于自注意力机制,Transformer模型可以同时处理整个序列,而不是像RNN那样需要逐个处理序列中的元素。这使得模型能够更好地利用现代硬件的并行计算能力。
- 更好的长距离依赖关系捕捉:在许多序列处理任务中,元素间的依赖关系可能跨越很长的距离。例如,在处理语言时,一个词的含义可能会受到很远处的其他词的影响。由于Transformer模型可以直接处理这些远距离的依赖关系,因此它在这类任务中通常可以获得更好的性能。
Transformer模型的主要缺点是其计算和内存需求随序列长度的增加而显著增加。为了解决这个问题,研究人员已经提出了一些改进的模型,例如Reformer,Longformer等。
Seq2Seq模型
Transformer是一个序列到序列(Sequence-to-Sequence,Seq2Seq)的模型,序列到序列模型输入和输出都是一个序列,输入与输出序列长度之间的关系有两种情况,第一种情况是输入和输出的长度一样,第二种情况是机器决定输出的长度。
Seq2Seq模型的应用
语音识别、机器翻译和语音翻译
语音识别:输入是声音信号,输出是语音识别的结果。输入跟输出的长度有一些关系,但没有绝对的关系,输入的声音信号的长度是 T,并无法根据 T 得到输出的长度 N。其实可以由机器自己决定输出的长度,由机器去听这段声音信号的内容,决定输出的语音识别结果。
机器翻译:机器输入一个语言的句子,输出另一个语言的句子。输入长度和输出长度的关系也是由机器来决定。
语音翻译:对机器说一句话,机器直接把语音转换为另一种语言的文字输出。
聊天机器人
聊天机器人是我们对机器人说一句话,机器人要给出一个回应,输入和输出都是文字序列。
问答任务
问答任务也是输入一个序列,输出一个序列,下面是一些例子:
-
翻译。机器读的文章是一个英语句子,问题是这个句子的德文翻译是什么?输出的答案就是德文。
-
自动做摘要:给机器读一篇长的文章,让它把长的文章的重点找出来,即给机器一段文字,问题是这段文字的摘要是什么。
-
情感分析(sentiment analysis)︰机器要自动判断一个句子是正面的还是负面的。如果把情感分析看成是问答的问题,问题是给定句子是正面还是负面的,希望机器给出答案。
多标签分类
多标签分类 (multi-label classification)问题不能直接把它当作一个多分类问题的问题来解。比如把这些文章丢到一个分类器(classifier)里面,本来分类器只会输出分数最高的答案,如果直接取一个阈值(threshold),只输出分数最高的前三名。这种方法是不可行的,因为每篇文章对应的类别的数量根本不一样,因此需要用序列到序列模型来做。
Transformer结构
一般的序列到序列模型会分成编码器和解码器,编码器负责处理输入的序列,再把处理好的结果给解码器,由解码器决定要输出的序列。
Transformer的结构如下图所示:
位置编码
自注意力层少了一个也许很重要的信息,即位置的信息。对一个自注意力层而言,每一个输入是出现在序列的最前面还是最后面,它是完全没有这个信息的。对自注意力而言,位置 1、位置 2、位置 3 跟位置 4 没有任何差别,这四个位置的操作是一模一样的。
而有时候位置的信息很重要。举个例子,在做词性标注的时候,我们知道动词比较不容易出现在句首,如果某一个词汇它是放在句首的,它是动词的可能性就比较低,位置的信息往往也是有用的。可是到目前为止,自注意力的操作里面没有位置的信息。因此做自注意力的时候,如果我们觉得位置的信息很重要,需要考虑位置信息时,就要用到位置编码(positional encoding)。
位置编码为每一个位置设定一个向量,即位置向量(positional vector)。位置向量用
e
i
e^i
ei来表示,上标
i
i
i代表位置,不同的位置就有不同的向量,不同的位置都有一个专属的
e
e
e,把
e
e
e加到
a
i
a^i
ai上面就结束了。这相当于告诉自注意力位置的信息,如果看到
a
i
a^i
ai被加上
e
i
e^i
ei,它就知道现在出现的位置应该是在
i
i
i这个位置。
P
E
(
p
o
s
,
2
i
)
=
sin
(
pos
/
1000
0
2
i
/
d
model
)
P
E
(
p
o
s
,
2
i
+
1
)
=
cos
(
pos
/
1000
0
2
i
/
d
model
)
\begin{aligned} & P E(p o s, 2 i)=\sin \left(\text { pos } / 10000^{2 i / d_{\text {model }}}\right) \\ & P E(p o s, 2 i+1)=\cos \left(\text { pos } / 10000^{2 i / d_{\text {model }}}\right) \end{aligned}
PE(pos,2i)=sin( pos /100002i/dmodel )PE(pos,2i+1)=cos( pos /100002i/dmodel )
其中pos是位置,i为维度,表示位置编码的每一维对应一个正弦曲线,波长形成从2π到10000*2π的几何轨迹。之所以选择这个函数,是因为假设它可以让模型很容易地通过相对位置进行学习,因为对于任何固定的偏移量k,
P
E
p
o
s
+
k
PE_{pos+k}
PEpos+k可以被表示成
P
E
p
o
s
PE_{pos}
PEpos的线性函数。
下图所示的是位置编码,其中每一列代表一个位置向量 e i e^i ei
多头自注意力
自注意力
Self-attention机制的目的是帮助模型理解输入序列中的不同元素之间的关系。与传统的注意力机制不同,传统的注意力机制只关注输入序列中的某一个元素与其他元素的关系,而Self-attention允许模型在输入序列中的任意两个位置之间建立关联。
下面是Self-attention的工作流程:
- 输入序列的每个词语都会经过三个线性变换,分别是Query、Key和Value。这三个线性变换是为了将每个词语映射到一个高维空间,以便后续计算注意力权重。
- 对于每个词语的Query和所有词语的Key,通过计算它们之间的点积,再经过一个softmax操作得到注意力权重,表示该词语与其他词语的关联度。这里的点积操作可以看作一种相似度度量,用来衡量不同词语之间的相似程度。
- 注意力权重与每个词语的Value相乘,并对所有Value进行加权求和,得到该词语的上下文表示。这个上下文表示会包含输入序列中其他词语的信息,根据注意力权重的大小不同,模型可以更加关注与当前词语相关的信息。
- 对于输入序列的每个词语,重复步骤2和步骤3,得到最终的上下文表示。
Self-attention的结构:
从矩阵乘法来理解注意力:
Self-attention的优点是可以同时考虑到输入序列中的所有元素,而且计算过程可以并行化,加速了模型的训练和推理过程。此外,Self-attention还能够捕捉输入序列中不同元素之间的长距离依赖关系,这对于一些涉及到上下文理解的任务非常重要。
多头自注意力
在使用自注意力计算相关性的时候,就是用 q 去找相关的k。但是相关有很多种不同的形式,所以也许可以有多个 q,不同的 q 负责不同种类的相关性,这就是多头注意力。
具体实现上就是将原来的q,k,v再进行线性变换,得到多个q,k,v,然后将对应类别的q,k,v进行相乘。
类似地还可以得到 b i , 2 , b i , 3 b^{i,2},b^{i,3} bi,2,bi,3,然后将相同token的不同注意力头计算出来的结果进行拼接,然后通过矩阵乘法得到 b i b^i bi, b i b^i bi和 a i a^i ai的长度相同,然后再送到下一层。
残差连接
在每个层的编码器和解码器中,残差连接是通过将前一层的输入直接与当前层的输出相加的方式实现的。具体而言,假设一个层的输入为x,经过该层的操作得到输出为y,则残差连接的表示为y + x。这样,输入信息就能够直接传递到后续层,而不会受到层间操作的干扰。
残差连接的作用有以下几个方面:
- 信息传递:残差连接允许输入信息直接传递到后续层,使得模型能够更好地利用输入信息,特别是在深层网络中。这有助于减少信息的损失和变形,从而提高模型的表达能力。
- 缓解梯度消失问题:在深层网络中,由于多次连续的矩阵乘法和非线性变换,梯度可能会逐渐缩小并导致梯度消失问题。残差连接可以将输入的梯度直接传递到后续层,从而缓解梯度消失问题,使得模型更容易学习。
- 加速收敛:通过残差连接,模型可以更快地进行收敛,因为原始输入信息能够直接传递到较深的层,使得模型更容易学习到有效的特征表示。
总的来说,残差连接在Transformer模型中起到了两个重要作用:传递输入信息和梯度,缓解梯度消失问题。这使得模型能够更好地学习和理解输入序列的上下文关系,提高了模型的性能和效率。
层归一化
在传统的Batch Normalization(批归一化)中,对于每个批次的输入,归一化是在特征维度上进行的,即对于每个特征维度,对整个批次进行归一化操作。而在Layer Normalization中,归一化是在每个样本的所有特征维度上进行的,即对于每个样本,对所有特征维度进行归一化。
Layer Normalization的计算过程如下:
- 对于每个样本,计算其所有特征维度上的均值和标准差。这样,每个样本都有一个单独的均值和标准差。
- 对于每个特征维度,使用样本的均值和标准差对该维度上的数值进行归一化操作。即对于每个特征维度上的数值x,计算归一化后的数值为:(x - 均值) / 标准差。
- 可选地,可以应用缩放(scale)和平移(shift)操作,将归一化后的数值进行缩放和平移,以便更好地适应不同任务的需求。
Layer Normalization的优点包括:
- 不依赖于批次大小:与Batch Normalization不同,Layer Normalization在每个样本上进行归一化,因此不受批次大小的影响。
- 更适用于循环神经网络(RNN):由于Layer Normalization对每个样本的每个时间步进行归一化,因此在处理RNN等序列数据时更有效。
- 减少了梯度消失问题:Layer Normalization可以减少梯度消失问题,并有助于提高网络的训练效果和收敛速度。
总的来说,Layer Normalization是一种在神经网络中常用的归一化技术,通过对每个样本的所有特征维度进行归一化,可以提高网络的训练效果和收敛速度,尤其适用于处理序列数据和循环神经网络。
下面举一个具体的例子:
#x是一个包含两个样本的小批量数据,每个样本包含三个特征维度
x = [[1,2,3],
[4,5,6]]
#Layernorm
#1.计算每个样本的均值和标准差:
mean = np.mean(x, axis = 1, keepdims = True) #shape: (2, 1)
std = np.std(x, axis = 1, keepdims = True) #shape: (2, 1)
#2.对每个特征维度进行归一化
normalized_x = (x - mean) / std # shape: (2, 3)
#归一化后的数据为
normalized_x = [[-1.225, 0.0, 1.225],
[-1.225, 0.0, 1.]]
#Batchnorm
#1.计算每个特征维度上的均值和方差:
mean = np.mean(x, axis=0) # shape: (3,)
var = np.var(x, axis=0) # shape: (3,)
#2.对每个特征维度上的数据进行归一化:
normalized_x = (x - mean) / np.sqrt(var + epsilon) # shape: (2, 3)
#归一化后的数据为
normalized_x = [[-1.0, -1.0, -1.0],
[1.0, 1.0, 1.0]]
解码器
解码器的作用是产生输出,将编码器的输出读入,然后产生输出
同时解码器会将前一时刻自己的输出当作下一时刻的输入
掩蔽自注意力
原来的自注意力输入一排向量,输出另外一排向量,输出的每个向量都要看过完整的输入以后才做决定。根据 a 1 a^1 a1到 a 4 a^4 a4所有的信息去输出 b 1 b^1 b1。掩蔽自注意力的不同点是不能再看右边的部分,产生 b 1 b^1 b1的时候,只能考虑 a 1 a^1 a1的信息,不能再考虑 a 2 a^2 a2、 a 3 a^3 a3、 a 4 a^4 a4。产生 b 2 b^2 b2的时候,只能考虑 a 1 a^1 a1、 a 2 a^2 a2的信息,不能再考虑 a 3 a^3 a3、 a 4 a^4 a4的信息。产生 b 3 b^3 b3的时候,不能考虑 a 4 a^4 a4的信息。产生 b 4 b^4 b4的时候,可以用整个输入序列的信息。以 b 2 b^2 b2举例,其计算过程如下图所示。
编码器-解码器注意力
编码器和解码器通过编码器-解码器注意力(encoder-decoder attention)来传递信息,该注意力的key和value为编码器的输出,query来自解码器前一层的输出。
Transformer的变体网络
Transformer提出的一个关键概念是self-attention,但self-attention的运算量是很大的,所以针对这个问题,有许多基于Transformer的变体网络被提出
Local Attention/ Truncated Attention
对于每个key只与其邻近的query进行计算。
Stride Attention
每个key与间隔几步的query做运算
Global Attention
在一个sequence里选择special token,special token会和其余的token计算attention,普通的token之间不做attention运算。只有special-token掌握着全局的信息。
Longformer
Longformer就是融合了local attention,stride attention和global attention这三者。
https://arxiv.org/abs/2004.05150
Big Bird
Big Bird是融合了Random attention,window attention和global attention。
https://arxiv.org/abs/2007.14062
Reformer/ Routing Transformer
只关注与key更为相关的query的attention score,而不是所有query
这个相关性衡量的方式是聚类
Reformerhttps://openreview.net/forum?id=rkgNKkHtvB和Routing Transformerhttps://arxiv.org/abs/2003.05997采用的都是这种方法
Sinkhorn Sorting Network
对于每一个key-query计算出来的attention值是否应该被利用,是由另一个可学习的模块来决定的。https://arxiv.org/abs/2002.11296
Linformer
https://arxiv.org/abs/2006.04768
Linformer认为计算self-attention的时候有很多query-key的信息是冗余的,有很多的redundant columns,整个矩阵是低秩的,有很多列彼此之间是线性相关的。
所以只需要将线性无关的列挑出来进行进一步计算即可。
具体实现时是从key中选择出具有代表性的去与query进行计算。
那么能否将query的长度也进行缩短呢?这会改变输出序列的长度,要根据实际任务的需求来选择。比如做语者识别,我们将一整个序列输入只需要输出一个label,即语者是谁,那么是否缩短query是无妨的。但如果是序列中的每个元素都对应一个label的话,那么就不能将query的长度缩短。
那么该如何将key缩短呢?
第一种方案是Compressed Attention,它是通过对key进行卷积操作,将输出作为新的key
第二种方案是Linformer论文中提出的,将N个d维的key乘上一个N*K的矩阵,就会得到一个d*K的矩阵,这相当于是对原来的key进行不同的线性组合。
Efficient attention/ Linear Transformer/Performer
还有一种加速的方案是:
因为N的长度一般是远大于d的,所以先将 V V V和 K T K^T KT相乘,然后再乘上 Q Q Q,这样可以有效减小矩阵的运算量。
但这样该怎么添加softmax呢?
首先回顾一下self-attention的计算
b
1
=
∑
i
=
1
N
α
1
,
i
′
v
i
=
∑
i
=
1
N
exp
(
q
1
⋅
k
i
)
∑
j
=
1
N
exp
(
q
1
⋅
k
j
)
v
i
\boldsymbol{b}^1=\sum_{i=1}^N \alpha_{1, i}^{\prime} \boldsymbol{v}^i=\sum_{i=1}^N \frac{\exp \left(\boldsymbol{q}^1 \cdot \boldsymbol{k}^i\right)}{\sum_{j=1}^{\mathrm{N}} \exp \left(\boldsymbol{q}^1 \cdot \boldsymbol{k}^j\right)} \boldsymbol{v}^i
b1=i=1∑Nα1,i′vi=i=1∑N∑j=1Nexp(q1⋅kj)exp(q1⋅ki)vi
假设存在
ϕ
\phi
ϕ使得
e
x
p
(
q
⋅
k
)
≈
ϕ
(
q
)
⋅
ϕ
(
k
)
exp(q\cdot k) \approx \phi(q) \cdot \phi(k)
exp(q⋅k)≈ϕ(q)⋅ϕ(k)
b
1
=
∑
i
=
1
N
α
1
,
i
′
v
i
=
∑
i
=
1
N
exp
(
q
1
⋅
k
i
)
∑
j
=
1
N
exp
(
q
1
⋅
k
j
)
v
i
=
∑
i
=
1
N
ϕ
(
q
1
)
⋅
ϕ
(
k
i
)
∑
j
=
1
N
ϕ
(
q
1
)
⋅
ϕ
(
k
j
)
v
i
=
∑
i
=
1
N
[
ϕ
(
q
1
)
⋅
ϕ
(
k
i
)
]
v
i
∑
j
=
1
N
ϕ
(
q
1
)
⋅
ϕ
(
k
j
)
\boldsymbol{b}^1=\sum_{i=1}^N \alpha_{1, i}^{\prime} \boldsymbol{v}^i=\sum_{i=1}^N \frac{\exp \left(\boldsymbol{q}^1 \cdot \boldsymbol{k}^i\right)}{\sum_{j=1}^{\mathrm{N}} \exp \left(\boldsymbol{q}^1 \cdot \boldsymbol{k}^j\right)} \boldsymbol{v}^i\\ \begin{gathered} =\sum_{i=1}^N \frac{\phi\left(\boldsymbol{q}^1\right) \cdot \phi\left(\boldsymbol{k}^i\right)}{\sum_{j=1}^{\mathrm{N}} \phi\left(\boldsymbol{q}^1\right) \cdot \phi\left(\boldsymbol{k}^j\right)} \boldsymbol{v}^i \\ =\frac{\sum_{i=1}^N\left[\phi\left(\boldsymbol{q}^1\right) \cdot \phi\left(\boldsymbol{k}^i\right)\right] \boldsymbol{v}^i}{\sum_{j=1}^N \phi\left(\boldsymbol{q}^1\right) \cdot \phi\left(\boldsymbol{k}^j\right)} \end{gathered}
b1=i=1∑Nα1,i′vi=i=1∑N∑j=1Nexp(q1⋅kj)exp(q1⋅ki)vi=i=1∑N∑j=1Nϕ(q1)⋅ϕ(kj)ϕ(q1)⋅ϕ(ki)vi=∑j=1Nϕ(q1)⋅ϕ(kj)∑i=1N[ϕ(q1)⋅ϕ(ki)]vi
对
b
1
b^1
b1进行化简
可以发现除了 ϕ ( q i ) \phi(q^i) ϕ(qi)以外的内容不需要再重复计算
这个 ϕ \phi ϕ的计算方式也不是唯一的,有很多种选择
-
Efficient attention https://arxiv.org/pdf/1812.01243.pdf
-
Linear Transformer https://linear-transformers.com/
-
Random Feature Attention https://linear-transformers.com/
-
Performer https://arxiv.org/pdf/2009.14794.pdf
总结
基于human knowledge的:Local Attention、Big Bird
基于Clustering的:Reformer
基于Learnable Pattern的:Sinkhorn
基于Representative key的:Linformer
基于k,q first -> v,k first的:Linear Transformer、Performer
参考资料
李宏毅机器学习课程 2021 - Transformer (上)_哔哩哔哩_bilibili
Datawhalechina leedl-tutorial leedl-tutorial