【轻量化网络系列(5)】ShuffleNetV2论文超详细解读(翻译 +学习笔记+代码实现)

【轻量化网络系列(5)】ShuffleNetV2论文超详细解读(翻译 +学习笔记+代码实现)_第1张图片

前言 

今天我们要讲的是ShuffleNetV2,它是由旷视科技团队在 2018 年提出的,原论文发表在ECCV上。在同等复杂度下,ShuffleNetV2比ShuffleNet和MobileNetv2更准确。这篇论文除了提出这个全新的轻量化网络结构以外,还创新性地提出四个重要的实用性原则,并通过实验证明其准确性。

学习资料:

  • 论文题目:《ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design》(《ShuffleNet V2:高效 CNN 架构设计的实用指南》)
  • 原文地址: ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design

 前期回顾: 

【轻量化网络系列(1)】MobileNetV1论文超详细解读(翻译 +学习笔记+代码实现)

【轻量化网络系列(2)】MobileNetV2论文超详细解读(翻译 +学习笔记+代码实现)

【轻量化网络系列(3)】MobileNetV3论文超详细解读(翻译 +学习笔记+代码实现)

【轻量化网络系列(4)】ShuffleNetV1论文超详细解读(翻译 +学习笔记+代码实现)


目录

前言 

Abstract—摘要

一、Introduction—简介

二、Practical Guidelines for Efficient Network Design—高效网络设计的实用准则

G1) Equal channel width minimizes memory access cost (MAC)—G1)相同通道宽度能够最小化MAC(内存访问成本)

G2) Excessive group convolution increases MAC—过度的组卷积会增加 MAC

G3) Network fragmentation reduces degree of parallelism—网络碎片化会降低并行程度

G4) Element-wise operations are non-negligible—Element-wise操作的影响不可忽略

Conclusion and Discussions—结论和探讨

三、ShuffleNet V2: an Efficient Architecture—一个高效的架构

Review of ShuffleNet v1—回顾ShuffleNetV1

Channel Split and ShuffleNet V2—Channel Split和ShuffleNetV2

Analysis of Network Accuracy—网络精度分析

四、Experiment—实验

准确率 vs. FLOPs

推理速度 vs. FLOPs/Accuracy

与其他方法的结合

几个大型模型结果比较

目标检测任务评估

五、Conclusion—结论

代码实现 

Abstract—摘要

翻译

目前,神经网络架构设计主要由计算复杂度的间接度量(FLOP — 乘加数)衡量。然而,直接度量(例如,速度)还取决于诸如存储访问量和硬件平台特性等其他因素。因此,本文评估目标平台上的直接度量,而不仅仅考虑FLOP。基于一系列对照实验,本文为高效的网络设计提供了几个实用指南。最终提出了称为ShuffleNet V2的新架构。全面的对比实验验证了我们的模型在速度和准确性权衡方面是最先进的。


精读

之前的问题

神经网络架构的设计目前主要由计算复杂度的间接指标(即 FLOPs)来指导。

但是,直接指标(如速度)还依赖于其他因素。

本文主要工作

(1)提出了新的网络结构ShuffleNet V2

(2)指出过去在网络架构设计上仅注重间接指标 FLOPs 的不足,并提出两个基本原则和四个实用准则来指导网络架构设计


一、Introduction—简介

翻译

深度卷积神经网络(CNN)的体系结构已经发展了多年,变得更加准确和快速。自AlexNet的里程碑工作以来,ImageNet分类的准确性通过使用新的结构得到了显著改善,包括VGG,GoogLeNet,ResNet,DenseNet,ResNeXt ,SE-Net和自动架构搜索[9,10,11]等。

除了准确性,计算复杂性是另一个重要考虑因素。现实任务通常旨在由目标平台(例如,硬件)和应用场景(例如,自动驾驶需要低延迟时间)给出的有限计算资源下获得最佳准确度。这激发了一系列针对轻量级架构的设计和更好的速度 - 准确性权衡的工作,包括Xception,MobileNet,MobileNet V2 ,ShuffleNet和CondenseNet。组卷积和深度分离卷积在这些工作中发挥了重要作用。

为了衡量计算复杂度,广泛使用的度量标准是浮点运算量(FLOP)。但是,FLOP其实是一个间接指标,它是我们真正关心的直接度量(例如速度或延迟)的近似值,但通常不完全等价。前面有的论文也注意到了间接度量和直接度量的不同。例如,MobileNet v2比NASNET-A [9]快得多,但它们FLOP很接近。图1(c)(d)进一步呈现了这种现象,表明具有相似FLOP的网络具有不同的速度。因此,使用FLOP作为计算复杂性的唯一度量是不够的,只基于Flop度量可能会导致次优的设计结果。

间接(FLOP)和直接(速度)指标之间的差异可归因于两个主要原因。第一,FLOP没有考虑几个对速度有相当大影响的重要因素。一个因素是存储器访问成本(MAC)。在诸如组卷积的某些操作中,这些成本占了很大一部分的运行时间。它可能是具有强大计算能力的设备(例如GPU)的瓶颈。在网络架构设计期间,不应忽略此成本。另一个是并行化程度。在相同的FLOP下,具有高并行度的模型可能比具有低并行度的模型快得多。

