前言
自从Transformer[3]模型在NLP领域问世后,基于Transformer的深度学习模型性能逐渐在NLP和CV领域(Vision Transformer)取得了令人惊叹的提升。本文的主要目的是介绍经典Transformer模型和Vision Transformer的技术细节及基本原理,以方便读者在CV领域了解和使用Vision Transformer。由于篇幅过长,本文将分为四个部分进行介绍,包括:
(3)位置编码(positional encoding)的原理与实现。
(4)Transformer在CV领域的应用案例。
本文首先讲解第一个话题:自注意力(self-attention)与多头自注意力(multi-heads self-attention)模型的原理与实现。
注意力机制的直观认知
自注意力最早在自然语言处理领域提出,在Transformer[3]中,自注意力模型可以认为在学习一种关系,即一个序列中当前token与其他token之间的联系。
NLP领域
以一个自然语言处理中的序列翻译问题为例,直观地理解自注意力模型是如何发挥作用的,如下图所示。
第一幅效果图显示了一句话中单词it
与其他单词的联系。可以发现,通过自注意力机制,Transformer模型建立起了单词it
和所有其他单词的联系(橙色线条表示这种联系的权重,颜色越深表示联系越紧密)。并且,随着模型不断训练,我们发现it
和The animal
的联系最为密切,这正是我们想要获得的结果。通过这个例子可以看出,注意力机制有助于深度学习模型更好地理解文字序列中的每个token(单词)。
第二幅效果图显示了一句话中任何一个token与其他所有tokens的联系,并且以连接线颜色的深浅表示这种联系的紧密程度。
CV领域
基于上述自注意力机制,早在Vision Transformer之前,就有很多基于注意力机制的深度学习模型,如经典的spacial attention、channel attention、CBAM等,其核心思想是自适应地提升特征表达在空间维度或(和)通道维度对特定位置的权重,增加神经网络对特定区域的关注程度。
以channel attention为例,可以看到channel attention模型通过重新分配特征图通道的权重,使模型更加关注某些通道,如下图所示。
Channel attention首先采用global average pooling将维度为[H, W, C]的特征图转化为[1, 1, C],然后通过一个全连接层,最后将全连接层的输出结果与原始特征图相乘,在通道维度对原始特征图进行加权。这样,channel attention模型可在通道维度增加对某些通道的注意力,同时削弱对其余通道的注意力。
通过直观感受自注意力在不同领域发挥的作用,我们基本可以理解自注意力到底做了什么,并定性感受了为什么自注意力能够促进模型提升性能。后面,我们还将通过一些实际例子,发现自注意力机制在NLP和CV领域的一些联系,非常有意思。
自注意力
本节从自注意力的基本原理出发,首先通过一个序列翻译的例子讲解自注意力模型的计算过程及基本原理;然后,介绍自注意力模型的矩阵表达,以提升计算效率;接下来,将矩阵表达的过程整理为数学表达;最后,通过代码介绍自注意力模型的实现过程。
自注意力的过程和基本原理
整个自注意力的计算过程可以分为6个步骤。接下来逐步介绍每个步骤的操作过程和及其设计原理。
第一步:创建Query (q), Key (k)和Value (v)向量。 通过下图可以看到,对于输入tokens (单词) Thinking
和Machines
,首先采用Embedding算法将单词编码为词向量 X 1 X_1 X1和 X 2 X_2 X2;然后,对于每个词向量 X i X_i Xi,计算该词向量对应的Query, Key和Value,分别得到 q 1 q_1 q1, k 1 k_1 k1, v 1 v1 v1和 q 2 q_2 q2, k 2 k_2 k2, v 2 v_2 v2。需要注意的是,这里的 q i q_i qi, k i k_i ki和 v i v_i vi都是学习得到的,通过对应的输入向量 X i X_i Xi乘以对应的权重矩阵 W W W (即输入词向量通过一个全连接层)。
第二步:计算自注意力分数。 如下图所示,计算分数时,对于任意一个词向量,用该向量对应的 q q q与整个序列中所有 k k k进行点乘,并得到对应分数,这里将这个分数命名为‘自注意力分数’。例如,对于 X 1 X_1 X1,用其对应的 q 1 q_1 q1分别与该序列中 k 1 k_1 k1和 k 2 k_2 k2相乘,则得到 q 1 ⋅ k 1 = 112 q_1\cdot k_1 = 112 q1⋅k1=112, q 1 ⋅ k 2 = 96 q_1 \cdot k_2 = 96 q1⋅k2=96。得到的这两个分数为标量,可以用来表示当前Query对应的token与其他所有tokens之间的关系,且分值越大,表示关系越强烈。可以看到, q 1 q_1 q1与 k 1 k_1 k1点乘的数值比 q 1 q_1 q1与 k 2 k_2 k2点乘的数值更大。这个结果与我们的认知相符合,表明在一句话中,单词Thinking
和它自己的关系比Thinking
和Machines
之间的关系更紧密。
第三步和第四步的计算过程如下图所示。
第三步:调整自注意力分数幅值。 调整上一步自注意力分数幅值的目的是使神经网络训练更加稳定,这在一些迭代优化算法中是经常使用的操作。在实际训练中,如果q
和k
计算得到的自注意力分数幅值过大,则在进行softmax
操作时会导致梯度极小,很容易出现梯度消失现象(其原因很好理解,结合softmax
函数曲线可以看到,如果自注意力分数幅值过大,则会分布在softmax
函数两侧距离原点很远的位置,而这些位置的梯度极小)[3]。为了解决上述问题,一种常见的算法是给第二步得到的score
除以 d k \sqrt{d_{k}} dk,其中 d k d_k dk表示向量 k k k的维度。图中 d k d_k dk的数值为256,因此 d k \sqrt{d_{k}}