深度学习笔记之Transformer——自注意力机制
- 引言
- 回顾:缩放点积注意力机制
- 自注意力机制
- 自注意力机制与 RNN,CNN \text{RNN,CNN} RNN,CNN的对比
- 简单介绍:卷积神经网络处理序列信息的原理
- 从计算复杂度的角度观察
- 位置编码
引言
上一节对注意力分数 ( Attention Score ) (\text{Attention Score}) (Attention Score)这个概念进行了总结。本节将基于缩放点积注意力机制 ( Scaled Dot-Product Attention ) (\text{Scaled Dot-Product Attention}) (Scaled Dot-Product Attention)这种注意力分数的计算模式,介绍自注意力机制。
回顾:缩放点积注意力机制
缩放点积注意力机制的核心思路在于:使用内积描述刻意信息与无意信息之间的关联关系(相似性关系)。
关于‘刻意信息’、‘无意信息’的概念详见
注意力机制基本介绍
-
已知包含 N N N个向量的查询矩阵 Q \mathcal Q Q(刻意信息)与无意信息的 M \mathcal M M个键值对 { ( K j , V j ) } j = 1 M \{(\mathcal K_j,\mathcal V_j)\}_{j=1}^{\mathcal M} {(Kj,Vj)}j=1M,并且各向量维数表示为:
其中
K , V \mathcal K,\mathcal V K,V分别表示
Keys,Values \text{Keys,Values} Keys,Values组成的矩阵,它们的元素数量
( M ) (\mathcal M) (M)相同;而
Q \mathcal Q Q中各元素的维数
( d ) (d) (d)需要与
K \mathcal K K个元素维数大小相同,否则无法执行内积。
{ Q ∈ R N × d K j ∈ R 1 × d j = 1 , 2 , ⋯ , M ⇒ K ∈ R M × d V j ∈ R 1 × v j = 1 , 2 , ⋯ , M ⇒ V ∈ R M × v \begin{cases} \mathcal Q \in \mathbb R^{N \times d} \\ \mathcal K_j \in \mathbb R^{1 \times d} \quad j=1,2,\cdots,\mathcal M \Rightarrow \mathcal K \in \mathbb R^{\mathcal M \times d} \\ \mathcal V_j \in \mathbb R^{1 \times v} \quad j =1,2,\cdots,\mathcal M \Rightarrow \mathcal V \in \mathbb R^{\mathcal M \times v} \end{cases} ⎩ ⎨ ⎧Q∈RN×dKj∈R1×dj=1,2,⋯,M⇒K∈RM×dVj∈R1×vj=1,2,⋯,M⇒V∈RM×v -
基于缩放点积注意力机制的注意力分数 a ( Q , K ) a(\mathcal Q,\mathcal K) a(Q,K)表示如下:
a ( Q , K ) = [ Q K T d ] N × M a(\mathcal Q,\mathcal K) = \left[\frac{\mathcal Q\mathcal K^T}{\sqrt{d}}\right]_{N \times \mathcal M} a(Q,K)=[dQKT]N×M
其中 a ( Q , K ) a(\mathcal Q,\mathcal K) a(Q,K)返回的 N × M N \times \mathcal M N×M矩阵中的每一个元素均描述:某查询向量 Q i ( i = 1 , 2 , ⋯ , N ) ∈ R 1 × d \mathcal Q_i(i=1,2,\cdots,N) \in \mathbb R^{1 \times d} Qi(i=1,2,⋯,N)∈R1×d与某个 K j ( j = 1 , 2 , ⋯ , M ) ∈ R 1 × d \mathcal K_j(j=1,2,\cdots,\mathcal M) \in \mathbb R^{1 \times d} Kj(j=1,2,⋯,M)∈R1×d之间的内积结果;而 d \sqrt{d} d的作用是约束内积结果的解空间,使注意力分数结果对参数的敏感度降低。 -
对注意力分数做归一化 ( Softmax ) (\text{Softmax}) (Softmax)处理,再与对应 V j ( j = 1 , 2 , ⋯ , M ) \mathcal V_j(j=1,2,\cdots,\mathcal M) Vj(j=1,2,⋯,M)作内积运算:
f [ Q , ( K 1 , V 1 ) , ( K 2 , V 2 ) ⋯ , ( K M , V M ) ] = Softmax [ a ( Q , K ) ] ⋅ V M × v \begin{aligned} f [\mathcal Q,(\mathcal K_1,\mathcal V_1),(\mathcal K_2,\mathcal V_2)\cdots,(\mathcal K_{\mathcal M},\mathcal V_{\mathcal M})] & = \text{Softmax}[a(\mathcal Q,\mathcal K)] \cdot \mathcal V_{\mathcal M \times v} \end{aligned} f[Q,(K1,V1),(K2,V2)⋯,(KM,VM)]=Softmax[a(Q,K)]⋅VM×v
自注意力机制
归纳一下之前出现的注意力机制:
- 关于
Seq2seq
\text{Seq2seq}
Seq2seq,它的注意力机制中查询向量
Query
\text{Query}
Query和
Keys
\text{Keys}
Keys分别来自于独立的循环神经网络
(
Decoder,Encoder
)
(\text{Decoder,Encoder})
(Decoder,Encoder);并且
Keys,Values
\text{Keys,Values}
Keys,Values均来自于同一个事物:
Keys,Values ⇒ H B i = { [ h L ; j , h R ; T + 1 − j ] } j = 1 T \text{Keys,Values} \Rightarrow \mathcal H_{Bi} = \left\{[h_{\mathcal L;j},h_{\mathcal R;\mathcal T+1-j}]\right\}_{j=1}^{\mathcal T} Keys,Values⇒HBi={[hL;j,hR;T+1−j]}j=1T - 关于 Nadaraya-Watson \text{Nadaraya-Watson} Nadaraya-Watson核回归,它的注意力机制中查询向量指的是训练集 D \mathcal D D之外的陌生样本 x x x;而 Keys,Values \text{Keys,Values} Keys,Values分别描述训练集 D \mathcal D D中的特征与标签信息 x ( i ) , y ( i ) ( i = 1 , 2 , ⋯ , N ) x^{(i)},y^{(i)}(i=1,2,\cdots,N) x(i),y(i)(i=1,2,⋯,N)。很明显, Keys,Values \text{Keys,Values} Keys,Values之间同样存在显式的关联关系。
关于 Keys,Values \text{Keys,Values} Keys,Values在各模型的注意力机制中关联性都很强;但作为执行相关性(相似性)计算的双方: Query,Keys \text{Query,Keys} Query,Keys,上述两种注意力机制似乎都有意地区分它们。
而自注意力机制 ( Self-Attention ) (\text{Self-Attention}) (Self-Attention)的核心在于:将特征既作为 Query \text{Query} Query,也作为 Keys \text{Keys} Keys,也作为 Values \text{Values} Values进行运算,三者在内容上没有区别。通过这种方式对序列抽取特征。
给定一个长度为
T
\mathcal T
T的序列:
X
=
(
x
1
,
x
2
,
⋯
,
x
T
)
T
\mathcal X = (x_1,x_2,\cdots,x_{\mathcal T})^T
X=(x1,x2,⋯,xT)T,并且序列中的每个元素
x
t
(
t
=
1
,
2
,
⋯
,
T
)
x_t(t=1,2,\cdots,\mathcal T)
xt(t=1,2,⋯,T)均有
d
d
d维向量进行特征表示:
{
x
t
∈
R
1
×
d
t
=
1
,
2
,
⋯
,
T
X
∈
R
T
×
d
\begin{cases} x_t \in \mathbb R^{1 \times d} \quad t=1,2,\cdots,\mathcal T \\ \mathcal X \in \mathbb R^{\mathcal T \times d} \end{cases}
{xt∈R1×dt=1,2,⋯,TX∈RT×d
关于
x
t
x_t
xt的注意力信息
y
t
y_t
yt表示如下:
其中元素
x
t
x_t
xt需要与序列中的所有元素
x
1
,
x
2
,
⋯
,
x
T
x_1,x_2,\cdots,x_{\mathcal T}
x1,x2,⋯,xT计算注意力分数。
y
t
∈
R
1
×
d
=
f
[
Q
=
x
t
,
(
K
1
=
x
1
,
V
1
=
x
1
)
,
⋯
,
(
K
T
=
x
T
,
V
T
=
x
T
)
]
=
Softmax
{
[
x
t
X
T
d
]
1
×
T
}
⋅
[
X
]
T
×
d
\begin{aligned} y_t \in \mathbb R^{1 \times d} & = f[\mathcal Q = x_t,(\mathcal K_1 = x_1,\mathcal V_1 = x_1),\cdots,(\mathcal K_{\mathcal T} = x_{\mathcal T},\mathcal V_{\mathcal T} = x_{\mathcal T})] \\ & = \text{Softmax} \left\{\left[\frac{x_t \mathcal X^T}{\sqrt{d}}\right]_{1 \times \mathcal T}\right\} \cdot [\mathcal X]_{\mathcal T \times d} \end{aligned}
yt∈R1×d=f[Q=xt,(K1=x1,V1=x1),⋯,(KT=xT,VT=xT)]=Softmax{[dxtXT]1×T}⋅[X]T×d
同理,将完整序列
X
∈
R
T
×
d
\mathcal X \in \mathbb R^{\mathcal T \times d}
X∈RT×d作为输入,它的注意力信息
Y
\mathcal Y
Y表示如下:
注意:这里的
Softmax
\text{Softmax}
Softmax对各行的
T
\mathcal T
T个元素做归一化,而不是对
[
X
X
T
d
]
T
×
T
\begin{aligned}\left[\frac{\mathcal X \mathcal X^T}{\sqrt{d}}\right]_{\mathcal T \times \mathcal T}\end{aligned}
[dXXT]T×T整体做归一化。
Y
∈
R
T
×
d
=
Softmax
{
[
X
X
T
d
]
T
×
T
}
⋅
[
X
]
T
×
d
\mathcal Y \in \mathbb R^{\mathcal T \times d} = \text{Softmax} \left\{\left[\frac{\mathcal X \mathcal X^T}{\sqrt{d}}\right]_{\mathcal T \times \mathcal T}\right\} \cdot [\mathcal X]_{\mathcal T \times d}
Y∈RT×d=Softmax{[dXXT]T×T}⋅[X]T×d
这种操作与循环神经网络结构有些许相似:给定一个长度为
T
\mathcal T
T的序列,自注意力操作后同样返回一个长度为
T
\mathcal T
T的输出。在不额外添加
Query,Keys,Values
\text{Query,Keys,Values}
Query,Keys,Values的情况下,我们可以直接使用自注意力来处理序列。
关于自注意力的流程图表示如下:
如果仅仅从‘自注意力’执行过程的角度观察,感觉
自注意力的输出信息并不像是一个序列信息。在循环神经网络系列中如
LSTM
\text{LSTM}
LSTM,
GRU
\text{GRU}
GRU,之所以认为它们各时刻的输出信息是‘序列信息’,是因为
它们的各时刻输出中均包含上一时刻的信息:
{
RNN :
h
t
+
1
=
Tanh
(
W
h
t
⇒
h
t
+
1
⋅
h
t
+
W
x
t
⇒
h
t
+
1
⋅
x
t
+
b
h
)
LSTM :
C
t
=
f
t
∗
C
t
−
1
+
i
t
∗
C
~
t
\begin{cases} \text{RNN : } h_{t+1} = \text{Tanh} \left(\mathcal W_{h_t \Rightarrow h_{t+1}} \cdot h_t + \mathcal W_{x_t \Rightarrow h_{t+1}} \cdot x_t +b_{h}\right) \\ \quad \\ \text{LSTM : } \mathcal C_t = f_t * \mathcal C_{t-1} + i_t * \widetilde{\mathcal C}_t \end{cases}
⎩
⎨
⎧RNN : ht+1=Tanh(Wht⇒ht+1⋅ht+Wxt⇒ht+1⋅xt+bh)LSTM : Ct=ft∗Ct−1+it∗C
t
在下面的‘位置编码’部分进行解答。
自注意力机制与 RNN,CNN \text{RNN,CNN} RNN,CNN的对比
简单介绍:卷积神经网络处理序列信息的原理
事先说明:卷积神经网络
(
Convolutional Neural Network,CNN
)
(\text{Convolutional Neural Network,CNN})
(Convolutional Neural Network,CNN)同样可以执行序列运算。在卷积神经网络处理图像数据过程中,通常将图像数据描述为
[
Width,Height,Channels
]
[\text{Width,Height,Channels}]
[Width,Height,Channels]的张量格式。
其中
Width,Height,Channel
\text{Width,Height,Channel}
Width,Height,Channel分别表示图像的宽、高、通道数。
而在处理序列数据中,可以将序列数据(例如文本序列)描述为
[
BatchSize
,
MaxLength
,
Embedding
]
[\text{BatchSize},\text{MaxLength},\text{Embedding}]
[BatchSize,MaxLength,Embedding]的张量格式。在卷积核的选择过程中,由于
BatchSize
\text{BatchSize}
BatchSize内不同文本序列之间相互独立,因而卷积核的格式被限制为
[
WindowSize
,
1
]
[\text{WindowSize},1]
[WindowSize,1]的大小格式。其中
WindowSize
\text{WindowSize}
WindowSize表示覆盖序列信息的窗口大小。
其中
BatchSize,MaxLength,Embedding
\text{BatchSize,MaxLength,Embedding}
BatchSize,MaxLength,Embedding分别表示该批次中的序列数量、最大序列长度、序列中各元素的向量表示维数。
在算法八股系列——卷积函数中介绍过单层卷积神经网络的模型结构。其结构图表示如下:
该图左侧表示‘卷积核大小’为
3
3
3的卷积神经网络结构;右侧则表示相同输入大小
(
5
)
(5)
(5)的全连接神经网络结构。
虽然右侧图描述的是全连接神经网络结构,但我们可以将其视作自注意力机制的网络结构。原因在于:在自注意力机制中,每个元素均要与序列中的所有元素计算注意力分数。
同理,对应循环神经网络的网络结构表示如下:
该图看起来像‘隐马尔可夫模型’的概率图结构。这里简化了很多,需要注意的是,这里的
这里的蓝色点既表示各时刻的序列信息,也表示输出信息。
从计算复杂度的角度观察
三结构中计算复杂度最小的自然是循环神经网络结构。原因在于:序列中的每个元素仅需执行一次前馈计算,就可得到对应时刻的输出结果。因此它的计算复杂度为:
O
(
T
∗
d
2
)
\mathcal O(\mathcal T * d^2)
O(T∗d2)。
其中
T
\mathcal T
T表示序列长度;
d
d
d表示序列各元素的维数信息。在循环神经网络中,共经历两个层:隐藏层
h
t
(
t
=
1
,
2
,
⋯
,
T
)
h_t(t=1,2,\cdots,\mathcal T)
ht(t=1,2,⋯,T)和输出层
O
t
(
t
=
1
,
2
,
⋯
,
T
)
\mathcal O_t(t=1,2,\cdots,\mathcal T)
Ot(t=1,2,⋯,T)。关于序列中的每个输入元素,每一层都是关于输入与对应权重矩阵的线性运算。因此是
T
×
d
×
d
\mathcal T \times d \times d
T×d×d。
其次是卷积神经网络,它的计算复杂度表示为: O ( K ∗ T ∗ d 2 ) \mathcal O(\mathcal K*\mathcal T*d^2) O(K∗T∗d2)
卷积神经网络与循环神经网络的差别在于:序列中的各元素可能不止执行了一次运算。这取决于‘窗口大小’
( WindowSize ) (\text{WindowSize}) (WindowSize)与步长
Step \text{Step} Step之间的关系。
其中
K \mathcal K K表示窗口大小。如果
K < Step \mathcal K < \text{Step} K<Step,这意味着序列中某些元素在不同的窗口中连续地重复出现。如果执行了
Padding \text{Padding} Padding操作,导致输出大小与输入大小相同,最终执行的序列长度是
K × T \mathcal K \times \mathcal T K×T。其中卷积核的大小是
K × d \mathcal K \times d K×d,并且它的输出结果也包含
d d d个维度(通道数)(和自注意力机制相比,输出大小相同,为公平起见),这意味着包含
d d d个卷积核。因此最终的复杂度是
K ∗ T ∗ d 2 \mathcal K * \mathcal T * d^2 K∗T∗d2。
最终最慢的是自注意力机制,它的计算复杂度表示为:
O
(
T
2
∗
d
)
\mathcal O(\mathcal T^{2}* d)
O(T2∗d)
由于自注意力机制需要序列中每个元素之间均执行一次内积运算,因此是
T
×
T
\mathcal T \times \mathcal T
T×T次线性运算。而每次内积运算均包含
d
d
d次运算。因此是
T
2
∗
d
\mathcal T^2 * d
T2∗d。
位置编码
在上面介绍自注意力机制时,觉得它并不像一个序列信息。是因为:我们仅仅是记录了目标词与序列中各个词之间的关联关系(相似性关系),但并没有记录序列信息(序列中各元素的相对位置/上下文位置关系)
补充:卷积神经网络处理序列数据时,其生成的输出内包含‘序列信息’。其原因是:卷积核在移动过程中,遵循某一方向移动。而移动过程中卷积核内的参数会学习序列信息。
这会产生什么效果
?
?
?依然拿序列
X
=
(
x
1
,
x
2
,
⋯
,
x
T
)
T
\mathcal X = (x_1,x_2,\cdots,x_{\mathcal T})^T
X=(x1,x2,⋯,xT)T为例,这会导致:序列中某元素
x
t
x_t
xt与序列中所有元素
x
1
,
x
2
,
⋯
,
x
T
x_1,x_2,\cdots,x_{\mathcal T}
x1,x2,⋯,xT之间的相似性关系
a
(
x
t
,
x
j
)
(
j
=
1
,
2
,
⋯
,
T
)
a(x_t,x_j)(j=1,2,\cdots,\mathcal T)
a(xt,xj)(j=1,2,⋯,T)是离散的。也就是说,将序列中的元素打乱顺序
⇒
\Rightarrow
⇒ 并不会影响其相似性结果。
但相反,一个文本句子打乱顺序后,就可能不是一个正确的文本句子了。
而位置编码 ( Position Encoding ) (\text{Position Encoding}) (Position Encoding)就是加入位置信息的一种方式。这种编码的特点在于:它并不作用在模型内部,而是单独计算出来,直接作用在输入内(让输入自身存在位置信息)。其具体做法表示如下:
已知输入数据 X ∈ R T × d \mathcal X \in \mathbb R^{\mathcal T \times d} X∈RT×d,对应的位置编码矩阵 P ∈ R T × d \mathcal P \in \mathbb R^{\mathcal T \times d} P∈RT×d(张量大小与 X \mathcal X X相同),并执行对应元素相加操作,并将该结果重新作为自注意力机制的输入;
P
\mathcal P
P内元素的计算方式表示如下:
{
P
i
,
2
j
=
sin
(
i
1000
0
2
j
d
)
P
i
,
2
j
+
1
=
cos
(
i
1000
0
2
j
d
)
\begin{cases} \begin{aligned} \mathcal P_{i,2j} & = \sin \left(\frac{i}{10000^{\frac{2j}{d}}}\right) \\ \mathcal P_{i,2j+1} & = \cos \left(\frac{i}{10000^{\frac{2j}{d}}}\right) \end{aligned} \end{cases}
⎩
⎨
⎧Pi,2jPi,2j+1=sin(10000d2ji)=cos(10000d2ji)
其中
P
i
,
2
j
\mathcal P_{i,2j}
Pi,2j表示编码矩阵
P
\mathcal P
P中第
i
i
i行、第
2
j
2j
2j列的元素值,
P
i
,
2
j
+
1
\mathcal P_{i,2j+1}
Pi,2j+1同理。这里选择若干个行、列作为例子,对应代码表示如下:
import math
import matplotlib.pyplot as plt
def PositionEncoding(i,j,d=64):
if j % 2 == 0:
return math.sin(i / (10000 ** ((2 * j) / d)))
else:
return math.cos(i / (10000 ** ((2 * j) / d)))
def DrawPicture():
j = [6,7,8,9]
LineStyleList = ["-","--","-.",":"]
i = [k for k in range(60)]
for Index,Elem in enumerate(j):
print(Elem)
Res = list()
jList = [Elem for _ in range(len(i))]
for _,(Elemi,Elemj) in enumerate(zip(i,jList)):
Res.append(PositionEncoding(Elemi,Elemj))
plt.plot(i,Res,label="Col "+ str(Elem),linestyle=LineStyleList[Index])
plt.legend()
plt.show()
if __name__ == '__main__':
DrawPicture()
对应函数图像表示如下:
其中关于序列中各元素的'特征表示'维数
d
d
d设置为
64
64
64。
从该函数图像中可以看出:横坐标表示序列长度编号
1
,
2
,
⋯
,
T
1,2,\cdots,\mathcal T
1,2,⋯,T,纵坐标表示函数的映射区间。
每一条函数图像表示同一维度的位置特征在序列
X
\mathcal X
X中的各元素
x
1
,
x
2
,
⋯
,
x
T
x_1,x_2,\cdots,x_{\mathcal T}
x1,x2,⋯,xT的映射信息。能够看出,即便是同一维度的信息在序列不同位置的映射结果也存在不同程度的差异。
为什么要设计成这种形式呢
?
?
?上面描述的是对向量特征表示的
6
,
7
,
8
,
9
6,7,8,9
6,7,8,9维度的位置信息,发现各维度图像的周期总是会出现因错位而产生的少许差异,并且特征维度
d
d
d的数值越大,这个差异就越不明显(细致):
下图表示
d
=
1024
d=1024
d=1024时的对应图像。
当然,这里选择的是
6
,
7
,
8
,
9
6,7,8,9
6,7,8,9四个连续维数在
d
=
1024
d=1024
d=1024维度下的效果,如果将其设置为
32
,
64
,
128
,
256
32,64,128,256
32,64,128,256四个维数,各维数的周期差异会更加明显:
而这些周期性差异并非是完全不重合的,而是存在一些重合的点。为了更方便理解,我们可以观察如下例子:关于
0
−
7
0-7
0−7的二进制编码。
关于十进制数
0
−
7
0-7
0−7的二进制编码表示如下:
二进制的特点很简单:逢二进一。如果将这里每一个十进制数看作是由
3
3
3个特征表示的编码结果,来观察各特征(每一列)的变化情况:
对应图像从左到右进行表示。
很明显,由于逢二进一的原因,导致位数越高的特征周期越长(变化速度更慢)。并且它们同样存在部分重合的情况(上图中的红色框部分示例)。
并且十进制数的二进制表示,其特征仅仅是
{
0
,
1
}
\{0,1\}
{0,1}内的离散特征,但这种特征可以保证每个十进制数均存在独一无二的位置编码;同理,可以将自注意力机制中位置编码的错位现象想象成位数的进化,对应的重合现象也是表示序列中的各元素
x
1
,
x
2
,
⋯
,
x
T
x_1,x_2,\cdots,x_{\mathcal T}
x1,x2,⋯,xT在
d
d
d维特征空间中独一无二的特征信息重要的组成部分。
相比于二进制的离散信息,各维度的位置编码信息是使用函数表示的连续信息,它描述的信息更加丰富,因为我们设置的数据特征维数可能是任意大小的。
相关参考:
67 自注意力【动手学深度学习v2】