CNN模型之ShuffleNet

 CNN

作者:叶 虎

                编辑:李文臣


引言
1


        ShuffleNet是旷视科技最近提出的一种计算高效的CNN模型,其和MobileNet和SqueezeNet等一样主要是想应用在移动端。所以,ShuffleNet的设计目标也是如何利用有限的计算资源来达到最好的模型精度,这需要很好地在速度和精度之间做平衡。ShuffleNet的核心是采用了两种操作:pointwise group convolution和channle shuffle,这在保持精度的同时大大降低了模型的计算量。目前移动端CNN模型主要设计思路主要是两个方面:模型结构设计和模型压缩。ShuffleNet和MobileNet一样属于前者,都是通过设计更高效的网络结构来实现模型变小和变快,而不是对一个训练好的大模型做压缩或者迁移。下面我们将详细讲述ShuffleNet的设计思路,网络结构及模型效果,最后使用Pytorch来实现ShuffleNet网络。


设计理念                                                                   
2


        ShuffleNet的核心设计理念是对不同的channels进行shuffle来解决group convolution带来的弊端。Group convolution是将输入层的不同特征图进行分组,然后采用不同的卷积核再对各个组进行卷积,这样会降低卷积的计算量。因为一般的卷积都是在所有的输入特征图上做卷积,可以说是全通道卷积,这是一种通道密集连接方式(channel dense connection)。而group convolution相比则是一种通道稀疏连接方式(channel sparse connection)。使用group convolution的网络如Xception,MobileNet,ResNeXt等。Xception和MobileNet采用了depthwise convolution,这其实是一种比较特殊的group convolution,因此此时分组数恰好等于通道数,意味着每个组只有一个特征图。但是这些网络存在一个很大的弊端是采用了密集的1x1卷积,或者说是dense pointwise convolution,这里说的密集指的是卷积是在所有通道上进行的。所以,实际上比如ResNeXt模型中1x1卷积基本上占据了93.4%的乘加运算。那么不如也对1x1卷积采用channel sparse connection,那样计算量就可以降下来了。但是group convolution存在另外一个弊端,如图1-a所示,其中GConv是group convolution,这里分组数是3。可以看到当堆积GConv层后一个问题是不同组之间的特征图是不通信的,这就好像分了三个互不相干的路,大家各走各的,这目测会降低网络的特征提取能力。这样你也可以理解为什么Xception,MobileNet等网络采用密集的1x1卷积,因为要保证group convolution之后不同组的特征图之间的信息交流。但是达到上面那个目的,我们不一定非要采用dense pointwise convolution。如图1-b所示,你可以对group convolution之后的特征图进行“重组”,这样可以保证接下了采用的group convolution其输入来自不同的组,因此信息可以在不同组之间流转。这个操作等价于图2-c,即group convolution之后对channles进行shuffle,但并不是随机的,其实是“均匀地打乱”。在程序上实现channle shuffle是非常容易的:假定将输入层分为g组,总通道数为g x n,首先你将通道那个维度拆分为(g x n)个维度,然后将这两个维度转置变成(n x g),最后重新reshape成一个维度。如果你不太理解这个操作,你可以试着动手去试一下,发现仅需要简单的维度操作和转置就可以实现均匀的shuffle。利用channle shuffle就可以充分发挥group convolution的优点,而避免其缺点。

图1 使用channle shuffle后的group convolution


网络结构

3


        基于上面的设计理念,首先来构造ShuffleNet的基本单元,如图2所示。ShuffleNet的基本单元是在一个残差单元的基础上改进而成的。如图2-a所示,这是一个包含3层的残差单元:首先是1x1卷积,然后是3x3的depthwise convolution(DWConv,主要是为了降低计算量),这里的3x3卷积是瓶颈层(bottleneck),紧接着是1x1卷积,最后是一个短路连接,将输入直接加到输出上。现在,进行如下的改进:将密集的1x1卷积替换成1x1的group convolution,不过在第一个1x1卷积之后增加了一个channle shuffle操作。值得注意的是3x3卷积后面没有增加channle shuffle,按paper的意思,对于这样一个残差单元,一个channle shuffle操作是足够了。还有就是3x3的depthwise convolution之后没有使用ReLU激活函数。改进之后如图2-b所示。对于残差单元,如果stride=1时,此时输入与输出shape一致可以直接相加,而当stride=2时,通道数增加,而特征图大小减小,此时输入与输出不匹配。一般情况下可以采用一个1x1卷积将输入映射成和输出一样的shape。但是在ShuffleNet中,却采用了不一样的策略,如图2-c所示:对原输入采用stride=2的3x3 avg pool,这样得到和输出一样大小的特征图,然后将得到特征图与输出进行连接(concat),而不是相加。这样做的目的主要是降低计算量与参数大小。

