最近出了一篇旷视科技的孙剑团队出了一篇关于利用Channel Shuffle实现的卷积网络优化——ShuffleNet。我关注了一下,原理相当简单。它只是为了解决分组卷积时,不同feature maps分组之间的channels信息交互问题,而提出Channel Shuffle操作为不同分组提供channels信息的通信的渠道。然而,当我读到ShuffleNet Unit和Network Architecture的章节,考虑如何复现作者的实验网络时,总感觉看透这个网络的实现,尤其是我验算Table 1的结果时,总出现各种不对。因此我将作者引用的最近几个比较火的网络优化结构(MobileNet,Xception,ResNeXt)学习了一下,终于在ResNeXt的引导下,把作者的整个实现搞清楚了。顺带着,我也把这项技术的发展情况屡了一下,产生了一些个人看法,就写下这篇学习笔记。
MobileNet,Xception,ResNeXt,ShuffleNet, MobileID
自从2016年3月,谷歌用一场围棋比赛把人工智能(AI, Artificial Intelligence)正式推上了风口。深度学习突然间成为了整个IT行业的必备知识,不掌握也需要去了解。然而,在2014年我刚毕业的时候,这项技术并没有现在那么火。当时概念大家都还是比较模糊,我也只是在实习听报告时,听邵岭博士提了一下,却没想到现在已经成为模式识别界的一枚巨星。尽管我后悔当时没在这块狠下功夫,但庆幸还能赶上末班船的站票。
CNN的提出其实很早,在1985年Hinton就提出了BP(反向传播算法),1998年LeCun就基于这项工作发表了LeNet用于解决手写邮政编码的识别问题。之后这项技术很少人去接触,有观点认为当时并没有资源能承担CNN的计算消耗。而它的转折点却是十多年后,在ImageNet比赛中,Alex在Nvidia的两GPU上跑他设计的AlexNet架构,成功在众人面前秀了一把CNN的并行计算操作并一举夺冠,吸引了少量学者眼球。两年过去,VGG和GoogLeNet在ImageNet上展开冠军争夺战,标志着深度学习正式起跑,业界开始关注并尝试利用CNN解决一些过去的难题,比如目标跟踪,目标检测,人脸识别等,它这些领域也得到了不少突破。
然而,CNN的这些突破大多都是在计算代价巨大的条件下产生的,比方说令人目瞪狗呆的千层网络—ResNet。其实这并不利于深度学习在消费类产业的推广,毕竟消费类产品很多是嵌入式的终端产品,而且嵌入式芯片的计算性能并不很强。即使我们考虑云计算,也需要消耗大量的带宽资源和计算资源。因此,CNN的优化已成为深度学习产品能否在消费市场落脚生根的一个重要课题之一。所以,有不少学者着手研究CNN的网络优化,如韩松的SqueezeNet,Deep Compression,LeCun的SVD,Google的MobileNet以及这个孙剑的ShuffleNet等。
其实,网络压缩优化的方法有两个发展方向,一个是迁移学习,另一个是网络稀疏。迁移学习是指一种学习对另一种学习的影响,好比我们常说的举一反三行为,以减少模型对数据量的依赖。不过,它也可以通过知识蒸馏实现大模型到小模型的迁移,这方面的工作有港中文汤晓鸥组的MobileID。他们运用模型压缩技术和domain knowledge,用小的网络去拟合大量的数据,并以大型teacher network的知识作为监督,训练了一个小而紧凑的student network打算将DeepID迁移到移动终端与嵌入式设备中。本文先不对这项工作进行详细介绍,后面打算再补充一篇相关博文。网络稀疏是现在比较主流的压缩优化方向,这方面的工作主要是以网络结构的剪枝和调整卷积方式为主。比如之前提到深度压缩,它先通过dropout,L1/L2-regularization等能产生权重稀疏性的方法训练体积和密度都很大的网络,然后把网络中贡献小(也就是被稀疏过的)的权重裁剪掉,相当于去除了一些冗余连接,最后对模型做一下fine-tune就得到他所说的30%压缩率的效果。但它在效率上的提高并不适合大多数的通用CPU,因为它的存储不连续,索引权重时容易发生Cache Miss,反而得不偿失。下面介绍的MobileNet在这方面更有优势。
MobileNet的主要工作是用depthwise sparable convolutions替代过去的standard convolutions来解决卷积网络的计算效率和参数量的问题。它默认一个假设,就是常规卷积核在feature maps的channels维度映射中,存在一种类似线性组合的分解特性。我们用K表示一个常规卷积核,则
MobileNet的Sparable Convolutions结构,除了上面提到的核分解压缩参数量,减少计算量的优点以外,它还有另一个很好的亮点就是,这种分离结构对现在绝大多数移动终端的CPU指令加速硬件,是非常友善的。这也是现在移动端的深度学习平台都必须要支持MobileNet(可分离卷积操作)的重要原因。
我大致分析一下这个点,我们先要了解SIMD硬件加速指令的概念。百度百科的概述是,“SIMD全称Single Instruction Multiple Data,单指令多数据流,能够复制多个操作数,并把它们打包在大型寄存器的一组指令集”,它的硬件是一个大型寄存器,我原来芯片公司的SIMD矢量寄存器是128位,也就是一条指令译码后几个执行部件同时访问内存,一次性获得128位操作数进行运算,比如它能在一个cycle内完成16个8bit类型( 128=16×8 )操作数加法运算。现在大部分ARM的移动终端产品都一套NEON指令集,这是基于ARM的128\256位SIMD寄存器集成的,最近它们还开源了名为ARM ComputeLirary的深度学习计算平台,里面的各种算子大量使用NEON指令和OpenCL库。这似乎意味着,图像处理这种数据密集型运算很适合利用SIMD指令加速来得到很高的效率,然而对于卷积神经网络而言结果并不那么理想。问题的主要原因在于读取内存,朴素的卷积运算是需要跨行取数的。
如上图所示,若我想得到Convolved Feature中的4,我需要从Image那里把黄色部分的[1,1,1,0,1,1,0,0,1]取出,并与卷积核[1,0,1,0,1,0,1,0,1]做内积(inner product)才能的到结果4。而Image的存储方式大多是行连续存储的,即Image=[1,1,1,0,0,0,1,1,…,0,0],然后我们就会发现当要取出[0,1,1]时,这个向量在内存上并不是紧接在[1,1,1]后面。我一条指令取出的8个数(假设16bit类型),其中就需要扔掉两个,这个例子是Image比较小的情况,实践中除前3个数以外,后面所有数都要丢弃。这种数据带宽的利用率很低,并且很容易引发Cache Miss问题,毕竟CPU都会做Cache数据预取控制,不连续的内存访问会影响这种控制的效果。CPU也会经常空闲,不会被有效利用。虽然也有不少人在卷积算子实现上下功夫缓解这个问题,但是也无非只是时间与空间上的妥协,难以真正克服这一矛盾。
MobileNet的可分离卷积结构对这个矛盾很有效果。
上图右边是一个可分离卷积模块,左边是常规卷积模块。如果它们的Input Channels数是 n ,Ouput Channels数是m,那么我们可以知道左边需要做的卷积次数是 n×m ,而右边的可分离模块只有 n ,在结构上大幅减少了SIMD跨行访存的机会,也就是Cache miss的可能性。而 1×1 Conv在实现当中仅是一个数乘向量运算,这相当适合SIMD的大数据访存机制,使得带宽和CPU能被有效利用。
简言之,正因为MobileNet的可分离卷积模块在压缩参数同时还压缩了计算量,还能充分发挥现代CPU计算能力与数据读取效率,再加上这些优化还不影响准确率,所以它被移动终端领域广泛应用。从下面的数据对比,我们可以看到参数和准确率差不多的情况下,MobileNet的计算量远小于SqueezeNet。而且SqueezeNet的Fire module实际上只是bottle neck module的变形与InceptionV1模块区别不大,只是少做了几种尺度的卷积而已。
Xception的主要工作是解释常规卷积(regular convolution
)如何从Inception模块过渡到可分离卷积(depthwise separable convolution)。它认为Inception模块背后有一个基本假设,就是输入通道间的相关性和空间相关性是可以退耦合的,即使不把它俩连接起来映射,也能达到很好的效果。它的提出背景是把一个卷积层看作三维空间的滤波器(2维平面空间+1维feature map通道),其中一个卷积核需要同时对通道间的相关性和空间相关性联合做映射。
然后,考虑一下Inception简化情形,将所有分支统一到最简瓶颈模块的形式(图Figure 2)。基于Inception的简化情形,可以将所有 1×1 核重新组合形成一个大卷积核,比如图Figure 2中3组32个 1×1×128 可以直接重组成96个 1×1×128 卷积核(图Figure 3)。Xception在Figure 3的情况下,将Inception模块的假设推向极限,即输入通道间的相关性和空间相关性是完全可分的,也就是说Figure 3的3组256个 64×64×32 卷积核变成96组256个 64×64×1 的卷积核,其实就是分组数与feature maps channels相等(图Figure 4)。Xception认为这种“极限”Inception,先做 1×1 的通道相关映射,在分别对应每个channel的Feature Map做空间相关映射,可以等同于前面MobileNet章节所题到depthwise separable convolution,因此可分离卷积(MobileNet类网络)可以完全代替过去的常规卷积网络,Inception是这个替代过渡的重要桥梁。
可是,Xception发现这个“极限”Inception模块与可分离卷积模块有两大区别:
1、可分离卷积模块是先做 3×3 的channel-wise卷积,再做 1×1 的常规卷积,进行通道相关性映射。“极限”Inception模块却恰好相反。
2、“极限”Inception模块的 1×1 和 3×3 之间会有ReLU非线性激活,而可分离卷积没有。
对于区别1,前文已经介绍 Xception 认为两者是可以相互认同的,它当然不会自相矛盾,因此Xception认为区别1并不重要。所以Xception的后续论证与实验部分基本都是在讨论非线性激活、残差连接等因素非但不会影响可分离卷积模块的发挥,而且在模型参数大小相当时,可分离卷积的效果还比Inception好。以此可以论证出Inception基本进入历史,以可分离卷积为基础的Xception开始接班。
虽然Xception认为它的工作是解释可分离卷积如何从Inception发展过来的,但是我认为它的贡献只是在实验数据上验证了可分离卷积模块的有效性,这是我从MobileNet的角度看所得到的结论,我甚至还认为Xception只是补充了MobileNet论文的一些在效果方面的实验。因为Xception论文只是在文中提到了MobileNet,而并没有跟它做直接对比,毕竟二者太相近,所以我猜可能是有意规避的。下面我想从两个方面简单讨论一下Xception的理论问题,一个方面是“极限”Inception模块与可分离卷积模块区别1的重要性,另一方面是MobileNet与Xception的区别,其实也就只是“极限”Inception模块与可分离卷积的区别2。
我先讨论MobileNet与Xception的区别。这个区别是MobileNet的 3×3 的 channels-wise 和 1×1 的 point-wise 之间会有ReLU非线性激活与Batch Normalization计算。我认为Batch Normalization是可以忽略的,因为这在Inference阶段只是一个线性运算,可以优化到weights和biases当中,我也验证过这个结论。至于ReLU非线性,Xception的4.7实验表示在训练阶段中间没有任何激活函数比有激活函数要好,但我认为在inference阶段没有太大贡献。所以Xception与MobileNet相比可能在训练效果上,会有更好的收敛,因此在MobileNet优化上可以考虑去掉中间的一些冗余的ReLU激活。
而“极限”Inception模块与可分离卷积模块是否相互认同,我认为这件事情需要满足某个条件。这条件我用式( 1 )的形式表示为
简言之,我认为从理论上来看,Xception并没有太大创新,只是编了个故事,验证了一个结论。它最大的贡献可能是给出了能论证可分离卷积的有效性的实验数据。从论文引用看来,其实Xception和MobileNet是作者们对同一个成果的不同挖掘点,也是不同的发展思路,它们的separable convolution都是来自于一篇学位论文——Rigid-Motion Scattering For Image Classification。而MobileNet对它的带来的效率与空间节省方面的好处更感兴趣,而Xception更纠结于它设计出来的网络是否具备很好的准确性。
ResNeXt 的主要贡献是引入 Cardinality 维度,在参数量不变情况下提高网络的准确性。
上图左边是ResNet中的一块,也叫做瓶颈模块( bottleneck module ),因为它的输入和输出 Feature Map 的 channels 都等于 256 ,这个数大于中间的 64 个 channels ,符合瓶颈的中间小两头大的特性,所以这个模块名很形象。而上图右边是ResNeXt模块,它是在瓶颈模块的基础上,使用 Inception 模块的 split-transform-merge 策略而设计的新模块,这也可以说是 VGG/ResNet 与 Inception 的结合。
然后,我们看下 ResNeXt 的实现方法。
上图是原文的 ResNet 和 ResNeXt 的对照模板,右栏是 ResNeXt ,其中的C=32表示它的 Cardinality 有32维,也就是在spilt步骤,它用 1×1 卷积将输入 Feature Maps 分成32组,而这个分组数却成为了影响网络效果的关键点,后面实验分析会讨论这个问题。同时,我们也会发现ResNet和ResNeXt跟VGG其实很像,它们都会对几个相同的卷积结构重复几次(参数不重复),从而增加网络的深度。
对 ResNeXt 有整体概念之后,我们看下 ResNeXt 的集合变换( Aggregating Transformations )分析。ResNeXt 先从 Neuron 的最简形式内积( inner product )开始分析,其实内积也是一种集合变换。它的数学形式可以表示成
接着,我们对这个最简形式的 wixi 进行推广。
为了得到良好的实际效果,ResNeXt 在实现上对式( 7 )做了三个处理,一是所有的 τi 都采用相同的拓扑结构——瓶颈结构,采用相同结构这个结论与 Xception 不谋而合。二是加入了残差连接,把公式改成
在模型大小方面,ResNeXt 给出了一个计算公式
最后,我想讨论 ResNeXt 的实验部分,但由于篇幅原因,我不打算贴原文数据逐一分析。所以,我只对实验设计做分析并给出一些个人看法。先说实验设计,我认为 ResNeXt 的实验设计相当科学,用最基本的控制变量法,深入浅出讨论 C 在众多卷积网络超参数的突出地位。ResNeXt 在参数量不变的基础上,从四个方面设计了对照实验论证此观点,首先是Cardinarlity v.s. Width,它分别在ResNet-50和ResNet-100上引入Cardinality,并不断增大这个参数,发现随着Cardinality的增大,top-1 error 会一直减小,如此论证了 Cardinarlity 与 Width 的关系,再是Increasing Cardinality vs. Deeper/Wider,然后是Residual connections,排除残差连接的作用,还在不同的数据集上做验证结论是否一致。最后得到一个结论,Cardinality参数对网络的提升作用是绝对的,也就是说不管网络如何设定超参数,只要提高Cardinality必然能提高网络的效果。有兴趣的读者,可以研读它的实验部分,确实写得很精彩。
然而,我从实验数据上细致分析过后,发现ResNeXt并不是一个飞跃性进展。与 Xception 不同,它的问题并不在创新型上,相反我认为其理论创新是有些的。ResNeXt的问题我认为有三个,其一是它提高的精准度并不算大,甚至在现实场景上看算很小,因为几乎不到1%的效果提升。消费类场景,一般效果提升不到5%,个体用户体验基本没有感觉。而且ImageNet数据含有噪声,Top-5 error 基本足以评定网络分类能力,再纠结top-1 error意义不大。其二,没有明确讨论ResNeXt在计算量和参数量方面的优化情况,只是一直暗示相同参数量情况下,效果比别人好。但从论文数据上对 MobileNet 和 ResNeXt 大致比较,只发现准确率上 MobileNet 比 ResNeXt 要差10%,但在参数量上 MobileNet 只有 ResNeXt 的 50%,也就是说可能 MobileNet 再来点参数量就比 ResNeXt 效果好了。我认为在这个问题上 ResNeXt 为孙剑团队的 ShuffleNet 留下了机会。其三,感觉很傻瓜式,貌似论文建议Cardinality调大就好,没有给出一个调参策略。而且人们明显能发现,分组数等于输入向量维度就是 Cardinality 的极限值,这还需要调么?总的来说,ResNeXt 主要引入了Cardinality 参数并论证这个参数的重要性。
下个章节就到本博客的主要讨论对象ShuffleNet了。这里我想穿插一段关于论文的题外话,由于受论文审评流程等影响,一般follow到一篇工作的时间会比writer做这篇文章(包括找点、实验、成文等,实验报告可以认为是成果)的时间晚个半年到一年左右,甚至再有的保密成果,两年也是有可能的。因此arxiv的论文基本很前沿了,能看到初稿,那基本可认为是两三月前的设计成果。为何说到这,因为我们能看到 ShuffleNet 在 arxiv 上的日期是 2017.7.4 ,而 ResNeXt 的日期是 2016.11.6-2017.4.11 ,所以 ShuffleNet 是很有眼光的,在ResNeXt 未被正式接受的时候就 follow 了,不然三个月搞一篇高水平估计挺折腾的。看来每一篇 Popular Paper 都有它的雏型,越早 follow 越能找到主动性观点。
由于本人近段时间患上拖延症,当我写到这里的时候,出一段与 ShuffleNet 相关的行业新闻。新闻的内容是雷总在小米发布会上,展现了新款手机小米MIX2,它自带的 0.5 秒人脸识别自动解锁功能让我很是感兴趣。因为合作方貌似就是本博主角 ShuffleNet 的作者团队 Face++ ,所以小米MIX2该功能的技术基础很有可能就是我正在介绍的 ShuffleNet。小米发布会的时间是 2017.9.11 ,也就是 ShuffleNet 挂在 arxiv 后的两个月,做一个应用落地这个时间很充足。而且,据我对 FaceNet 的了解,其实人脸识别用时很少的,尤其这种解锁应用,基本是需要足够近的情况下实做识别,输入图像分辨率不需要很大。再是,与用户交互实现解锁,因此可以不做人脸定位,一般人脸定位比识别时耗更高。最后,一部手机最多3个主人,再多就没必要上锁,也就是anchor特别少,匹配次数不多。所以,500ms 基本全是 ShuffleNet 在跑了。我再补充点人脸识别算法知识,现在深度学习做的人脸识别流程,比过去简单很多,可以不需要 landmarks,大致就是输入图先进入 CNN 得到出特征向量,再跟用户录入的特征向量做直接匹配,匹配成功解锁就好。最后,我认为这个 ShuffleNet 应该很深,因为我用小米 Note5A 跑 MobileNet 也到不了 300ms,除非 Face++ 团队在CPU上优化不到位,不然直接使用 ShuffleNet 不会这么慢。
开始进入正题,因为有前面三个网络的知识储备,所以我只对 ShuffleNet 的一些让我关注的点做介绍,打算详细了解它的读者,可以把原文好好读读。首先,还是介绍ShuffleNet的主要工作,而我认为它的主要贡献是相比 MobileNet 提高了准确率,还就如前文所述,突显了分组卷积在运算效率上的优势。
然后,我们分析一下 Xception 与 ResNeXt 的问题。先说效率,Xception 和 ResNeXt 所引入的 depthwise separable convolution 和 group convolution 虽然能协调模型的能力与计算量,但被发现它们的 pointwise convolution 占据着很大的计算量。因此ShuffleNet考虑引入 pointwise group convolution 来解决这个问题,后文有例子能看出这点。再说准确率,前面也提到过 ResNeXt 的 C 参数是有极限的,也就是说给 ResNeXt 调参是没有前途的,仅有的参数还有极限。而且 group convolution 用 groups 数来协调模型效果与计算量,这本身就是一对技术矛盾。TRIZ理论告诉我们遇到技术矛盾,一定要打消协调的念头,并深入挖掘矛盾本质,寻找机会消除矛盾。我认为 ShuffleNet 解决这两个问题的思路是,先引入 pointwise group convolution 解决效率问题,再想办法把它所带来的次级问题与原来的效果问题合在一起解决,原因是次级问题也是group的调整。实际上,引入 pointwise group convolution 可以认为利用 TRIZ 的 STC 算子或提前做原则,这跟 Xception 把 groups 分到最小变成 depthwise 的极限思路也像。既然 ResNeXt 在瓶颈模块中间采用了 splitting 策略,为何就不在输入就采用这种策略呢?这样不网络整体就分离了么?然而,这个分组数 g 跟 Cardinality 一样影响着模型的能力,由此 pointwise group convolution 带来了次级问题。而这个问题的本质是什么呢?对比分组卷积和常规卷积的运算规则,我们能够发现根本矛盾可能是分组卷积没有 Channel Correlation,那么需要解决的矛盾就变成如何让分组卷积也有 Channel Correlation。Face++ 用 Channel Shuffle 来解决这个问题。
如上图(b)所示,Input 分成 3 组并分别做了对应的变换(3x3 GConv1),然后在下一次变换(3x3 GConv2)之前做了一次分组间的 Channel Shuffle。如此一来,每个分组就含有了 其它分组的局部 Channel Correlation 了。如果 Channel Shuffle 次数足够多,我觉着就可以认为这完全等效于常规卷积运算了。这是一个不错的创新点,只是效率看起来并不那么完美,原因是 Channels Shuffle 操作会导致内存不连续这个影响有待评估。另外,即使两个分组的大小不一样,Channel Shuffle 仍然是可以做的。
ShuffleNet 以 Channel Shuffle 为基础构造出 ShuffleNet Unit,最后我们看一下这个 ShuffleNet Unit。
对于上图 Figure2(a) 的ResNet瓶颈模块,比如输入大小为 c×h×w 与 瓶颈channels数为 m 的情况,ResNet 模块的计算量是 hw(2cm+9m2) FLOPs,ResNeXt 模块的计算量是 hw(2cm+9m2/g) FLOPs, 而 ShuffleNet 模块是 hw(2cm/g+9m) FLOPs,其中 g 代表分组数。可以看出,ShuffleNet 确实比 ResNeXt 要少些计算量。另外,我之前在验算上图 Table1 的时候,犯了个概念性错误。这里给大家指出一下,就是上图 Figure2 的 1×1 GConv 跟 ResNeXt 的并不一样,ResNeXt 的输入卷积组可以等效成一个常规的 1×1 卷积,但 1×1 GConv 完全不能等效,因为对于不同分组的 GConv 的输入维度是不一样的。下面我给出 Table1 Stage3(g=2)的流程笔记。
Repeat ShuffleNet unit 用的是 Figure2(b) 结构,Stage之间的过渡会用 Figure2(c) 结构。顺着笔记结构的思路去验证Table 1,我们会发现 Complexity 基本一致,那么复现 ShuffleNet 是完全有可能的。值得一提的是,ShuffleNet 虽然引入了 Channel Shuffle,但是它还是留了分组数 g 用来控制 pointwise convolution 的稀疏性,这个参数跟 ResNeXt 的 C 一样。
小结一下 ShuffleNet,通过引入 Channel Shuffle 解决分组卷积的 Channel Correlation 问题,并充分验证它的有效性,同时具备理论创新与实用性。理论上,用了一种轻量级的方法解决了 AlexNet 原有的分组并行信息交互问题。而且这个网络的效率很高,适合嵌入式产品,我怀疑小米 MIX2 的人脸解锁用了这个网络。美中不足的是,Channel Shuffle 看起来对现有 CPU 不大友好,毕竟破坏了数据存储的连续性,使得 SIMD 的发挥不是特别理想,估计在实现上需要再下点功夫。
其实现在网络优化的思路是,数据的密集连续性和连接的稀疏性。这是一个密集连续性与稀疏性的矛盾。现代计算架构的Cache预取机制更擅长存储密集型的数据读取,大多的密集型数据会带来高昂的计算量,因此我们希望数据稀疏一些,来减少计算量。但稀疏的数据又恰恰带来了一个相当不友好的访存方式,引入cache miss问题,CPU性能得不到充分利用。而韩松的工作相当于放弃了现代计算架构访存的优势,打算将这个问题转用专用的稀疏访存硬件解决,毕竟计算量是有本质上的下降。而ShuffleNet等人们是从分离物理矛盾情景解决这个问题,数据存储依然密集,但采用了极限的分组卷积,是的数据的计算相对稀疏,所以他们是一条分组卷积的思路。