首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >​深度神经网络经典模型结构-shufflenet系列

​深度神经网络经典模型结构-shufflenet系列

作者头像
小草AI
发布2019-11-01 16:58:31
1.5K0
发布2019-11-01 16:58:31
举报

转自:机器学习算法工程师

前 言

卷积神经网络在计算机视觉任务中表现优异,但由于嵌入式设备内存空间小,能耗要求低,因此需要使用更加高效的模型。例如,与单纯的堆叠卷积层相比GoogLeNet增加了网络的宽度但降低了复杂度,SqueezeNet在保持精度的同时大大减少参数和计算量,ResNet利用高效的bottleneck结构实现惊人的效果,Xception中提出深度可分卷积概括了Inception序列,MobileNet利用深度可分卷积构建的轻量级模型获得了不错的效果,Senet(取得了 ImageNet 2017 的冠军)引入了一个架构单元对 Channel 之间的信息进行显式建模,它以很小的计算成本提高了性能。而ShuffleNet主要使用了pointwise group convolution(分组的1×1卷积核)代替密集的1×1卷积来减小计算复杂度(因为作者发现,对于resnext中的每个residual unit,1×1卷积(逐点卷积)占据93.4%的运算)。同时为了消除pointwise group convolution带来的副作用,提出了一个新的channel shuffle操作来使得信息能够在特征通道上进行交叉流动,因此shuffleNet在保持原有准确率的条件下大大降低计算量。

章节目录

  • 分组卷积(group convolution)和通道重排(Channel shuffle)
  • ShuffleNet Units
  • 模型结构
  • 改进(shufflenet v2)
  • 代码

01

分组卷积(group convolution)和通道重排(Channel shuffle)

分组卷积是在通道上采用稀疏连接方式,将特征图在通道上切分为多个组(可以想象成一根甘蔗,横着切成多份),然后通过确保每个卷积只在相应的输入信道组上运行,来减少了计算量。为什么计算量会减少呢。假设input的特征图大小是W*H*C1,output的特征图大小是W*H*C2,卷积核大小为1*1的卷积,那么如果不采用分组卷积的话,卷积的计算量为1*1*C1*C2*W*H,如果采用分组卷积,将特征图按照通道分为g分,那么每一份的大小是W*H*C1/g,由于输出通道最终为C2,所以平分下来每一份单独卷积之后的输出也只需要C2/g个通道就行了,因此对于每一份来说,它的计算量为1*1*C1/g*C2/g*W*H,所以g份总的计算量为g*(1*1* C1/g*C2/g *W*H)=1*1*C1*C2*W*H/g。可以发现,分为g份,计算量就降低到1/g。分组卷积有个缺点就是每个输出通道只能从有限输入通道获得信息,即一个分组的输出只和这个分组的输入有关,阻止通道组之间的信息流的流动,限制了模型表达能力。

上图表示用两个叠加的分组卷积进行信道shuffle操作。gconv代表分组卷积。a)中两个具有相同组数的叠加卷积层。每个输出通道仅与组内的输入通道相关。没有信息交流;b)中当gconv1之后gconv2从不同分组获取部分通道信息;c)是b)中通道重排的等效实现,将每个 group 分为更小的n个 subgroup,然后将每个 subgroup 输出分散到每个 group 下一层输入。具体来说就是加入 GConv1 有 g x n 个输出通道,则首先将输出通道变维为 (g, n),再转置,最后展开成一维送入下一个层,这样就实现了通道重排,如下所示:

02

ShuffleNet Units

利用channel shuffle操作的优点,提出了一种专为小型网络设计的ShuffleNet unit。从图2(a)中可以看到ShuffleNet unit采用残差单元的设计原理。不同之处在于将之前残差单元分支中的3*3卷积改用为深度可分离卷积(DWConv)。然后,将第一个1×1层替换为1x1的pointwise group convolution(GConv),然后进行channel shuffle操作,形成channel shuffle单元,如图2(b)所示。第二个pointwise group convolution(GConv)的目的是为了恢复通道尺寸使其能够和快捷路径的特征图进行Add运算。为了简单起见,第二个GConv之后没有使用channel shuffle操作。对于带下采样功能的 ShuffleNet unit,参见图2(c):(1)在快捷路径上添加一个3×3的AVG Pool;(2)用concat连接替换元素加操作(Add),从而弥补了分辨率减小而带来的信息损失。

03

模型结构

该网络有三个阶段组成,对应的分辨率分别是图中的28,14,7,对应的shuffle util重复次数分别是3,7,3。每个阶段的第一个shuffle util块的步幅为2,同一个阶段下的shuffle util中的其他超参数保持不变,shuffle util的瓶颈通道的数量设置为每个shuffle util单元输出通道的1/4。