图2 ShuffleNet的基本单元


基于上面改进的ShuffleNet基本单元,设计的ShuffleNet模型如表1所示。可以看到开始使用的普通的3x3的卷积和max pool层。然后是三个阶段,每个阶段都是重复堆积了几个ShuffleNet的基本单元。对于每个阶段,第一个基本单元采用的是stride=2,这样特征图width和height各降低一半,而通道数增加一倍。后面的基本单元都是stride=1,特征图和通道数都保持不变。对于基本单元来说,其中瓶颈层,就是3x3卷积层的通道数为输出通道数的1/4,这和残差单元的设计理念是一样的。不过有个细节是,对于stride=2的基本单元,由于原输入会贡献一部分最终输出的通道数,那么在计算1/4时到底使用最终的通道数,还是仅仅未concat之前的通道数。文章没有说清楚,但是个人认为应该是后者吧。其中g控制了group convolution中的分组数,分组越多,在相同计算资源下,可以使用更多的通道数,所以g越大时,采用了更多的卷积核。这里给个例子,当g=3时,对于第一阶段的第一个基本单元,其输入通道数为24,输出通道数为240,但是其stride=2,那么由于原输入通过avg pool可以贡献24个通道,所以相当于左支只需要产生240-24=216通道,中间瓶颈层的通道数就为216/4=54。其他的可以以此类推。当完成三阶段后,采用global pool将特征图大小降为1x1,最后是输出类别预测值的全连接层。

表1 ShuffleNet网络结构


模型效果
4

        那么ShuffleNet的模型效果如何呢?表2给出了采用不同的g值的ShuffleNet在ImageNet上的实验结果。可以看到基本上当g越大时,效果越好,这是因为采用更多的分组后,在相同的计算约束下可以使用更多的通道数,或者说特征图数量增加,网络的特征提取能力增强,网络性能得到提升。注意Shuffle 1x是基准模型,而0.5x和0.25x表示的是在基准模型上将通道数缩小为原来的0.5和0.25。

表2 采用不同g值的ShuffleNet的分类误差

除此之外,作者还对比了不采用channle shuffle和采用之后的网络性能对比,如表3所示。可以清楚的看到,采用channle shuffle之后,网络性能更好,从而证明channle shuffle的有效性。

表3 不采用channle shuffle和采用之后的网络性能对比

然后是ShuffleNet与MobileNet的对比,如表4所示。可以看到ShuffleNet不仅计算复杂度更低,而且精度更好。

表4 ShuffleNet与MobileNet对比

ShuffleNet的Pythorch实现
5

  

      这里我们使用Pytorch来实现ShuffleNet,Pytorch是Facebook提出的一种深度学习动态框架,之所以采用Pytorch是因为其nn.Conv2d天生支持group convolution,不过尽管TensorFlow不支持直接的group convolution,但是其实可以自己间接地来实现。不过患有懒癌的我还是使用Pytorch吧。

首先我们来实现channle shuffle操作,就按照前面讲述的思路来实现:

def shuffle_channels(x, groups):
   """shuffle channels of a 4-D Tensor"""
   batch_size, channels, height, width
= x.size()
   assert channels % groups == 0
   channels_per_group = channels // groups
   # split into groups
   x = x.view(batch_size, groups, channels_per_group,
              height, width)
   # transpose 1, 2 axis
   x = x.transpose(1, 2).contiguous()
   # reshape into orignal
   x = x.view(batch_size, channels, height, width)
   return x

然后我们实现ShuffleNet中stride=1的基本单元:

class ShuffleNetUnitA(nn.Module):
   """ShuffleNet unit for stride=1"""
   def __init__(self, in_channels, out_channels, groups=3):
       super(ShuffleNetUnitA, self).__init__()
       assert in_channels == out_channels
       assert out_channels % 4 == 0
       bottleneck_channels = out_channels // 4
       self.groups = groups
       self.group_conv1 = nn.Conv2d(in_channels, bottleneck_channels,
                     1, groups=groups, stride=1)
       self.bn2 = nn.BatchNorm2d(bottleneck_channels)
       self.depthwise_conv3 = nn.Conv2d(bottleneck_channels,
               bottleneck_channels,3, padding=1, stride=1,
               groups=bottleneck_channels)
       self.bn4 = nn.BatchNorm2d(bottleneck_channels)
       self.group_conv5 = nn.Conv2d(bottleneck_channels, out_channels,
                           1, stride=1, groups=groups)
       self.bn6 = nn.BatchNorm2d(out_channels)

   def forward(self, x):
       out = self.group_conv1(x)
       out = F.relu(self.bn2(out))
       out = shuffle_channels(out, groups=self.groups)
       out = self.depthwise_conv3(out)
       out = self.bn4(out)
       out = self.group_conv5(out)
       out = self.bn6(out)
       out = F.relu(x + out)

