ShuffleNet: An Extremely Efficient Convolutional Neural Network for Mobile Devices

(首先给助教老师说声抱歉,由于我们组的疏忽导致读书报告交晚了,非常感激助教老师的提醒,谢谢!由于微信群文件没有保存,我们没有查到组号,我们的成员是:刘博艺、刁颂辉、陈帆、李在林)

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

设 计 思 想

        卷积神经网络是现代视觉人工智能系统的核心组件。近年来关于卷积模型的研究层出不穷,产生了如VGG、ResNet、Xception 和 ResNeXt等性能优异的网络结构,在多个视觉任务上超过了人类水平。然而,这些成功的模型往往伴随着巨大的计算复杂度(数十亿次浮点操作,甚至更多)。这就限制了此类模型只能用于高性能的服务器集群,而对于很多移动端应用(通常最多容许数百万至数千万次浮点操作)则无能为力。

        解决这一难题的方法之一是设计更为轻量级的模型结构。现代卷积神经网络的绝大多数计算量集中在卷积操作上,因此高效的卷积层设计是减少网络复杂度的关键。其中,稀疏连接(sparse connection)是提高卷积运算效率的有效途径,当前不少优秀的卷积模型均沿用了这一思路。例如,谷歌的”Xception“网络[1]引入了”深度可分离卷积”的概念,将普通的卷积运算拆分成逐通道卷积(depthwise convolution)和逐点卷积(pointwise convolution)两部进行,有效地减少了计算量和参数量;而 Facebook 的“ResNeXt”网络[2]则首先使用逐点卷积减少输入特征的通道数,再利用计算量较小的分组卷积(group convolution)结构取代原有的卷积运算,同样可以减少整体的计算复杂度。ShuffleNet 网络结构同样沿袭了稀疏连接的设计理念。作者通过分析 Xception 和 ResNeXt模型,发现这两种结构通过卷积核拆分虽然计算复杂度均较原始卷积运算有所下降,然而拆分所产生的逐点卷积计算量却相当可观,成为了新的瓶颈。例如对于ResNeXt 模型逐点卷积占据了 93.4% 的运算复杂度。可见,为了进一步提升模型的速度,就必须寻求更为高效的结构来取代逐点卷积。受 ResNeXt 的启发,作者提出使用分组逐点卷积(group pointwise convolution)来代替原来的结构。通过将卷积运算的输入限制在每个组内,模型的计算量取得了显著的下降。然而这样做也带来了明显的问题:在多层逐点卷积堆叠时,模型的信息流被分割在各个组内,组与组之间没有信息交换(如图 1(a) 所示)。这将可能影响到模型的表示能力和识别精度。

图 1 逐点卷积与通道重排操作

因此,在使用分组逐点卷积的同时,需要引入组间信息交换的机制。也就是说,对于第二层卷积而言,每个卷积核需要同时接收各组的特征作为输入,如图 1(b) 所示。作者指出,通过引入“通道重排”(channel shuffle,见图 1(c) )可以很方便地实现这一机制;并且由于通道重排操作是可导的,因此可以嵌在网络结构中实现端到端的学习

网 络 结 构

      首先来构造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的3x3avg 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网络结构

实 验 结 果

作者通过一系列在 ImageNet 2016 分类数据集上的控制实验说明了 ShuffleNet结构单元每个部件存在的必要性、对于其他网络结构单元的优越性。接着作者通过在 MS COCO目标检测上的结果说明模型的泛化能力。最后,作者给出了在 ARM 计算平台上 ShuffleNet 实际运行时的加速效果。分组化逐点卷积。作者对于计算复杂度为 140 MFLOPs 、 40 MFLOPs、13 MFLOPs的 ShuffleNet模型,在控制模型复杂度的同时对比了分组化逐点卷积的组数在1~8时分别对于性能的影响。从 表1中可以看出,带有分组的(g>1)的网络的始终比不带分组(g=1)的网络的错误率低。作者观察到对于较小的网络(如 ShuffleNet 0.25x),较大的分组会得到更好结果,认为更宽的通道对于小网络尤其重要。受这点启发,作者移除了网络第三阶段的两个结构单元,将节省下来的运算量用来增加网络宽度后,网络性能进一步提高。

                                                                    表1 组数对分类错误率的影响

通道重排

通道重排的目的是使得组间信息能够互相交流。在实验中,有通道重排的网络始终优于没有通道重排的网络,错误率降低 0.9%~4.0%。尤其是在组数较大时(如g=8),前者远远优于后者。对比其他结构单元作者使用一样的整体网络布局,在保持计算复杂度的同时将 ShuffleNet 结构单元分别替换为 VGG-like、ResNet、Xception-like 和 ResNeXt 中的结构单元,使用完全一样训练方法。

表2 中的结果显示在不同的计算复杂度下,ShuffleNet 始终大大优于其他网络。

