一 为什么读这篇
传说中的ResNeXt,也是ResNet官方系列的第三篇,看完这个才算是对ResNet流派有个完整的认识,此外当前很多SOTA模型的底子都是用这个,所以不得不读。
二 截止阅读时这篇论文的引用次数
2019.1.14 671次。
三 相关背景介绍
2016年11月挂在arXiv上。中了17年的CVPR。是2016年ImageNet比赛的第二。一作Saining Xie13年本科毕业于上交ACM班,现在在UCSD读phd,这个工作是在FAIR实习时完成的,所以自然,二作Ross Girshick(rbg)和五作何恺明两位大神的出现就不奇怪了。
四 关键词
ResNeXt
cardinality
五 论文的主要贡献
1 提出ResNeXt结构
2 除了深度和宽度,引入一个全新的维度cardinality(基数)
3 相比Inception,设计更简洁优雅
六 详细解读
1 介绍
本文通过重复的building block来构建网络,该block聚合了具有相同结构的一组变换。并引入了一个新的维度,『cardinality』(变换集合的大小),它是除了深度和宽度之外又一个重要的因素。此外发现当增加容量时,增加cardinality比增加深度和宽度更有效。
最近视觉识别的研究更多的从『特征工程』转变为『网络工程』。然而架构的设计随着超参数(宽度-即通道数,filter大小,strides)的增多而越来越困难。VGG提出了一种简单且有效的策略:stacking同样形状的building block,ResNet也继承了这种策略。这种策略减少了超参数的选择,同时将深度作为至关重要的一个维度暴露出来。相比VGG,Inception系列证明了通过仔细设计的结构也能达到很好的效果。Inception的一个重要策略即『split-transform-merge』(这个总结就有意思了,并没有出现在Inception系列论文中,而是在本文中提到):把输入split成一些低维的embeddings(通过1x1卷积)通过一组特定的filter(3x3,5x5)做transform,最后merge起来。这种策略能保持在较低的计算复杂度下拥有很强的表示能力。不过本文作者也吐槽了Inception尽管效果好,但是超参太多,太难设计。
本文提出的架构采用VGG/ResNet重复堆叠层,同时使用split-transform-merge的策略。这样可以在不用特殊设计的情况下扩展任意数量的变换。
ResNeXt结构(图3-a)等效于Inception-ResNet(图3-b)和组卷积(图3-c)
实验证明,在同样计算复杂度和模型大小的条件下,ResNeXt优于ResNet。本作方法说明cardinality是除了宽度和深度之外很重要的一个维度。实验证明,增加cardinality比增加深度或宽度更有效。ResNeXt名字的寓意即next dimension。ResNeXt-101的效果优于ResNet-200,但是只有其50%的复杂度,这个就很碉堡了。更重要的是,相比所有的Inception,ResNeXt采用了更简单的设计。
2 相关工作
多分支卷积网络
Inception是典型的自定义多分支架构。ResNet可以视为2分支的网络,其中一个分支是恒等映射。深度神经决策森林是树模式的多分支网络。
组卷积
AlexNet应该是组卷积的鼻祖。
压缩卷积网络
分解操作(在空间和/或通道层面)被广泛应用于减少卷积网络的冗余度。这些方法都是准确率和复杂度之间的妥协,而经实验证明ResNeXt架构具有更强的表达能力。
Ensembling
本文的方法是使用加法操作来聚合一组变换。但如果将该方法视为ensembling则有点不太准确,因为聚合的成员都是联合一起训练的,而不是独立训练的。
3 方法
3.1 模板
还是沿用VGG/ResNet的设计套路。并采用两个简单的规则:1)如果产生的spatial map有同样的大小,那么block共享同样的超参(宽度和filter大小)2)每次spatial map以因子2做下采样时,block的宽度就相应的乘上2。第二条规则确保了计算复杂度。根据这两条规则,极大地缩小了设计空间,使得可以聚焦在几个关键因素上。依据这些规则构建的网络可以参考表1。
3.2 重温简单神经元
内积操作可以视为最简单的神经元。内积也可以被视为一种聚合变换的形式(公式1)
上面这个运算也可以被重塑为splitting,transforming,aggregating的联合体。(感觉这个联系好牵强,不知道用意在哪里,硬找个理论依靠?)
Splitting:
Transforming:
Aggregating:
3.3 聚合变换
基于以上分析,考虑将初级变换()替换为更通用的函数,它当然也可以是一个网络。类比『Network-in-Network』,本文提出的『Network-in-Neuron』扩展出了一个新的维度。公式2为聚合变换的定义:
其中是任意函数。类比简单神经元,将x投影到一个embedding,之后对它做变换。C是聚合变换的的个数。也就是cardinality。公式2中的C类似于公式1中的D。
本文中考虑用一种简单的方式来设计变换函数,所有的都是相同的结构,用ResNet中提到的bottleneck-shape架构(图1右侧)
公式3就是加上残差函数的形式:
与Inception-ResNet的关系
图3a和图3b说明一些张量运算是等价的。但是不像Inception,本方法在多个路径上共享了相同的结构,用最少的努力来设计每个路径。(这才是正道,Inception-v4看完后给人一种不批判都不行的感觉)
与组卷积的关系
以上模块如果使用组卷积的符号来表示会更简洁。这里看图3c就够了。32x4d中的32表示32个组卷积,4d表示组卷积的输入输出通道都是4维。图3c和图1左很像,不过图3c更宽同时是稀疏连接。(感觉不就是把ResNet变宽吗。。)
当block的深度为2时,可以参考图4。
讨论
尽管图3b,图3c和图3a是等价的,但是它们并不一直适用于公式3的通用形式。本文使用均质的形式因为他们更简单更易于扩展。
3.4 模型容量
复杂度和参数量表示了模型的固有能力。
在图1左的ResNet bottleneck block中,大约有个参数。在图1右的ResNeXt中,有公式4这么多的参数:
当C=32,d=4时,公式4约等于70k个参数,和ResNet一样。表2说明了cardinality和bottleneck宽度d的关系。
根据表1可以看到ResNet-50和ResNeXt-50的有相似的容量。
4 实现细节
实验通过torch完成,输入图像大小为224x224,数据增强策略和Inception-v1一样。batch_size为256跑在8块GPU上。。。
用SGD,权重衰减为0.0001,动量为0.9。初始学习率为0.1,3次后除10。用He初始化。通过剥除比较,评估是在224x224中间裁剪的图像上(更短的一边是256)。
模型是基于图3c实现的,不过加上了BN,BN加在聚合和变换之后,捷径连接加法操作之前。ReLU在每个BN后执行。
图3的三种形式都有训练,发现结果是一样的,不过图3c更简洁,同时比另外两个要快。
5 实验
5.1 ImageNet-1K
Cardinality vs. Width
ResNeXt-50(32x4d)比ResNet-50的top1错误率还要低1.7%,另外,它的训练误差也要更低,这暗示收益可能不是来自正则化,而是来自更强的表达能力。
另外,从表3也能看出,在同样的复杂度下,当以减少宽度的代价来增加cardinality时,bottleneck宽度很小时准确率开始饱和。
Increasing Cardinality vs. Deeper/Wider
表3说明增加cardinality比增加深度和宽度更有效。
残差连接
有无残差连接的对比还是很明显的,说明残差连接还是很有用的。另外这个比较暗示了残差连接有助于优化,而聚合变换则是有更强的表达能力。
性能
8卡的M40上,ResNeXt-101(32x4d)每个batch0.95s,ResNet-101 0.7s。
ResNeXt-101(64x4d) 1.7s,耗时10天。。
与SOTA的比较
5.2 ImageNet-5K
ImageNet-1k的效果开始饱和了,但是本文怀疑这并不是因为模型的能力导致的,而应该是数据集的复杂度导致的,因此用5K。5K有680万张图,大约是1K的5倍。
5.3 CIFAR
再一次发现增加cardinality比增加宽度更有效。
表7又和Wide ResNet PK一下。
5.4 COCO目标检测
基于Faster R-CNN实现。用ImageNet-1K的预训练权重,之后在检测数据集上fine-tuned。另外Mask R-CNN用的就是ResNeXt,达到了COCO上的SOTA。
七 读后感
论文确实得穿插着读,像split-transform-merge这种总结,Inception系列自己就没有提到。另外第一眼看到图1,有种我屮的感觉,右图感觉其实和ResNet差别不大,就是重复了一下的样子,这样就产生了一篇paper,看来理念有时候在并不是很复杂的情况下也很有效,并能发paper。。还是看原文有意思,也就原文会解释ResNeXt中的next是什么意思这种八卦。整体感觉这篇文章比Inception-v4好太多了。
八 补充
https://towardsdatascience.com/history-of-convolutional-blocks-in-simple-code-96a7ddceac0c
def resnext_block(x, f=32, r=2, c=4):
l = []
for i in range(c):
m = conv(x, f // (c * r), k=1)
m = conv(m, f // (c * r), k=3)
m = conv(m, f, k=1)
l.append(m)
m = add(l)
return add([x, m])
btw,如果cardinality和通道数相等就称为深度可分卷积。Xception提出的。