然后是中stride=2的基本单元:

class ShuffleNetUnitB(nn.Module):
   """ShuffleNet unit for stride=2"""
   def __init__(self, in_channels, out_channels, groups=3):
       super(ShuffleNetUnitB, self).__init__()
       out_channels -= in_channels
       assert out_channels % 4 == 0
       bottleneck_channels = out_channels // 4
       self.groups = groups
       self.group_conv1 = nn.Conv2d(in_channels, bottleneck_channels,
                       1, groups=groups, stride=1)
       self.bn2 = nn.BatchNorm2d(bottleneck_channels)
       self.depthwise_conv3 = nn.Conv2d(bottleneck_channels,
          bottleneck_channels,3, padding=1, stride=2,groups=bottleneck_channels)
       self.bn4 = nn.BatchNorm2d(bottleneck_channels)
       self.group_conv5 = nn.Conv2d(bottleneck_channels, out_channels,
                       1, stride=1, groups=groups)
       self.bn6 = nn.BatchNorm2d(out_channels)

   def forward(self, x):
       out = self.group_conv1(x)
       out = F.relu(self.bn2(out))
       out = shuffle_channels(out, groups=self.groups)
       out = self.depthwise_conv3(out)
       out = self.bn4(out)
       out = self.group_conv5(out)
       out = self.bn6(out)
       x = F.avg_pool2d(x, 3, stride=2, padding=1)
       out = F.relu(torch.cat([x, out], dim=1))
       return out

后是g=3的ShuffleNet的实现:

class ShuffleNet(nn.Module):
   """ShuffleNet for groups=3"""
   def __init__(self, groups=3, in_channels=3, num_classes=1000):
       super(ShuffleNet, self).__init__()

       self.conv1 = nn.Conv2d(in_channels, 24, 3, stride=2, padding=1)
       stage2_seq = [ShuffleNetUnitB(24, 240, groups=3)] +
           [ShuffleNetUnitA(240, 240, groups=3) for i in range(3)]
       self.stage2 = nn.Sequential(*stage2_seq)
       stage3_seq = [ShuffleNetUnitB(240, 480, groups=3)] +
           [ShuffleNetUnitA(480, 480, groups=3) for i in range(7)]
       self.stage3 = nn.Sequential(*stage3_seq)
       stage4_seq = [ShuffleNetUnitB(480, 960, groups=3)] +
           [ShuffleNetUnitA(960, 960, groups=3) for i in range(3)]
       self.stage4 = nn.Sequential(*stage4_seq)
       self.fc = nn.Linear(960, num_classes)

   def forward(self, x):
       net = self.conv1(x)
       net = F.max_pool2d(net, 3, stride=2, padding=1)
       net = self.stage2(net)
       net = self.stage3(net)
       net = self.stage4(net)
       net = F.avg_pool2d(net, 7)
       net = net.view(net.size(0), -1)
       net = self.fc(net)
       logits = F.softmax(net)
       return logits

