第二十七周:文献阅读笔记
- 摘要
- Abstract
- DenseNet 网络
- 1. 文献摘要
- 2. 引言
- 3. ResNets
- 4. Dense Block
- 5. Pooling layers
- 6. Implementation Details
- 7. Experiments
- 8. Feature Reuse
- 9. 代码实现
- 总结
摘要
DenseNet(密集连接网络)是一种深度学习神经网络架构,由Kaiming He等人在2017年提出。相较于传统的卷积神经网络(CNN),DenseNet具有更加密集的连接方式,每一层都与其前面所有层直接相连。这种结构有助于缓解梯度消失问题,并且可以促进信息和梯度的流动,有助于提升训练深度网络的效果。DenseNet在一些图像识别、物体检测和图像分割等领域取得了很好的效果。本文将详细介绍DenseNet网络,让我们来深入了解。
Abstract
DenseNet (Dense Connected Network) is a deep learning neural network architecture, proposed by Kaiming He et al. in 2017. Compared to traditional Convolutional Neural Networks (CNNs), DenseNet has a more densely connected approach, where each layer is directly connected to all its preceding layers. This structure helps to alleviate the problem of gradient vanishing and can facilitate the flow of information and gradients, which helps to improve the effect of training deep networks.DenseNet has achieved good results in some fields such as image recognition, object detection and image segmentation. In this article, we will introduce the DenseNet network in detail, so that we can get a deeper understanding.
DenseNet 网络
文献链接:Densely Connected Convolutional Networks
1. 文献摘要
目前的工作表明如果将接近输入和输出的层之间短接,卷积神经网络可以更深、精度更高且高效。在本篇论文中,我们利用到观察到的这个结果提出了密集卷积网络(DenseNet),它的每一层在前向反馈模式中都和后面的层有连接,与L层传统卷积神经网络有L个连接不同,DenseNet中每个层都和其之后的层有连接,因此L层的DenseNet有 L(L+1)/2 个连接关系。对于每一层,它的输入包括前一层的输出和该层之前所有层的输入。DenseNets有几个引入注目的优势:
- 缓解了梯度消失
- 加强了特征传播
- 增强了特征复用
- 减少了参数量
我们在四个目标识别的基准测试集(CIFAR-10、CIFAR-100、SVHN 和 ImageNet)上评估了我们的结构,可以发现DenseNet在减少计算量的同时取得了更好的表现。
2. 引言
当CNN网络的深度持续增加时,一个新的研究问题就会出现:输入部分的信息流或者梯度在经过很多层之后,当到达网络结束(或开始)的地方时会消失。关于这个问题的研究有很多,可以发现的是虽然在网络拓扑和训练时方法各异,但是它们都有一个关键的特征:即前层和后层之间有短接。
在这篇文章中,遵从上面的直觉并提出了一种简单的连接模式:可以最大化网络中各层之间的信息流动,我们将所有层之间都相互连接,为了保留前向的特征,每个层都会获得前层的额外输入并将本层的特征传递给后续的层。Figure 1展示了这个关系,与ResNets相比,我们没有对上一层的输入和上一层的输出进行特征融合后再送入下一层,而是将前面所有层的特征图它们进行拼接。因此,第l层有l个输入,它由前面卷积层的特征图构成,并且本层的特征图也会传递给后续的L-l个层,所以一个L层的网络总共会有 L(L+1)/2 个连接,而不是传统结构的L个。
- 一个反直觉的事实就是相比传统卷积神经网络而言,DenseNet的参数量更少,这是因为它每一层的通道数都大大缩减了;
- 另一个比较大的优势是提高了整个网络的信息和梯度流动,这使得网络更容易。每一层都可以将得到来自损失函数的梯度和原始的输入信号,这相当于深度监督的影响,对网络训练很有帮助;
- 同时DenseNet还有正则化的影响,它使得面对小样本任务时过拟合的风险大大降低;
x0作为整个网络结构的输入,网络由L层组成,每一层的运算用一个非线性转换Hl()表示,这里的l表示第几层。Hl()可以看成由BN、ReLU、池化、卷积操作定义的复合运算。同时将第l层的输出定义为xl。
3. ResNets
传统的卷积神经网络中,第l层的输入为第l-1层的输出,第l层的输出为: x l = H l ( x l − 1 ) x_l=H_l(x_{l}-1) xl=Hl(xl−1)ResNets添加了旁路支路: x l = H l ( x l − 1 ) + x l − 1 x_l=H_l(x_l-1)+x_{l-1} xl=Hl(xl−1)+xl−1ResNets的优势是梯度可以直接通过恒等映射从后面的层传到前层来,然而,恒等映射 和 Hl的输出通过叠加结合在一起,这一定程度上阻碍了网络中的信息流。
4. Dense Block
DenseBlock包含很多层,每个层的特征图大小相同(才可以在通道上进行连结),层与层之间采用密集连接方式。
上图是一个包含5层layer的Dense Block。可以看出Dense Block互相连接所有的层,具体来说就是每一层的输入都来自于它前面所有层的特征图,每一层的输出均会直接连接到它后面所有层的输入。所以对于一个L层的DenseBlock,共包含 L*(L+1)/2 个连接(等差数列求和公式),如果是ResNet的话则为(L-1)*2+1。从这里可以看出:相比ResNet,Dense Block采用密集连接。而且Dense Block是直接concat来自不同层的特征图,这可以实现特征重用(即对不同“级别”的特征——不同表征进行总体性地再探索),提升效率,这一特点是DenseNet与ResNet最主要的区别。
Note:k —— DenseNet中的growth rate(增长率),这是一个超参数。一般情况下使用较小的k(比如12),就可以得到较佳的性能。
假定输入层的特征图的通道数为k0,那么L层输入的channel数为 k0+k*(L-1),因此随着层数增加,尽管k设定得较小,DenseBlock中每一层输入依旧会越来越多。
另外一个特殊的点:DenseBlock中采用BN+ReLU+Conv的结构,平常我们常见的是Conv+BN+ReLU。这么做的原因是:卷积层的输入包含了它前面所有层的输出特征,它们来自不同层的输出,因此数值分布差异比较大,所以它们在输入到下一个卷积层时,必须先经过BN层将其数值进行标准化,然后再进行卷积操作。
5. Pooling layers
X l = H l ( [ x 0 , x 1 , . . . , x l − 1 ] ) X_l=H_l([x_0,x_1,...,x_l-1]) Xl=Hl([x0,x1,...,xl−1])
[x0,x1,…,x(l-1)]是将第0、1、…、l-1层的feature map拼接在一起。当特征图的尺寸发生变化时,上式中的拼接操作是不可行的,然而,卷积网络的一个重要部分就是通过下采样改变特征图的尺寸。为了在我们的网络结构中做下采样,我们将网络划分为多个密集连接卷积网络块,如Figure 2所示,我们将块之间的层看作转换层,它是由BN、1x1卷积层、池化层构成,目的是做卷积和池化。
如果每一个Hl都产生k个feature map,那么第l个层就会有 k0 + k x (l - 1) 个输入的feature map,k0表示输入层的通道数,DenseNet和已经存在的网络结构中一个重要区别是DenseNet的通道数很窄,比如k=12,我们将超参数k定义为网络的growth rate,在后面的分析中我们会看到小的growth rate对于在测试集上获得很好的表现也是足够的。一个解释就是每一层都可以访问块中前面层,因此,可以理解为网络的“集体认识”,可以把网络的特征图看作是全局变量,每过一个层,就往全局变量中添加k个特征图。
尽管每个层的输出都只有k个通道,但是它的输入通道数很大。可以在 3x3 的卷积层之前使用 1x1 的瓶颈层来提高计算效率,我们发现这样的设计非常高效,一个 bottleneck层代指 BN-ReLU-Conv(1x1)-BN-ReLU-Conv(3x3),这样的网络结构称为 DenseNet-B。在我们的实验中,1x1 的卷积层的输出通道为 4k。
为了使模型更加紧密,我们通过transition层减少特征图的数量。如果一个dense block包含m个特征图,那么通过transition层会产生 theta * m(下取整)个输出feature map,在这里 0<theta<=1。当 theta=1时,输出通道数不会发生变化,我们将 theta<1 的DenseNet 记为 DenseNet-C,同时在我们的实验中,将theta设置为0.5.当bottleneck层和theta<1的transition层同时使用时,DenseNet可以称为 DenseNet-BC。
6. Implementation Details
在除了ImageNet之外的数据集上,实验中的DenseNet都用了三个dense block(ImageNet用了四个块),每个块总包含有相同数量的层。其他的具体细节可以参考论文第三节中的Implementation Details部分。
在ImageNet数据集上,我们使用了带有四个dense block的DenseNet-BC结构,输入图片尺寸为224x224。最开始的卷积层为2k个7x7x输入图片通道数的卷积核,步长为2;所有层的feature-maps的数量也都由k设置,对ImageNet使用的网络配置如table1所示:
DenseNet-121是指网络总共有121层:(6+12+24+16)*2 + 3(transition layer) + 1(7x7 Conv) + 1(Classification layer) = 121;
再详细说下bottleneck和transition layer操作。在每个Dense Block中都包含很多个子结构,以DenseNet-169的Dense Block(3)为例,包含32个11和33的卷积操作,也就是第32个子结构的输入是前面31层的输出结果,每层输出的channel是32(growth rate),那么如果不做bottleneck操作,第32层的33卷积操作的输入就是3132+(上一个Transition Layer的输出channel),近1000了。而加上11的卷积,代码中的11卷积的channel是growth rate4,也就是128,然后再作为33卷积的输入。这就大大减少了计算量,这就是bottleneck。至于transition layer,放在两个Dense Block中间,是因为每个Dense Block结束后的输出channel个数很多,需要用11的卷积核来降维。还是以DenseNet-169的Dense Block(3)为例,虽然第32层的33卷积输出channel只有32个(growth rate),但是紧接着还会像前面几层一样有通道的concat操作,即将第32层的输出和第32层的输入做concat,前面说过第32层的输入是1000左右的channel,所以最后每个Dense Block的输出也是1000多的channel。因此这个transition layer有个参数reduction(范围是0到1),表示将这些输出缩小到原来的多少倍,默认是0.5,这样传给下一个Dense Block的时候channel数量就会减少一半,这就是transition layer的作用。文中还用到dropout操作来随机减少分支,避免过拟合,毕竟这篇文章的连接确实多。
7. Experiments
我们设计实验在几个基准测试集上验证了DenseNet的有效性,并着重与ResNet 和它的几个变体做了比较。
训练的具体细节:
- SGD训练网络;
- 在CIFAR和SVHN上的batch size为64,epochs为300和40,初始学习率为0.1,在训练10%和75%的epochs之后衰减为原来的10%;
- 在ImageNet上,我们训练网络时的batch size 为256,epochs为90,初始学习率为0.1,epoch为30和60时衰减到上次的10%;
- 训练过程采用了和 ResNet 的文章完全相同的设定。但仍然存在一些技巧,例如因为多次 Concatenate 操作,同样的数据在网络中会存在多个复制,这里需要采用一些显存优化技术,使得训练时的显存占用可以随着层数线性增加,而非增加的更快,相关代码在链接中可以查看;
- 权重衰减率为10**(-4),没有dampening的Nesterov momentum为0.9,dropout率为0.2;
在DenseNet和stochastic depth regularization之间存在着一种有趣的联系,在stochastic depth中,残差块中的层可以随意丢弃,这使得周围层之间可能直接相连。但是池化层从未丢弃,这看起来有些和DenseNet类似,尽管两者的方法不同,但我们可以从stochastic depth的角度理解DenseNet——引入了正则化的意味。
8. Feature Reuse
DenseNet允许本层访问之前所有层的feature maps,我们设计了一组实验来验证了这个想法,在C10+数据集上L=40、k=12,对于一个块内的l层,我们计算和它相连接的s层的权重平均值的绝对值,Figure 5展示了三个dense块的热力图,权重平均值绝对值展示了这一层对之前某一层特征的复用率。
可以发现:
- 在同一个块内所有层都经过了很多层传播它的权重,这表明了较早层提取的特征仍然会被较深层直接使用;
- 即便是transition层,也使用到 之前dense块内所有层的特征。
- 第二个和第三个dense块对之前transition层的复用率很低,这说明transition层的输出仍然有很多的冗余特征。这也为DenseNet提供了压缩必要性的证据支持。
- 尽管最后的位于最右面分类层也使用了dense块多层的特征信息,但是似乎它更倾向于使用最后几个层的feature-maps,说明在网络的最后几层产生了一些高级的特征。
9. 代码实现
BN-ReLu-Conv
class BN_Conv2d(nn.Module):"""BN_CONV_RELU"""def __init__(self, in_channels: object, out_channels: object, kernel_size: object, stride: object, padding: object,dilation=1, groups=1, bias=False) -> object:super(BN_Conv2d, self).__init__()self.seq = nn.Sequential(nn.Conv2d(in_channels, out_channels, kernel_size=kernel_size, stride=stride,padding=padding, dilation=dilation, groups=groups, bias=bias),nn.BatchNorm2d(out_channels))def forward(self, x):return F.relu(self.seq(x))
Dense Block
class DenseBlock(nn.Module):def __init__(self, input_channels, num_layers, growth_rate):super(DenseBlock, self).__init__()self.num_layers = num_layersself.k0 = input_channelsself.k = growth_rateself.layers = self.__make_layers()def __make_layers(self):layer_list = []for i in range(self.num_layers):layer_list.append(nn.Sequential(BN_Conv2d(self.k0+i*self.k, 4*self.k, 1, 1, 0),BN_Conv2d(4 * self.k, self.k, 3, 1, 1)))return layer_listdef forward(self, x):feature = self.layers[0](x)out = torch.cat((x, feature), 1)for i in range(1, len(self.layers)):feature = self.layers[i](out)out = torch.cat((feature, out), 1)return out
网络搭建并测试
class DenseNet(nn.Module):def __init__(self, layers: object, k, theta, num_classes) -> object:super(DenseNet, self).__init__()# paramsself.layers = layersself.k = kself.theta = theta# layersself.conv = BN_Conv2d(3, 2*k, 7, 2, 3)self.blocks, patches = self.__make_blocks(2*k)self.fc = nn.Linear(patches, num_classes)def __make_transition(self, in_chls):out_chls = int(self.theta*in_chls)return nn.Sequential(BN_Conv2d(in_chls, out_chls, 1, 1, 0),nn.AvgPool2d(2)), out_chlsdef __make_blocks(self, k0):"""make block-transition structures:param k0::return:"""layers_list = []patches = 0for i in range(len(self.layers)):layers_list.append(DenseBlock(k0, self.layers[i], self.k))patches = k0+self.layers[i]*self.k # output feature patches from Dense Blockif i != len(self.layers)-1:transition, k0 = self.__make_transition(patches)layers_list.append(transition)return nn.Sequential(*layers_list), patchesdef forward(self, x):out = self.conv(x)out = F.max_pool2d(out, 3, 2, 1)# print(out.shape)out = self.blocks(out)# print(out.shape)out = F.avg_pool2d(out, 7)# print(out.shape)out = out.view(out.size(0), -1)out = F.softmax(self.fc(out))return out
搭建网络并测试:
def densenet_121(num_classes=1000):return DenseNet([6, 12, 24, 16], k=32, theta=0.5, num_classes=num_classes)def densenet_169(num_classes=1000):return DenseNet([6, 12, 32, 32], k=32, theta=0.5, num_classes=num_classes)def densenet_201(num_classes=1000):return DenseNet([6, 12, 48, 32], k=32, theta=0.5, num_classes=num_classes)def densenet_264(num_classes=1000):return DenseNet([6, 12, 64, 48], k=32, theta=0.5, num_classes=num_classes)def test():net = densenet_264()summary(net, (3, 224, 224))x = torch.randn((2, 3, 224, 224))y = net(x)print(y.shape)test()
当k=32,θ=0.5时,DenseNet_264网络的测试结果如下图,可以看到DenseNet的参数量确实比ResNet要少得多。
总结
本周看了 DenseNet 网络这篇经典论文,让我对DenseNet网络基本原理有了一定的了解,DenseNet作为一种新型神经网络架构,其密集连接方式和优秀的性能使其成为一种值得研究的模型,下周我将继续保持论文阅读的习惯,同时也进一步提升自己的代码能力。