论文DenseNet(Densely Connected Convolutional Networks)解读

论文地址:arxiv-paper

实现代码:github

Introduction

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

mark

原本 L L L层的网络有 L L L个连接,现在 L L L层的网络共有 C L + 1 2 = L ( L + 1 ) 2 C_{L+1}^2=\frac{L(L+1)}{2} CL+12=2L(L+1)个连接。

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回传为例,示意图如下:

在这里插入图片描述

KaTeX parse error: No such environment: align at position 7: \begin{̲a̲l̲i̲g̲n̲}̲ y_2 &= y_1+f_2…

注意上式的不等于,为什么backward的部分gradient路径无法联通?这是因为 f 2 ( ⋅ ) f_2(·) f2()是非线性的,即变现为 f 2 ( y 0 + f 1 ( y 0 , w 1 ) , w 2 ) ≠ f 2 ( y 0 , w 2 ) + f 2 ( f 1 ( y 0 , w 1 ) , w 2 ) f_2(y_0+f_1(y_0,w_1),w_2)≠ f_2(y_0,w_2)+f_2(f_1(y_0,w_1),w_2) 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

记模型的输入图片为 x o x_o xo,模型由 L L L层组成,每层的非线性转换函数为 H l ( ⋅ ) H_l(·) Hl() l l l是层的序号。将 l t h l^{th} lth层的输出记为 x l x_l xl

Dense connectivity

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

mark

Composite function

即单个Block内,层与层之间的非线性转换函数 H l ( ⋅ ) H_l(·) Hl()就是Composite function,,每个Composite function的结构如下:
B N → R e L U → C o n v ( 3 × 3 ) BN \rightarrow ReLU \rightarrow Conv_{(3×3)} BNReLUConv(3×3)

Transition layer

不同层的feature map大小不同,考虑到池化层在CNN模型内的重要性,提出一个Transition layer用于连接Block与Block,每个Transition layer的结构如下:
B N → C o n v ( 1 × 1 ) → A v g    P o o l ( 2 × 2 ) BN \rightarrow Conv_{(1×1)} \rightarrow Avg \ \ Pool_{(2×2)} BNConv(1×1)Avg  Pool(2×2)

Growth rate

如果一个 H l H_l Hl输出 k k k个feature maps,那么 l t h l^{th} lth层有 k 0 + k × ( l − 1 ) k_0+k×(l-1) k0+k×(l1)个feature maps输入。 k 0 k_0 k0是输入层的通道数。如果 k k k太多,即feature map太多,从而导致模型参数太多。这里我们定义Growth rate就是超参数 k k k,用于控制feature maps的数量。

DenseNet-BC

Bottleneck layers

尽管每层只产生 k k k个feature maps,但还是很多。**这里就要用到 1 × 1 × n 1×1×n 1×1×n的小卷积来降维了。**作者发现在DenseNet上使用 1 × 1 × n 1×1×n 1×1×n小卷积很有效,并定义了Bottleneck layers,结构如下:
B N → R e L U → C o n v ( 1 × 1 ) → B N → R e L U → C o n v ( 3 × 3 ) BN \rightarrow ReLU \rightarrow Conv_{(1×1)} \rightarrow BN \rightarrow ReLU \rightarrow Conv_{(3×3)} BNReLUConv(1×1)BNReLUConv(3×3)

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

Compression

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

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

一般 0 &lt; θ &lt; 1 0&lt;\theta&lt;1 0<θ<1,在paper的实验中, θ = 0.5 \theta=0.5 θ=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=40,k=12\}, {L=40,k=12} { L = 100 , k = 12 } , \{L=100,k=12\}, {L=100,k=12} { L = 100 , k = 24 } \{L=100,k=24\} {L=100,k=24}
  • 模型为DenseNet-BC,配置为 { L = 100 , k = 12 } , \{L=100,k=12\}, {L=100,k=12} { L = 250 , k = 24 } , \{L=250,k=24\}, {L=250,k=24} { L = 190 , k = 40 } \{L=190,k=40\} {L=190,k=40}
  • 在送入第一个Dense Block前,会先送到一个16通道的卷积层
  • 使用 3 × 3 3×3 3×3的小卷积,采用zero-padding保持feature map尺寸
  • 最后一个Dense Block后接一个global average pooling,再跟softmax分类。

ImageNet数据集

  • 使用的是DenseNet-BC

  • 使用4个Dense Block

  • 在送入第一个Dense Block前,会先送到一个 7 × 7 × 2 k 7×7×2k 7×7×2k s t r i d e = 2 stride=2 stride=2的卷积层

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

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



Experiments

Training Details

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

数据集description
CIFAR and SCHNbatch size 64 for 300 and 40 epochs
learning rate初始设置为0.1,在epoch执行到50%和75%的时候降低10倍
ImageNetbatch 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 1 0 − 4 10^{-4} 104
Nesterov momentum0.9
dropout在C10,C100,SVHN上,没有使用data augmentation,则在每个卷积层后添加dropout layer(除了第一个),并设置为0.2

实验结果

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

mark

L L L表示网络深度, k k 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

i n p u t i = f w i ( i n p u t i − 1 ) g r a d I n p u t i = b w i ( i n p u t i − 1 , g r a d I n p u t i + 1 ) input_i = fw_i(input_{i-1}) \\ gradInput_i = bw_i(input_{i-1}, gradInput_{i+1}) inputi=fwi(inputi1)gradInputi=bwi(inputi1,gradInputi+1)

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

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

mark

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



参考资料

Lyken回答

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值