DenseNet是在ResNet发表后深受其影响,同时又更为优秀的一种网络结构,由康威大学清华大学、facebook的三位作者共同提出,论文发表于2017,获得了CVPR 2017的最佳论文奖。其核心即dense block稠密块继承和发扬了ResNet中shortcut这一设计使得layer之间可以“稠密”互联,同时,正如其名,不只是层于层间的连接,而且是稠密连接(也借鉴了Inception模块的思想),DenseNet里对于dense block中的每一层,都以前面的所有层作为输入(而其自身也用作后续所有层的输入),层和层之间正是通过ResNet中的shortcut方式进行连接。这种稠密连接的优点:它们减轻了消失梯度的问题,增强了特征传播,鼓励特征重用,并大大减少了参数数量。
下面对该论文的相关技术原理进行学习。
Abstract
Recent work has shown that convolutional networks can be substantially deeper, more accurate, and efficient to trainif they contain shorter connections between layers close to the input and those close to the output. In this paper, we embrace this observation and introduce the Dense Convolutional Network (DenseNet), which connects each layer to every other layer in a feed-forward fashion. Whereas traditional convolutional networks with L layers have L connections—one between each layer and its subsequen tlayer—our network has
direct connections. Foreach layer, the feature-maps of all preceding layers areused as inputs, and its own feature-maps are used as inputs into all subsequent layers. DenseNets have several compelling advantages: they alleviate the vanishing-gradient problem, strengthen feature propagation, encourage feature reuse, and substantially reduce the number of parameters. We evaluate our proposed architecture on four highly competitive object recognition benchmark tasks (CIFAR-10,CIFAR-100, SVHN, and ImageNet). DenseNets obtain significant improvements over the state-of-the-art on most ofthem, whilst requiring less computation to achieve high performance.
翻译
最近的工作表明,如果卷积网络在靠近输入的层和靠近输出的层之间包含较短的连接,则可以训练它们更深入,更准确和有效。 在本文中,我们支持此结论并介绍了密集卷积网络(DenseNet),该网络将每个层以前馈方式连接到每个其他层。 具有L层的传统卷积网络具有L个连接——每个层与其后续层之间有一个连接——我们的网络具有
个连接。 对于每个层,所有先前层的特征图都用作输入,而其自己的特征图则用作所有后续层的输入。 DenseNets具有多种引人注目的优点:它们减轻了梯度消失的问题,加强了特征的传播,鼓励了功能的重用并大大减少了参数数量。 我们在四个竞争激烈的对象识别基准测试任务(CIFAR-10,CIFAR-100,SVHN和ImageNet)上评估了我们提出的体系结构。 DenseNets在大多数方面都比最新技术有了显着改进,同时需要较少的计算即可实现高性能。代码和预训练模型在: https://github.com/liuzhuang13/DenseNet
上图为一个由3个dense block稠密块构成的DenseNet。稠密块之间的连接称为Transition layer过渡层,过渡层由BN+卷积层+池化层构成。论文中主要由BN+1×1卷积+2×2池化构成。连接层除了连接dense block外,主要作用有二:1.通过1×1卷积改变通道维;2.池化层down sampling + 使feature maps的尺寸减半
论文中给出了一个5层的稠密块示意图:
d****ense block稠密块内部的layer通过复合函数H来连接,****复合函数H由BN + ReLU + Conv串联构成。layer包含特征图的层数由输入和增长率k是(超参数)决定,上图k = 4。在DenseNet的实现里,H有两种版本:
1.BN + RuLU + 3×3卷积,主要作用为特征提取
2.BN + ReLU + 1×1卷积 →输出→ BN + RuLU + 3×3卷积
主要作用除了特征提取外,还通过1×1卷积改变通道维控制整体维度,尤其是dense block内靠后的layer。
以DenseNet-121为例,看下其网络构成:
1.DenseNet-121由121层权重层组成(其中4个Dense block,共计2×(6+12+24+16) = 116层权重,加上初始输入的1卷积层+3过渡层+最后输出的全连接层,共计121层);
2.训练时采用了DenseNet-BC结构,压缩因子0.5,增长率k = 32;
3.初始卷积层有2k个filter,经过7×7卷积将224×224的输入图片缩减至112×112;Denseblock块由layer堆叠而成,layer的尺寸都相同:1×1+3×3的两层conv(每层conv = BN+ReLU+Conv);Denseblock间由过渡层构成,过渡层通过2×2 stride2使特征图尺寸缩小一半。最后经过全局平均池化 + 全连接层的1000路softmax得到输出
从20多年前深度卷积神经网络CNN引入到计算机视觉领域中用于图像分类和识别开始,深度卷积神经网络中的“深度”其实不是很深。直到最近,CNNs才可以被称得上是Deep CNNs,因为CNN的深度从最初的LeNet-5的5层网络,VGG的19层,直到最近Highway Networks和ResNets,才突破了100层。这得益于硬件设备的提升以及网络结构的改进。
随着深度越来越深,又带来一个问题:一些特征/梯度在穿过很多层以后,可能会损失殆尽。最近不少相关论文都在解决相关问题,而他们都致力于:在输入和输出之间创建尽可能短的连接路径。
作者团队基于上述思路提出了一种架构:为了确保最大的信息流,将所有层直接互相连接,为了保持前馈的特性,每一层都从先前的所有层中获得附加的输入并将自身的特征图传给所有后续的层(如图1所示)。
图1:5层的dense block,增长率k = 4,每层都接收之前所有层的feature maps作为输入
和ResNet不同,其在特征传入layer之前需经过相加求和,得到组合特征后传递给下一层;在图示的架构中,组合特征的方式是通过【串联】而不是【求和】。在作者的架构中,假设总共有L层,第l层具有l个输入(之前的所有层都会是其输入);而第**l **层的输出也会作为之后所有层的输入。这样L层网络的连接总数将有:
[图片上传失败...(image-e03975-1583759176159)]
由于层于层之间密集的连接模式,这种方式称为稠密卷积网络——DenseNet。
归纳一下,论文中提到的DenseNets的优点:
论文中提到,DenseNets于传统CNNs相比,参数更少,因为其不需要重新学习冗余的feature maps。
这种密集的连接模式的可能与直觉相反的效果是,与传统的卷积网络相比,它需要的参数更少,因为不需要重新学习冗余的特征图。 可以将传统的前馈体系结构视为具有状态的算法,该状态会逐层传递。 每一层都从其上一层读取状态并写入下一层,它不仅改变状态,还传递需要保留的信息。 ResNets [11]通过附加身份转换使信息保存变得明确。ResNets[13]的最新变化表明,许多层的贡献很小,实际上在训练过程中可以随意丢弃。 这使得ResNets的状态类似于(展开的)递归神经网络[21],但是ResNets的参数数量大得多,因为每一层都有自己的权重。 我们提出的DenseNet体系结构明确区分了添加到网络的信息和保留的信息。DenseNet的层非常狭窄(例如,每层12个过滤器),仅向网络的“集体特征”添加了少量的特征子集并保持其余特征图不变-最终分类器根据网络中的所有特征图做出决定。
除了更少的参数,DenseNets的另一优势是改善了整个网络中的information flow和梯度,使训练更容易。
每层都可以直接从损失函数和最初的输入信号访问梯度,从而导致隐式深度监管[20]。 这有助于训练更深层次的网络结构。 此外,我们还观察到密集连接具有正则化效果,减少了训练集size较小任务的过拟合现象。
最后,作者及其团队在CIFAR-10,CIFAR-100,SVHN和ImageNet评估了DenseNet,得出结论:与现有算法比,DenseNet的参数少得多,训练得到的模型精度更高,在大多数任务上性能明显由于当前state-of-art结果。state-
论文中指出,除了增加网络深度以外,还可以考虑增加网络的宽度,使得模型表现更为优异。譬如GoogLeNet中通过Inception模块,将不同size的filter生成的feature maps在通道上连接;实际上,在深度足够的情况下,完全可以通过增加filter的数量来提高网络表现(譬如在ResNets中增加每层filter的数量)。
虽然DenseNet借鉴了ResNet中shortcut连线的思路,但重点不在于加深网络,而是通过功能重用来挖掘网络潜力,在DenseNets中,模型更浓缩,参数高效且更易于训练,这构成了和ResNet 的主要差别。
作者还提到了对比和点评了一些其他网络如Network in Network、深度监督网络(DSN)、Ladder Networks等
CNNs
论文中,复合函数H有两种:
第1种:BN + RuLU + 3×3卷积,采用此方式的DenseNet为标准DenseNet;
第2种:在第1种的基础上增加了BN + ReLU + 1×1卷积的操作,增加的这层操作,也被称为瓶颈层,采用此种方式的DenseNet版本被称为DenseNet-B。尽管,可以通过超参数k来控制dense block的总体层数,但有时叠加起来导致特征图的通道数目仍然很大,故瓶颈层存在的主要目的就是降维,提高计算效率。
如上图,通过瓶颈层,将通道维度从l → 4
增长率Growth rate
在一个Dense Block内,每一个layer产生的特征图数量通常由输入和超参数k共同决定,超参数k称为增长率.增长率k决定了每一个复合函数输出的特征图数量为k,则得出第l层具有
个输入特征图,k0为上一层输入。
DenseNet与现有网络体系结构之间的一个重要区别是DenseNet可以具有非常狭窄的层,例如k =12。较小的增长率即可取得较好的学习效果,因为在dense block中每一层都可以学习和访问之前所有层的特征图,即可以方便地学到“集体知识”。在稠密块中,特征图可以视为网络的全局状态,每层都将自己产生的k个特征图贡献到此状态中,写入后的全局状态可以在网络的任意位置访问。
过渡层Transition layer
符合函数H和增长率k控制的是Dense block稠密块内部的事情,而过渡层则用于稠密块之间的连接。论文中的过渡层由BN + conv(1×1) + average pool(2×2 stride2)组成。过渡层保留了传统卷积神经网络中的池化层,且减半池化可以控制feature maps的尺寸。
压缩
为了进一步提高模型的紧凑性,我们可以在dense block块之间的瓶颈层减少特征图数量(维度)。 其中0 <θ≤1称为压缩因子。当θ= 1时,跨过过渡层后特征图数量保持不变。当θ<1时,网络称为DenseNet-C;当瓶颈层和过渡层都采用θ<1时,模型称为DenseNet-BC。论文实验中给定的****θ = 0.5,即通道维压缩一半。
实现细节
直接看论文翻译和下面的图表:
在除ImageNet之外的所有数据集上,我们实验中使用的DenseNet具有三个稠密块,每个稠密块具有相等的层数。 在进入第一个稠密块之前,在输入图像上执行具有16个卷积(或DenseNet-BC增长率为两倍)的卷积。 对于内核尺寸为3×3的卷积层,输入的每一侧都用一个像素补零,以保持特征图尺寸固定。 我们使用1×1卷积,然后使用2×2平均池作为两个连续稠密块之间的过渡层。 在最后一个密集块的末尾,执行全局平均池化,然后附加softmax分类器。 三个稠密块中的特征图大小分别为32×32、16×16和8×8。 我们使用配置{L = 40,k = 12},{L = 100,k = 12}和{L = 100,k = 24}的基本DenseNet结构进行实验。 对于DenseNet-BC,评估配置为{L = 100,k = 12},{L = 250,k = 24}和{L = 190,k = 40}的网络。
在ImageNet上的实验中,我们使用DenseNet-BC结构,在224×224个输入图像上使用4个稠密块。初始卷积层包括2k个卷积,大小为7×7,步幅为2。其他层的特征图的数量根据超参数k指定。 表1显示了我们在ImageNet上使用的确切网络配置。
训练使用了CIFAR-10,CIFAR-100,SVHN,ImageNet数据集。
整体使用了0.0001的权重衰减,0.9的Nesterov动量,权重初始化方式参考[10]. He, X. Zhang, S. Ren, and J. Sun. Delving deep intorectifiers: Surpassing human-level performance on imagenetclassification. InICCV, 2015。
CIFAR和SVHN
训练采用随机梯度下降SGD,在CIFAR和SVHN上采用batch size 64分别训练了300和40轮;学习率初始设为0.1,在达到迭代总轮数50%和75%时,分别除10。在除第一个外的每个卷积层都添加了丢弃率0.2的Dropout。
ImageNet
batch size = 256,epoch = 90,初始学习率0.1,第30轮和第60轮时分别除10;在ImageNet上使用了数据增强。
表格中粗体字表示优于现有最佳模型的结果;标蓝表示总体最佳的结果;+表示标准数据增强处理。
可见在CIFAR-10和CIFAR-100上,所有系列的DenseNet都取得了很好的准确性;深度190 k = 24的DenseNet-BC取得了最佳水平;在CIFAR数据集整体来说,没用数据增强时表现更优秀,比最好的ResNet降低了近30%;
在没有压缩或瓶颈层的情况下,DenseNets的总体趋势是随着层数L和k的增加而表现得更好,对此作者概况其原因为模型参数容量的增加:在C10 +上,随着参数数量从1.0M增加到7.0M到27.2M,错误从5.24%下降到4.10%,最后下降到3.74%;在C100 +上,也有类似的趋势。这表明DenseNets可以利用更大和更深层模型的增强表示能力,也表明DenseNets不存在过拟合的担忧,也不像残差网络存在优化困难的问题。
表2中的结果表明,DenseNets比其他体系结构(尤其是ResNets)更有效地利用参数,具有瓶颈层和过渡层的DenseNet-BC更是能有效减少参数规模。如:250层的DenseNet-BC具有1530万个参数,但始终优于其他具有30M以上参数的模型,例如FractalNet和Wide ResNets
该部分使用了不同深度的DenseNet-BC,对比对象是目前最好的ResNet模型,为了保证结果公平,所有的数据预处理,优化设置等都和ResNet的实现保持一致,其代码实现:
https://github.com/facebook/fb.resnet.torch
图中显示的结果表明,DenseNets的性能与最新的ResNets相当,而所需的参数和计算量却大大减少。不过作者表示,此对比结果建立在所有的参数优化和设定完全遵循ResNet的配置,如果针对DenseNet重新优化,会得到更好的效果。
DenseNet是一种新的卷积网络架构,它在任何两个具有相同特征图尺寸的layer间引入了直接连接。DenseNet可拓展至数百层而不存在优化困难。随着DenseNet的深度和kernel size的提升,大参数会使得精度不断提高,且不会有过拟合现象。可以通过进一步调整超参数和学习率等来优化DenseNet的表现。
在遵循简单连接规则的同时,DenseNets还自然地集成了identity mappings,deep supervision深度监督
,depth多样化等特征。而且其允许特征在网络中重复使用,因而使得模型结构更紧凑,减少冗余。基于以上优点,作者任务DenseNets可以在各种计算机视觉任务中,作为卷积特征提取器使用。
这里使用tensorflow2.0实现DenseNet-121的网络结构,完整模型+训练可参考:5.12 稠密连接网络(DenseNet)
定义组成Denseblock的layer
import tensorflow as tf
class BottleNeck(tf.keras.layers.Layer):
def __init__(self, growth_rate, drop_rate):
super(BottleNeck, self).__init__()
self.bn1 = tf.keras.layers.BatchNormalization()
self.conv1 = tf.keras.layers.Conv2D(filters=4 * growth_rate,
kernel_size=(1, 1),
strides=1,
padding="same")
self.bn2 = tf.keras.layers.BatchNormalization()
self.conv2 = tf.keras.layers.Conv2D(filters=growth_rate,
kernel_size=(3, 3),
strides=1,
padding="same")
self.dropout = tf.keras.layers.Dropout(rate=drop_rate)
self.listLayers = [self.bn1,
tf.keras.layers.Activation("relu"),
self.conv1,
self.bn2,
tf.keras.layers.Activation("relu"),
self.conv2,
self.dropout]
def call(self, x):
y = x
for layer in self.listLayers.layers:
y = layer(y)
y = tf.keras.layers.concatenate([x,y], axis=-1)
return y
定义Denseblock
Denseblock由多个BottleNeck
组成,其输出通道数相同。
class DenseBlock(tf.keras.layers.Layer):
def init(self, num_layers, growth_rate, drop_rate=0.5):
super(DenseBlock, self).init()
self.num_layers = num_layers
self.growth_rate = growth_rate
self.drop_rate = drop_rate
self.listLayers = []
for _ in range(num_layers):
self.listLayers.append(BottleNeck(growth_rate=self.growth_rate, drop_rate=self.drop_rate))
def call(self, x):
for layer in self.listLayers.layers:
x = layer(x)
return
定义过渡层
过渡层用来控制模型复杂度。它通过1×1卷积层来减小通道数,并使用步幅为2的平均池化层减半高和宽,从而进一步降低模型复杂度。
class TransitionLayer(tf.keras.layers.Layer):
def __init__(self, out_channels):
super(TransitionLayer, self).__init__()
self.bn = tf.keras.layers.BatchNormalization()
self.conv = tf.keras.layers.Conv2D(filters=out_channels,
kernel_size=(1, 1),
strides=1,
padding="same")
self.pool = tf.keras.layers.MaxPool2D(pool_size=(2, 2),
strides=2,
padding="same")
def call(self, inputs):
x = self.bn(inputs)
x = tf.keras.activations.relu(x)
x = self.conv(x)
x = self.pool(x)
return x
定义DenseNet模型
class DenseNet(tf.keras.Model):
def __init__(self, num_init_features, growth_rate, block_layers, compression_rate, drop_rate):
super(DenseNet, self).__init__()
self.conv = tf.keras.layers.Conv2D(filters=num_init_features,
kernel_size=(7, 7),
strides=2,
padding="same")
self.bn = tf.keras.layers.BatchNormalization()
self.pool = tf.keras.layers.MaxPool2D(pool_size=(3, 3),
strides=2,
padding="same")
self.num_channels = num_init_features
self.dense_block_1 = DenseBlock(num_layers=block_layers[0], growth_rate=growth_rate, drop_rate=drop_rate)
self.num_channels += growth_rate * block_layers[0]
self.num_channels = compression_rate * self.num_channels
self.transition_1 = TransitionLayer(out_channels=int(self.num_channels))
self.dense_block_2 = DenseBlock(num_layers=block_layers[1], growth_rate=growth_rate, drop_rate=drop_rate)
self.num_channels += growth_rate * block_layers[1]
self.num_channels = compression_rate * self.num_channels
self.transition_2 = TransitionLayer(out_channels=int(self.num_channels))
self.dense_block_3 = DenseBlock(num_layers=block_layers[2], growth_rate=growth_rate, drop_rate=drop_rate)
self.num_channels += growth_rate * block_layers[2]
self.num_channels = compression_rate * self.num_channels
self.transition_3 = TransitionLayer(out_channels=int(self.num_channels))
self.dense_block_4 = DenseBlock(num_layers=block_layers[3], growth_rate=growth_rate, drop_rate=drop_rate)
self.avgpool = tf.keras.layers.GlobalAveragePooling2D()
self.fc = tf.keras.layers.Dense(units=1000,
activation=tf.keras.activations.softmax)
def call(self, inputs):
x = self.conv(inputs)
x = self.bn(x)
x = tf.keras.activations.relu(x)
x = self.pool(x)
x = self.dense_block_1(x)
x = self.transition_1(x)
x = self.dense_block_2(x)
x = self.transition_2(x)
x = self.dense_block_3(x)
x = self.transition_3(x,)
x = self.dense_block_4(x)
x = self.avgpool(x)
x = self.fc(x)
return x
输出网络结构
def densenet():
return DenseNet(num_init_features=64, growth_rate=32, block_layers=[6,12,24,16], compression_rate=0.5, drop_rate=0.5)
mynet=densenet()
X = tf.random.uniform(shape=(1, 224, 224 , 3))
for layer in mynet.layers:
X = layer(X)
print(layer.name, ‘output shape:\t’, X.shape)
conv2d output shape: (1, 112, 112, 2000)
batch_normalization output shape: (1, 112, 112, 2000)
max_pooling2d output shape: (1, 56, 56, 2000)
dense_block output shape: (1, 56, 56, 2192)
transition_layer output shape: (1, 28, 28, 1096)
dense_block_1 output shape: (1, 28, 28, 1480)
transition_layer_1 output shape: (1, 14, 14, 740)
dense_block_2 output shape: (1, 14, 14, 1508)
transition_layer_2 output shape: (1, 7, 7, 754)
dense_block_3 output shape: (1, 7, 7, 1266)
global_average_pooling2d output shape: (1, 1266)
dense output shape: (1, 1000)
DenseNet是继ResNet以后的又一创新且优秀的网络结构,借鉴了ResNet中shortcut的设计,也参考了Inception模块的设计,虽然DenseNet借鉴了ResNet中shortcut连线的思路,但重点不在于加深网络,而是通过功能重用来挖掘网络潜力,在DenseNets中,模型更浓缩,这构成了和ResNet的主要差别。
DenseNet中每一层可以共享之前所有层的“共同特征”从而增强了特征传播,鼓励特征重用,并减少了参数数量,还减轻了消失梯度的问题