第二,运算速度取决于部署平台,具有相同FLOP的操作可能具有不同的运行时间。例如,张量分解广泛用于早期工作[20,21,22]以加速矩阵乘法。然而,最近的工作[19]发现[22]中的分解在GPU上甚至更慢,尽管它将FLOP降低了75%。我们调查了这个问题并发现这是因为最新的CUDNN [23]库专门针对3×3卷积而优化。我们不能想当然的认为3×3卷积比1×1卷积慢9倍。

通过这些观察,我们建议应该考虑两个原则来进行有效的网络架构设计。首先,应该使用直接度量(例如,速度)而不是间接度量(例如,FLOP)。其次,应在目标平台上评估直接度量指标。

本文中,我们遵循这两个原则,并提出一个更高效的网络架构。在第2节中,我们首先分析了两个具有代表性的最先进网络ShuffleNet V1和MobileNet V2的运行时性能。然后,我们提出了四个高效网络设计的指导方针,这些方针不仅仅考虑了FLOP。虽然这些方针与平台无关,但我们进行了一系列的对比实验,以便在两个不同平台(GPU和ARM)上通过专用代码优化对其进行验证,从而确保我们的结论是最先进的。

在第3节中,根据指导方针,我们设计了一个新的网络结构。由于受到ShuffleNet V1的启发,它被称为ShuffleNet V2。通过第4节中的综合验证实验证明,它比以前的网络在两个平台上都更快,更准确。图1(a)(b)给出了比较的概述。例如,考虑到40M FLOP的计算复杂度预算,ShuffleNet v2分别比ShuffleNet v1和MobileNet v2精确率高3.5%和3.7%。


精读

FLOPs作为衡量指标的不足

FLOPs是一个间接的指标,是一个间接的指标。

直接指标是指速度或延迟。

【轻量化网络系列(5)】ShuffleNetV2论文超详细解读(翻译 +学习笔记+代码实现)_第2张图片 图 1:四个网络架构在两个硬件平台、四种不同计算复杂度上的(验证集 ImageNet 分类)精度、速度和 FLOPs 结果。

如上图,具有相似FLOPs的网络却具有不同的网速。因此,使用FLOPs作为计算复杂度的唯一指标是不够的,可能会导致次优化设计。

FLOPS和FLOPs:

  • FLOPS: 全大写,指每秒浮点运算次数,可以理解为计算的速度,是衡量硬件性能的一个指标 (硬件)
  • FLOPs: s小写,指浮点运算数,理解为计算量,可以用来衡量算法/模型的复杂度,(模型)在论文中常用GFLOPs(1 GFLOPs = 10^9FLOPs)

间接指标和直接指标差异的原因

   (1)对速度有较大影响的几个重要因素对 FLOPs 不产生太大作用。

   (2)由于平台的不同是,使用相同的FLOPs操作可能有不同的运行时间。

ShuffleNetv2提出的两个原则

     第一,应该用直接指标(例如速度)替换间接指标(例如 FLOPs)。

     第二,这些指标应该在目标平台上进行评估。


二、Practical Guidelines for Efficient Network Design—高效网络设计的实用准则

G1) Equal channel width minimizes memory access cost (MAC)—G1)相同通道宽度能够最小化MAC(内存访问成本)

翻译

G1:相同的channel可最大限度地降低内存访问成本(MAC):轻量化网络通常采用深度可分离卷积,其中逐点卷积(即1×1卷积)占了绝大部分的计算量。我们研究了1×1卷积的核心形状,其由两个参数指定:输入通道的数量c1和输出通道的数量c2。设h和w为特征映射图的空间大小,1×1卷积的FLOP为B = h * w * c1 * c2。

为简单起见,我们假设计算设备中的高速缓存足够大以存储整个特征映射图和参数。因此,存储器的访问成本(MAC)或存储器的访问次数 MAC = hw(c1 + c2)+ c1c2。注意,这两项分别对应于输入/输出特征映射图和卷积核所占用的存储空间。

根据均值不等式(c1+c2\geq 2\sqrt{c1c2} , c 1 = c 2 时,等号成立),因此有
因此,MAC的下限和FLOP有关。并且当输入和输出通道的数量相等时,MAC达到下限。

上面的结论是理论上的。实际上,许多设备上的缓存都不够大。此外,现代计算库通常采用复杂的阻塞策略来充分利用缓存机制。因此,实际MAC可能偏离理论MAC。为了验证上述结论,我们进行了如下实验。通过重复堆叠10个网络块来构建基准网络。每个块包含两个卷积层,第一个包含c1输入通道和c2输出通道,第二个相反。

表1通过在固定总FLOP的同时改变c1:c2的比值来测试运行速度。很明显,当c1:c2接近1:1时,MAC更小,网络评估速度更快。


精读

原因:现代网络通常采用深度可分离卷积 ,其中逐点卷积(即1 × 1卷积)占了复杂性的大部分

1×1卷积核的参数由两个量决定:

  • 1.输入通道数C1
  • 2.输出通道数C2

假设一个1×1卷积层的输入特征通道数是c1,输出特征尺寸是hw,输出特征通道数是c2,那么这样一个1×1卷积层的FLOPs就是下面式子所示 :

B=h\times w\times c1 \times c2

(更具体的写法是B=1×1×c1×c2×h×w,这里省略了1×1)

假设计算设备的内存足够大能够储存整个计算图和参数,那么:

MAC=h\times w\times (c1+c2)+c1\times c2

由中值不等式得:

MAC\geq 2\sqrt{h\times w \times B}+\frac{B}{h\times w}

