DenseNet密集卷积网络详解(附代码实现)
前言
DenseNet是CVPR2017的最佳论文,由康奈尔大学黄高博士(Gao Huang)、清华大学本科生刘壮(Zhuang Liu)、Facebook 人工智能研究院研究科学家 Laurens van der Maaten 及康奈尔大学计算机系教授 Kilian Q. Weinberger 所作,有兴趣的同学可以结合原文阅读。
ResNet通过前层与后层的“短路连接”(Shortcuts),加强了前后层之间的信息流通,在一定程度上缓解了梯度消失现象,从而可以将神经网络搭建得很深,具体可以参考ResNet残差网络及变体详解。更进一步,这次的主角DenseNet最大化了这种前后层信息交流,通过建立前面所有层与后面层的密集连接,实现了特征在通道维度上的复用,不但减缓了梯度消失的现象,也使其可以在参数与计算量更少的情况下实现比ResNet更优的性能。连接方式可以看下面这张图:
标准的 L 层卷积网络有 个连接,即每一层与它的前一层和后一层相连,而DenseNet将前面所有层与后面层连接,故有 个连接。这里看完有些摸不着头脑没关系,接下来我们会具体展开。
Dense Block
Dense Block是DenseNet的一个基本模块,这里我们从一般的神经网络说起:
上图是标准神经网络的一个图,输入和输出的公式是,其中是一个组合函数,通常包括BN、ReLU、Pooling、Conv操作,是第 层输入的特征图,是第 层输出的特征图。
上图则是ResNet的示意图,我们知道ResNet是跨层相加,输入和输出的公式是
而对于DesNet,则是采用跨通道concat的形式来连接,用公式来说则是),这里要注意所有的层的输入都来源于前面所有层在channel维度的concat,我们用一张动图体会一下:
特征传递方式是直接将前面所有层的特征concat后传到下一层,而不是前面层都要有一个箭头指向后面的所有层,这与具体代码实现是一致的,后面会具体的实现。
这里要注意,因为我们是直接跨通道直接做concat,所以这里要求不同层concat之前他们的特征图大小应当是相同的,所以DenseNet分为了好几个Dense Block,每个Dense Block内部的feature map的大小相同,而每个Dense Block之间使用一个Transition模块来进行下采样过渡连接,这个后文会介绍。
Growth rate
假如输入特征图的channel为,那么第 层的channel数就为 ,我们将其称之为网络的增长率(growth rate)。因为每一层都接受前面所有层的特征图,即特征传递方式是直接将前面所有层的特征concat后传到下一层,所以这个不能很大,要注意这个K的实际含义就是这层新提取出的特征。
Bottleneck
在刚才Dense Block中的非线性组合函数是指BN+ReLU+3x3 Conv的组合,尽管每前进一层,只产生K张新特征图,但还是嫌多,于是在进行3×3卷积之前先用一个 1×1卷积将输入的特征图个数降低到 4*k,我们发现这个设计对于DenseNet来说特别有效。所以我们的非线性组合函数就变成了BN+ReLU+1x1 Conv+BN+ReLU+3x3 Conv的结构,由此形成的网络结构我们称之为DenseNet-B。
增加了1x1的卷积的Dense Block也称为Bottleneck结构,实现细节如下:
有以下几个细节需要注意:
- 每一个Bottleneck输出的特征通道数是相同的,例如这里的K=32。同时可以看到,经过concat操作后的通道数是按K的增长量增加的,因此这个K也被称为GrowthRate。
- 这里1×1卷积的作用是固定输出通道数,达到降维的作用,1×1卷积输出的通道数通常是GrowthRate的4倍。当几十个Bottleneck相连接时,concat后的通道数会增加到上千,如果不增加1×1的卷积来降维,后续3×3卷积所需的参数量会急剧增加。比如,输入通道数64,增长率K=32,经过15个Bottleneck,通道数输出为
64+15*32=544
,再经过第16个Bottleneck时,如果不使用1×1卷积,第16个Bottleneck层参数量是3*3*544*32=156672
,如果使用1×1卷积,第16个Bottleneck层参数量是1*1*544*128+3*3*128*32=106496
,可以看到参数量大大降低。 - Dense Block采用了激活函数在前、卷积层在后的顺序,即BN-ReLU-Conv的顺序,这种方式也被称为pre-activation。通常的模型relu等激活函数处于卷积conv、批归一化batchnorm之后,即Conv-BN-ReLU,也被称为post-activation。作者证明,如果采用post-activation设计,性能会变差。想要更清晰的了解pre-activition,可以参考我的博客ResNet残差网络及变体详解中的Pre Activation ResNet。
Transition layer
两个相邻的Dense Block之间的部分被称为Transition层,具体包括BN、ReLU、1×1卷积、2×2平均池化操作。通过1×1卷积层来减小通道数,并使用步幅为2的平均池化层减半高和宽,从而进一步降低模型复杂度。
压缩因子
为进一步提高网络的紧密度,我们可以在转换层(transition layers)减少feature-maps的数量。我们引入一个压缩因子,假定上一层得到的feature map的channel大小为,那经过Transition层就可以产生 个特征,其中在0和1之间。在DenseNet-C中,我们令=0.5。当模型结构即含瓶颈层,又含压缩层时,我们记模型为DenseNet-BC。
DenseNet网络结构
DenseNet网络构成如下:
上图中,增长率K=32,采用pre-activation,即BN-ReLU-Conv的顺序。
以DenseNet-121为例,看下其网络构成:
- DenseNet-121由121层权重层组成,其中4个Dense block,共计2×(6+12+24+16) = 116层权重,加上初始输入的1卷积层+3过渡层+最后输出的全连接层,共计121层;
- 训练时采用了DenseNet-BC结构,压缩因子0.5,增长率k = 32;
- 初始卷积层有2k个通道数,经过7×7卷积将224×224的输入图片缩减至112×112;Denseblock块由layer堆叠而成,layer的尺寸都相同:1×1+3×3的两层conv(每层conv = BN+ReLU+Conv);Denseblock间由过渡层构成,过渡层通过1×1卷积层来减小通道数,并使用步幅为2的平均池化层减半高和宽。最后经过全局平均池化 + 全连接层的1000路softmax得到输出。
DenseNet优缺点
DenseNet的优点主要有3个:
- 更强的梯度流动
DenseNet可以说是一种隐式的强监督模式,因为每一层都建立起了与前面层的连接,误差信号可以很容易地传播到较早的层,所以较早的层可以从最终分类层获得直接监管(监督)。 - 能够减少参数总量
3.保存了低维度的特征
在标准的卷积网络中,最终输出只会利用提取最高层次的特征。
而在DenseNet中,它使用了不同层次的特征,倾向于给出更平滑的决策边界。这也解释了为什么训练数据不足时DenseNet表现依旧良好。
DenseNet的不足在于由于需要进行多次Concatnate操作,数据需要被复制多次,显存容易增加得很快,需要一定的显存优化技术。另外,DenseNet是一种更为特殊的网络,ResNet则相对一般化一些,因此ResNet的应用范围更广泛。
实验效果
这里给出DenseNet在CIFAR-100和ImageNet数据集上与ResNet的对比结果,首先来看下DenseNet与ResNet在CIFAR-100数据集上实验结果,如下图所示,可以看出,只有0.8M大小的DenseNet-100性能已经超越ResNet-1001,并且后者参数大小为10.2M。
下面是DenseNet与ResNet在ImageNet数据集上的比较,可以看出,同等参数大小时,DenseNet也优于ResNet网络。其它实验结果见原论文。
Pytorch实现DenseNet
首先实现DenseBlock中的内部结构,这里是BN+ReLU+1x1 Conv+BN+ReLU+3x3 Conv结构,最后也加入dropout层以用于训练过程。
1 | class _DenseLayer(nn.Sequential): |
据此,实现DenseBlock模块,内部是密集连接方式(输入特征数线性增长):
1 | class _DenseBlock(nn.Sequential): |
此外,实现Transition层,它主要是一个卷积层和一个池化层:
1 | class _Transition(nn.Sequential): |
最后我们实现DenseNet网络:
1 | class DenseNet(nn.Module): |
【参考文档】
深入解析DenseNet(含大量可视化及计算)
来聊聊DenseNet及其变体PeleeNet、VoVNet
稠密连接网络(DenseNet)
深度学习网络篇——DenseNet
论文笔记DenseNet
DenseNet:比ResNet更优的CNN模型
Densely Connected Convolutional Networks