- 参考《Vision Transformer详解》、bilibili视频讲解
- 论文名称: An Image Is Worth 16x16 Words: Transformers For Image Recognition At Scale、论文源码
- PytorchAPI、Pytorch官方源码、Tensorflow2实现代码:tensorflow_classification/vision_transformer
- transformer原理可参考我的博文《多图详解attention和mask。从循环神经网络、transformer到GPT2》
Transformer最初提出是针对NLP领域的,并且在NLP领域大获成功。这篇论文也是受到其启发,尝试将Transformer应用到CV领域。过这篇文章的实验,给出的最佳模型在ImageNet1K
上能够达到88.55%的准确率,说明Transformer在CV领域确实是有效的,而且效果还挺惊人。
下图
Ours-JFT
表示在在Google自家的JFT数据集上进行了预训练
Ours-I21K
表示在ImageNet 21K上预训练,再在ImageNet 1K上测试。
在这篇文章中,作者主要拿ResNet
、ViT
(纯Transformer模型)以及Hybrid
(卷积和Transformer混合模型)三个模型进行比较,所以本博文除了讲ViT模型外还会简单聊聊Hybrid模型。
下图是原论文中给出的关于Vision Transformer(ViT)的模型框架。简单而言,模型由三个模块组成:
embedding
层
patchs
,然后这些patchs一个个输入Linear Projection of Flattened Patches
层(Embedding层)。比如ViT-L/16
表示每个patchs
大小是16×16。Transformer Encoder
层得到其输出。MLP Head
得到分类结果。对于标准的Transformer模块,要求输入的是token(向量)序列,即二维矩阵[num_token, token_dim]。对于图像数据而言,其数据为[H, W, C]格式的三维矩阵,明显不是Transformer想要的。所以需要先通过一个Embedding层来对数据做个变换。
ViT-B/16
为例(后面都是以此模型举例),将输入图片(224x224)按照16x16大小的Patch尺寸进行划分,划分后会得到 ( 224 / 16 ) 2 = 196 (224/16)^2=196 (224/16)2=196个Patches。在代码实现中,直接通过一个卷积层来实现。卷积核大小为16x16,步距为16,卷积核个数为768。通过卷积
[224, 224, 3] -> [14, 14, 768]
,然后把H以及W两个维度展平即可[14, 14, 768] -> [196, 768]
,此时正好变成了一个二维矩阵,正是Transformer想要的。
3. 加上[class]token以及Position Embedding。
在原论文中,作者说参考BERT,在刚刚得到的一堆tokens中插入一个专门用于分类的[class]token,这个[class]token是一个可训练的参数,数据格式和其他token一样也是768维向量。然后与之前从图片中生成的tokens拼接在一起,
Cat([1, 768], [196, 768]) -> [197, 768]
这里的Position Embedding采用的是一个可训练的参数(1D Pos. Emb.),是直接叠加在tokens上的(add),所以shape要一样,也是[197, 768]
对于Position Embedding
作者也有做一系列对比试验,在源码中默认使用的是1D Pos. Emb
.,对比不使用Position Embedding准确率提升了大概3个点,和2D Pos. Emb.
比起来没太大差别。
Transformer Encoder其实就是重复堆叠Encoder Block L次,主要由以下几部分组成:
[197, 768] -> [197, 3072]
,第二个全连接层会还原回原节点个数[197, 3072] -> [197, 768]
,原来跟transformer中做法一样。画法不一样啊,其实还是跟NLP中transformer的encoder结构差不多:(Feed Forword前馈网络就是类似MLP)
为什么Transformer用Layer Normalization而不是Batch Normalization?
- NLP领域输入的句子序列,在一个batch中是不定长的,即输入的一连串句子,每句话长度不等。通过pad之后在句子结尾补0(也可以是其它的pad字符),使一个batch保持等长。如果使用BN,等于不同句子之间在每列单词维度进行Nrom,就涉及到会将pad字符Norm的情况,这样明显是不对的。
- 对于文字序列来说,一个序列的输入同一”维度“上的信息可能不是同一个维度,比如下面两个句子,按BN方式,在第一”维度“进行归一化的话就是将”你“和”机“的特征进行归一化,但这明显不是一个维度的信息。显然BN在此处使用是很不合理的。NLP中同一batch样本的信息关联不大(差异很大,但要学习的就是这种特征),更多应该概率句子内部(单个样本内部)维度的归一化。
为什么MLP Block先升维再降维?
神经网络中线性连接可以写成 d l = W l ⋅ x d^l=W^{l}\cdot x dl=Wl⋅x。其中三者维度分别是m×1、m×n、n×1。
- m>n:升维,将特征进行各种类型的特征组合,提高模型分辨能力
- m
ViT-B/16
模型结构图 上面通过Transformer Encoder
后输出的shape和输入的shape是保持不变的,以ViT-B/16为例,输入的是[197, 768]输出的还是[197, 768]。对于分类,我们只需要提取出[class]token生成的对应结果就行,即[197, 768]
中抽取出[class]token
对应的[1, 768]
。
接着我们通过MLP Head得到我们最终的分类结果。MLP Head原论文中说在训练ImageNet21K时是由Linear+tanh激活函数+Linear组成。但是迁移到ImageNet1K上或者你自己的数据上时,只定义一个Linear即可。
下面是小绿豆绘制的ViT-B/16
模型结构图:(Pre-Logits就是Linear+tanh,一般迁移学习是可以不用的。)
在论文的Table1中有给出三个模型(Base/ Large/ Huge)的参数,在源码中除了有Patch Size为16x16的外还有32x32的。其中:
在论文4.1章节的Model Variants中有比较详细的讲到Hybrid
混合模型,就是将传统CNN特征提取和Transformer进行结合。下图绘制的是以ResNet50
作为特征提取器的混合模型,但这里的Resnet与之前讲的Resnet有些不同。
StdConv2d
不是传统的Conv2d
,然后将所有的BatchNorm
层替换成GroupNorm
层。如果有stage4就是下采样32倍,改为只有stage3就是下采样16倍,这样224×224的图片输出的就是14×14大小。
通过ResNet50 Backbone
进行特征提取后,得到的特征矩阵shape是[14, 14, 1024],接着再输入Patch Embedding层,注意Patch Embedding中卷积层Conv2d的kernel_size和stride都变成了1,只是用来调整channel。
后面的部分和前面ViT中讲的完全一样,就不在赘述。
简单说,ViT是图片经过Conv2d
卷积层得到Patchs,而Hybrid是多加了一步,图片经过ResNet50 Backbone
进行特征提取后,经过卷积得到Patchs。然后都是加上class token和位置向量输入Transformer Encoder,得到class token都是输出。再经过MLP得到分类结果。
下表是论文用来对比ViT,Resnet(和刚刚讲的一样,使用的卷积层和Norm层都进行了修改)以及Hybrid模型的效果。通过对比发现,在训练epoch较少时Hybrid优于ViT,但当epoch增大后ViT优于Hybrid。
项目代码github地址、B站代码讲解《使用pytorch搭建Vision Transformer(vit)模型》
class PatchEmbed(nn.Module):
"""
2D Image to Patch Embedding
"""
def __init__(self, img_size=224, patch_size=16, in_c=3, embed_dim=768, norm_layer=None):
super().__init__()
img_size = (img_size, img_size)
patch_size = (patch_size, patch_size)
self.img_size = img_size
self.patch_size = patch_size
self.grid_size = (img_size[0] // patch_size[0], img_size[1] // patch_size[1])
self.num_patches = self.grid_size[0] * self.grid_size[1] # 在VIT-B/16中就是16*16
self.proj = nn.Conv2d(in_c, embed_dim, kernel_size=patch_size, stride=patch_size)
# 默认不传入norm_layer, nn.Identity()表示不做任何操作
self.norm = norm_layer(embed_dim) if norm_layer else nn.Identity()
def forward(self, x):
B, C, H, W = x.shape # x就表示传入的图片
# 需要注意的是,VIT模型不像传统的CNN模型那样,可以更改图片的入网尺寸。
assert H == self.img_size[0] and W == self.img_size[1], \
f"Input image size ({H}*{W}) doesn't match model ({self.img_size[0]}*{self.img_size[1]})."
# flatten: [B, C, H, W] -> [B, C, HW],最后两个维度拉平
# transpose: [B, C, HW] -> [B, HW, C],后两个维度交换
x = self.proj(x).flatten(2).transpose(1, 2)
x = self.norm(x) # 得到 Patch Embedding
return x
class Attention(nn.Module):
def __init__(self,
dim, # 输入token的dim
num_heads=8, # 多头注意力使用几个head
qkv_bias=False, # 生成qkv时是否使用偏置
qk_scale=None,
attn_drop_ratio=0., # dropout概率,下同
proj_drop_ratio=0.):
super(Attention, self).__init__()
self.num_heads = num_heads
head_dim = dim // num_heads # 每个head的维度,一般要求dim能整除num_heads
self.scale = qk_scale or head_dim ** -0.5 # 不传入qk_scale时,self.scale=根号dk
self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias) # 使用一个全连接层得到qkv
self.attn_drop = nn.Dropout(attn_drop_ratio)
self.proj = nn.Linear(dim, dim) # 多头拼接后用Wo映射,Wo就是全连接层实现
self.proj_drop = nn.Dropout(proj_drop_ratio)
def forward(self, x):
# [batch_size, num_patches + 1, total_embed_dim]
# num_patches + 1就是196+1(class token),total_embed_dim=768
B, N, C = x.shape
# qkv(): -> [batch_size, num_patches + 1, 3 * total_embed_dim]
# reshape: -> [batch_size, num_patches + 1, 3, num_heads, embed_dim_per_head]
# permute: -> [3, batch_size, num_heads, num_patches + 1, embed_dim_per_head]
qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)
# [batch_size, num_heads, num_patches + 1, embed_dim_per_head]
q, k, v = qkv[0], qkv[1], qkv[2] # make torchscript happy (cannot use tensor as tuple)
# transpose: -> [batch_size, num_heads, embed_dim_per_head, num_patches + 1]
# @: multiply -> [batch_size, num_heads, num_patches + 1, num_patches + 1]
attn = (q @ k.transpose(-2, -1)) * self.scale
attn = attn.softmax(dim=-1)
attn = self.attn_drop(attn)
# @: multiply -> [batch_size, num_heads, num_patches + 1, embed_dim_per_head]
# transpose: -> [batch_size, num_patches + 1, num_heads, embed_dim_per_head]
# reshape: -> [batch_size, num_patches + 1, total_embed_dim]
x = (attn @ v).transpose(1, 2).reshape(B, N, C)
x = self.proj(x)
x = self.proj_drop(x)
return x
- 参考:太阳花的小绿豆帖子《Swin-Transformer网络结构详解》、讲解视频、代码地址、代码讲解视频
- 论文名称:Swin Transformer: Hierarchical Vision Transformer using Shifted Windows、官方开源代码
- Pytorch实现代码及Swin Transformer API、Tensorflow2实现代码
- 《Swin Transformer 论文详解及程序解读》
Swin transformer
(Shifted Windows移动窗口)是微软研究院于2021年3月25日发表,利用transformer架构处理计算机视觉任务,在图像分割,目标检测各个领域已经霸榜(之前的VIT只能做图片分类)。比如打开其官网代码可以看到:(这排名不是卷死?)
点开第一个coco测试集上的精度:
标准的Transformer直接用到视觉领域有一些挑战,即:
- Vision Transformer进行WSA计算时,任何一个patch都要与其他所有的patch都进行attention计算。当patch的大小固定时,计算量与图片的大小成平方增长。
- Swin Transformer中采用了W-MSA,只对window内部进行MSA,当图片大小增大时,计算量仅仅是呈线性增加。
所以最终Swin Transformer计算量比VIT更少,且在ImageNet上的效果也比VIT更好。
对比下Swin Transformer和之前的Vision Transformer,可以看出两点不同:
比如特征图尺寸中有对图像下采样4倍的,8倍的以及16倍的,这样的backbone有助于在此基础上构建目标检测,实例分割等任务。而在之前的Vision Transformer中是一开始就直接下采样16倍,且一直不变
比如在下图的4倍下采样和8倍下采样中,相对于Vision Transformer中直接对整个(Global)特征图进行Multi-Head Self-Attention,Swin Transformer只在窗口内做。这样能够减少计算量,尤其是在浅层特征图很大的时候。
这样做虽然减少了计算量,但也会隔绝不同窗口之间的信息传递。所以在论文中作者又提出了 Shifted Windows Multi-Head Self-Attention(SW-MSA)的概念,通过此方法能够让信息在相邻的窗口中进行传递,后面会细讲。
原论文中给出的关于Swin Transformer(Swin-T)网络的架构图如下:
假设输入的是RGB三通道图片,那么每个patch就有4x4=16个像素,然后每个像素有R、G、B三个值所以展平后是16x3=48,所以通过
Patch Partition
后图像shape由[H, W, 3]
变成了[H/4, W/4, 48]
。
线性变换时由48变成C。图像shape由[H/4, W/4, 48]
变成了[H/4, W/4, C]
。
其实在源码中Patch Partition
和Linear Embeding
就是直接通过一个卷积层实现的,和之前Vision Transformer中讲的 Embedding层结构一模一样。(kernel size=4×4,stride=4,num_kernel=48)
如图(b)中所示。这两种结构的不同之处仅在于一个使用了W-MSA结构,一个使用了SW-MSA结构。先使用一个W-MSA结构再使用一个SW-MSA结构。
接下来,在分别对Patch Merging
、W-MSA
、SW-MSA
以及使用到的相对位置偏置(relative position bias
)进行详解。关于Swin Transformer Block中的MLP结构和Vision Transformer中的结构是一样的,不再赘述。
上面讲了,在每个Stage中首先要通过一个Patch Merging层进行下采样(Stage1除外)。
如上图所示,假设输入的是一个4x4大小的单通道特征图(feature map),分割窗口大小为2×2,进行的操作如下:
可以看出,通过Patch Merging层后,feature map的高和宽会减半,深度会翻倍(channel由1变为2,高宽由4变为2)。
W-MSA也就是window Multi-heads Self-attention
模块。普通的Multi-heads Self-attention
会对feature map
中的每个像素(或称作token,patch)都要计算Self-Attention
。而W-MSA模块,首先将feature map按照MxM大小划分成一个个Windows,然后单独对每个Windows内部进行Self-Attention。
目的:减少计算量(下图可以看出两种方式分别是多少计算量)
缺点:窗口之间无法进行信息交互,等于减少了感受野,无法看到全局信息
具体的计算过程可以参考:太阳花的小绿豆帖子《Swin-Transformer网络结构详解》第三章
与W-MSA不同的地方在于这个模块存在滑动,所以叫做shifted window。滑动距离是window_size//2,方向是向右和向下。
滑动窗口是为了解决W-MSA计算attention时,窗口与窗口之间无法进行信息传递的问题。如下图所示,左侧是网络第L层使用的W-MSA模块,右侧是第L+1层使用SW-MSA模块。对比可以发现,窗口(Windows)发生了偏移。
比如对于第一行第2列的2x4的窗口,它能够使第L层的第一排的两个窗口信息进行交流。再比如,第二行第二列的4x4的窗口,他能够使第L层的四个窗口信息进行交流,其他的同理。那么这就解决了不同窗口之间无法进行信息交流的问题。
但是这样也有一个问题:偏移后,窗口数由原来的4个变成了9个,后面又要对每个窗口内部进行MSA,非常麻烦。为此,作者又提出而了Efficient batch computation for shifted configuration
,一种更加高效的计算方法。(论文中是上图下部分图示)
小绿豆感觉上图不够明晰,又重新画了个。下图左侧是刚刚通过偏移窗口后得到的新窗口,右侧是为了方便大家理解,对每个窗口加上了一个标识。
Efficient batch computation for shifted configuration
简单说,就是通过移动合并,将9个窗口还是变为原来的4个窗口,再进行Masked MSA计算。计算量和普通的MSA一样,只是多了个mask。
Masked MSA是为了解决合并窗口中各个窗口应该单独计算的问题。使用Masked MSA,将不同窗口元素计算的attention score-100,等价于屏蔽掉不同窗口元素的attention结果。最终达到了4个窗口同时进行MSA计算,又保证得到只在在窗口内进行计算的效果。
论文中还提到,使用相对位置偏置后给够带来明显的提升。(下图B就是偏置,ADE20k是图片分割数据集)
上图蓝色框是只使用W-MSA和同时使用W-MSA&SW-MSA的效果对比,后者更好。红色框是使用相对位置偏置之后的结果,效果更好。下面具体讲解什么是Relative Position Bias(假设特征图大小为2×2)
计算相对位置索引:比如蓝色像素,在蓝色像素使用q与所有像素k进行匹配过程中,是以蓝色像素为参考点。然后用蓝色像素的绝对位置索引与其他位置索引进行相减,就得到其他位置相对蓝色像素的相对位置索引,同理可以得到其他位置相对蓝色像素的相对位置索引矩阵(第一排四个位置矩阵)。
展平拼接:将每个相对位置索引矩阵按行展平,并拼接在一起可以得到第二排的这个4x4矩阵。
索引转换为一维:在源码中作者为了方便把二维索引给转成了一维索引。
取出相对位置偏置参数。真正使用到的可训练参数B 是保存在relative position bias table表里的,其长度是等于 ( 2 M − 1 ) × ( 2 M − 1 ) (2M-1) \times (2M-1) (2M−1)×(2M−1)。相对位置偏置参数B,是根据相对位置索引来查relative position bias table表得到的,如下图所示。
为啥表长是 ( 2 M − 1 ) × ( 2 M − 1 ) (2M-1) \times (2M-1) (2M−1)×(2M−1)?考虑两个极端位置,(0,0)能取到的相对位置极值为(-1,-1),(-1,-1)能取到的极值是(1,1),即行和列都能取到(2M-1)个数。考虑到所有的排列组合,表的长度就是 ( 2 M − 1 ) × ( 2 M − 1 ) (2M-1) \times (2M-1) (2M−1)×(2M−1)
以Swin-T举例:
Patch Partition
和Linear Embeding
层,其效果和Patch Merging
层一样,都是进行下采样和调整channel。 concat4×4,96d,LN表示宽高都下采样4倍,调整后channel数为96,再经过一个Layer Norm层。Swin Transformer Block
,其中:
win. sz. 7x7
表示使用的窗口的大小(window size)dim
表示这个层输出的feature map的channel深度(或者说token的向量长度)head
表示多头注意力模块中head的个数W-MSA
和SW-MSA
。代码地址、讲解视频
论文名称:MobileViT: Light-Weight, General-Purpose, and Mobile-Friendly Vision Transformer
参考小绿豆的博文《MobileViT模型简介》
官方源码(Pytorch实现)、小绿豆的项目代码
自从2020年ViT(Vision Transformer)模型的横空出世,人们发现了Transformer架构在视觉领域的巨大潜力,视觉领域的各项任务也不断被Transformer架构模型刷新。同时其缺点也很明显,模型参数太大(比如ViT Large Patch16模型光权重就有1个多G),算力要求太高,这基本就给移动端部署Transformer模型判了死刑。
Apple公司在2021年发表的一篇CNN与Transfomrer的混合架构模型——MobileViT:CNN的轻量和高效+Transformer的自注意力机制和全局视野。
纯Transformer架构除了模型太重,还有一些其他的问题,比如:
比如在Imagenet上预训练好的网络(224x224),直接对位置偏置进行插值不去微调,在384x384的尺度上进行验证可能会掉点(CNN一般会涨点)。
针对以上问题,现有的、最简单的方式就是采用CNN与Transformer的混合架构。
Tranaformer
的模型(无论是否为混合架构)推理速度比纯CNN的模型还是要慢很多的(移动端)。作者在论文中给出解释主要还是说当前移动端对Transformer架构优化的还太少。Vision Transformer结构
在讲MobileViT网络之前先简单回顾下Vision Transformer的网络结构。下图是MobileViT
论文中绘制的Standard visual Transformer
。
Transformer Block
得到输出MobileViT结构
下图对应的是论文中的图1(b),可以看到MobileViT
主要由普通卷积,MV2
,MobileViT block
,全局池化以及全连接层共同组成。
MV2
即MobiletNetV2
中的Inverted Residual block
,在本文4.3.1 Inverted residual block
中有详细讲解。MV2
结构代表stride=2的情况,即需要进行下采样。MV2
结构,有shortcut连接(输入输出size相同)nxn
(代码中是3x3)的卷积层来完成Unfold
-> Transformer
-> Fold
结构来完成shortcut
:捷径分支与原始输入特征图沿通道方向拼接进行Concat拼接nxn
(代码中是3x3)的卷积层做特征融合得到输出。 为了方便我们将Unfold -> Transformer -> Fold
简写成Global representations
,下面对此进行介绍。
- 计算量降为原来的1/4。原始的Self-Attention计算,每个Token是需要和所有的Token进行Attention.
- 为什么能这么做?
Unfold/Fold
:只是为了将数据reshape成计算Self-Attention时所需的数据格式。
大的patch_size能够提升网络推理速度,但是会丢失一些细节信息。论文中给的图8,展示了两组不同的patch_size组合,在图像分类,目标检测以及语义分割三个任务中的性能。
下采样倍率为8,16,32的特征图所采用的patch_size大小分别为:
在论文中,关于MobileViT作者提出了三种不同的配置,分别是:MobileViT-S(small),MobileViT-XS(extra small)和MobileViT-XXS(extra extra small),三者的主要区别在于特征图的通道数不同。下图中的标出的Layer1~5,这里是根据源码中的部分配置信息划分的。
MobileViT-XXS,Layer1~5的详细配置信息如下:
layer out_channels mv2_exp transformer_channels ffn_dim patch_h patch_w num_heads
layer1 16 2 None None None None None
layer2 24 2 None None None None None
layer3 48 2 64 128 2 2 4
layer4 64 2 80 160 2 2 4
layer5 80 2 96 192 2 2 4
MobileViT-XS,Layer1~5的详细配置信息如下:
layer out_channels mv2_exp transformer_channels ffn_dim patch_h patch_w num_heads
layer1 32 4 None None None None None
layer2 48 4 None None None None None
layer3 64 4 96 192 2 2 4
layer4 80 4 120 240 2 2 4
layer5 96 4 144 288 2 2 4
对于MobileViT-S,Layer1~5的详细配置信息如下:
layer out_channels mv2_exp transformer_channels ffn_dim patch_h patch_w num_heads
layer1 32 4 None None None None None
layer2 64 4 None None None None None
layer3 96 4 144 288 2 2 4
layer4 128 4 192 384 2 2 4
layer5 160 4 240 480 2 2 4
其中:
- 参考博文《MobileNet(v1、v2)网络详解与模型的搭建》、《轻量级神经网络MobileNet全家桶详解》
- 模型讲解视频 《MobileNet(v1,v2)网络详解视频》、《MobileNetv3网络详解》
- github代码地址、代码讲解《使用pytorch搭建MobileNetV2》、《使用Pytorch搭建MobileNetV3》、PyTorch API
传统的CNN网络已经普遍应用在计算机视觉领域,并且已经取得了不错的效果。但是发展到现在,模型深度越来越深,模型越来越复杂,预测和训练需要的硬件资源也逐步增多,导致无法在移动设备以及嵌入式设备上运行。
深度学习领域内也在努力促使神经网络向小型化发展。在保证模型准确率的同时体积更小,速度更快。2016年直至现在,业内提出了SqueezeNet、ShuffleNet、NasNet、MnasNet以及MobileNet等轻量级网络模型,这些模型使移动终端、嵌入式设备运行神经网络模型成为可能。
MobileNet在轻量级神经网络中较具代表性,特别是谷歌在2019年5月份推出了最新的MobileNetV3。在ImageNet数据集上,最新的MobileNetV3-Large的Top1准确率达到75.2%。本章将对MobileNet进行详细解析。
MobileNet系列模型性能对比:
MobileNet可以在移动终端实现众多的应用,包括目标检测,目标分类,人脸属性识别和人脸识别等。
MobileNetV1网络类似于VGG,各个block串行连接,只不过在传统卷积层基础上加入了Depthwise Separable Convolution(深度可分卷积)。
为了适配更定制化的场景,MobileNet引入了两个超参数: 宽度因子width multiplier和分辨率因子resolution multiplier,分别记为α和β:
MobileNet网络基本单元是深度可分离卷积(depthwise separable convolution) ,可以大大减少运算量和参数数量,是其一大亮点。
计算量对比
深度可分卷积与传统的卷积相比有到底能节省多少计算量呢,下图对比了这两个卷积方式的计算量。
卷积计算量≈卷积核宽高×输入channel×卷积核个数×输出矩阵宽高。假设stride=1,输入输出特征矩阵大小一样,宽高都是Df。则有:
右边表格是对比MobileNetV1和其它网络,以及使用不同的α、β参数时的准确率、计算量和参数量。其中:
在MobileNet v1的网络结构表中能够发现,网络的结构就像VGG一样是个直筒型的,没有残差结构。而且网络中的DW卷积很容易训练废掉,效果并没有那么理想。MobileNet v2网络是由google团队在2018年提出的,相比MobileNet V1网络,准确率更高,模型更小。
如果说MobileNet v1网络中的亮点是DW卷积,那么在MobileNet v2中的亮点就是Inverted residual block(倒残差结构)
Linear Bottleneck
线性瓶颈结构。 MobileNetV2
网络模型中有共有17个Bottleneck
层(每个Bottleneck包含两个pw卷积层和一个dw卷积层),一个标准卷积层(conv),两个pw conv组成,共计有54层可训练参数层。
MobileNetV2中使用线性瓶颈和Inverted Residuals结构优化了网络,使得网络层次更深了,但是模型体积更小,速度更快了。
按照上一节讲的,只有当stride=1且输入特征矩阵与输出特征矩阵shape相同时才有shortcut连接。所以上图红色框选的模块:
MobileNet的提出,基本实现了在移动设备和嵌入式设备上跑深度学习模型了。
github项目地址
MobileNetv3
是Google在2019年提出的,主要有三点比较重要:
在ImageNet数据集上,V3-Large 1.0
和V2 1.0
的Acc分别是75.2%和72.0%,前者推理速度快了20%。(MAdds是计算量,P-1、P-2、P-3是不同手机上的推理速度。这里的1.0是上面讲的宽度因子α)
SE模块首次提出是在2017年的Squeeze-and-Excitation Networks(SENet)
网络结构中,在MobileNetV3
中进行改进并大量使用。
研究人员期望通过精确的建模卷积特征各个通道之间的作用关系来改善网络模型的表达能力。为了达到这个期望,提出了一种能够让网络模型对特征进行校准的机制,使得有效的权重大,无效或效果小的权重小的效果,这就是SE模块。
MobileNetV3
的SE模块由一个全局平均池化层,两个全连接层组成。
Squeeze
:将输入矩阵的每个channel都进行池化处理得到一维向量。向量维度=输入矩阵channelExcitation
:再经过两个全连接层得到每个channel的权重。scale
:将SE模块输入矩阵与通道权重相乘,得到最终输出下面举一个简单的例子说明:
如上图所示,输入矩阵有两个channel(黄色和蓝色),池化后一维向量有两个元素[0.25,0.3]。将其经过两个Linear层处理得到两个channel的权重[0.5,0.6]。将这个权重每个元素分别和输入矩阵各个channel相乘,得到SE模块的最终输出。
MobileNetV3
更新了block,论文中称为bneck,其改动如下:
在V2中,Inverted residual block
基本都是使用的RELU6
激活函数。但在V3中,对其进行改进。如下图所示:
swish
:可以提高Acc,但是求导、计算复杂,而且量化过程不友好(移动端一般都会对计算过程量化)h-sigmoid
激活函数:下图可见,其曲线接近于sigmoid函数。h-swish
激活函数:将公式中的sigmoid激活函数用h-sigmoid激活函数替代,则swish
替换为h-swish
,sigmoid
替换为h-sigmoid
之后,推理速度得到提升,而且对量化过程比较友好。对耗时层结构改进了两点:
1. NAS简介
V3的特别之处在于网络结构是使用基于神经架构搜索技术(NAS)学习出来的,使得V3得以将其他很多网络中的优秀特性都集于一身,而在V3之前的V1、V2版本中,网络结构则是由研究人员经过人工设计和计算得到的。本节仅介绍MobileNetV3与NAS的一些相关内容,不对NAS的技术细节展开。
神经架构搜索(Network Architecture Search, NAS
):是一种试图使用机器,自动的在指定数据集的基础上通过某种算法,找到在此数据集上效果最好的神经网络结构和超参数的方法 ,可以一定程度上解决研究人员设计一个网络的耗时的问题。NAS甚至可以发现某些人类之前未曾提出的网络结构,这可以有效的降低神经网络的使用和实现成本。
目前市面上也已经出现了许多运用NAS技术的产品,如AutoML、OneClick.AI、Custom Vision Service、EasyDL等。在之前一些网络模型中,如NasNet、MNasNet、AmoebaNet、MobileNet,也使用了NAS技术。
2. NAS原理
搜索空间,搜索策略,性能评估策略是NAS算法的核心要素。
对这些要素的不同实现得到了各种不同的NAS算法。
3. MobileNetV3中的NAS
platform-aware NAS
搜索全局网络结构的优化block,也就是从搜索空间的集合中根据预定义的网络模板搜索出网络结构;NetAdapt
算法针对block中的每一层搜索需要使用的卷积核个数。MobileNetv3
有small和large两个版本。
out
:输出矩阵通道数HL
:非线性激活函数。HS表示h-swish
,RE表示RELU激活函数bneck 3×3
:bneck就是左侧图结构,3×3是dw卷积核的大小exp size
:bneck第一个卷积层升维后的channel数。经过dw卷积核SE模块后通道数不变。最后经过pw卷积进行降维,降维后的维度就是outSE
:是否使用SE模块NBN
:最后两个卷积层不使用Batch Norm参考博文《ConvNeXt网络详解》、模型讲解视频;代码地址、代码讲解视频;PyTorch API
自从ViT(Vision Transformer)
在CV领域大放异彩,越来越多的研究人员开始拥入Transformer的怀抱。回顾近一年,在CV领域发的文章绝大多数都是基于Transformer的,比如2021年ICCV 的best paper Swin Transformer
,而卷积神经网络已经开始慢慢淡出舞台中央。
2022年一月份,Facebook AI Research和UC Berkeley一起发表了一篇文章A ConvNet for the 2020s
,在文章中提出了ConvNeXt
纯卷积神经网络,它对标的是2021年非常火的Swin Transformer
。
通过一系列实验比对,在相同的FLOPs下,ConvNeXt
相比Swin Transformer
拥有更快的推理速度以及更高的准确率,在ImageNet 22K上ConvNeXt-XL达到了87.8%的准确率,参看下图:
上图红色框选这一栏是在A100上,每秒推理图片的数量。不仅是在ImageNet上,在coco数据集和ADE20K数据集上的效果也更好。
阅读论文会发现,ConvNeXt
使用的全部都是现有的结构和方法,没有任何结构或者方法的创新。而且源码也非常的精简,100多行代码就能搭建完成,相比Swin Transformer
简直不要太简单。
为什么现在基于Transformer架构的模型效果比卷积神经网络要好呢?论文中的作者认为可能是随着技术的不断发展,各种新的架构以及优化策略促使Transformer模型的效果更好,那么使用相同的策略去训练卷积神经网络也能达到相同的效果吗?抱着这个疑问作者就以Swin Transformer作为参考进行一系列实验。
作者首先利用训练vision Transformers
的策略去训练原始的ResNet50
模型,发现在ImageNet上的Top 1 ACC=78.8%,比原始效果要好很多,并将此结果作为后续实验的基准baseline。
然后作者罗列了接下来实验包含哪些部分,以及每个方案对最终结果的影响(Imagenet 1K的准确率,见下图)。很明显最后得到的ConvNeXt在相同FLOPs下准确率(82%)已经超过了Swin-T(81.3%)。接下来,针对每一个实验进行解析。
ConvNeXt也有四个不同大小的版本,ConvNeXt 50对标的就是Swin-T,下图准确率也是这两者的对比数据。
Our starting point is a ResNet-50 model. We first train it with similar training techniques used to train vision Transformers and obtain much improved results compared to the original ResNet-50. This will be our baseline. We then study a series of design decisions which we summarized as 1) macro design, 2) ResNeXt, 3) inverted bottleneck, 4) large kernel size, and 5) various layer-wise micro designs.
在这个部分作者主要研究两方面:
Changing stage compute ratio:
ResNet50
中stage1到stage4堆叠block的次数是(3, 4, 6, 3)比例大概是1:1:2:1Swin Transformer
中,比如Swin-T的比例是1:1:3:1,Swin-L的比例是1:1:9:1。很明显,在Swin Transformer中,stage3堆叠block的占比更高。78.8%
提升到了79.4%
。Changing stem to “Patchify”
Swin Transformer
中,采用的是一个卷积核大小为4x4步距为4的卷积层构成patchify
,同样是下采样4倍。 接下来作者借鉴了ResNeXt
中的组卷积grouped convolution
,而作者采用的是更激进的depthwise convolution
,即group数和通道数channel相同。这样做的另一个原因是作者认为depthwise convolution
和self-attention
中的加权求和操作很相似。
grouped convolution
参考《学习笔记五:卷积神经网络原理、常见模型》3.8章节Resnext、Resnext讲解视频。
depthwise convolution
参考《MobileNet(v1、v2)网络详解与模型的搭建》、讲解视频。简单讲就是每个卷积核的channel=1。
如上图所示,ResNet和ResNeXt唯一区别就是将残差模块中间的普通卷积层替换为了组卷积层。但这个模块都是两头粗中间细的瓶颈结构(ResNet首尾两层channel=256,中间层channel=64。ResNeXt三层通道数分别是256,128,256)。
79.5%
降到78.3%
,FLOPs差不多降低了一半。80.5%
ResNet
第一个stage输入特征层channel=64,而在Swin-T
中是96。所以作者将最初的通道数由64调整成96,和Swin Transformer保持一致,最终准确率达到了80.5%
。 作者认为Transformer block
中的MLP
模块非常像MobileNetV2
中的Inverted Bottleneck
模块,即两头细中间粗。所以将ResNet
中采用的Bottleneck
模块替换为Inverted Bottleneck
模块。在较大模型上acc由81.9%提升到82.6%。
ResNet
中采用的Bottleneck
模块,是一个两头粗中间细的瓶颈结构MobileNetV2
采用的Inverted Botleneck
模块(图b的最后一个1x1的卷积层画错了,应该是384->96,后面如果作者发现后应该会修正过来)ConvNeXt
采用的是Inverted Bottleneck
模块。关于MLP模块可以参考本文1.2.4 MLP Head,关于Inverted Bottleneck模块可以参考之前讲的MobileNetv2博文。
Large Kernel Sizes部分的两个改动是Moving up depthwise conv layer
(depthwise conv
模块上移)和Increasing the kernel size
,如下图所示:
depthwise conv
模块上移。上移后,准确率下降到了79.9%,同时FLOPs也减小了。这么做是因为作者认为depthwise conv
类似于Transformer中的MSA,而在transformer中,MSA模块是放在MLP模块之前的,所以这里进行效仿,将depthwise conv上移。79.9%
(3×3) 增长到 80.6%
(7×7)。
Vision Transformer
。即使是Swin Transformer
也有7x7大小的窗口,所以作者将kernel size增大为7×7. 接下来作者在聚焦到一些更细小的差异,比如激活函数以及Normalization。
将CNN常用的激活函数ReLU
替换为GELU
(transformer),替换后准确率没变化。
使用更少的激活函数。在卷积神经网络中,一般会在每个卷积层或全连接后都接上一个激活函数。但在Transformer中并不是每个模块后都跟有激活函数,比如MLP中只有第一个全连接层后跟了GELU激活函数。所以作者在ConvNeXt Block中也减少激活函数的使用,如下图所示,减少后发现准确率从80.6%增长到81.3%
使用更少的Normalization。同样在Transformer中,Normalization使用的也比较少,接着作者也减少了ConvNeXt Block中的Normalization层,只保留了depthwise conv后的Normalization层。此时准确率已经达到了81.4%,已经超过了Swin-T。
将BN替换成LN,准确率小幅提升到81.5%。卷积神经网络中使用的是Batch Normalization(加速网络的收敛并减少过拟合),但在Transformer中基本都用的Layer Normalization(LN)。因为最开始Transformer是应用在NLP领域的,BN又不适用于NLP相关任务。
单独的下采样层(Separate downsampling layers),更改后准确率就提升到了82.0%。
ResNet网络中下采样方式并不合理
在Swin Transformer中,下采样是通过一个单独的Patch Merging
实现的。
作者就为ConvNext网络单独使用了一个下采样层,就是通过Laryer Norm+Conv 2×2,stride=2
的卷积层构成。
下面是小绿豆手绘的ConvNeXt模型结构图。仔细观察ConvNeXt Block
会发现其中还有一个Layer Scale
操作(即对每个channel的数据进行缩放,论文中并没有提到)。它将输入的特征层乘上一个可训练的参数,该参数就是一个向量,元素个数与特征层channel相同。
Layer Scale操作出自于Going deeper with image transformers. ICCV, 2021这篇文章,有兴趣的可以自行了解。
对于ConvNeXt网络,作者提出了T/S/B/L
四个版本,计算复杂度好和Swin Transformer中的T/S/B/L相似。其中C代表4个stage中输入的通道数,B代表每个stage重复堆叠block的次数。
参考ConvNeXt代码地址、代码讲解视频
- 原论文名称:EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks、源码地址
- 小绿豆实现的 pytorch代码、 tensorflow代码、代码讲解视频。博客《EfficientNet网络详解》、模型讲解视频
在之前的一些手工设计网络中(AlexNet,VGG,ResNet等等)经常有人问,为什么输入图像分辨率要固定为224,为什么卷积的个数要设置为这个值,为什么网络的深度设为这么深?这些问题你要问设计作者的话,估计回复就四个字——工程经验。
而这篇论文主要是用NAS(Neural Architecture Search)
技术来搜索网络的图像输入分辨率r
,网络的深度depth
以及channel的宽度width
三个参数的合理化配置。
论文中提到,本文提出的EfficientNet-B7
在Imagenet top-1
上达到了当年最高准确率84.3%
,与之前准确率最高的GPipe相比,参数数量(Params)仅为其1/8.4,推理速度提升了6.1倍(看上去又快又轻量,但个人实际使用起来发现很吃显存)。下图是EfficientNet与当时主流的网络的对比(注意,参数数量少并不意味推理速度就快)。
下图虚线是分别增加width、depth、resolution后的结果,可以看到Accuracy达到80%时就趋于饱和了。而红色实线是三者同时增加,同计算量下,效果更好。
那么该如何同时增加width、depth和分辨率呢?
d
用来缩放深度 L ^ i \widehat{L}_i L ir
用来缩放分辨率即影响H ^ i H ^ i \widehat{H}_i H i和 W ^ i \widehat{W}_i W iw
就是用来缩放特征矩阵的channel即 C ^ i \widehat{C}_i C itarget_memory
为memory限制target_flops
为FLOPs限制compound scaling method
。在这个方法中使用了一个混合因子 ϕ \phi ϕ去统一的缩放width,depth,resolution参数,具体的计算公式如下(s.t.代表限制条件): d e p t h : d = α ϕ w i d t h : w = β ϕ r e s o l u t i o n : r = γ ϕ ( 3 ) s . t . α ⋅ β 2 ⋅ γ 2 ≈ 2 α ≥ 1 , β ≥ 1 , γ ≥ 1 depth: d={\alpha}^{\phi} \\ width: w={\beta}^{\phi} \\ \ \ \ \ \ \ resolution: r={\gamma}^{\phi} \ \ \ \ \ \ \ \ \ \ (3) \\ s.t. \ \ \ \ \ \ \ {\alpha} \cdot {\beta}^{2} \cdot {\gamma}^{2} \approx 2 \\ \alpha \geq 1, \beta \geq 1, \gamma \geq 1 depth:d=αϕwidth:w=βϕ resolution:r=γϕ (3)s.t. α⋅β2⋅γ2≈2α≥1,β≥1,γ≥1
其中:
下表为EfficientNet-B0的网络框架,网络总共分成了9个Stage(B1-B7就是在B0的基础上修改Resolution,Channels以及Layers):
Stage1
:就是一个卷积核大小为3x3步距为2的普通卷积层(包含BN和激活函数Swish)Stage2~Stage8
:都是在重复堆叠MBConv结构(最后一列的Layers表示该Stage重复MBConv结构多少次)Stage9
:由一个普通的Conv 1×1(包含BN和激活函数Swish)、Pooling层和全连接层组成。
如图所示,MBConv
(Mobile Conv)结构主要以下几个部分组成:
Conv 1×1,stride=1
:普通卷积层,作用是升维,包含BN和Swish;卷积核个数是输入特征矩阵channel的n倍。(上面提的倍率因子n,当n=1时不需要这一层)
Depthwise Conv
卷积,kernel size可以是3x3和5x5,也包含BN和Swish。
SE
模块:由一个全局平均池化,两个全连接层组成。
Conv 1×1,stride=1
:普通卷积层,作用是降维
Droupout
层
需要注意的是:
EfficientNetB0网络结构上面已经给出,其他版本的详细参数可见下表:
input_size
:训练网络时输入网络的图像大小width_coefficient
:代表channel维度上的倍率因子,比如在 EfficientNetB0中Stage1的3x3卷积层所使用的卷积核个数是32,那么在B6中就是 32 × 1.8 = 57.6 32 \times 1.8=57.6 32×1.8=57.6,接着取整到离它最近的8的整数倍即56,其它Stage同理。depth_coefficient
:代表depth维度上的倍率因子(仅针对Stage2到Stage8),比如在EfficientNetB0中Stage7的L L ^ i = 4 {\widehat L}_i=4 L i=4 ,那么在B6中就是 4 × 2.6 = 10.4 4 \times 2.6=10.4 4×2.6=10.4接着向上取整即11。drop_connect_rate
:在MBConv结构中dropout层使用的drop_rate,在官方keras模块的实现中MBConv结构的drop_rate是从0递增到drop_connect_rate的(具体实现可以看下官方源码,注意,在源码实现中只有使用shortcut的时候才有Dropout层)。dropout_rate
是最后一个全连接层前的dropout层(在stage9的Pooling与FC之间)的dropout_rate。最后给出原论文中关于EfficientNet与当时主流网络的性能参数对比:
参考:
- 《EfficientNetV2网络详解》、bilibil视频、项目地址
- 《论文推荐:EfficientNetV2 - 通过NAS、Scaling和Fused-MBConv获得更小的模型和更快的训练速度》
EfficientNetV2
是在2021年4月份发布的,主要是做了两点改进:
MBConv
主分支中的expansion conv1x1和depthwise conv3x3替换成一个普通的conv3x3。替换后参数更少,训练更快。
EfficientNetV2-M
对比EfficientNetV1-B7
,训练速度提升11倍,参数数量减少为1/6.8。
对比传统卷积神经网络/混合网络以及ViT
网络:
EfficientNetV2-S
对标的就是EfficientNet=B5
,可以发现其训练和推理速度更快。
对比EfficientNetV1
:
在EfficientNetV1
中作者关注的是准确率,参数数量以及FLOPs(理论计算量小不代表推理速度快),在EfficientNetV2
中作者进一步关注模型的训练和推理速度。
作者系统性的研究了EfficientNet的训练过程,并总结出了三个问题:
训练图像的尺寸很大时,训练速度非常慢。
网络浅层中使用Depthwise convolutions速度会很慢,故引入Fused-MBConv
。
Fused-MBConv
结构也非常简单,即将原来MBConv
主分支中的expansion conv1x1和depthwise conv3x3替换成一个普通的conv3x3,如下图左侧所示。
下表是不同stage的MBConv
替换为Fused-MBConv
的准确率和训练速度。作者使用NAS技术去搜索最佳组合,将浅层(stage1-3)替换为Fused-MBConv
时效果最好。
非均匀缩放stage
在EfficientNetV1
中,每个stage的深度和宽度都是同等放大的(同等的看待每个stage)。但每个stage对网络的训练速度以及参数数量的贡献并不相同,所以直接使用同等缩放的策略并不合理。在这篇文章中,作者采用了非均匀的缩放策略来缩放模型(论文中没有给出策略,直接给出缩放后的参数)。
V1、V2框架的不同点
表4展示了作者使用NAS搜索得到的EfficientNetV2-S模型框架,相比与EfficientNetV1,主要有以下不同:
Fused-MBConv
模块(stage1-3)
expansion ratio
就是上一节提到的倍率因子n,也就是下表中的MBConv4中的4,表示MBConv模块第一层会将输入通道数增大为原来的4倍。而在V1中,基本都是6。
EfficientNetV1
中的stage8(stride=1)。Operator
:在当前Stage中使用的模块
stride
:该Stage第一个 Operator
的步距,剩下的 Operator
步距都是1(也就是每个模块只有第一层可能会下采样,后面层不变)
Channels
:该Stage输出的特征矩阵的Channels
Layers
:该Stage重复堆叠Operator的次数
Conv3x3
:普通的3x3卷积 + 激活函数(SiLU)+ BN
Fused-MBConv1,k3×3
:1表示expansion ratio
,k表示kernel_size。
MBConv
模块和EfficientNetV1中是一样的。SE0.25表示使用了SE模块,且模块中第一个全连接层的节点个数,是输入特征矩阵channels的1/4 。(当stride=1且输入输出Channels相等时,才有shortcut连接和Stochastic Depth,详细的可以参考本文《4.1.4 MBConv结构》)
unbalanced regularization
导致的。在训练不同尺寸的图像时,应该使用动态的正则方法(之前都是使用固定的正则方法)。 于是作者接着做了一些实验,训练过程中尝试使用不同的图像尺寸以及不同强度的数据增强。当训练的图片尺寸较小时,使用较弱的数据增强;当训练的图像尺寸较大时,使用更强的数据增强。如下表所示,当Size=128,RandAug magnitude=5时效果最好;当Size=300,RandAug magnitude=15时效果最好:
基于以上实验,作者就提出了渐进式训练策略Progressive Learning
。如上图所示:
训练流程的伪代码:
下表给出了EfficientNetV2(S,M,L)三个模型的渐进学习策略参数:
作者还在Resnet以及EfficientNetV1上进行了测试,使用了渐进式学习策略后确实能够有效提升训练速度并且能够小幅提升Accuracy。
EfficientNetV2-S
:在baseline的基础上采用了width倍率因子1.4, depth倍率因子1.8得到的(block里不包含stage0这个普通卷积层)。
其它模型配置参考见小绿豆的文章,这里就不写了。其它训练参数:
# (block, width, depth, train_size, eval_size, dropout, randaug, mixup, aug)
efficientnetv2_params = {
'efficientnetv2-s': # 83.9% @ 22M
(v2_s_block, 1.0, 1.0, 300, 384, 0.2, 10, 0, 'randaug'),
'efficientnetv2-m': # 85.2% @ 54M
(v2_m_block, 1.0, 1.0, 384, 480, 0.3, 15, 0.2, 'randaug'),
'efficientnetv2-l': # 85.7% @ 120M
(v2_l_block, 1.0, 1.0, 384, 480, 0.4, 20, 0.5, 'randaug'),
}