04

改进(shufflenet v2)

(1)有效的网络架构设计推导出的一些实用指南:

(Ⅰ)G1:相等的通道宽度可最大限度地降低内存访问成本(MAC);

(Ⅱ)G2:过多的组卷积会增加MAC;

(Ⅲ)G3:网络碎片降低了并行度;

(Ⅳ)G4:逐元素操作的执行时间是不可忽略的;

基于上述指导原则和研究,有效的网络架构应该:

(Ⅰ)使用“平衡卷积"(相等的通道宽度);

(Ⅱ)注意使用组卷积的成本;

(Ⅲ)降低碎片程度;

(Ⅳ)减少逐元素操作。

(2)ShuffleNet V2

上图(a)(b)是shufflenet v1中的结构,在ShuffleNet v1中采用了两种技术:逐点组卷积和瓶颈状结构。然后引入“channel shuffle”操作以实现不同信道组之间的信息通信并提高准确性。根据指导原则,逐点组卷积和瓶颈结构都会增加MAC(G1和G2),使用太多组违反了G3。在直连通道中进行逐元素相加的操作也是不合需要的(G4)。

因此,为实现高模型容量和效率,关键问题是如何保持大量且同样宽的信道,既没有密集卷积也没有太多组。为了达到上述目的,我们引入了一个名为channel split的简单运算符。如上图(c)所示。在每个单元的开始处,c个特征通道的输入被分成两个分支,分别具有c−c′和c′个通道(为简单起见,c′=c/2)。按照G3,一个分支是恒等函数。另一个分支由三个卷积组成,这三个卷积具有相同的输入和输出通道以满足G1。不同于ShuffleNetV1,两个1×1的卷积不再是分组的了。这部分是为了遵循G2,部分原因是拆分操作已经产生了两个组。卷积后,两个分支连接在一起。因此,通道数保持不变(G1)。然后使用与ShuffleNet V1中相同的“channel shuffle“操作来实现两个分支之间的信息通信。shuffle后,进入了下一个网络块。请注意,ShuffleNet v1中的“Add“操作不再存在。ReLU和depthwise convolutions等元素操作仅存在于一个分支中。此外,三个连续的元素操作,”Concat“,“Channel Shuffle”和”Channel Split“,合并为单个逐元素操作。根据G4,这些更改是有益的。

对于空间下采样,该单元稍作修改,如上图(d)所示。删除了channel split。因此,输出通道的数量加倍。还需要注意的是,shufflenetv2在全局平均池化之前添加额外的1×1卷积层来混合特征。

05

代码

(1)Conv-Bn-Relu moduule

    """
    Conv-Bn-Relu moduule
    """
    class ConvBnRelu(nn.Module):
        def __init__(self,in_channels,out_channels,kernel_size,stride=1,
                     padding=0,dilation=1,groups=1,relu6=False):
            super(ConvBnRelu,self).__init__()
            self.conv=nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding, dilation, groups, bias=False)
            self.bn=nn.BatchNorm2d(out_channels)
            self.relu=nn.ReLU6(inplace=True) if relu6 else nn.ReLU(inplace=True) 
        def forward(self,x):
            x=self.conv(x)
            x=self.bn(x)
            x=self.relu(x)
            return x

(2)channel_shuffle

    def channel_shuffle(x,groups):
        n,c,h,w=x.size()
        channels_per_group=c//groups
        x = x.view(n, groups, channels_per_group, h, w)
        x = torch.transpose(x, 1, 2).contiguous()
        x = x.view(n, -1, h, w)
        return x

(3)ShuffleNetUtil(V1)

    class ShuffleNetUtil(nn.Module):
        def __init__(self,in_channels,out_channels,stride,groups,dilation=1):
            super(ShuffleNetUtil,self).__init__()
            self.stride=stride   #步幅
            self.groups=groups   #分组
            self.dilation=dilation    #空洞率
            assert stride in [1,2,3]           #
            inter_channels=out_channels//4     #瓶颈通道的数量
            if(stride>1):
                self.shortcut = nn.AvgPool2d(3, stride, 1)
                out_channels -= in_channels
            elif(dilation>1):
                out_channels-=in_channels
            if in_channels==24:
                g=1 
            else:
                g=groups
            self.conv1=ConvBnRelu(in_channels,inter_channels,1,groups=g)
            self.conv2=ConvBnRelu(inter_channels,inter_channels,3,stride,dilation,dilation,groups)
            self.conv3=nn.Sequential(
                    nn.Conv2d(inter_channels, out_channels, 1, groups=groups, bias=False),
                    nn.BatchNorm2d(out_channels))
        def forward(self,x):
            out=self.conv1(x)                      #分组卷积
            out=channel_shuffle(out,self.groups)   #channle shuffle
            out=self.conv2(out)                    #深度可分卷积
            out=self.conv3(out)                    #分组卷积,不进行激活
            if self.stride > 1:
                x = self.shortcut(x)               #下采样功能的 ShuffleNet unit中,需要将x的分辨率降低,然后才能concat连接,使用concat连接这样做的目的主要是降低计算量与参数大小
                out = torch.cat([out, x], dim=1)
            elif self.dilation > 1:                #如果采用空洞卷积的话,也是采用concat的连接方式
                out = torch.cat([out, x], dim=1)
            else:                                   #残差块里面如果没有降低分辨率的,连接方式使用加操作(Add)
                out = out + x
            out = F.relu(out)
            return out