因此理论上MAC的下界由FLOPs决定,当且仅当C1 = C2 时取得最小值

在实验中,由于内存的限制,加上卷积库对于卷积使用的模块优化,真实情况会略有差异,因此作者在现实情况中做了实验结果如图:

【轻量化网络系列(5)】ShuffleNetV2论文超详细解读(翻译 +学习笔记+代码实现)_第3张图片


G2) Excessive group convolution increases MAC—过度的组卷积会增加 MAC

翻译

G2:组卷积的组数越大,MAC越大:组卷积是轻量化网络的核心,它通过将所有channel之间的密集卷积改变为稀疏(仅在同一组内)来降低计算复杂度(FLOP)。一方面,因为组卷积相比普通卷积降低了计算量,因此在给定FLOP的情况下使用组卷积可以使用更多的channel,增加了网络的容量(从而提高了精度)。然而,另一方面,增加的channel数导致更多的MAC。

为了研究实践中的影响,通过堆叠10个逐点组卷积层来构建基准网络。表2报告了在FLOP相同时不同组大小的模型的运行速度。很明显,使用大的组数会显著降低运行速度。例如,在GPU上,g=8比g=1(标准密集卷积)慢两倍,而ARM上则慢30%。这主要是由于MAC的增加。
因此,我们建议应根据目标平台和任务仔细选择组数目。轻易使用大的组是不明智的,虽然这样允许我们可以使用更多的通道,但是快速增加的计算成本带来的损失会轻易地抵消掉准确度增加带来的好处。


精读

组卷积是ResNext提出的结构,使用了稀疏的平行拓扑结构来减少计算复杂度:

【轻量化网络系列(5)】ShuffleNetV2论文超详细解读(翻译 +学习笔记+代码实现)_第4张图片

作者发现,虽然使用组卷积在相同计算复杂度的情况下拓展了通道数,但是通道数的增加也使得MAC大大增加。

假设 g 是1x1组卷积的组数,则有:

MAC=h\times w\times (c1+c2)+\frac{c1\times c2}{g}

MAC=h\times w\times c1+\frac{B\times g}{c1}+\frac{B}{h\times w}

可以看到,对于固定的输入大小 (h,w,ci) 和计算复杂度FLOPS (B),MAC随着 g 的增大而增加

实验中的结果如图:


G3) Network fragmentation reduces degree of parallelism—网络碎片化会降低并行程度

翻译

G3:网络碎片程度会降低推理的并行度:在GoogLeNet系列和自动生成的体系结构[9,11,10]中,每个网络块都广泛采用了一种“多路径”结构。许多小型操作(这里称为“碎片操作”)被用来代替几个大的操作。例如,在NASNET-A中,碎片运算符的数量(即一个构建块中的单个卷积或池化操作的数量)为13。相比之下,在ResNet等常规结构中,此数字为2或3。

虽然这种碎片结构已被证明有利于提升准确性,但它可能会降低效率,因为它对具有强大并行计算能力的设备(如GPU)不友好。它还引入了额外的开销,例如内核启动和同步。

为了量化网络碎片如何影响效率,我们评估了一系列具有不同碎片程度的网络块。每一个基础块都由1到4个1 x 1卷积组成,所不同的是排列方式为顺序排列还是并行排列。块结构如下图所示。每个块重复堆叠10次。表3中的结果表明,碎片程度越大,在GPU上的速度会显著降低,例如: 4分段结构比1分段结构慢3倍。在ARM上,速度降低相对较小。


精读

在GoogleNet系列中,大量使用的Inception这样的multi-path结构(即存在一个lock中很多不同的小卷积或者pooling)增加了准确度:

【轻量化网络系列(5)】ShuffleNetV2论文超详细解读(翻译 +学习笔记+代码实现)_第5张图片

这很容易造成网络碎片化,减低模型的并行度,相应速度会慢。

为了评估分支结构对MAC的影响,作者对几种不同分支化程度的结构进行了实验,实验结果如图:

【轻量化网络系列(5)】ShuffleNetV2论文超详细解读(翻译 +学习笔记+代码实现)_第6张图片


G4) Element-wise operations are non-negligible—Element-wise操作的影响不可忽略

翻译

G4:逐元素操作的执行时间是不可忽略的:如图2所示,在像ShuffleNet V1和MobileNet V2这样的轻量级模型中,逐元素操作占用了相当多的时间,尤其是在GPU上。这里,逐元素运算包括ReLU,AddTensor,AddBias等。它们具有小的FLOP但是相对较重的MAC。特别地,我们还将深度分离卷积视为逐元素运算符,因为它也具有较高的MAC / FLOP比。

为了验证这个结论,我们在ResNet中尝试了“瓶颈”单元(1×1卷积后跟3×3卷积,然后是1×1卷积,带有ReLU和直连通道)的不同变体,表4中报告了不同变体的运行时间。在删除ReLU和直连通道后(减少了逐元素操作的计算量),我们观察到在GPU和ARM上都获得了大约20%的加速。


精读

对于元素级(element-wise operators)比如ReLU和Add,虽然它们的FLOPs较小,但是却需要较大的MAC。

【轻量化网络系列(5)】ShuffleNetV2论文超详细解读(翻译 +学习笔记+代码实现)_第7张图片

这里实验发现如果将ResNet中残差单元中的ReLU和shortcut移除的话,速度有20%的提升。

【轻量化网络系列(5)】ShuffleNetV2论文超详细解读(翻译 +学习笔记+代码实现)_第8张图片


