论文DenseNet(Densely Connected Convolutional Networks)解读

来源:互联网 发布:c语言告白 编辑:程序博客网 时间:2024/06/13 22:51

论文地址:arxiv-paper

实现代码:github

Introduction

DenseNet在ResNet的基础上(ResNet介绍),进一步扩展网络连接,对于网络的任意一层,该层前面所有层的feature map都是这层的输入,该层的feature map是后面所有层的输入。示意图如下:

mark

原本L层的网络有L个连接,现在L层的网络共有C2L+1=L(L+1)2个连接。

DenseNet有几个明显的优点:

  • 减轻了梯度消失问题(vanishing-gradient problem)
  • 增强了feature map的传播,利用率也上升了(前面层的feature map直接传给后面,利用更充分了)
  • 大大减少了参数量


Related Work

在CNN模型里,传统的feed-forward架构可以视为状态模型,状态在层与层之间传播,每一层读取它上一层状态,改变状态并保留一些需要保留的信息并将装备传给下一层。ResNet通过增加额外的identity transformations让状态内需要保留的信息显性化。作者的另一篇paper指出ResNet中有一个非常有意义的现象:网络的许多层贡献较小并且在训练过程中可以被随机丢弃。

这里引用Lyken的回答,分析ResNet,其gradient的主要来源是residual分支;在测试过程中,即便移除掉正常链接(仅留下 shortcut),模型照样能保持较好的正确率。

本论文指出了residual connection实质是 highway network 的一种特殊例子,将 ResNet 展开以后,论文1指出带 Residual Connection 的深网络可以“近似地”看作宽模型(印证了为什么移除主干连接不会大幅影响正确率)。

ResNet再分析

但是ResNet和真正的宽模型还是不同的:Forward 时两者可以看做相同,但 backward 时有部分 gradient 路径无法联通。也就是说, ResNet 在回传 gradient 时,尚有提升的空间,这就是为什么 ResNeXt,Wide-ResNet 等文章能有提升的原因:因为 ResNet 并不是真正的宽模型

以ResNet中一个Residual unit的gradient回传为例,示意图如下:

mark

y2=y1+f2(y1,w2)=y0+f1(y0,w1)+f2(y0+f1(y0,w1),w2)y0+f1(y0,w1)+f2(y0,w2)+f2(f1(y0,w1),w2)

注意上式的不等于,为什么backward的部分gradient路径无法联通?这是因为f2()是非线性的,即变现为f2(y0+f1(y0,w1),w2)f2(y0,w2)+f2(f1(y0,w1),w2)

DenseNet的Insight

既然 Residual Connection 能够让模型趋向于宽网络,那么为什么不直接来个狠得,这就是 Densenet论文核心思想:对每一层的前面所有层都加一个单独的 shortcut到该层,使得任意两层网络都可以直接“沟通”。即下图:

mark

这一举看似粗暴,实则带来不少好处:

  • 从feature来考虑,每一层feature 被用到时,都可以被看作做了新的 normalization,论文3可以看到即便去掉BN, 深层 DenseNet也可以保证较好的收敛率。
  • 从perceptual field来看,浅层和深层的field 可以更自由的组合,会使得模型的结果更加robust。
  • 从 wide-network 来看, DenseNet 看以被看作一个真正的宽网络,在训练时会有比 ResNet 更稳定的梯度,收敛速度自然更好(paper的实验可以佐证)


DenseNet

记模型的输入图片为xo,模型由L层组成,每层的非线性转换函数为Hl()l是层的序号。将lth层的输出记为xl

Dense connectivity

DenseNet中每层的输入是前面的所有层,故任何两层之间都有连接。但在实际情况下,因为多层之间feature maps大小不同,不便于任何两层之间的组合,受到GoogleNet的启发,论文提出了Dense Block,即在每个Block内,所有layer都保持dense connectivity,而在Block之间是没有dense connectivity,而是通过transition layer连接的。如下图:

mark

Composite function

即单个Block内,层与层之间的非线性转换函数Hl()就是Composite function,,每个Composite function的结构如下:

BNReLUConv(3×3)

Transition layer

不同层的feature map大小不同,考虑到池化层在CNN模型内的重要性,提出一个Transition layer用于连接Block与Block,每个Transition layer的结构如下:

BNConv(1×1)Avg  Pool(2×2)

Growth rate

如果一个Hl输出k个feature maps,那么lth层有k0+k×(l1)个feature maps输入。k0是输入层的通道数。如果k太多,即feature map太多,从而导致模型参数太多。这里我们定义Growth rate就是超参数k,用于控制feature maps的数量。

DenseNet-BC

Bottleneck layers

尽管每层只产生k个feature maps,但还是很多。这里就要用到1×1×n的小卷积来降维了。作者发现在DenseNet上使用1×1×n小卷积很有效,并定义了Bottleneck layers,结构如下:

BNReLUConv(1×1)BNReLUConv(3×3)

并将使用Bottleneck layers的DenseNet表示为DenseNet-B。(在paper实验里,将1×1×n小卷积里的n设置为4k)

Compression

考虑到feature maps的数量多,为了进一步的提高模型的紧凑性,我们可以在transition layers上下手,如果Dense Block内包含m个feature maps,那么可以通过transition layers减少feature maps。

这里让transition layers输出θm个feature maps,这样就能通过控制参数θ来控制feature maps的数量了。我们把参数θ定义为compression factor。

一般0<θ<1,在paper的实验中,θ=0.5使用compression factor的DenseNet记为DenseNet-C。同时使用compression factor 和 Bottleneck layers的DenseNet记为DenseNet-BC

Implementation Details

非ImageNet数据集

  • 使用3个Dense Block
  • 每个Block都有相同的层数
  • 模型为DenseNet,配置为{L=40,k=12}{L=100,k=12}{L=100,k=24}
  • 模型为DenseNet-BC,配置为{L=100,k=12}{L=250,k=24}{L=190,k=40}
  • 在送入第一个Dense Block前,会先送到一个16通道的卷积层
  • 使用3×3的小卷积,采用zero-padding保持feature map尺寸
  • 最后一个Dense Block后接一个global average pooling,再跟softmax分类。

ImageNet数据集

  • 使用的是DenseNet-BC
  • 使用4个Dense Block

  • 在送入第一个Dense Block前,会先送到一个7×7×2kstride=2的卷积层

  • 所有的layers的feature map都设置为k

在ImageNet上,具体的DenseNet-BC如下图:
mark



Experiments

Training Details

所有的网络都是使用SGD训练的,具体的batch和learning rate设置如下:

数据集 description CIFAR and SCHN batch size 64 for 300 and 40 epochs
learning rate初始设置为0.1,在epoch执行到50%和75%的时候降低10倍 ImageNet batch size 256 for 90 epochs
learning rate初始设置为0.1,在epoch执行到30和60的时候降低10倍 DenseNet-161
考虑到GPU显存问题 mini-batch size 128 for 100 epochs
learning rate初始设置为0.1,在epoch执行到90的时候降低10倍 其他设置 description weight decay 104 Nesterov momentum 0.9 dropout 在C10,C100,SVHN上,没有使用data augmentation,则在每个卷积层后添加dropout layer(除了第一个),并设置为0.2

实验结果

DenseNet在CIFAR和SVHV上的表现如下:

mark

L表示网络深度,k为growth rate。蓝色字体表示最优结果,+表示对原数据库进行data augmentation。DenseNet相比ResNet取得更低的错误率,且参数更少。

DenseNet在ImageNet上的表现如下:

mark

可以看到DenseNet相比于ResNet有着更少的参数,更好的测试结果。



Conclusion

DenseNet的优点在前面讲过了,总结的来说就是Feature Reuse,模型Robustness。这里主要关注DenseNet的缺点。

DenseNet 的缺点

mark

在图中可以看到,DenseNet-100层增长率为24时(无BottleNeck的最早版),parameter快要是ResNet-1001的三倍了。一般显卡根本塞不下更深的DenseNet。在不断的优化后,DenseNet 的显存问题已大有改善。

但Flops消耗问题仍令人头疼。本来 DenseNet 的实时性尚还可以(拓扑序跟普通网络一样),但由于其过多的Dense 的num_filters,计算量就超过了很多卡的上限。为了优化这两个问题,论文中采用了bottleneck和compression来大幅压缩filters数目。(将DenseNet实用–>bengio组的DenseNet for segmentation )

为什么会很耗费显存

引用taineleau的回答。首先,无论是什么 framework的NN,都由forward和backward两部分构成。假设只考虑一个 feed-forward network,并且移除所有 in-place 操作(如 ReLU),那么内存依赖大概是这个样子的:

mark

inputi=fwi(inputi1)gradInputi=bwi(inputi1,gradInputi+1)

对于 Backward 来说,深红色的gradInput算完一块就可以扔掉(它的出度为1),这也是几乎所有16 年以后新framework都会做的 shareGradient 优化。

但是浅红色的内存块因为要在backward的时候还会被用到,所以不能扔,那肿么办?[1] 说可以用时间换空间,即在需要用粉红块的时候,重新计算即可。而对于 DenseNet 来说,每个 DenseLayer (Concat-BN-ReLU-Conv),Concat 和 BN 两层的 output 全扔掉就可以省下很多内存,却只多花了 15% 的计算量。

mark

现在已经整理出来的干净代码有 Torch 版本,见PyTorch版,有不同 level 的优化,最多能省 70% 的显存。



参考资料

Lyken回答

原创粉丝点击