(4)DWConv

    class DWConv(nn.Module):
        def __init__(self,in_channels,out_channels,kernel_size,stride=1,padding=0, dilation=1,bias=False):
            super(DWConv,self).__init__()
            self.conv=nn.Conv2d(in_channels,out_channels,kernel_size,stride,
                                  padding, dilation, groups=in_channels, bias=bias)
        def forward(self,x):
            return self.conv(x)

(5)ShuffleNetV2Util

    class ShuffleNetV2Util(nn.Module):
        '''
        有效的网络架构设计推导出的一些实用指南:
        (Ⅰ)G1:相等的通道宽度可最大限度地降低内存访问成本(MAC);
        (Ⅱ)G2:过多的组卷积会增加MAC;
        (Ⅲ)G3:网络碎片降低了并行度;
        (Ⅳ)G4:逐元素操作的执行时间是不可忽略的;
        基于上述指导原则和研究,有效的网络架构应该:
        (Ⅰ)使用“平衡卷积"(相等的通道宽度); 
        (Ⅱ)注意使用组卷积的成本; 
        (Ⅲ)降低碎片程度; 
        (Ⅳ)减少逐元素操作。
        '''
        def __init__(self,in_channels,out_channels,stride,dilation=1):
            super(ShuffleNetV2Util,self).__init__()
            assert stride in [1,2,3]
            self.stride = stride
            self.dilation = dilation
            inter_channels = out_channels // 2   #channel split
            if(stride>1 or dilation>1):#带下采样的模块,左边的路径的特征图也需要进行相应的下采样,同时也不使用channel split
                self.branch1=nn.Sequential(
                        DWConv(in_channels,in_channels,3,stride, dilation, dilation),
                        nn.BatchNorm2d(in_channels),
                        ConvBnRelu(in_channels,inter_channels,1))
            self.branch2=nn.Sequential(#如果带下采样的模块,右侧的路径有所不同,也就是不需要进行channel split
                    ConvBnRelu(in_channels if (stride > 1) else inter_channels,inter_channels, 1),
                    DWConv(inter_channels,inter_channels,3,stride,dilation, dilation),
                    nn.BatchNorm2d(inter_channels),
                    ConvBnRelu(inter_channels,inter_channels,1)
                    )
        def init_weights(self):
            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)
                elif isinstance(m, nn.BatchNorm2d):
                    nn.init.ones_(m.weight)
                    nn.init.zeros_(m.bias)
                elif isinstance(m, nn.Linear):
                    nn.init.normal_(m.weight, 0, 0.01)
                    if m.bias is not None:
                        nn.init.zeros_(m.bias)
        def forward(self,x):
            if(self.stride==1 and self.dilation==1):#如果不进行下采样,则左路不需要做任何运算
                x1,x2=x.chunk(2,dim=1)#torch.chunk(input, chunks, dim),与torch.cat()的作用相反。注意,返回值的数量会随chunks的值而发生变化.
                out=torch.cat((x1, self.branch2(x2)), dim=1)
            else:
                out=torch.cat((self.branch1(x), self.branch2(x)),dim=1)
            out=channel_shuffle(out,2)#参数2表示groups为2组,因为分成两条路径,生成两组特征图
            return out

参考

https://mp.weixin.qq.com/s/-AJ3RQK9vpV1rYNk4CLQ_A https://mp.weixin.qq.com/s/0MvCnm46pgeMGEw-EdNv_w https://arxiv.org/pdf/1707.01083.pdf https://arxiv.org/pdf/1807.11164.pdf

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 机器学习与python集中营 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 分组卷积(group convolution)和通道重排(Channel shuffle)
  • ShuffleNet Units
  • 模型结构
    • (1)有效的网络架构设计推导出的一些实用指南:
      • (2)ShuffleNet V2
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档