Conclusion and Discussions—结论和探讨

翻译

基于上述准则和实证研究,本文总结出一个高效的网络架构应该:

1)使用「平衡」的卷积(相同的通道宽度,逐点组卷积);

2)考虑使用组卷积的成本(使用小的分组);

3)降低碎片化程度(减少并行);

4)减少元素级运算(捷径连接)。

这些所需特性依赖于平台特征(例如内存控制和代码优化),且超越了理论化的 FLOPs。它们都应该在实际的网络设计中被考虑到。

近期,轻量级神经网络架构 [15,13,14,9, 11,10,12] 上的研究进展主要基于 FLOPs 间接指标,并且没有考虑上述四个准则。例如,ShuffleNet V1 [15] 严重依赖组卷积(违反 G2)和瓶颈形态的构造块(违反 G1)。MobileNet V2 [14] 使用一种倒置的瓶颈结构,违反了 G1。它在「厚」特征图上使用了深度卷积和 ReLU 激活函数,违反了 G4。自动生成结构 [9,10,11] 的碎片化程度很高,违反了 G3。


精读

基于上述准则和实证研究,本文总结出一个高效的网络架构应该:

(1)使用“平衡卷积"(相等的通道数)

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

(3)降低碎片化程度

(4)减少逐元素操作


三、ShuffleNet V2: an Efficient Architecture—一个高效的架构

Review of ShuffleNet v1—回顾ShuffleNetV1

翻译

回顾下ShuffleNet v1,它是一种先进的网络架构,它广泛应用于移动设备等低端设备,它激发了我们的工作。因此,首先对其进行审查和分析。

根据ShuffleNet v1所述,轻量级网络面临的主要挑战是,在给定的计算预算(FLOP)下,只能使用有限数量的channel。为了在不显着增加FLOP的情况下增加通道数,在ShuffleNet v1中采用了两种技术:逐点组卷积和bottleneck结构。然后引入“channel shuffle”操作以实现不同channel组之间的信息交互并提高准确性。网络基础块如图3(a)(b)所示。

如第2节所述,逐点组卷积和bottleneck结构都会增加MAC(G1和G2),这个成本是不可忽视的,特别是对于轻量级的模型。此外,使用太多组违反了G3。在直连通道中进行逐元素相加的操作也是不合需要的(G4)。因此,关键问题是如何在保持较大channel的情况下,既不使用密集卷积也不使用太大的组卷积时,实现高容量和高效率的模型。


精读

ShuffleNetV1回顾→【轻量化网络系列(4)】ShuffleNetV1论文超详细解读(翻译 +学习笔记+代码实现)

【轻量化网络系列(5)】ShuffleNetV2论文超详细解读(翻译 +学习笔记+代码实现)_第9张图片 (a)ShuffleNet 基本单元;(b) 用于空间下采样 (2×) 的 ShuffleNet 单元;

ShuffleNetV1采用的两种技术

  • pointwise group convolution
  • channel shuffle

不足之处

  • 大量使用了1×1卷积。——违背了 G2。
  • 采用了类似ResNet中的瓶颈层(bottleneck layer),输入和输出通道数不同。——违背了 G1。
  • 使用太多分组。——违背了 G3。
  • 短路连接中存在大量的元素级Add运算。—— 违背了G4。

Channel Split and ShuffleNet V2—Channel Split和ShuffleNetV2

翻译

Channel Spilt 和 ShuffleNet V2:为了达到上述目的,我们引入了channel split操作。如图3(c)所示。在每个块的开始处,c个特征通道的输入被分成两个分支,分别具有c − c ′ 和c ′个通道。按照G3,一个分支是恒等函数。另一个分支由三个卷积组成,这三个卷积具有相同的输入和输出通道以满足G1。不同于ShuffleNet V1,两个1×1的卷积不再是分组的了,这部分是一是为了遵循G2,二是拆分操作已经产生了两个组。

卷积后,两个分支连接在一起。因此,通道数保持不变(G1)。然后使用与ShuffleNet V1中相同的“channel shuffle“操作来实现不同channel之间的信息交互。

shuffle后,进入了下一个网络块。请注意,ShuffleNet v1中的“Add”操作不再存在。ReLU和depthwise convolutions等元素操作仅存在于一个分支中。此外,三个连续的逐元素操作,“Concat”,“Channel Shuffle”和“Channel Split”,合并为单个逐元素操作。根据G4,这些更改是有益的。

需要进行下采样的网络块,如图3(d)所示。删除了通道拆分运算符,因此,输出通道的数量加倍。

构建块(c)(d)以及由此产生的网络称为ShuffleNet V2。基于上述分析,我们得出结论,该架构设计高效,因为它遵循所有的准则。

重复堆叠构建块以构建整个网络。为简单起见,我们设置c ′ =c/2。整体网络结构类似于ShuffleNet v1,如表5所示。唯一的一个区别是,在全局平均池化之前添加额外的1×1卷积层以混合特征,这在ShuffleNet v1中是不存在的。与ShuffleNet v1类似,缩放每个块中的通道数以生成不同复杂度的网络,标记为0.5×,1×等。


精读

ShuffleNet V2 的基本单元 

【轻量化网络系列(5)】ShuffleNetV2论文超详细解读(翻译 +学习笔记+代码实现)_第10张图片 (c) ShuffleNet V2 的基本单元

