神经网络-DenseNet 18

     上一节的ResNet通过前层与后层的短路连接Shortcuts) , 加强了前后层之间的信息流通, 在一定程度上缓解了梯度消失现象, 从而可以将神经网络搭建得很深。 更进一步, 本节的主角DenseNet最大化了这种前后层信息交流, 通过建立前面所有层与后面层的密集连接, 实现了特征在通道维度上的复用, 使其可以在参数与计算量更少的情况下实现比ResNet更优的性能, 提出DenseNet的《Densely ConnectedConvolutional Networks》 也一举拿下了2017CVPR的最佳论文。

     DenseNet的网络架构如图3.19所示, 网络由多个Dense Block与中间的卷积池化组成, 核心就在Dense Block中。 Dense Block中的黑点代表一个卷积层, 其中的多条黑线代表数据的流动, 每一层的输入由前面的所有卷积层的输出组成。 注意这里使用了通道拼接(Concatnate) 操作, 而非ResNet的逐元素相加操作。

 

 

 DenseNet的结构有如下两个特性:

·神经网络一般需要使用池化等操作缩小特征图尺寸来提取语义特征, 而Dense Block需要保持每一个Block内的特征图尺寸一致来直接进行Concatnate操作, 因此DenseNet被分成了多个BlockBlock的数量一般为4

·两个相邻的Dense Block之间的部分被称为Transition层, 具体包括BNReLU1×1卷积、 2×2平均池化操作。 1×1卷积的作用是降维, 起到压缩模型的作用, 而平均池化则是降低特征图的尺寸,

具体的Block实现细节如图3.20所示, 每一个Block由若干个Bottleneck的卷积层组成, 对应图3.19中的黑点。 BottleneckBNReLU1×1卷积、 BNReLU3×3卷积的顺序构成。

 

 关于Block, 有以下4个细节需要注意:
·每一个Bottleneck输出的特征通道数是相同的, 例如这里的32。 同时可以看到, 经过Concatnate操作后的通道数是按32的增长量增加的,因此这个32也被称为GrowthRate
·这里1×1卷积的作用是固定输出通道数, 达到降维的作用。 当几十个Bottleneck相连接时, Concatnate后的通道数会增加到上千, 如果不增加1×1的卷积来降维, 后续3×3卷积所需的参数量会急剧增加。 1×1卷积的通道数通常是GrowthRate4倍。
·3.20中的特征传递方式是直接将前面所有层的特征Concatnate后传到下一层, 这种方式与具体代码实现的方式是一致的, 而不像图3.19中, 前面层都要有一个箭头指向后面的所有层。

·Block采用了激活函数在前、 卷积层在后的顺序, 这与一般的网络上是不同的。

利用PyTorch来实现DenseNet的一个Block, 新建一个densenet_block.py文件, 代码如下:

 1 import torch
 2 from torch import nn
 3 import torch.nn.functional as F
 4 
 5 # 实现一个Bottleneck的类, 初始化需要输入通道数与GrowthRate这两个参数
 6 class Bottleneck(nn.Module):
 7 
 8     def __init__(self, nChannels, growthRate):
 9 
10         super(Bottleneck, self).__init__()
11         # 通常1×1卷积的通道数为GrowthRate的4倍
12         interChannels = 4*growthRate
13         self.bn1 = nn.BatchNorm2d(nChannels)
14         self.conv1 = nn.Conv2d(nChannels, interChannels, kernel_size=1, bias=False)
15 
16         self.bn2 = nn.BatchNorm2d(interChannels)
17         self.conv2 = nn.Conv2d(interChannels, growthRate, kernel_size=3, 
18                                 padding=1, bias=1)
19     
20     def forward(self, x):
21         out = self.conv1(F.relu(self.bn1(x)))
22         out = self.conv2(F.relu(self.bn2(out)))
23         # 将输入x同计算的结果out进行通道拼接
24         out = torch.cat((x, out), 1)
25         return out
26 
27 class Denseblock(nn.Module):
28     
29     def __init__(self, nChannels, growthRate, nDenseBlocks):
30         super(Denseblock, self).__init__()
31         layers = []
32         # 将每一个Bottleneck利用nn.Sequential()整合起来, 输入通道数需要线性增长
33         for i in range(int(nDenseBlocks)):
34             layers.append(Bottleneck(nChannels, growthRate))
35             nChannels += growthRate
36         self.denseblock = nn.Sequential(*layers)
37     
38     def forward(self, x):
39         return self.denseblock(x)
View Code
 1 import torch
 2 from densenet_block import Denseblock
 3 
 4 # 实例化DenseBlock, 包含了6个Bottleneck
 5 denseblock = Denseblock(64, 32, 6).cuda()
 6 
 7 # 查看denseblock的网络结构, 由6个Bottleneck组成
 8 print(denseblock)
 9 >>  Denseblock(
10   (denseblock): Sequential(
11       # 第1个Bottleneck的输入通道数为64, 输出固定为32
12     (0): Bottleneck(
13       (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
14       (conv1): Conv2d(64, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
15       (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
16       (conv2): Conv2d(128, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
17     )
18     #
19     # 第2个Bottleneck的输入通道数为96, 输出固定为32
20     (1): Bottleneck(
21       (bn1): BatchNorm2d(96, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
22       (conv1): Conv2d(96, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
23       (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
24       (conv2): Conv2d(128, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
25     )
26     # 第3个Bottleneck的输入通道数为128, 输出固定为32
27     (2): Bottleneck(
28       (bn1): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
29       (conv1): Conv2d(128, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
30       (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
31       (conv2): Conv2d(128, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
32     )
33     #第4个Bottleneck的输入通道数为160, 输出固定为32
34     (3): Bottleneck(
35       (bn1): BatchNorm2d(160, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
36       (conv1): Conv2d(160, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
37       (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
38       (conv2): Conv2d(128, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
39     )
40     # 第5个Bottleneck的输入通道数为192, 输出固定为32
41     (4): Bottleneck(
42       (bn1): BatchNorm2d(192, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
43       (conv1): Conv2d(192, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
44       (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
45       (conv2): Conv2d(128, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
46     )
47     # 第6个Bottleneck的输入通道数为224, 输出固定为32
48     (5): Bottleneck(
49       (bn1): BatchNorm2d(224, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
50       (conv1): Conv2d(224, 128, kernel_size=(1, 1), stride=(1, 1), bias=False)
51       (bn2): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
52       (conv2): Conv2d(128, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
53     )
54   )
55 )
56 
57 input = torch.randn(1, 64, 256, 256).cuda()
58 output = denseblock(input) # 将输入传入denseblock结构中
59 print(input.shape)
60 >> torch.Size([1, 64, 256, 256])
61 
62 # 输出的通道数为: 224+32=64+32×6=256
63 print(output.shape)
64 >> torch.Size([1, 256, 256, 256])
View Code

 DenseNet网络的优势主要体现在以下两个方面:
·密集连接的特殊网络, 使得每一层都会接受其后所有层的梯度,而不是像普通卷积链式的反传, 因此一定程度上解决了梯度消失的问题。
·通过Concatnate操作使得大量的特征被复用, 每个层独有的特征图
的通道是较少的, 因此相比ResNetDenseNet参数更少且计算更高效。DenseNet的不足在于由于需要进行多次Concatnate操作, 数据需要被复制多次, 显存容易增加得很快, 需要一定的显存优化技术。 另外,DenseNet是一种更为特殊的网络, ResNet则相对一般化一些, 因此ResNet的应用范围更广泛。

posted @ 2020-09-23 20:50  赵家小伙儿  阅读(746)  评论(0编辑  收藏  举报