@huanghaian
2020-12-02T14:05:24.000000Z
字数 4960
阅读 1123
分类
论文名称:ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design
论文地址: https://arxiv.org/abs/1807.11164
shufflenetv2论文最大贡献不是提出了一个新的模型,而是提出了4条设计高效CNN的规则,该4条规则考虑的映射比较多,不仅仅FLOPS参数,还可以到内存使用量、不同平台差异和速度指标等等,非常全面。在不违背这4条规则的前提下进行改进有望设计出速度和精度平衡的高效模型。
到目前为止,前人所提轻量级CNN框架取得了很好效果,例如Xception、MobileNet、MobileNetV2和ShuffleNetv1等,主要采用技术手段是分组卷积Group convolution和逐深度方向卷积depthwise convolution。
在上述算法中,除了精度要求外,评价模型复杂度都是采用FLOPs指标(每秒浮点加乘运算次数),在mobilnetv1、v2和ShuffleNet都是采用该指标的。然而FLOPs 其实是一种间接指标,它只是真正关心的直接指标(如速度或延迟)的一种近似形式,通常无法与直接指标划等号。先前研究已对这种差异有所察觉,比如MobileNet V2要远快于 NASNET-A(自动搜索出的神经网络),但是两者FLOPs相当,它表明FLOPs近似的网络也会有不同的速度。所以,将FLOPs作为衡量计算复杂度的唯一标准是不够的,这样会导致次优设计。
研究者注意到FLOPs仅和卷积部分相关,尽管这一部分需要消耗大部分的时间,但其它过程例如数据 I/O、数据重排和元素级运算(张量加法、ReLU 等)也需要消耗一定程度的时间。
间接指标(FLOPs)和直接指标(速度)之间存在差异的原因可以归结为两点:
其次,FLOPs相同的运算可能有着不同的运行时间,这取决于平台。例如,早期研究广泛使用张量分解来加速矩阵相乘。但是,近期研究发现张量分解在GPU上甚至更慢,尽管它减少了75%的 FLOPs。本文研究人员对此进行了调查,发现原因在于最近的 CUDNN库专为3×3 卷积优化:当然不能简单地认为3×3卷积的速度是1×1卷积速度的1/9。
总结如下就是:
据此,提出了高效网络架构设计应该考虑的两个基本原则:第一,应该用直接指标(例如速度)替换间接指标(例如 FLOPs);第二,这些指标应该在目标平台上进行评估。在这项研究中,作者遵循这两个原则,并提出了一种更加高效的网络架构。
G1. 相同的通道宽度可最小化内存访问成本(MAC)
假设内存足够大一次性可存储所有特征图和参数;卷积核大小为 ;输入通道有c1个;输出通道有c2个;特征图分辨率为,则在1x1的卷积上,FLOPS:,,容易推出:
MAC是内存访问量,hwc1是输入特征图内存大小,hwc2是输出特征图内存大小,c1xc2是卷积核内存大小。从公式中我们可以得出MAC的一个下界,即当c1==c2 时,MAC取得最小值。以上是理论证明,下面是实验证明:
可以看出,当c1==c2时,无论在GPU平台还是ARM平台,均获得了最快的runtime。
G2. 过度使用组卷积会增加MAC
和(1)假设一样,设g表示分组数,则有:
其中,当固定c1 w h和B,增加分组g,MAC也会增加,证明了上述说法。其中c1 w h固定,g增加的同时B复杂度也要固定,则需要把输出通道c2增加,因为g增加,复杂度是要下降的。以上是理论证明,下面是实验证明:
可以看出,g增加,runtime减少。
G3. 网络碎片化(例如 GoogLeNet 的多路径结构)会降低并行度
理论上,网络的碎片化虽然能给网络的accuracy带来帮助,但是在平行计算平台(如GPU)中,网络的碎片化会引起并行度的降低,最终增加runtime,同样的该文用实验验证:
可以看出,碎片化越多,runtime越大。
G4. 元素级运算不可忽视
element-wise operations也占了不少runtime,尤其是GPU平台下,高达15%。ReLU、AddTensor及AddBias等这一类的操作就属于element-wise operations. 这一类操作,虽然flops很低,但是MAC很大,因而也占据相当时间。同样地,通过实验分析element-wise operations越少的网络,其速度越快。
以上就是作者提出的4点设计要求,总结如下
结合上述4点可以对目前移动端网络进行分析:
在ShuffleNetv1的模块中,大量使用了1x1组卷积,这违背了G2原则,另外v1采用了类似ResNet中的瓶颈层(bottleneck layer),输入和输出通道数不同,这违背了G1原则。同时使用过多的组,也违背了G3原则。短路连接中存在大量的元素级Add运算,这违背了G4原则。
基于以上缺点,作者重新设计了v2版本。V2主要结构是多个blocks堆叠构成,block又分为两种,一种是带通道分离的Channel Spilit,一种是带Stride=2。图示如下:
(a)为v1中的stride=1的block,(b)为v1中的stride=2的block,(c)是新设计的v2中的stride=1的block,(d)是新设计的v2中的stride=2的block。
在每个单元开始,c 特征通道的输入被分为两支,分别占据c−c'和c'个通道(一般设置c=c'*2)。左边分支做同等映射,右边的分支包含3个连续的卷积,并且输入和输出通道相同,这符合G1。而且两个1x1卷积不再是组卷积,这符合G2,另外两个分支相当于已经分成两组。两个分支的输出不再是Add元素,而是concat在一起,紧接着是对两个分支concat结果进行channle shuffle,以保证两个分支信息交流。其实concat和channel shuffle可以和下一个模块单元的channel split合成一个元素级运算,这符合原则G4。
对于stride=2的模块,不再有channel split,而是每个分支都是直接copy一份输入,每个分支都有stride=2的下采样,最后concat在一起后,特征图空间大小减半,但是通道数翻倍。
具体代码上非常简单,如下所示:
class InvertedResidual(nn.Module):
def __init__(self,
in_channels,
out_channels,
stride=1,
conv_cfg=None,
norm_cfg=dict(type='BN'),
act_cfg=dict(type='ReLU'),
with_cp=False):
super(InvertedResidual, self).__init__()
self.stride = stride
branch_features = out_channels // 2
if self.stride > 1:
# 当stride=2才需要
self.branch1 = nn.Sequential(
ConvModule(
in_channels,
in_channels,
kernel_size=3,
stride=self.stride,
padding=1,
groups=in_channels,
conv_cfg=conv_cfg,
norm_cfg=norm_cfg,
act_cfg=None), # 没有激活函数
ConvModule(
in_channels,
branch_features,
kernel_size=1,
stride=1,
padding=0,
conv_cfg=conv_cfg,
norm_cfg=norm_cfg,
act_cfg=act_cfg),
)
# 和mobilenetv2中的反转残差块差不多,但是激活函数relu位置不一样
self.branch2 = nn.Sequential(
ConvModule(
in_channels if (self.stride > 1) else branch_features,
branch_features,
kernel_size=1,
stride=1,
padding=0,
conv_cfg=conv_cfg,
norm_cfg=norm_cfg,
act_cfg=act_cfg),
ConvModule(
branch_features,
branch_features,
kernel_size=3,
stride=self.stride,
padding=1,
groups=branch_features,
conv_cfg=conv_cfg,
norm_cfg=norm_cfg,
act_cfg=None),
ConvModule(
branch_features,
branch_features,
kernel_size=1,
stride=1,
padding=0,
conv_cfg=conv_cfg,
norm_cfg=norm_cfg,
act_cfg=act_cfg))
def forward(self, x):
def _inner_forward(x):
if self.stride > 1:
# stride=2的模块
out = torch.cat((self.branch1(x), self.branch2(x)), dim=1)
else:
# stride=1的模块,直接spilt,x1不变,x2进行逐深度可分离卷积计算
x1, x2 = x.chunk(2, dim=1)
out = torch.cat((x1, self.branch2(x2)), dim=1)
out = channel_shuffle(out, 2)
return out
return _inner_forward(x)
以上两个部分就可以构成各种block,在block基础上构造网络,如下图:
同样的有宽度因子来对通道进行缩减,主要0.5、1.0、1.5和2.0 4中配置。
可以看出,和v1相比,在相同FLOP情况下,错误率更低,且和其他模型对比,复杂度明显下降。
上述结果是在coco目标检测上的结果,输入图像大小是 800×1200。FLOPs 行列出了输入图像大小为 224×224 时的复杂度水平。带*的shuffleNet v2是变种,区别是扩大了感受野,具体为在每个block的第一个pointwise卷积的前面插入一个额外的3x3深度卷积。可以看出,精度提高不少,且FLOPs仅仅增加一点。这就留下一个疑问:如何增加网络的感受野也是一个值得探讨的问题。
shufflenetv2针对目前移动端模型都仅仅考虑精度和FLOPS参数,但是这两个参数无法直接反映真正高效网络推理要求,故作者提出了4条高效网络设计准则,全面考虑了各种能够影响速度和内存访问量的因素,并给出了设计规则。再此基础上设计了shufflenetv2,实验结果表明速度和精度更加优异。