(1)增加了Channel Split操作,实际上就是把输入通道分为2个部分。

(2)根据G1: 左边分支做恒等映射,右边的分支包含3个连续的卷积,并且输入和输出通道相同,每个分支中的卷积层的输入输出通道数都一致。

(3)根据G2: 两个1x1卷积不再是组卷积。

(4)根据G3: 减少基本单元数。因此有一个分支不做任何操作,直接做恒等映射。

(5)根据G4: 两个分支的输出不再是Add元素,而是concat在一起,紧接着是对两个分支concat结果进行channel shuffle,以保证两个分支信息交流。

注意: 右边分支最后那个1×1卷积后面是跟了BN以及ReLU的,这与v1先Add再ReLU不同。

用于空间下采样 (2×) 的 ShuffleNet V2 单元 

【轻量化网络系列(5)】ShuffleNetV2论文超详细解读(翻译 +学习笔记+代码实现)_第11张图片 (d) 用于空间下采样 (2×) 的 ShuffleNet V2 单元

对于下采样模块,不再有channel split,每个分支都有stride=2的下采样,最后concat在一起后,特征图空间大小减半,但是通道数翻倍。

ShuffleNet V2 的整体架构

【轻量化网络系列(5)】ShuffleNetV2论文超详细解读(翻译 +学习笔记+代码实现)_第12张图片

值得注意的一点是,v2在全局池化层之前增加了个conv5卷积,这是与v1的一个区别。


Analysis of Network Accuracy—网络精度分析

翻译

网络准确率分析:ShuffleNet v2不仅高效,而且准确。主要有两个原因,首先,每个构建块的高效率使得能够使用更多的channel,从而网络的容量更大。其次,在每个块中,一半的特征通道(当c ′ = c / 2 时)直接通过该块并入下一个块,这可以被视为一种特征重用,与DenseNet和CondenseNet类似。
在DenseNet中,为了分析特征重用模式,绘制了层间权重的l1范数,如图4(a)所示。很明显,相邻层之间的连接比其他层更强。这意味着所有层之间的密集连接可能引入冗余。最近的CondenseNet也支持这一观点。

在ShuffleNet V2中,很容易证明第i 个和第(i+j)个构建块之间的直接连接的“通道”的数量是 r^jc 
其中r = 1 − c ′ / c  。换句话说,特征重用量随着两个块之间的距离增大而呈指数级衰减。在远程块之间,特征重用变得更弱。对于r = 0.5,图4(b)绘制了与(a)中类似的可视化图。注意(b)中的模式类似于(a)。

因此,ShuffleNet V2的结构设计实现了这种特征重用。与DenseNet一样,它具有高精度特征重用的优点,但如前所述,它的效率更高。这在实验中得到验证,见表8。


精读

ShuffleNet V2高效且准确率高的原因:

(1)每个构建模块非常高效,可以产生更多的通道和高强的网络

(2)在每个模块中,一半的特征通道直接穿过该模块去了下一个模块。这可以被看作为某种“特征复用”

【轻量化网络系列(5)】ShuffleNetV2论文超详细解读(翻译 +学习笔记+代码实现)_第13张图片 图 4:DenseNet [6] 和 ShuffleNet V2 中特征重用模式的图示。

(a)DenseNet论文中,第一个Dense Block各Dense layer之间的权重绝对值

(b)ShuffleNet V2各block之间通过channel split共享的复用通道数


四、Experiment—实验

数据集

  • 分类使用ImageNet 2012
  • 目标检测使用COCO

准确率 vs. FLOPs

翻译

很明显,本文所提出的ShuffleNet v2模型在很大的范围内表现优于所有其他网络,特别是在较小的计算量下。此外,我们注意到MobileNet v2使用224×224大小的图像在40 MFLOP级别上效果很差,这可能是由于channel太少造成的。相比之下,我们的模型没有这个缺点,因为我们的高效设计允许使用更多的通道。此外,虽然我们的模型和DenseNet都重用了特征,但我们的模型效率更高,如第3节所述。

表8还将我们的模型与其他最先进的网络进行了比较,包括CondenseNet ,IGCV2和IGCV3。我们的模型在各种复杂程度上表现更好。


精读

【轻量化网络系列(5)】ShuffleNetV2论文超详细解读(翻译 +学习笔记+代码实现)_第14张图片结论:ShuffleNet V2精确度最高,在小的 FLOPs 上,MobileNet v2 效果很差,因为通道数太少了。


推理速度 vs. FLOPs/Accuracy

翻译

推理速度 vs. FLOPs/Accuracy:对于四个具有良好准确性的架构,ShuffleNet v2,MobileNet v2,ShuffleNet v1和Xception,我们将它们的实际速度与FLOP进行比较,如图1(c)(d)所示。附录表1提供了有关不同分辨率的更多结果。

例如,在500MFLOPs时,ShuffleNet v2比MobileNet v2快58%,比ShuffleNet v1快63%,比Xception快25%。在ARM上,ShuffleNet v1,Xception和ShuffleNet v2的速度相当;但是,MobileNet v2要慢得多,特别是在较小的FLOP上。我们认为这是因为MobileNet v2具有更高的MAC(参见第2节中的G1和G4),这在移动设备上非常重要。

