1、MoblieNetV1
2017年提出,论文地址为:https://arxiv.org/pdf/1704.04861.pdf
1.1 相关知识
提到了标准卷积、深度可分卷积、点卷积,并分析了不同卷积结构的计算量,(假设 D k D_k Dk为ksize,M为卷积的输入层通道数,N为卷积的输出层通道数, D f D_f Df位feature map的size)。
标准卷积涉及到的kernel数量为M*N(其卷积运算涉及参数共享、局部视野感知,卷积kernel内的全连接涉及channel间的交互融合,多个卷积核涉及多视角提取特征
),即针对每一个输出(共N个)都采用M个卷积去做运算(本质就是实现channel间的全连接,每一个连接线都要进行卷积运算
),M个卷积的结果加到一起得到一个输出,每一个输出都有一个偏置项。标准卷积的计算量如下(不考虑偏置项):
D k ∗ D k ∗ M ∗ N ∗ D f ∗ D f D_k*D_k*M*N*D_f*D_f Dk∗Dk∗M∗N∗Df∗Df
深度卷积与标准卷积不同,其kernel数量与输入数据的channel相同,其不改变输入数据的通道数。针对每一个输入(共M个),都采用1个卷积去做运算(一个输出对应一个卷积,实现输入与输出的一一对应
)。故深度可分卷积的计算量如下:
D k ∗ D k ∗ M ∗ D f ∗ D f D_k*D_k*M*D_f*D_f Dk∗Dk∗M∗Df∗Df
点卷积是一种特殊的标准卷积,其ksize为1x1,也就是说其不对数据进行卷积运算,只是对数据进行缩放和平移,并实现通道间的数据交互。故点卷积的计算量如下:
M ∗ N ∗ D f ∗ D f M*N*D_f*D_f M∗N∗Df∗Df
1.2 标准卷积与深度可分离卷积
MobliNetV1认为标准卷积的运算量太大,其本质就是卷积运算提取图像特征,卷积核内进行全连接实现channel间特征的交互融合,故认为可以使用 深度卷积+点卷积 来近似 标准卷积,并将这种结合定义为深度可分离卷积。故此,深度可分离卷积的运算量如下:
D k ∗ D k ∗ M ∗ D f ∗ D f + M ∗ N ∗ D f ∗ D f D_k*D_k*M*D_f*D_f+M*N*D_f*D_f Dk∗Dk∗M∗Df∗Df+M∗N∗Df∗Df
传统卷积的计算示意图如下:
传统卷积在卷积核为1x1时的计算示意图如下
深度可分离卷积与普通卷积的运算量对比如下:
D k ∗ D k ∗ M ∗ D f ∗ D f + M ∗ N ∗ D f ∗ D f D k ∗ D k ∗ M ∗ N ∗ D f ∗ D f = 1 N + 1 D k ∗ D k \frac{D_k*D_k*M*D_f*D_f+M*N*D_f*D_f}{D_k*D_k*M*N*D_f*D_f}=\frac{1}{N}+\frac{1}{D_k*D_k} Dk∗Dk∗M∗N∗Df∗DfDk∗Dk∗M∗Df∗Df+M∗N∗Df∗Df=N1+Dk∗Dk1
深度可分卷积的计算示意图如下:
在MobliNet中,并不是直接使用深度卷积+点卷积来替换标准卷积。其在深度卷积与点卷积之间还插入了BN层和relu层。
由此推断,直接使用深度卷积+点卷积来替换标准卷积并不能取得优质效果(因为缺少了标准卷积中多个卷积核涉及多视角提取特征)。在中间插入BN层和relu层后,增强了网络的非线性能力。
1.3 MobliNetV1
MobliNetV网络结构如下图所示,在一个框内的Conv dw+Conv实际上是在等价于一个标准的conv。需要注意的是,论文图中最后一个Conv dw的stride是s1,不是s2;同时,紫色框中的模块是重复了5次的深度分离卷积
完整实现代码如下:
import torch
import torch.nn as nndef conv_bn(in_channel, out_channel, stride = 1):"""传统卷积块:Conv+BN+Act"""return nn.Sequential(nn.Conv2d(in_channel, out_channel, 3, stride, 1, bias=False),nn.BatchNorm2d(out_channel),nn.ReLU6(inplace=True))def conv_dsc(in_channel, out_channel, stride = 1):"""深度可分离卷积:DW+BN+Act + Conv+BN+Act"""return nn.Sequential(nn.Conv2d(in_channel, in_channel, 3, stride, 1, groups=in_channel, bias=False),nn.BatchNorm2d(in_channel),nn.ReLU6(inplace=True),nn.Conv2d(in_channel, out_channel, 1, 1, 0, bias=False),nn.BatchNorm2d(out_channel),nn.ReLU6(inplace=True),)class MobileNetV1(nn.Module):def __init__(self,in_dim=3, num_classes=1000):super(MobileNetV1, self).__init__()self.num_classes = num_classesself.stage1 = nn.Sequential(conv_bn(in_dim, 32, 2),conv_dsc(32, 64, 1), conv_dsc(64, 128, 2),conv_dsc(128, 128, 1),conv_dsc(128, 256, 2),conv_dsc(256, 256, 1), )self.stage2 = nn.Sequential(conv_dsc(256, 512, 2),conv_dsc(512, 512, 1),conv_dsc(512, 512, 1),conv_dsc(512, 512, 1), conv_dsc(512, 512, 1),conv_dsc(512, 512, 1),)self.stage3 = nn.Sequential(conv_dsc(512, 1024, 2),conv_dsc(1024, 1024, 1),)self.avg = nn.AdaptiveAvgPool2d((1,1))self.fc = nn.Linear(1024, self.num_classes)def forward(self, x):x = self.stage1(x)x = self.stage2(x)x = self.stage3(x)x = self.avg(x)x = x.view(-1, 1024)x = self.fc(x)return x
1.4 效果对比
将moblienet的深度分离卷积替换为标准卷积后,可见运算量增加了8倍(使用深度可分卷积,节省了约1/9的计算量),参数量减少了6倍。而精度,仅降低了1%。
模型结构验证,移除了模型结构中重复了5次的深度分离卷积,并将标准MoblieNet的参数缩放到原来额0.75倍,保持参数量相同,可以看到拥有更多深度的0.75 MoblieNet模型效果更好。
作者又在不同宽度缩放倍数和分辨率下对模型进行测试,得出MoblieNetV1的最佳精度为70.6%。同时也展示了不同分辨率输入下,模型计算量的差异。
2、MoblieNetV2
2019年3月提出,论文地址为:https://arxiv.org/pdf/1801.04381.pdf
2.1 背景知识
2015年提出了resnet,并在多项imagenet大赛中取得成绩。然而,在MoblieNetV1中只是研究了标准卷积的轻量化替代。MoblieNetV2的本质是对残差结构的一次分析和改进,首先从以block为单位分析了特征输入输出在relu函数前后的变化,然后论证出内部relu函数要在高维度才能保留特征,而输出前的relu函数需要减少维度,以保留感兴趣的维度。并基于此,对原始的残差结构进行修改,提出倒残差结构。
2.2 倒残差结构论证
将特征图进行低纬嵌入可视化,并对relu函数的输出进行分析。认为在低维情况下,relu函数会截断部分数据特性。而当输出维度较高时,被relu截断的信息在其他维度有所体现,从而保留了原始的流行特性。
其认为感兴趣的流形[目标特征]应该位于高维激活空间的低维子空间中,并得出以下特征:1、如果感兴趣的流形在ReLU变换后保持不变,它对应于一个线性变换。2、只有当输入流形位于输入空间的低维子空间时,ReLU才能够保存关于输入流形的完整信息。
作者假设感兴趣的流形是低维的,通过在卷积块中插入线性瓶颈层来捕获。实验证据表明,使用线性层是至关重要的,因为它可以防止非线性破坏太多的信息故此在PW卷积后移除了relu函数
。
直觉认为残差结构中【漏斗形结构
】瓶颈层block中参数少的部分
实际上包含了所有必要的信息,而扩展层block中参数多的部分
仅仅作为伴随张量非线性转换的实现细节。希望提高梯度在乘数层之间传播的能力。然而,倒置设计【纺锤体结构
】的内存效率要高得多。参数低,未必计算快,要靠内存效率
MobliNetV2中倒残差bottleneck如下所示,可见中间层参数要多很多。
倒残差bottleneck的具体示意如下
2.3 网络结构
MoblieNetV2的网络结构如下所示,其中所有的空间卷积的size都为,表头中t表示卷积的扩展倍数(具体见上图的表一),c表示channel的数量,n表示bottleneck的重复次数,s表示conv的步长。从中可以看出,相比于MoblieNetV1,MoblieNetV2网络的深度较深,但通道数更少。
对于不同的网络架构,在每个空间分辨率下需要实现的最大通道/内存数(以Kb为单位),对比如下,可见MoblieNetV2对内存的消耗是最低的。
与其他模型的bottleneck相比,MoblieNetV2的更加简单高效。单从涉及上看,个人觉得ShuffleNet在残差分支上在使用avg pool会更高效一些
在性能上,可以发现ShuffleNet本质上与MoblieNetV2区别不大。但是ShuffleNet的扩展性差一些(增大网络宽带,性能增加不如V2版本)
2.4 实现代码
正常的CBR结构,用在MobileNetV2中卷积基的第一个和最后一个
class ConvBNReLU(nn.Sequential):#depthwise conv+BN+relu6,用于构建InvertedResidual。 def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1): #参数:输入的tensor的通道数,输出通道数,卷积核大小,卷积步距,输入与输出对应的块数(改为输入的层数就是depth wise conv了,详见https://blog.csdn.net/weixin_43572595/article/details/110563397) padding = (kernel_size - 1) // 2#根据卷积核大小获取padding大小 super(ConvBNReLU, self).__init__( nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False), #构建depthwise conv卷积,不使用偏置,因为后面有BN nn.BatchNorm2d(out_channel),#BN层,输入参数为输入的tensor的层数 nn.ReLU6(inplace=True)#relu6激活函数,inplace原地操作tensor减少内存占用 )
MobileNetV2所提出的倒残差结构,当不需要进行维度放大时则不使用conv1x1,当使用时则添加。需要注意的是,其使用groups来对卷积进行分组,当groups_num==output_channel时则表示为深度卷积【分组数与通道数相同;此外,block的输出并没有使用relu函数】
。
class InvertedResidual(nn.Module):#逆向残差结构 def __init__(self, in_channel, out_channel, stride, expand_ratio): #参数:输入的tensor的通道数,输出的通道数,中间depthwise conv的步距,第一个1x1普通卷积的channel放大倍数 super(InvertedResidual, self).__init__() hidden_channel = in_channel * expand_ratio#隐层的输入通道数,对应中间depthwise conv的输入通道数 self.use_shortcut = stride == 1 and in_channel == out_channel #使用shortcut的条件:输入与输出shape一样,即没有缩放(stride=1),维度一样(in_channel = out_channel) layers = []#搜集各个层 if expand_ratio != 1:#这个是由于第一个卷积没有维度放大,因而不需要第一个1x1普通卷积 # 1x1 pointwise conv,第一个普通1x1卷积,升维 layers.append(ConvBNReLU(in_channel,hidden_channel, kernel_size=1)) layers.extend([ # 3x3 depthwise conv ConvBNReLU(hidden_channel, hidden_channel, stride=stride, groups=hidden_channel), # 1x1 pointwise conv(linear)第二个普通1x1卷积,降维 nn.Conv2d(hidden_channel, out_channel, kernel_size=1, bias=False), nn.BatchNorm2d(out_channel),#BN ]) self.conv = nn.Sequential(*layers)#将上面的集成到一起 def forward(self, x):#前向传播过程,直接用上面弄好的conv层 if self.use_shortcut: return x + self.conv(x) else: return self.conv(x)
完整模型代码 代码参考地址: https://zhuanlan.zhihu.com/p/432471190?utm_id=0&wd=&eqid=aa79af36000a2d65000000046461d5b8
class MobileNetV2(nn.Module):#整体的网络 def __init__(self, num_classes=1000, alpha=1.0, round_nearest=8): #参数:类的个数,各个层的深度缩放系数(width multiplier),深度数需要改成离其最近的倍数 super(MobileNetV2, self).__init__() input_channel = _make_divisible(32 * alpha, round_nearest) #获取原始图片进来后的卷积的输出通道数离round_nearest最近的倍数 last_channel = _make_divisible(1280 * alpha, round_nearest) #获取特征提取部分最后的输出通道数离round_nearest最近的倍数 inverted_residual_setting = [ #配置表,这些都是逆向残差层 t:维度放大倍数,c:该层的输出通道数,n:该层的重复次数,s:该层的中间的depthwise conv的步距(仅限第一个inverted_residual)。 # t, c, n, s [1, 16, 1, 1], [6, 24, 2, 2], [6, 32, 3, 2], [6, 64, 4, 2], [6, 96, 3, 1], [6, 160, 3, 2], [6, 320, 1, 1], ] features = []#收集特征提取部分网络层 # conv1 layer,第一层,一个3x3普通卷积,步距为2 features.append(ConvBNReLU(3, input_channel, stride=2)) # building inverted residual residual blockes,遍历上面的配置,构建中间的逆向残差层 for t, c, n, s in inverted_residual_setting: output_channel = _make_divisible(c * alpha, round_nearest) #获取缩放后的通道数离round_nearest最近的倍数 for i in range(n):#构建n层逆向残差 stride = s if i == 0 else 1#只有第一层逆向残差层的depthwise conv才有步距不为1 features.append(InvertedResidual(input_channel, output_channel, stride, expand_ratio=t)) input_channel = output_channel # building last several layers,构建逆向残差后面的1x1普通卷积 features.append(ConvBNReLU(input_channel, last_channel, 1)) # combine feature layers,整合特征提取部分的网络 self.features = nn.Sequential(*features) # building classifier,自适应池化层,参数是输出的tensor的大小 self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) self.classifier = nn.Sequential(#分类层,drop+全连接层 nn.Dropout(0.2), nn.Linear(last_channel, num_classes) ) # weight initialization,初始化各层 for m in self.modules(): if isinstance(m, nn.Conv2d):#卷积层 nn.init.kaiming_normal_(m.weight, mode='fan_out')#权重用何凯明初始化 if m.bias is not None: nn.init.zeros_(m.bias)#偏置初始化为0 elif isinstance(m, nn.BatchNorm2d):#BN,(x-bias)/weight nn.init.ones_(m.weight)#初始化为1 nn.init.zeros_(m.bias)#初始化为0 elif isinstance(m, nn.Linear):#全连接层 nn.init.normal_(m.weight, 0, 0.01)#权重初始化为均值为0.方差为0.01的正态分布 nn.init.zeros_(m.bias)#偏置初始化为0 def forward(self, x):#前向传播过程 x = self.features(x)#获得特征图 x = self.avgpool(x)#自适应池化,把每层大小从7x7变成了1x1 x = torch.flatten(x, 1)#展平操作,从dim=1开始 x = self.classifier(x)#通过分类层获取每个类的概率 return x
3、MoblieNetV3
2019年11月提出,论文地址:https://openaccess.thecvf.com/content_ICCV_2019/papers/Howard_Searching_for_MobileNetV3_ICCV_2019_paper.pdf
3.1 相关知识
设计深度神经网络结构以实现精度和效率之间的最佳权衡一直是近年来一个活跃的研究领域。
在现有的轻量化模型设计中主要利用点卷积和channel shuffle操作进行模型轻量化操作。
在当时使用NAS技术来构建模型已经时极为流行的,并有人提出使用基于梯度优化的可微架构搜索框架,专注于使现有网络适应受限的移动平台。
此外,模型量化、知识蒸馏也成为了模型轻量化的一种技巧被普片应用。
为了优化移动设备上的精度延迟权衡,谷歌团队将NAS技术应用到了MoblieNetV3上。
3.2 模块设计
将MoblieNetV2中的block与SENet中的设计相结合,得到V3的block设计,同时还在DW与PW的连接中额外使用了SE模块。
SE模块的实现如下:
class senet(nn.Module):""""""def __init__(self, chennl, reduction=4):super(senet, self).__init__()"""Constructor"""self.avg_pool = nn.AdaptiveAvgPool2d(1)self.fc = nn.Sequential(nn.Linear(chennl, _make_divisible(chennl // reduction, 8)),nn.ReLU(inplace=True), nn.Linear(_make_divisible(chennl // reduction, 8), chennl),h_sigmoid())def forward(self, x):in_chennl, out_chennl, h, w = x.size()y = self.avg_pool(x).view(in_chennl, out_chennl)y = self.fc(y).view(in_chennl, out_chennl, 1, 1)return x * y
*MV3的block的实现代码如下:
class residual(nn.Module):def __init__(self, in_place, scale_place, out_place, k_size, stride, use_se, use_hs):super(residual, self).__init__()"""Constructor"""assert stride in [1, 2]self.identity = in_place == out_place and stride == 1if in_place == scale_place:self.conv = nn.Sequential(nn.Conv2d(scale_place, scale_place, k_size, stride, padding=(k_size - 1) // 2, groups=scale_place, bias=False),nn.BatchNorm2d(scale_place),h_swish() if use_hs else nn.ReLU(inplace=True),senet(scale_place) if use_se else nn.Identity(),nn.Conv2d(scale_place, out_place, 1, 1, 0, bias=False),nn.BatchNorm2d(out_place))else:self.conv = nn.Sequential(nn.Conv2d(in_place, scale_place, 1, 1, 0, bias=False),nn.BatchNorm2d(scale_place),h_swish() if use_sh else nn.ReLU(inplace=True),nn.Conv2d(scale_place, scale_place, k_size, stride, padding=(k_size - 1) // 2, groups=scale_place, bias=False),nn.BatchNorm2d(scale_place),senet(scale_place) if use_se else nn.Identity(), h_swish() if use_hs else nn.ReLU(inplace=True),nn.Conv2d(scale_place, out_place, 1, 1, 0, bias=False), nn.BatchNorm2d(out_place))def foward(self, x):if self.identity:x += self.conv(x)else:x = self.conv(x)return x
同时,为了得到理想的网络结构,谷歌团队在设定优化目标后使用NAS技术进行结构搜索。同时在结构优化外,还进行了分类头优化和激活函数优化。
分类头优化 主要是对MoblieNetV2中最后几个conv和fc层进行重新设计,削减了其中的大量参数和计算量。
激活函数优化 将swish x函数用hard-swish x来进行近似。与hard-sigmoid函数的近似不同,hard-swish消除了由于近似函数不同实现所造成的潜在的数值精度损失。
h_swish的实现代码如下:
import torch
import torch.nn as nn
import torch.nn.functional as F########################################################################
class h_sigmoid(nn.Module):""""""#----------------------------------------------------------------------def __init__(self, inplace=True):super(h_sigmoid, self).__init__()"""Constructor"""self.relu_6 = nn.ReLU6(inplace=inplace)#----------------------------------------------------------------------def forward(self, x):""""""x = self.relu_6(x + 3) / 6return x########################################################################
class h_swish(nn.Module):""""""#----------------------------------------------------------------------def __init__(self, inplace=True):super(h_swish, self).__init__()"""Constructor"""self.h_sigmoid = h_sigmoid(inplace=inplace)#----------------------------------------------------------------------def forward(self, x):""""""x = x * self.h_sigmoid(x)return x
3.3 模型结构
MobileNetV3-Large的模型结构如下所示
MobileNetV3-Small的模型结构如下所示,因为整个模型结构都是使用NAS搜索而来,故Large与Small不存在结构相似性。
这种NAS技术,在2019-2021年风头无两,这并不能代表未来,只是在比拼算力。后续的很多模型研究,都还是喜欢使用宽度、深度系数对模型进行缩放操作。
实现代码参考自:https://blog.csdn.net/qq_43318374/article/details/125883126
import torch
import torch.nn as nn
import torch.nn.functional as F
#----------------------------------------------------------------------
def conv_3_x_3_bn(in_place, out_place, stride):""""""return nn.Sequential(nn.Conv2d(in_place, out_place, 3, stride, 1, bias=False),nn.BatchNorm2d(out_place),h_swish())#----------------------------------------------------------------------
def conv_1_x_1_bn(in_place, out_place):""""""return nn.Sequential(nn.Conv2d(in_place, out_place, 1, 1, 0, bias=False),nn.BatchNorm2d(out_place),h_swish())
########################################################################
class mobilentv3(nn.Module):""""""#----------------------------------------------------------------------def __init__(self, net_cfg, mode = False, num_class=False, fpn_ids=False, width_mult=1):super(mobilentv3, self).__init__()"""Constructor"""self.net_cfg = net_cfgself.fpn_ids = fpn_idsin_chennl = _make_divisible(16 * width_mult, 8)layers = [conv_3_x_3_bn(3, in_channl, 2)]block = residualfor k, scale, chennl, stride, use_se, use_hs, in self.net_cfg:out_place = _make_divisible(chennl * width_mult)scale_place = _make_divisible(in_channl * scale)layers.append(block(in_chennl, scale_place, out_place, k, stride, use_se, use_hs))in_chennl = out_placeself.features = nn.Sequential(*layers)self.last_conv = conv_1_x_1_bn(in_chennl, scale_place)self.avg_pool = nn.AdaptiveAvgPool2d((1, 1))output_channel = {'large': 1280, 'small': 1024}output_channel = _make_divisible(output_channel[mode] * width_mult, 8) if width_mult > 1.0 else output_channel[mode]self.classifi = num_class != Noneif self.classifi:self.classifier = nn.Sequential(nn.Linear(scale_place, output_channel),h_swish(),nn.Dropout(0.2), nn.Linear(output_channel, num_class))self._init_weights()#----------------------------------------------------------------------def forward(self, x):""""""fnp_layers = []for i, l in enumerate(self.features):x = l(x)if self.fnp_ids:if i in fnp_ids:fnp_layers.append(x)if self.classifi:x = self.last_conv(x)x = self.avg_pool(x)x = x.view(x.size(0), -1)x = self.classifier(x)return xelse:fnp_layers.insert(0, x)return fnp_layers#----------------------------------------------------------------------def _init_weights(self):""""""for m in self.modules():if isinstance(m, nn.Conv2d):n = m.kernel_size[0] * m.kernel_size[1] * m.out_channelsm.weight.data.normal_(0, math.sqrt(2. / n))if m.bias is not None:m.bias.data.zero_()elif isinstance(m, nn.BatchNorm2d):m.weight.data.fill_(1)m.bias.data.zero_()elif isinstance(m, nn.Linear):n = m.weight.size(1)m.weight.data.normal_(0, 0.01)m.bias.data.zero_()def mobilenetv3_large(**kwargs):"""Constructs a MobileNetV3-Large model"""cfgs = [# k, sclae, c, stride SE, HS[3, 1, 16, 1, 0, 0],[3, 4, 24, 2, 0, 0],[3, 3, 24, 1, 0, 0],[5, 3, 40, 2, 1, 0],[5, 3, 40, 1, 1, 0],[5, 3, 40, 1, 1, 0], # P3 5[3, 6, 80, 2, 0, 1],[3, 2.5, 80, 1, 0, 1],[3, 2.3, 80, 1, 0, 1],[3, 2.3, 80, 1, 0, 1], # P4 9[3, 6, 112, 1, 1, 1],[3, 6, 112, 1, 1, 1],[5, 6, 160, 2, 1, 1],[5, 6, 160, 1, 1, 1],[5, 6, 160, 1, 1, 1] # P5 14]return MobileNetV3(cfgs, mode='large', **kwargs)def mobilenetv3_small(**kwargs):"""Constructs a MobileNetV3-Small model"""cfgs = [# k, scale, c, stride SE, HS[3, 1, 16, 2, 1, 0],[3, 4.5, 24, 2, 0, 0],[3, 3.67, 24, 1, 0, 0],[5, 4, 40, 2, 1, 1],[5, 6, 40, 1, 1, 1],[5, 6, 40, 1, 1, 1],[5, 3, 48, 1, 1, 1],[5, 3, 48, 1, 1, 1],[5, 6, 96, 2, 1, 1],[5, 6, 96, 1, 1, 1],[5, 6, 96, 1, 1, 1],]return MobileNetV3(cfgs, mode='small', **kwargs)
3.4 模型性能
从延时上对比,v3貌似比v2强很多,
同时在与其它模型对比中,MobileNetV3看着比其他同类模型要强。
在下游任务中应用来看,V3比V2实际上没有优化效果。
目标检测对比,V3与V2具备类似的性能
语义分割检测对比,V3与V2具备类似的性能
基于以上来看,应该是V3模型结构是由NAS建设而来(模型结构可能只适应于检索的imagenet数据集),并未通过人工先验设计(所设计的模型结构可以适用于更多下游任务),故在下游任务效果提升有限。
4、MoblieVit
发表时间:2022.03.04 论文地址:https://blog.csdn.net/a486259/article/details/131485111?spm=1001.2014.3001.5501
csdn翻译:https://blog.csdn.net/a486259/article/details/131485111?spm=1001.2014.3001.5501
4.1 相关知识
1、现在基于self-attention的模型,特别是Vision Transformer,是卷积神经网络(CNNs)学习视觉表征的替代方法,Transformer正在视觉方面逐步取代cnn。
2、目前ViT的发展趋势是通过增加参数量来提升性能,故此现在的一些轻量化ViT模型在移动端上,性能总是低于一些轻量型的cnn。
3、ViT缺乏了cnn固有的归纳偏置,故需要更多的参数
4、现有的CNN+ViT混合模型,权重比较大,仍然需要更多的数据增强
4.2 核心设计
现有的轻量化cnn模型在处理空间特征上是局部的,MobileViT将Transformer视为卷积的一部分;利用卷积(例如,通用和简单的训练)和Transformer(例如,全局处理)的优点来构建轻量级和通用的ViTs。
1、MobliVit将标准卷积定义为:展开、局部处理、折叠
2、将卷积中的局部处理与使用Transformer的全局处理取代
3、MobileViT通过简单的训练即可达到轻量cnn的水平
MoblieVit block的设计如下,其在block内先使用conv提取图像的局部特征对应蓝色箭头线
,然后再使用transformer将图像划分为patch提取patch间的全局图像特征对应黄色箭头线
。寻常混合模型只是在深层使用transformer提取图像特征包括特征之间细节
,而MoblieVit block的这种设定降低了transformer的训练难度,它的transformer的目的是提取patch间的全局关联性。
MoblieVit表面使用多尺度进行模型训练有利于模型快速收敛,且精度会高一些。并提出在多尺度训练时,imgsize小则使用大batchsize,imgsize大则使用小batchsize充分有效的利用了gpu
4.3 网络设计
MoblieVit的网络设计如下所示,需要注意的有以下两点:
1、MoblieVit使用MV2block,并未使用MV3block(其中在DW与PW之间使用了SE),并不适合在block中嵌入transformer
2、在stage设计中,MoblieVit只在32、16、8的尺度下使用MobileViT block重复次数比较多
,并未在大尺度特征图上使用transformer,充分的节省了计算量
3、MoblieVit在进行下采样时使用MV2block,未使用transformer进行下采样。
4.3 实施效果
与MoblieNetV1、MoblieNetV2、MoblieNetV3等模型相比,MoblieNetVit现状要强很多。通过以下图中,可以显著的看到MV2是比MV3是要略强一些的,MV3仅是解决了延时问题
与其他同类型的混合模型相比,MolienetVit要强一大截。这预计是模型设计思路差异导致,其他混合模型都是前面使用cnn、后面使用transform,而MVit将卷积进行采用,融合了transform的特性
5、扩展思考
5.1 MoblieNet的本质
从MoblieNetV1、MoblieNetV2、MoblieNetV3到MoblieNetVit,模型系列经过了4次的迭代发展,但不离本质就是对conv的近似替代。先对conv的功能进行定义和拆分,然后对每一个子功能进行实现。试图以一个低参数高flop的block来替换掉原来的cnn层
;在MoblieNet中,都是使用conv进行下采样,并没有使用池化层。
MoblieNetV1提出了conv可以拆分为深度卷积和点卷积的组合,用深度分离卷积来近似标准卷积,虽然有一定的精度损失(越一个点),但能极大的降低参数量和提升flop;
MoblieNetV2提出残差结构在深度分离卷积的应用,并对残差结构进行分析指出使用倒残差纺锤体结构
可以提升模型性能(对特征流形进行分析,认为relu函数会截断特征表现应该用于channel大的部分,而block为纺锤体结构,其输出通道较少,故不需要使用激活函数
);
MoblieNetV3并未提出深刻的见解,只是略微修改了V2block的结构,将NAS技术应用到了V2结构中;然后提出对swish激活函数 x ∗ σ ( x ) x* σ(x) x∗σ(x)其中 σ可以是relu、sigmoid等
的近似。 感觉就像来凑数的
;
MoblieNetVit则又将标准卷积定义为:展开、局部处理、折叠,然后针对每个步骤分别使用conv和transformer进行实现。以conv提取局部特征,以transformer提取patch间的全局信息 这种设定降低了transformer的训练难度
,从而能使MoblieNetVit在一众混合模型中脱颖而出。
5.2 MoblieNet的泛化性
这里主要集合MoblieNetVit论文中的图片进行分析,毋庸置疑的是MoblieNetVit为最强。但,MoblieNetV2、MoblieNetV3孰优孰劣却有待争议。V2和V3在同一年出品,时间相差无几。从V3的论文图片来看,限定flop的情况下v3是大幅度优于v2的,但那只是在imagenet上的结论。
在MoblieNetVit中给出的图片中可以看出,v1、v2、v3差异并不多,v3在参数增加后性能会略超v2;同时在V3的论文中也未体现出显著的精度优势,而是延时上的优势,而在MoblieNetVit所展示的也仅仅是精度上的优势。故此推断,MoblieNetVit在速度上有优化空间;此外,硬件性能的发展拉低了移动端模型的性能差异
在MV3和MVit中给出的图片中可以看出,v1、v2、v3差异并不多; 基于此可以基本确定,v1、v2、v3的迭代是升级了模型在ImageNet数据集上的精度,但并未提升模型在实际应用中的泛化能力,其核心只是提升了模型在移动端的运行速度