论文链接:https://arxiv.org/pdf/1608.06993.pdf
顾名思义,DenseNet采用了高密度的跳连结构,对于每一层,使用先前所有层的输出作为输入,该层的输出将作为之后所有层的输入的一部分。因此对于一个dense模块,假设有LLL层,那么存在L(L+1)2\frac{L(L+1)}{2}2L(L+1)直接的连接。
dense模块之后会连接一个transition层,由1x1卷积和平均池化构成。
作者认为有如下优点:
(1)由于存在很多跳连,减轻了空梯度问题,加强了梯度和信息流动,更容易训练。
(2)加强了特征的重用。
(3)DenseNet层的filter数量比较少,使得层比较狭窄。每层只生成了很小的特征图,成为共同知识中的一员。这样网络的参数更少,且更有效率。
DenseNet结构分析
定义混合函数 Hl(⋅)H_{l}(\cdot)Hl(⋅),由三个连接操作组成:批标准化(BN),ReLU和3x3卷积。
ResNet比较 :恒等函数和混合函数的加和为输出,这样的做法可能会阻止网络中的信息流动。
密度连接: 对于某层,前l−1l-1l−1层的输出x0,⋯ ,xl−1\bm{x}_{0},\cdots,\bm{x}_{l-1}x0,⋯,xl−1进行channel水平的连接,生成一个单一的特征图:
xl=Hl([x0,⋯ ,xl−1])\bm{x}_{l}=H_{l}([\bm{x}_{0},\cdots,\bm{x}_{l-1}])xl=Hl([x0,⋯,xl−1])
池化层:当特征图的尺寸改变时,就无法进行连接了,然后在设计网络时必须要求特征图的尺寸在往后时减少,所以将dense模块分为几个模块。在模块之间设计transition层进行如下:BN,1x1卷积和2x2平均池化。
增长率kkk: 在一个dense模块中,初始输入的特征图模块是k0k_{0}k0,若则每层的输出通道kkk,HlH_{l}Hl的输入为k0+k(l−1)k_{0}+k(l-1)k0+k(l−1)。kkk调整每层多少信息加入到全局信息。
瓶颈层: 虽然每层仅仅产生kkk通道的输出特征图,但是具有很多的输入。使用预激活的瓶颈层,采用如下的处理顺序:BN-ReLU-Conv(1x1)-BN-ReLU-Conv(3x3),称为DenseNet-B。在实验中,每1x1卷积降维变成4k4k4k通道特征图。
压缩: 在transition层减少特征图数量,设置压缩因子0<θ≤10<\theta \leq 10<θ≤1,通过transition层后,产生了⌊θm⌋\left \lfloor \theta m\right \rfloor⌊θm⌋个通道的输出特征图。当θ<1\theta<1θ<1,称为DenseNet-C。在实验中,作者设为θ=0.5\theta=0.5θ=0.5。在DenseNet-C上使用瓶颈层被称为DenseNet-BC。
特征重用分析
DenseNet中由于使用该密度的跳连结构使得能够复用前面的feature-map,使用heap-map来分析前面层不同feature-map对当前层的重要程度。
上图展示了卷积层filter权重绝对值的平均值。坐标(s,l)表示在一个dense块中,由第s层传到l层的feature-map,l层权重的平均L1-norm(即权重绝对值的平均值)。我们知道,权重绝对值越大,表示对应的特征越重要,因此也反映了l层对s层的依赖性。红色部分表示强大的使用了s层的feature-map。
从图中作者得到如下4点结论:
1.在相同的块中,所有层的对于输入都具有权重。特征提取在早期层,直接使用在深度层。
2.transition层对于输入都具有权重。DenseNet从第一层到最后一层的信息流动仅通过很少的间接连接。
3.第二层和第三层的dense块一致的对transition层的输出赋予了很小的权重(第一层除外),表明了transition层的输出对第一层之后的层产生了冗余的特征。
4.最终的分类层更加关注最终的feature-maps,表明有一些高水平的特征产生在后面。
Pytorch实现DenseNet-BC
在论文中,作者公开了ImageNet的DenseNet结构。对于CIFAR,denseNet采用3个dense模块,每个dense模块采用相同数量的bottleneck。例如在DenseNet-BC(k=12) 100中,每个dense模块有16个bottleneck层,3 * (16 *2)=96层,剩下的4层分别为第一层卷积,中间的2层transition层,和最后的全连接层。
import torch
import torch.nn as nn
from collections import OrderedDict
class _DenseLayer(nn.Sequential):
def __init__(self, in_channels, growth_rate, bn_size):
super(_DenseLayer, self).__init__()
self.add_module('norm1', nn.BatchNorm2d(in_channels))
self.add_module('relu1', nn.ReLU(inplace=True))
self.add_module('conv1', nn.Conv2d(in_channels, 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))
# 重载forward函数
def forward(self, x):
new_features = super(_DenseLayer, self).forward(x)
return torch.cat([x, new_features], 1)
class _DenseBlock(nn.Sequential):
def __init__(self, num_layers, in_channels, bn_size, growth_rate):
super(_DenseBlock, self).__init__()
for i in range(num_layers):
self.add_module('denselayer%d' % (i+1),
_DenseLayer(in_channels+growth_rate*i,
growth_rate, bn_size))
class _Transition(nn.Sequential):
def __init__(self, in_channels, out_channels):
super(_Transition, self).__init__()
self.add_module('norm', nn.BatchNorm2d(in_channels))
self.add_module('relu', nn.ReLU(inplace=True))
self.add_module('conv', nn.Conv2d(in_channels, out_channels,
kernel_size=1,
stride=1, bias=False))
self.add_module('pool', nn.AvgPool2d(kernel_size=2, stride=2))
class DenseNet_BC(nn.Module):
def __init__(self, growth_rate=12, block_config=(6,12,24,16),
bn_size=4, theta=0.5, num_classes=10):
super(DenseNet_BC, self).__init__()
# 初始的卷积为filter:2倍的growth_rate
num_init_feature = 2 * growth_rate
# 表示cifar-10
if num_classes == 10:
self.features = nn.Sequential(OrderedDict([
('conv0', nn.Conv2d(3, num_init_feature,
kernel_size=3, stride=1,
padding=1, bias=False)),
]))
else:
self.features = nn.Sequential(OrderedDict([
('conv0', nn.Conv2d(3, num_init_feature,
kernel_size=7, stride=2,
padding=3, bias=False)),
('norm0', nn.BatchNorm2d(num_init_feature)),
('relu0', nn.ReLU(inplace=True)),
('pool0', nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
]))
num_feature = num_init_feature
for i, num_layers in enumerate(block_config):
self.features.add_module('denseblock%d' % (i+1),
_DenseBlock(num_layers, num_feature,
bn_size, growth_rate))
num_feature = num_feature + growth_rate * num_layers
if i != len(block_config)-1:
self.features.add_module('transition%d' % (i + 1),
_Transition(num_feature,
int(num_feature * theta)))
num_feature = int(num_feature * theta)
self.features.add_module('norm5', nn.BatchNorm2d(num_feature))
self.features.add_module('relu5', nn.ReLU(inplace=True))
self.features.add_module('avg_pool', nn.AdaptiveAvgPool2d((1, 1)))
self.classifier = nn.Linear(num_feature, num_classes)
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight)
elif isinstance(m, nn.BatchNorm2d):
nn.init.constant_(m.weight, 1)
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
nn.init.constant_(m.bias, 0)
def forward(self, x):
features = self.features(x)
out = features.view(features.size(0), -1)
out = self.classifier(out)
return out
# DenseNet_BC for ImageNet
def DenseNet121():
return DenseNet_BC(growth_rate=32, block_config=(6, 12, 24, 16), num_classes=1000)
def DenseNet169():
return DenseNet_BC(growth_rate=32, block_config=(6, 12, 32, 32), num_classes=1000)
def DenseNet201():
return DenseNet_BC(growth_rate=32, block_config=(6, 12, 48, 32), num_classes=1000)
def DenseNet161():
return DenseNet_BC(growth_rate=48, block_config=(6, 12, 36, 24), num_classes=1000,)
# DenseNet_BC for cifar
def densenet_BC_100():
return DenseNet_BC(growth_rate=12, block_config=(16, 16, 16))