与MobileNet v1,IGCV2和IGCV3相比,我们有两个观察结果。首先,尽管MobileNet v1的准确性不是很好,但它在GPU上的速度要快于所有同类产品,包括ShuffleNet v2。我们认为这是因为它的结构满足了大多数提议的指南(例如,对于G3,MobileNet v1的片段甚至比ShuffleNet v2更少)。其次,IGCV2和IGCV3很慢,这是由于使用了太多的卷积组([27,28]中的4或8)。两项意见均与我们提出的指引一致。

最近,自动模型搜索[9,10,11,35,36,37]已成为CNN架构设计的一种有前途的趋势。表8中的底部评估了一些自动生成的模型。我们发现它们的速度相对较慢。我们认为这主要是由于使用了太多碎片化设计(见G3)。尽管如此,这一研究方向仍然很有希望。例如,如果模型搜索算法与我们提出的指南相结合,并且在目标平台上评估直接度量(速度),则可以获得更好的模型。

最后,图1(a)(b)总结了精确度与速度的结果,即直接度量。我们得出结论,ShuffeNet v2在GPU和ARM上都是最好的。


精读

【轻量化网络系列(5)】ShuffleNetV2论文超详细解读(翻译 +学习笔记+代码实现)_第15张图片

结论:

  • 在GPU 上, 在高 FLOPs 下,shuffleNet v2 性能远远高于其他网络
  • 在ARM 上, 在高 FLOPs 下,各个网络不分上下;但是在低 FLOPs 下,MobileNet v2 就落后很多

与其他方法的结合

翻译

ShuffeNet v2可与其他技术相结合,进一步提升性能。当配备Squeezeand-excitation(SE)模块时,ShuffleNet v2的分类精度提高了0.5%,但代价是速度有一定的损失。块结构如附录图2(b)所示。结果显示在表8中(底部)。


精读

【轻量化网络系列(5)】ShuffleNetV2论文超详细解读(翻译 +学习笔记+代码实现)_第16张图片

结论:配合 SE 提升了 0.5% 个点(25.1 to 24.6),FLOPs 从 591 to 597


几个大型模型结果比较

翻译

尽管我们的主要对比实验是针对轻量级情况进行的,但ShuffleNet v2可用于大型模型(例如,FLOP≥2G)。表6比较了50层ShuffleNet v2(附录中的详细信息)与ShuffleNet v1和ResNet-50的对应结果。 ShuffleNet v2在2.3GFLOPs上仍然优于ShuffleNet v1和ResNet-50,FLOP比ResNet-50减少了40%。

对于非常深的ShuffleNet v2(例如超过100层),为了使训练更快收敛,我们通过添加残差路径稍微修改基本的ShuffleNet v2单元(详见附录)。表6显示了一个配有SE [8]组件的164层ShuffleNet v2模型(详见附录)。与先前最先进的模型相比,它获得了更高的精度[8],FLOP更少。


精读

表 6:大模型的结果

【轻量化网络系列(5)】ShuffleNetV2论文超详细解读(翻译 +学习笔记+代码实现)_第17张图片

结论:ShuffleNet V2比ShuffleNet V1、ResNet具有更少的FLOPs精度更高。和SE模块结合要比原始SENet效果更好。


目标检测任务评估

翻译

为了评估泛化能力,我们还测试了COCO目标检测[38]任务。我们使用最先进的轻量级探测器Light-Head RCNN作为我们的框架并遵循相同的训练和测试协议。只有骨干网被我们的网络替换。模型在ImageNet上预先训练,然后在检测任务上进行微调。对于训练,我们使用COCO中的train + val进行训练,除了来自minival的5000张图像,并使用minival集进行测试。精度度量是COCO标准mmAP,即检测框的IoU阈值从0.5到0.95的平均mAP。

将ShuffleNet v2与其他三种轻量级模型进行比较:Xception,ShuffleNet v1 和MobileNet v2,分为四个复杂程度。表7中的结果表明ShuffleNet v2表现最佳。

将检测结果(表7)与分类结果(表8)进行比较,有趣的是,在分类上,准确度等级为ShuffleNetv2≥MobileNetv2> ShuffeNet v1> Xception,而在检测时,等级变为ShuffleNet v2>Xception≥ShuffleNet v1≥MobileNetv2。这表明Xception在检测任务方面很有用。这可能是由于Xception构建块的感受野比其他对比算法(7对3)更大。受此启发,*我们还通过在每个构建块中的第一个逐点卷积之前引入额外的3×3深度卷积来扩大ShuffleNet v2的感受野。该变体表示为ShuffleNet v2 。只需增加几个额外的FLOP,它进一步提高了准确性。

我们还在GPU上对运行时间进行基准测试。为了公平比较,批量大小设置为4以确保完全利用GPU。由于数据复制的开销(分辨率高达800×1200)和其他特定检测操作(如PSRoI Pooling),不同算法之间的速度差距小于分类的速度差距。尽管如此,ShuffleNet v2仍然胜过其他人,例如比ShuffleNet v1快40%左右,比MobileNet v2快16%。

此外,变体ShuffleNet v2 *具有最佳精度,并且仍然比其他方法更快。这激发了一个实际问题:如何增加感受野的大小,这对于高分辨率图像中的目标检测至关重要。我们将来会研究这个话题。


精读

表 7:ShuffleNet V2 在 COCO 目标检测任务上的性能

【轻量化网络系列(5)】ShuffleNetV2论文超详细解读(翻译 +学习笔记+代码实现)_第18张图片

结论:ShuffleNet v2 做 backbone 的模型精度比其他网络更高、速度更快,全面超越其他网络。


