Vision Transformer (ViT)
文章目录
- Vision Transformer (ViT)
- 模型详解
- 模型总体结构
- Embedding 层
- Transformer Encoder
- MLP Head
- 例子
- 结果
- ViT模型搭建参数
- 代码
- 参考
论文名称: An Image Is Worth 16x16 Words: Transformers For Image Recognition At Scale
论文下载链接:https://arxiv.org/abs/2010.11929
原论文对应源码:https://github.com/google-research/vision_transformer
模型详解
在这篇文章中,作者主要拿ResNet、ViT(纯Transformer模型)以及Hybrid(卷积和Transformer混合模型)三个模型进行比较,所以本博文除了讲ViT模型外还会简单聊聊Hybrid模型。
模型总体结构
下图是原论文中给出的关于Vision Transformer(ViT)的模型框架。简单而言,模型由三个模块组成:
- Linear Projection of Flattened Patches(Embedding层)
- Transformer Encoder(图右侧有给出更加详细的结构)
- MLP Head(最终用于分类的层结构)
过程:
标准 Transformer 的输入是一维标记嵌入序列(sequence of token embeddings),所以这里也需要将图片转为序列数据,那怎么做的呢?
首先,将图片划分为一个个的patch,这里是划分为9个patch,后面会看到“ViT-L/16"这里的16指的就是一个patch的大小为16x16。
然后将每个patch重组一起输入到Linear Projection of Flattened Patches层,输出称为patch embeddings,也就是一个个的token(图中粉红色部分
),每个patch都会得到一个token,该层也可以称之为embedding层。
然后在这一系列的token前面加上一个新的token,专门用于分类的class token(图中粉红色带*的
)。
接着给这一系列的token加上位置信息Position embedding(图中紫色部分,0~9
)。
然后将这些所有的token输入到Transformer Encoder(图中灰色部分
)中,它的结构对应于右图的灰色部分。
最后提取class token 所对应的输出,通过MLP Head,得到最终分类的结果。
图片来源: ViT的动态过程
Embedding 层
对于标准的Transformer模块,要求输入的是token(向量)序列,即二维矩阵[num_token, token_dim]。
在代码实现中,直接通过一个卷积层来实现,以ViT-B/16为例,使用卷积核大小为16x16,stride为16,卷积核个数为768,则变化过程为:[224,224,3]--卷积->[14,14,768]--展平宽高->[196,768]
加上[class] token:采用拼接方法,cat([1,768],[196,768])->[197,768]
加上Position token:采用叠加方法(+),[197,768]->[197,768]
\
对于Position Embedding作者也有做一系列对比试验,在源码中默认使用的是1D Pos. Emb.
,对比不使用Position Embedding准确率提升了大概3个点,和2D Pos. Emb.
比起来没太大差别。
位置编码的可视化:
该图展示的是ViT-L/32的位置编码的余弦相似度
由图可知,输入图像被划分为 7×7 的 patch网格,每一个小图(tile)表示特定位置 patch 的位置嵌入和其他所有位置 patch 嵌入之间的 余弦相似度。余弦相似度范围为-1~1,黄色表示高相似度。
位置为(1,1)的小图中,其7x7的网格中第一个位置是黄色,说明它就是对应大图的(1,1)
Transformer Encoder
Transformer Encoder就是将Encoder Block重复堆叠L次
主要由以下几部分组成:
- Layer Norm,这种Normalization方法主要是针对NLP领域提出的,这里是对每个token进行Norm处理。
- Multi-Head Attention
- Dropout/DropPath,在原论文的代码中是直接使用的Dropout层,在但rwightman实现的代码中使用的是DropPath(stochastic depth),可能后者会更好一点。
- MLP Block,如图右侧所示,就是全连接+GELU激活函数+Dropout组成也非常简单,需要注意的是第一个全连接层会把输入节点个数翻4倍
[197, 768] -> [197, 3072]
,第二个全连接层会还原回原节点个数[197, 3072] -> [197, 768]
图片解释:最左边是原论文中Transformer Encoder的结构图;中间是Encoder Block的结构图,这是根据代码来的,更加详细;最右边是MLP Block的结构图
MLP Head
上面通过Transformer Encoder后输出的shape和输入的shape是保持不变的,以ViT-B/16为例,输入的是[197, 768]输出的还是[197, 768]。注意,在Transformer Encoder后其实还有一个Layer Norm没有画出来,后面有我自己画的ViT的模型可以看到详细结构。这里我们只是需要分类的信息,所以我们只需要提取出[class]token生成的对应结果就行,即[197, 768]中抽取出[class]token对应的[1, 768]。接着我们通过MLP Head得到我们最终的分类结果。MLP Head原论文中说在训练ImageNet21K时是由Linear+tanh激活函数+Linear组成。但是迁移到ImageNet1K上或者你自己的数据上时,只用一个Linear即可。
注意,在Transformer Encoder前有个Dropout层,后有一个LayerNorm;
训练ImageNet21K时是由Linear+tanh激活函数+Linear,但是迁移到ImageNet1K上或者你自己的数据上时,只有一个Linear
例子
Hybrid模型详解
在论文4.1章节的Model Variants中有比较详细的讲到Hybrid混合模型,就是将传统CNN特征提取和Transformer进行结合。下图绘制的是以ResNet50作为特征提取器的混合模型,但这里的Resnet与之前讲的Resnet有些不同。
- 首先这里的R50的卷积层采用的StdConv2d不是传统的Conv2d,
- 然后将所有的BatchNorm层替换成GroupNorm层。
- 在原Resnet50网络中,stage1重复堆叠3次,stage2重复堆叠4次,stage3重复堆叠6次,stage4重复堆叠3次,但在这里的R50中,把stage4中的3个Block移至stage3中,所以stage3中共重复堆叠9次。
通过R50 Backbone进行特征提取后,得到的特征矩阵shape是[14, 14, 1024],接着再输入Patch Embedding层,注意Patch Embedding中卷积层Conv2d的kernel_size和stride都变成了1,只是用来调整channel。后面的部分和前面ViT中讲的完全一样。
结果
对比ViT,Resnet(和刚刚讲的一样,使用的卷积层和Norm层都进行了修改)以及Hybrid模型的效果。通过对比发现,在训练epoch较少时Hybrid优于ViT,但当epoch增大后ViT优于Hybrid
ViT模型搭建参数
在论文的Table1中有给出三个模型(Base/ Large/ Huge)的参数,在源码中除了有Patch Size为16x16的外还有32x32的。
- 其中的Layers就是Transformer Encoder中重复堆叠Encoder Block的次数,
- Hidden Size就是对应通过Embedding层后每个token的dim(向量的长度),
- MLP size是Transformer Encoder中MLP Block第一个全连接的节点个数(是Hidden Size的四倍),
- Heads代表Transformer中Multi-Head Attention的heads数。
代码
源码解析:
Vision Transformer(ViT)PyTorch代码全解析(附图解)_vit代码-CSDN博客
【ViT系列(2)】ViT(Vision Transformer)代码超详细解读(Pytorch)_vit代码-CSDN博客
参考
11.1 Vision Transformer(vit)网络详解_哔哩哔哩_bilibili
Vision Transformer详解-CSDN博客