表2 和其他网络结构的分类错误率对比(百分制)对比MobileNets和其他的一些网络结构最近 Howard et al. 提出了 MobileNets[4],利用[1]里的逐通道卷积的设计移动设备上高效的网络结构。虽然ShuffleNet 是为了小于 150 MFLOPs 的模型设计的,在增大到 MobileNet 的 500~600 MFLOPs量级,依然优于 MobileNet。而在 40 MFLOPs 量级,ShuffleNet 比 MobileNet 错误率低 6.7%。详细结果可以从表3中得到。

表3 ShuffleNet 和 MobileNet 对比

和其他一些网络结构相比,ShuffleNet 也体现出很大的优势。从表4中可以看出,ShuffleNet 0.5x 仅用 40

MFLOPs 就达到了 AlexNet 的性能,而 AlexNet 的计算复杂度达到了 720 MFLOPs,是 ShuffleNet 的

18 倍。

表4 ShuffleNet 和其他网络结构计算复杂度的对比

MS COCO物体检测

在 Faster-RCNN[5]框架下,和 1.0 MobileNet-224 网络复杂度可比的 ShuffleNet 2x,在 600 分辨率的图上的 mAP 达到 24.5%,而 MobileNet 为 19.8%,表明网络在检测任务上良好的泛化能力。最后作者在一款 ARM 平台上测试了网络的实际运行速度。在作者的实现里 40 MFLOPs 的 ShuffleNet对比相似精度的 AlexNet 实际运行速度快约 13x 倍。224 x 224 输入下只需 15.2 毫秒便可完成一次推理,在 1280 x720 的输入下也只需要 260.1 毫秒。

Python实现(参考相关博客和github)


这里我们使用Pytorch来实现ShuffleNet,Pytorch是Facebook提出的一种深度学习动态框架,之所以采用Pytorch是因为其nn.Conv2d天生支持group

convolution,不过尽管TensorFlow不支持直接的group

convolution,但是其实可以自己间接地来实现。不过患有懒癌的我还是使用Pytorch吧。

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

defshuffle_channels(x, groups):

"""shuffle channels of a 4-D Tensor"""

batch_size, channels, height, width

= x.size()

assertchannels % groups ==0

channels_per_group = channels// groups

# split into groups

x = x.view(batch_size, groups, channels_per_group,

height, width)

# transpose1,2axis

x = x.transpose(1,2).contiguous()

# reshape into orignal

x = x.view(batch_size, channels, height, width)

returnx

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

classShuffleNetUnitA(nn.Module):

"""ShuffleNet unit for stride=1"""

def__init__(self, in_channels, out_channels, groups=3):

super(ShuffleNetUnitA, self).__init__()

assertin_channels == out_channels

assertout_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)

defforward(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的基本单元:

classShuffleNetUnitB(nn.Module):

"""ShuffleNet unit for stride=2"""

def__init__(self, in_channels, out_channels, groups=3):

super(ShuffleNetUnitB, self).__init__()

out_channels -= in_channels

assertout_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)

defforward(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))

returnout

最后是g=3的ShuffleNet的实现:

classShuffleNet(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)foriinrange(3)]

self.stage2 = nn.Sequential(*stage2_seq)

stage3_seq = [ShuffleNetUnitB(240,480, groups=3)] +

[ShuffleNetUnitA(480,480, groups=3)foriinrange(7)]

self.stage3 = nn.Sequential(*stage3_seq)

stage4_seq = [ShuffleNetUnitB(480,960, groups=3)] +

[ShuffleNetUnitA(960,960, groups=3)foriinrange(3)]

self.stage4 = nn.Sequential(*stage4_seq)

self.fc = nn.Linear(960, num_classes)

defforward(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)

returnlogits

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 文章作者:Tyan博客:noahsnail.com | CSDN | 简书 声明:作者翻译论文仅为学习,如有侵权请...
    SnailTyan阅读 5,654评论 0 4
  • CNN on TensorFlow 本文大部分内容均参考于: An Intuitive Explanation o...
    _Randolph_阅读 7,674评论 2 31
  • 一旦做到简洁,你将无所不能。 只有去掉繁杂,找到若舍弃毋宁死的东西,人才会开始自己的精彩,否则只是随波逐流。 极简...
    JovyMU阅读 309评论 3 2
  • 痛风又称“高尿酸血症”,是人体嘌呤代谢障碍引起的,属于关节炎一种。痛风是人体内嘌呤的物质的新陈代谢发生紊乱,致使人...
    雨下的阳光阅读 331评论 0 1
  • 昨天去看伏魔记,其实有很多话想说的。一直憋着没说,一方面是陪我一起看的那个老同学,真的太长时间没见面了。 一见面就...
    鹳鸟踟蹰阅读 1,515评论 1 1