五、Conclusion—结论

翻译

我们建议网络架构设计应考虑直接度量,如速度,而不是像FLOP那样的间接度量。我们提出实用指南和新颖的架构,ShuffleNet v2。综合实验验证了我们新模型的有效性。我们希望这项工作能够激发未来的平台意识和更实用的网络架构设计工作。


精读

本文首先提出,网络架构设计应该考虑直接指标比如速度,而不是间接指标,比如 FLOPs。

紧接着本文给出四个重要的实用设计准则以及一个全新的架构——ShuffleNet V2,通过实验已经证实了其有效性。

最后研究者希望本文的工作可以启迪未来的网络架构设计,更加具有平台意识,并朝着实用的方向发展。


代码实现 

import torch
import torch.nn as nn
import torchvision


# 3x3DW卷积(含激活函数)
def Conv3x3BNReLU(in_channels,out_channels,stride,groups):
    return nn.Sequential(
            nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=3, stride=stride, padding=1, groups=groups),
            nn.BatchNorm2d(out_channels),
            nn.ReLU6(inplace=True)
        )


# 3x3DW卷积(不激活函数)
def Conv3x3BN(in_channels,out_channels,stride,groups):
    return nn.Sequential(
            nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=3, stride=stride, padding=1, groups=groups),
            nn.BatchNorm2d(out_channels)
        )


# 1x1PW卷积(含激活函数)
def Conv1x1BNReLU(in_channels,out_channels):
    return nn.Sequential(
            nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=1, stride=1),
            nn.BatchNorm2d(out_channels),
            nn.ReLU6(inplace=True)
        )


# 1x1PW卷积(不含激活函数)
def Conv1x1BN(in_channels,out_channels):
    return nn.Sequential(
            nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=1, stride=1),
            nn.BatchNorm2d(out_channels)
        )


# 划分channels: dim默认为0,但是由于channnels位置在1,所以传参为1
class HalfSplit(nn.Module):
    def __init__(self, dim=0, first_half=True):
        super(HalfSplit, self).__init__()
        self.first_half = first_half
        self.dim = dim

    def forward(self, input):
        # 对input的channesl进行分半操作
        splits = torch.chunk(input, 2, dim=self.dim)        # 由于shape=[b, c, h, w],对于dim=1,针对channels
        return splits[0] if self.first_half else splits[1]  # 返回其中的一半


# channels shuffle增加组间交流
class ChannelShuffle(nn.Module):
    def __init__(self, groups):
        super(ChannelShuffle, self).__init__()
        self.groups = groups

    def forward(self, x):
        '''Channel shuffle: [N,C,H,W] -> [N,g,C/g,H,W] -> [N,C/g,g,H,w] -> [N,C,H,W]'''
        N, C, H, W = x.size()
        g = self.groups
        return x.view(N, g, int(C / g), H, W).permute(0, 2, 1, 3, 4).contiguous().view(N, C, H, W)


# ShuffleNet的基本单元
class ShuffleNetUnits(nn.Module):
    def __init__(self, in_channels, out_channels, stride, groups):
        super(ShuffleNetUnits, self).__init__()
        self.stride = stride

        # 如果stride = 2,由于主分支需要加上从分支的channels,为了两者加起来等于planes,所以需要先减一下
        if self.stride > 1:
            mid_channels = out_channels - in_channels
        # 如果stride = 2,mid_channels是一半,直接除以2即可
        else:
            mid_channels = out_channels // 2
            in_channels = mid_channels
            # 进行两次切分,一次接受一半,一次接受另外一半
            self.first_half = HalfSplit(dim=1, first_half=True)     # 对channels进行切半操作, 第一次分: first_half=True
            self.second_split = HalfSplit(dim=1, first_half=False)  # 返回输入的另外一半channesl,两次合起来才是完整的一份channels

        # 两个结构的主分支都是一样的,只是3x3DW卷积中的stride不一样,所以可以调用同样的self.bottleneck,stride会自动改变
        self.bottleneck = nn.Sequential(
            Conv1x1BNReLU(in_channels, in_channels),                # 没有改变channels
            Conv3x3BN(in_channels, mid_channels, stride, groups),   # 升维
            Conv1x1BNReLU(mid_channels, mid_channels)                # 没有改变channels
        )

        # 结构(d)的从分支,3x3的DW卷积——>1x1卷积
        if self.stride > 1:
            self.shortcut = nn.Sequential(
                Conv3x3BN(in_channels=in_channels, out_channels=in_channels, stride=stride, groups=groups),
                Conv1x1BNReLU(in_channels, in_channels)
            )

        self.channel_shuffle = ChannelShuffle(groups)

    def forward(self, x):
        # stride = 2: 对于结构(d)
        if self.stride > 1:
            x1 = self.bottleneck(x)     # torch.Size([1, 220, 28, 28])
            x2 = self.shortcut(x)       # torch.Size([1, 24, 28, 28])
        # 两个分支作concat操作之后, 输出的channels便为224,与planes[0]值相等
        # out输出为: torch.Size([1, 244, 28, 28])

        # stride = 1: 对于结构(c)
        else:
            x1 = self.first_half(x)     # 一开始直接将channels等分两半,x1称为主分支的一半,此时的x1: channels = 112
            x2 = self.second_split(x)   # x2称为输入的另外一半channels: 此时x2:: channels = 112
            x1 = self.bottleneck(x1)    # 结构(c)的主分支处理
        # 两个分支作concat操作之后, 输出的channels便为224,与planes[0]值相等
        # out输出为: torch.Size([1, 244, 28, 28])

        out = torch.cat([x1, x2], dim=1)    # torch.Size([1, 244, 28, 28])
        out = self.channel_shuffle(out)     # ShuffleNet的精髓
        return out

