这里写目录标题
前言
DenseNet是指Densely connected convolutional networks(密集卷积网络)。它的优点主要包括有效缓解梯度消失、特征传递更加有效、计算量更小、参数量更小、性能比ResNet更好。它的缺点主要是较大的内存占用。
1. DenseNet网络
DenseNet网络与Resnet、GoogleNet类似,都是为了解决深层网络梯度消失问题的网络。
- Resnet从深度方向出发,通过建立前面层与后面层之间的“短路连接”或“捷径”,从而能训练出更深的CNN网络。
- GoogleNet从宽度方向出发,通过Inception(利用不同大小的卷积核实现不同尺度的感知,最后进行融合来得到图像更好的表征)。
- DenseNet从特征入手,通过对前面所有层与后面层的密集连接,来极致利用训练过程中的所有特征,进而达到更好的效果和减少参数。
2.设计理念
为了详细对比Resnet与DenseNet的区别,分别介绍Resnet网络和DenseNet网络。
2.1 Resnet
ResNet网络的短路连接机制(其中+代表的是元素级相加操作)
在Resnet网络中,通过一个残差连接将输入部分内容与卷积后的内容结合在一起。如果卷积层因为层数过深而不起作用,这不会影响最终的结果。哪一层卷积层起作用就调用哪一层卷积层,从而避免了层数过深的问题。这也是残差结构所带来的优势所在。
2.2 DenseNet
DenseNet网络的密集连接机制(其中c代表的是channel级连接操作)
在DenseNet中,一个更激进的密集连接机制被设计用来充分利用特征。简单来说就是每一层的特征都被保存下来,在后面的每次卷积操作中用到。因此,每一层的网络结构都用到了前面的所有信息(包括卷积操作前和卷积操作后的信息)。
注意:在Resnet网络结构中,网路的残差连接是+操作。即:两个数据规模是相同的,得到的数据跟前面的数据规模也是相同的。而DenseNet网络中,是合并操作。即:在channel维度上连接在一起(这里各个层的特征图大小是相同的)。
2.3 密集连接的实现
首先实现DenseBlock中的内部结构,这里是BN+ReLU+1x1 Conv+BN+ReLU+3x3 Conv结构,最后也加入dropout层以用于训练过程。
class_DenseLayer(nn.Sequential):"""Basic unit of DenseBlock (using bottleneck layer) """def__init__(self, num_input_features, growth_rate, bn_size, drop_rate):super(_DenseLayer, self).__init__()
self.add_module("norm1", nn.BatchNorm2d(num_input_features))
self.add_module("relu1", nn.ReLU(inplace=True))
self.add_module("conv1", nn.Conv2d(num_input_features, bn_size*growth_rate,
kernel_size=1, stride=1, bias=False))
self.add_module("norm2", nn.BatchNorm2d(bn_size*growth_rate))
self.add_module("relu2", nn.ReLU(inplace=True))
self.add_module("conv2", nn.Conv2d(bn_size*growth_rate, growth_rate,
kernel_size=3, stride=1, padding=1, bias=False))
self.drop_rate = drop_rate
defforward(self, x):
new_features =super(_DenseLayer, self).forward(x)if self.drop_rate >0:
new_features = F.dropout(new_features, p=self.drop_rate, training=self.training)#通过torch.cat操作将前面的数据合并在一起 #类似如:第一次操作:[x, features] # 第二次操作:[x, features_1] 其中,此时的x为[x, features] return torch.cat([x, new_features],1)
3. DenseNet的实现
DenseNet的网络结构如图所示。可以发现,该网络主要有Dense Block和Transition Layer两个部分组成。
DenseNet共在三个图像分类数据集(CIFAR,SVHN和ImageNet)上进行测试。对于前两个数据集,其输入图片大小为 32 × 32 ,所使用的DenseNet在进入第一个DenseBlock之前,首先进行进行一次3x3卷积(stride=1),卷积核数为16(对于DenseNet-BC为 2 k )。DenseNet共包含三个DenseBlock,各个模块的特征图大小分别为 32 × 32, 16 × 16 和 8 × 8 ,每个DenseBlock里面的层数相同。最后的DenseBlock之后是一个global AvgPooling层,然后送入一个softmax分类器。注意,在DenseNet中,所有的3x3卷积均采用padding=1的方式以保证特征图大小维持不变。对于基本的DenseNet,使用如下三种网络配置:{ L = 40 , k = 12 },{ L = 100 , k = 12 },{ L = 40 , k = 24 } 。而对于DenseNet-BC结构,使用如下三种网络配置:{ L = 100 , k = 12 } , { L = 250 , k = 24 } ,{ L = 190 , k = 40 } 。这里的 L 指的是网络总层数(网络深度),一般情况下,我们只把带有训练参数的层算入其中,而像Pooling这样的无参数层不纳入统计中,此外BN层尽管包含参数但是也不单独统计,而是可以计入它所附属的卷积层。对于普通的L = 40 , k = 12 网络,除去第一个卷积层、2个Transition中卷积层以及最后的Linear层,共剩余36层,均分到三个DenseBlock可知每个DenseBlock包含12层。其它的网络配置同样可以算出各个DenseBlock所含层数。
ImageNet数据集上所采用的DenseNet结构
3.1 Dense Block的实现
由于后面层的输入会非常大,DenseBlock内部可以采用bottleneck层来减少计算量,主要是原有的结构中增加1x1 Conv,如图所示,即BN+ReLU+1x1 Conv+BN+ReLU+3x3 Conv,称为DenseNet-B结构。其中1x1 Conv得到 4 k个特征图它起到的作用是降低特征数量,从而提升计算效率。
DenseBlock模块,内部是密集连接方式(输入特征数线性增长):
class_DenseBlock(nn.Sequential):"""DenseBlock"""def__init__(self, num_layers, num_input_features, bn_size, growth_rate, drop_rate):super(_DenseBlock, self).__init__()for i inrange(num_layers):
layer = _DenseLayer(num_input_features+i*growth_rate, growth_rate, bn_size,
drop_rate)
self.add_module("denselayer%d"%(i+1,), layer)
3.2 Transition Layer的实现
对于Transition层,它主要是连接两个相邻的DenseBlock,并且降低特征图大小。Transition层包括一个1x1的卷积和2x2的AvgPooling,结构为BN+ReLU+1x1 Conv+2x2 AvgPooling。另外,Transition层可以起到压缩模型的作用。假定Transition的上接DenseBlock得到的特征图channels数为m ,Transition层可以产生 θ m个特征(通过卷积层),其中 θ ∈ ( 0 , 1 ] 是压缩系数(compression rate)。当 θ = 1时,特征个数经过Transition层没有变化,即无压缩,而当压缩系数小于1时,这种结构称为DenseNet-C,文中使用 θ = 0.5。对于使用bottleneck层的DenseBlock结构和压缩系数小于1的Transition组合结构称为DenseNet-BC。
Transition层,它主要是一个卷积层和一个池化层:
class_Transition(nn.Sequential):"""Transition layer between two adjacent DenseBlock"""def__init__(self, num_input_feature, num_output_features):super(_Transition, self).__init__()
self.add_module("norm", nn.BatchNorm2d(num_input_feature))
self.add_module("relu", nn.ReLU(inplace=True))
self.add_module("conv", nn.Conv2d(num_input_feature, num_output_features,
kernel_size=1, stride=1, bias=False))
self.add_module("pool", nn.AvgPool2d(2, stride=2))
3.3 DenseNet网络
classDenseNet(nn.Module):"DenseNet-BC model"def__init__(self, growth_rate=32, block_config=(6,12,24,16), num_init_features=64,
bn_size=4, compression_rate=0.5, drop_rate=0, num_classes=1000):"""
:param growth_rate: (int) number of filters used in DenseLayer, `k` in the paper
:param block_config: (list of 4 ints) number of layers in each DenseBlock
:param num_init_features: (int) number of filters in the first Conv2d
:param bn_size: (int) the factor using in the bottleneck layer
:param compression_rate: (float) the compression rate used in Transition Layer
:param drop_rate: (float) the drop rate after each DenseLayer
:param num_classes: (int) number of classes for classification
"""super(DenseNet, self).__init__()# first Conv2d
self.features = nn.Sequential(OrderedDict([("conv0", nn.Conv2d(3, num_init_features, kernel_size=7, stride=2, padding=3, bias=False)),("norm0", nn.BatchNorm2d(num_init_features)),("relu0", nn.ReLU(inplace=True)),("pool0", nn.MaxPool2d(3, stride=2, padding=1))]))# DenseBlock
num_features = num_init_features
for i, num_layers inenumerate(block_config):
block = _DenseBlock(num_layers, num_features, bn_size, growth_rate, drop_rate)
self.features.add_module("denseblock%d"%(i +1), block)
num_features += num_layers*growth_rate
if i !=len(block_config)-1:
transition = _Transition(num_features,int(num_features*compression_rate))
self.features.add_module("transition%d"%(i +1), transition)
num_features =int(num_features * compression_rate)# final bn+ReLU
self.features.add_module("norm5", nn.BatchNorm2d(num_features))
self.features.add_module("relu5", nn.ReLU(inplace=True))# classification layer
self.classifier = nn.Linear(num_features, num_classes)# params initializationfor m in self.modules():ifisinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight)elifisinstance(m, nn.BatchNorm2d):
nn.init.constant_(m.bias,0)
nn.init.constant_(m.weight,1)elifisinstance(m, nn.Linear):
nn.init.constant_(m.bias,0)defforward(self, x):
features = self.features(x)
out = F.avg_pool2d(features,7, stride=1).view(features.size(0),-1)
out = self.classifier(out)return out
3.4 DenseNet-121网络
这里实现了DenseNet-121网络,且可以通过设置是否预训练来加载Pytorch模型中的DenseNet-121模型。
defdensenet121(pretrained=False,**kwargs):"""DenseNet121"""
model = DenseNet(num_init_features=64, growth_rate=32, block_config=(6,12,24,16),**kwargs)if pretrained:# '.'s are no longer allowed in module names, but pervious _DenseLayer# has keys 'norm.1', 'relu.1', 'conv.1', 'norm.2', 'relu.2', 'conv.2'.# They are also in the checkpoints in model_urls. This pattern is used# to find such keys.
pattern = re.compile(r'^(.*denselayer\d+\.(?:norm|relu|conv))\.((?:[12])\.(?:weight|bias|running_mean|running_var))$')
state_dict = model_zoo.load_url(model_urls['densenet121'])for key inlist(state_dict.keys()):
res = pattern.match(key)if res:
new_key = res.group(1)+ res.group(2)
state_dict[new_key]= state_dict[key]del state_dict[key]
model.load_state_dict(state_dict)return model
4. 测试
最后,我们使用一个简单的代码来对模型进行测试。
注意:输入必须是224,224的图片,如果不是的话模型参数需要进行调整。
model = densenet121()
x = torch.rand(size=(1,3,224,224))print(model(x).shape)
版权归原作者 sjx_alo 所有, 如有侵权,请联系我们删除。