完整实现可以参见GitHub(https://github.com/xiaohu2015/DeepLearning_tutorials/blob/master/CNNs/ShuffleNet.py)

总结
6


      本文主要介绍了ShuffleNet的核心设计思路以及网络架构,最后使用Pytorch来实现。说点题外话,在之前计算力不足时,CNN模型有时会采用group convolution,而随着计算力的提升,目前大部分的CNN采用dense channle connections,但是现在一些研究又转向了group convolution来提升速度,这有点戏剧化。不过ShuffleNet通过channle shuffle这一个trick解决了group convolution的一个副作用,还是值得称赞的。

参考文献
7


  1. ShuffleNet: An Extremely Efficient Convolutional Neural Network for Mobile Devices(https://arxiv.org/pdf/1707.01083.pdf)



阅 


  1. ResNet, AlexNet, VGG, Inception: 理解各种各样的CNN架构

  2. GAN系列学习(2)——前生今世

  3. 机器学习/深度学习入门资料汇总

  4. Logistic回归实战篇之预测病马死亡率(一)







机器学习算法全栈工程师


                            一个用心的公众号

长按,识别,加关注

进群,学习,得帮助

你的关注,我们的热度,

我们一定给你学习最大的帮助

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 1. VGGNet 2. ResNet 3. Inception 4. Xception 5. DenseNet 6. MobileNet 7. ShuffleNet 8. EfficientNet 9. DarkNet 10. Mask R-CNN 11. YOLO 12. U-Net 13. SegNet 14. GAN 15. DCGAN 16. WGAN 17. BigGAN 18. StyleGAN 19. CycleGAN 20. pix2pix ### 回答2: 以下是20个比较流行的CNN(卷积神经网络)模型的推荐: 1. LeNet-5:LeNet是最早应用于手写数字识别任务的CNN模型。 2. AlexNet:AlexNet是在ImageNet比赛取得较好表现的CNN模型,标志着深度学习的突破。 3. VGGNet:VGGNet拥有深层次的结构,通过增加卷积层和池化层的数量,提高了模型的准确率。 4. GoogLeNet:GoogLeNet采用Inception模块来降低模型参数量,还引入了1x1卷积核来提升模型效果。 5. ResNet:ResNet通过引入残差连接,解决了深层次网络的梯度消失问题,是图像分类的经典模型。 6. DenseNet:DenseNet通过连接不同层的特征图,增加了信息传递的路径,有效地提高了模型的表现。 7. MobileNet:MobileNet设计了深度可分离卷积层,减少了计算量,适用于移动设备。 8. ShuffleNet:ShuffleNet采用了通道重排和组卷积的思想,减少了计算开销,提高了模型性能。 9. InceptionV3:InceptionV3是Google团队在ImageNet比赛上提出的模型,具备良好的图像分类能力。 10. Xception:Xception采用了深度可分离卷积代替传统的卷积操作,在图像分类任务表现出色。 11. SqueezeNet:SqueezeNet通过牺牲参数量减小模型大小,适合在资源受限的设备上使用。 12. NASNet:NASNet利用强化学习自动搜索网络结构,具备很高的性能和较小的参数量。 13. MobileNetV2:MobileNetV2改进了MobileNet的结构,提高了性能和效率。 14. InceptionResNetV2:InceptionResNetV2在Inception和ResNet的基础上进行了结合,取得了优秀的结果。 15. SENet:SENet引入了Squeeze-and-Excitation模块,有助于模型学习通道间的依赖关系。 16. EfficientNet:EfficientNet通过在深度、宽度和分辨率上进行均衡来构建高效的模型。 17. ResNeXt:ResNeXt基于ResNet结构,采用分组卷积来提高模型的表示能力。 18. Wide ResNet:Wide ResNet利用增加每个卷积层的通道数来提高模型性能。 19. CapsNet:CapsNet引入了胶囊网络来替代传统的卷积操作,有助于处理对象变换。 20. Transformer:Transformer是用于自然语言处理任务的模型,但在图像领域也取得了一些成果。 ### 回答3: 以下是20个比较流行的CNN模型: 1. LeNet-5:是最早用于手写数字识别的CNN模型。 2. AlexNet:是在ImageNet数据集上取得突破性成果的深度CNN模型。 3. VGGNet:由于其深度和均匀的架构而受到广泛关注。 4. GoogLeNet:引入了Inception模块,大幅减少了模型参数。 5. ResNet:通过引入残差块,解决了梯度消失问题,使得网络更易训练。 6. MobileNet:使用深度可分离卷积,减少了参数数量,适合移动设备。 7. DenseNet:通过连接不同层的方式,增强了信息传递和梯度流动。 8. NASNet:使用搜索算法自动优化网络结构,取得了很好的性能。 9. ShuffleNet:使用逐通道组卷积和通道重排,降低了计算复杂度。 10. SqueezeNet:通过使用1x1卷积核,大幅降低了模型的大小。 11. InceptionV3:在GoogleNet的基础上进行了改进,提高了准确率。 12. InceptionResNetV2:结合了Inception模块和ResNet结构的优点。 13. Xception:将卷积和深度可分离卷积相结合,减少了计算量。 14. PNASNet:通过基于策略的方法搜索网络结构,并取得了较好性能。 15. ResNeXt:通过使用分支并行的方式,增强了模型的表示能力。 16. DarkNet:是YOLO目标检测算法所使用的轻量级CNN模型。 17. MobileNetV2:MobileNet的改进版,提高了模型的精度和效率。 18. EfficientNet:通过使用复合缩放方法,同时提高了精度和效率。 19. HRNet:使用高分辨率特征金字塔,并行提取多尺度特征。 20. GhostNet:通过引入ghost module,在减少模型复杂度的同时保持准确率。 以上是20个比较流行的CNN模型,它们在不同应用场景下,具有不同的特点和性能表现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值