class ShuffleNetV2(nn.Module):
    # shufflenet_v2_x2_0: planes = [244, 488, 976]  layers = [4, 8, 4]
    # shufflenet_v2_x1_5: planes = [176, 352, 704]  layers = [4, 8, 4]
    def __init__(self, planes, layers, groups, is_shuffle2_0, num_classes=5):
        super(ShuffleNetV2, self).__init__()
        # self.groups = 1
        self.groups = groups

        # input: torch.Size([1, 3, 224, 224])
        self.stage1 = nn.Sequential(
            # 结构图中,对于conv1与MaxPool的stride均为2
            Conv3x3BNReLU(in_channels=3, out_channels=24, stride=2, groups=1),  # torch.Size([1, 24, 112, 112])
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1)                    # torch.Size([1, 24, 56, 56])
        )

        self.stage2 = self._make_layer(24, planes[0], layers[0], True)          # torch.Size([1, 244, 28, 28])
        self.stage3 = self._make_layer(planes[0], planes[1], layers[1], False)  # torch.Size([1, 488, 14, 14])
        self.stage4 = self._make_layer(planes[1], planes[2], layers[2], False)  # torch.Size([1, 976, 7, 7])

        # 0.5x / 1x / 1.5x 输出为1024, 2x 输出为 2048
        self.conv5 = nn.Conv2d(in_channels=planes[2], out_channels=1024*is_shuffle2_0, kernel_size=1, stride=1)

        self.global_pool = nn.AdaptiveAvgPool2d(1)      # torch.Size([1, 976, 1, 1])
        self.dropout = nn.Dropout(p=0.2)    # 丢失概率为0.2

        # 0.5x / 1x / 1.5x 输入为1024, 2x 输入为 2048
        self.linear = nn.Linear(in_features=1024*is_shuffle2_0, out_features=num_classes)

        self.init_params()

    # 此处的is_stage2作用不大,以为均采用3x3的DW卷积,也就是group=1的组卷积
    def _make_layer(self, in_channels, out_channels, block_num, is_stage2):
        layers = []
        # 在ShuffleNetV2中,每个stage的第一个结构的stride均为2;此stage的其余结构的stride均为1.
        # 对于stride =2 的情况,对应结构(d): 一开始无切分操作,主分支经过1x1——>3x3——>1x1,从分支经过3x3——>1x1,两个分支作concat操作
        layers.append(ShuffleNetUnits(in_channels=in_channels, out_channels=out_channels, stride= 2, groups=1 if is_stage2 else self.groups))

        # 对于stride = 1的情况,对应结构(c): 一开始就切分channel,主分支经过1x1——>3x3——>1x1再与shortcut进行concat操作
        for idx in range(1, 2):
            layers.append(ShuffleNetUnits(in_channels=out_channels, out_channels=out_channels, stride=1, groups=self.groups))
        return nn.Sequential(*layers)

    # 何凯明的方法初始化权重
    def init_params(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight)
                nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.BatchNorm2d) or isinstance(m, nn.Linear):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)

    # input: torch.Size([1, 3, 224, 224])
    def forward(self, x):
        x = self.stage1(x)      # torch.Size([1, 24, 56, 56])
        x = self.stage2(x)      # torch.Size([1, 244, 28, 28])
        x = self.stage3(x)      # torch.Size([1, 488, 14, 14])
        x = self.stage4(x)      # torch.Size([1, 976, 7, 7])
        x = self.conv5(x)       # torch.Size([1, 2048, 7, 7])
        x = self.global_pool(x)     # torch.Size([1, 2048, 1, 1])
        x = x.view(x.size(0), -1)   # torch.Size([1, 2048])
        x = self.dropout(x)
        out = self.linear(x)    # torch.Size([1, 5])
        return out

def shufflenet_v2_x2_0(**kwargs):
    planes = [244, 488, 976]
    layers = [4, 8, 4]
    model = ShuffleNetV2(planes, layers, 1, 2)
    return model

def shufflenet_v2_x1_5(**kwargs):
    planes = [176, 352, 704]
    layers = [4, 8, 4]
    model = ShuffleNetV2(planes, layers, 1, 1)
    return model

def shufflenet_v2_x1_0(**kwargs):
    planes = [116, 232, 464]
    layers = [4, 8, 4]
    model = ShuffleNetV2(planes, layers, 1, 1)
    return model

def shufflenet_v2_x0_5(**kwargs):
    planes = [48, 96, 192]
    layers = [4, 8, 4]
    model = ShuffleNetV2(planes, layers, 1, 1)
    return model

if __name__ == '__main__':
    model = shufflenet_v2_x2_0()
    # model = shufflenet_v2_x1_5()
    # model = shufflenet_v2_x1_0()
    # model = shufflenet_v2_x0_5()
    # print(model)

    input = torch.randn(1, 3, 224, 224)
    out = model(input)
    print(out.shape)

    torch.save(model.state_dict(), "shufflenet_v2_x2_0.mdl")

你可能感兴趣的:(目标检测论文,轻量化网络,shufflenet,目标检测,人工智能,计算机视觉)