Acknowledge
论文名称: An Image Is Worth 16x16 Words: Transformers For Image Recognition At Scale 原论文对应源码:https://github.com/google-research/vision_transformer PyTorch实现代码: pytorch_classification/vision_transformer Tensorflow2实现代码:tensorflow_classification/vision_transformer 在bilibili上的视频讲解:https://www.bilibili.com/video/BV1Jh411Y7WQ 博客讲解:https://blog.csdn.net/qq_37541097/article/details/118242600
本文以霹雳为基础Wz讲解的Transformer,在此基础上,推荐阅读原版。
引言
Transformer最初的提议是针对的NLP领域,在NLP该领域取得了巨大的成功。这篇论文也受到它的启发,试图Transformer应用到CV领域。通过文章的实验,给出的最佳模型是ImageNet1K上能够达到88.55%的准确率(先在Google自家的JFT对数据集进行预训练),说明Transformer在CV领域确实有效,效果惊人。
2. 模型详解
本文主要以作者为主ResNet、ViT(纯Transformer模型)以及Hybrid(卷积和Transformer对比三个模型的混合模型),本博文将简要讨论Hybrid模型。
2.1 Vision Transformer模型概况
下图是原论文给出的关于Vision Transformer(ViT)模型框架。模型由三个模块组成:
- Linear Projection of Flattened Patches(Embedding层,又称嵌入层)
- Transformer Encoder(编码层,图右侧有更详细的结构)
- MLP Head(多层感知机头,用于最终分类的层结构)
以上ViT模型架构。网络工作流程如下:
- 首先输入一张图片,分成一个接一个patch。
- 然后对每一个patch输入到Linear Projection of Flattened Patches层(也就是Embedding层)。通过这个Embedding层后,我们可以得到一个接一个的向量。这里的向量通常被称为token。每个patch通过Embedding层都会得到一个token。
- 在这一系列token前面加一个新的token(专门用于分类class token)。
- 让每一个token考虑到位置信息,每一个都需要考虑token添加位置信息,即Position Embedding。对应图中0、1、2、3、4、5、6、7、8、9。
- 将Embedding完的token输入到Transformer Encoder中。Transformer Encoder对应右边的图片。Visual Transformer中,将Transformer Encoder重复堆叠 L L L 次。
- 因为我们的网络是用来分类的,所以我们只需要嵌入的网络class token取出来即可。
- 将class token送入MLP Head得到最终的分类结果。
2.2 Embedding层结构详解
对于标准的Transformer模块,需要输入token(向量)序列,即二维矩阵[num_token, token_dim],如下图,token0-9对应于向量,以ViT-B以/16为例,每一个token768的向量长度。
ViT-B/16中:
- ViT: Visual Transformer
- B: Base
- 16: Patch Size, 16×16
2.2.1 Patch Embedding层
对图像数据而言,其数据格式为 [ H , W , C ] [H, W, C] [H,W,C] 显然不是三维矩阵Transformer想要的。所以需要先通过一个。Embedding层来改变数据。
如下图所示,首先将图片按给定大小分成一堆Patches。以ViT-B以/16为例,输入图片( 224 × 224 224\times 224 224×224 )按照 16 × 16 16\times 16 16×16 大小的Patch进行划分,划分后会得到 ( 224 / 16 ) 2 = 14 × 14 = 196 (224 / 16)^2=14\times 14 = 196 (224/16)2=14×14=196 个Patches。接着通过线性映射(Linear Projection)将每个Patch映射到一维向量中,以ViT-B/16为例,每个patch的shape为 [ 16 , 16 , 3 ] [16, 16, 3] [16,16,3] ,通过映射得到一个长度为768的向量(后面都直接称为token)。整体过程为:
[ 224 , 224 , 3 ] I m a g e s → 196 p a t c h n u m × [ 16 , 16 , 3 ] p a t c h p a t c h e s → 196 p a t c h n u m × [ 768 ] t o k e n t o k e n s \underset{\mathrm{Images}}{[224, 224, 3]} → \underset{\mathrm{patches}}{\underset{\mathrm{patch \ num}}{196} \times \underset{\mathrm{patch}}{[16, 16, 3]}} → \underset{\mathrm{tokens}}{ \underset{\mathrm{patch \ num}}{196} \times \underset{\mathrm{token}}{[768]} } Images[224,224,3]→patchespatch num196×patch[16,16,3]→tokenspatch num196×token[768]
Note: , , ,
: 根据Patch Size得到的单独的patch,上面示例中的 [ P a t c h S i z e , P a t c h S i z e , C h a n n e l ] = [ 16 , 16 , 3 ] \mathrm{[Patch \ Size, Patch \ Size, Channel]=[16, 16, 3]} [Patch Size,Patch Size,Channel]=[16,16,3] 就是patch
: [patch总个数,patch],就是上面的 [ 196 , 16 , 16 , 3 ] = [ ( 224 / 16 ) 2 , 16 , 16 , 3 ] [196, 16, 16, 3] = \mathrm{[(224 / 16)^2, 16, 16, 3]} [196,16,16,3]=[(224/16)2,16,16,3]
: 一维向量,就是单独一个patch映射得到的一维向量,上面示例中的 [ 768 ] [768] [768] 就是token
: [patch个数, token],就是上面的 [ 196 , 768 ] [196, 768] [196,768]
和的区别:其实展平都是一样的,只不过是用不同的shape表示不同的含义罢了 顾名思义,patch意思为小块,我们就将其理解为是特征图,所以是 [ 16 , 16 , 3 ] [16, 16, 3] [16,16,3],而token这里我们理解为是一维序列就行,所以它的形状理所当然是 [ 16 × 16 × 3 ] = [ 768 ] [16 \times 16 \times 3] = [768] [16×16×3]=[768]
在代码实现中,这个过程是直接通过一个卷积层来实现。 以ViT-B/16为例,直接使用nn.conv2d(in_channels=224, out_channels=768, kernel=(16, 16), stride=16)
来实现,即:
[ 224 , 224 , 3 ] I m a g e s → [ 14 , 14 p a t c h n u m , 768 t o k e n ] p a t c h e s / t o k e n s \underset{\mathrm{Images}}{[224, 224, 3]} → \underset{\mathrm{patches/tokens}}{[\underset{\mathrm{patch \ num}}{14, 14}, \underset{\mathrm{token}}{768}]} Images[224,224,3]→patches/tokens[patch num14,14,token768]
输入输出就不用说了,这里比较巧妙的是
kernel=(16, 16)
,其实明白卷积是怎么运算的,也很好理解。
然后把 H , W H, W H,W 两个维度展平即可。 [ 14 , 14 , 768 ] → [ 196 , 768 ] t o k e n s [14, 14, 768] → \underset{\mathrm{tokens}}{[196, 768]} [14,14,768]→tokens[196,768],此时正好变成了一个二维矩阵,正是Transformer想要的。
[ 224 , 224 , 3 ] I m a g e s → [ 14 , 14 , 768 ] p a t c h e s → [ 196 , 768 ] t o k e n s \underset{\mathrm{Images}}{[224, 224, 3]} → \underset{\mathrm{patches}}{[14, 14, 768]} → \underset{\mathrm{tokens}}{[196, 768]} Images[224,224,3]→patches[14,14,768]→tokens[196,768]
2.2.2 Class Embedding 层
注意:在输入Transformer Encoder之前需要加上
- [class]token → 接下来会说的
- Position Embedding → 2.2.3会说
在原论文中,作者说参考BERT,在刚刚得到的一堆tokens中插入,,数据格式和其他token一样都是一个向量。
以ViT-B/16为例,,与之前从图片中生成的tokens [ 196 , 768 ] [196, 768] [196,768] 拼接在一起,即:
c o n c a t ( [ 1 , 768 ] ; [ 196 , 768 ] ) → [ 197 , 768 ] \mathrm{concat}([1, 768]; [196, 768]) \rightarrow [197, 768] concat([1,768];[196,768])→[197,768]
代码实现为:
nn.cat([class]tokent, token)
[ 196 , 768 ] ⟶ C o n c a t ( t o k e n s , [ c l a s s ] t o k e n ) ⟶ C o n c a t ( [ 196 , 768 ] , [ 1 , 768 ] ) ⟶ [ 197 , 768 ] [196, 768] \longrightarrow \mathrm{Concat(tokens, [class]token)} \\ {\longrightarrow} \mathrm{Concat([196, 768], [1, 768])}\\ \longrightarrow [197, 768] [196,768]⟶Concat(tokens,[class]token)⟶Concat([196,768],[1,768])⟶[197,768]
2.2.3 Position Embedding层
然后关于Position Embedding(就是之前Transformer中讲到的Positional Encoding),这里的(具体为),是直接叠加在tokens上的( ⊕ \oplus ⊕),所以shape要一样。
意思是说,前面的类别编码是一个trainable params,这里的位置编码也是一个trainable params,都是需要学习才能使得网络比较好的work。
以ViT-B/16为例,刚刚拼接[class]token(类别序列)后tokens的shape是 [ 197 , 768 ] [197, 768] [197,768] ,那么这里的Position Embedding的shape也是 [ 197 , 768 ] [197, 768] [197,768]。
- 与《Transformer Is All You Need》不同,ViT作者没有使用固定的function去做Position Embedding,而是使用可训练的Position Embedding。
- 一般ViT都是可训练的Position Embedding
2.2.3.1 Position Embedding有效性说明
对于Position Embedding作者也有做一系列对比试验,,对比不使用Position Embedding准确率提升了大概3个点,和2D Pos. Emb.比起来没太大差别。
- No Pos. Emb.: 不使用位置编码
- 1-D Pos. Emb.: 使用一维位置编码
- 2-D Pos. Emb.: 使用二维位置编码
- Rel Pos. Emb.: 使用相对位置编码
- “The differences in how to encode spatial information is less important”
- 位置编码很重要,但如何进行位置编码不是那么重要😂
论文展示了训练得到的位置编码的的余弦相似度的热力图。这里的Patch Size为 32 × 32 32 \times 32 32×32 ,即一张图片可以被划分为 224 / 32 × 224 / 32 = 7 × 7 224 / 32 \times 224 / 32 = 7 \times 7 224/32×224/32=7×7 个patch,每个patch的shape为: [ 32 , 32 , 3 ] [32, 32, 3] [32,32,3],共 7 × 7 = 49 7 \times 7 = 49 7×7=49 个,我们可以对每个patch进行线性映射得到所需要的token [ 32 × 32 × 3 ] = [ 3072 ] [32 \times 32 \times 3] = [3072] [32×32×3]=[3072],即
[ 224 , 224 , 3 ] I m a g e s → 49 p a t c h n u m × [ 32 , 32 , 3 ] p a t c h → 49 p a t c h n u m × [ 3072 ] t o k e n \underset{\mathrm{Images}}{[224, 224, 3]} \rightarrow \underset{\mathrm{patch \ num}}{49} \times \underset{\mathrm{patch}}{[32, 32, 3]} \rightarrow \underset{\mathrm{patch \ num}}{49} \times \underset{\mathrm{token}}{[3072]} Images[224,224,3]→patch num49×patch[32,32,3]→patch num49×token[3072]
即一张图片被切分为49个patch,对每个patch进行变换后得到shape为 [ 3072 ] [3072] [3072] 的token,即tokens的shape为 [ 49 , 3072 ] [49, 3072] [49,3