目录
1.概念
DenseNet:采用密集连接机制,即互相连接所有的层,每个层都会与前面的层在channel(通道)维度上连接道一起,实现特征重用,作为下一层的输入。
这样操作的好处在于 不仅减缓了梯度消失的问题,也可使其在参数与计算量更少的情况下实现比Resnet更优的性能。
2.公式
在DenseNet中,会连接前面所有层作为输入,它的公式为
其中H(.) 代表的是非线性转化函数,它是一个组合操作,其可能包括一系列的BN,ReLU,Pooling及Conv的操作。
特征传递方式 是 直接将前面所有层的特征concat后传到下一个层,而不是前面层都要有一个箭头指向后面的所有层。
3.网络结构
DenseNet网络中模块主要由 **DenseBlock+Transition** 组成
3.1DenseBlock
-
其中DenseBlock其中的每一个“圆点”代表的使用 BN+ReLU+Conv(3*3)层的组成。
-
假定输入层的特征层的channel为k0。DenseBlock中各个卷积之后均输出k个特征图。即得到得特征图的channel数为k。那么L层输入的channel为k0 + (L - 1)k.
-
采用的是BN+ReLU+Conv 的pre-position的结构。作者验证过这种反方向的组合效果更好。
-
bottleneck
由于后面的层输入channel会非常大,DenseBlock有其扩展版,即DensBlock_B。其和普通版的区别在于 DenseBlock_B在前面使用了bottleneck
(BN+ReLU+(1*1)Conv ) 的 1 * 1的卷积层用于降维。(即DensBlock_B的结构为( BN+ReLU+Conv (1* 1)+BN+ReLU+Conv(3*3))
1 * 1卷积输出的通道数通常是GrowthRate的4倍。(GrowthRate即为增长率,一般为16、32.....)
附:
若不适用1*1 的卷积层进行降维,则到后面输出的通道数可能会很大。
例如:
输入通道数为64,增长率为32,经过15个bottleneck,则通道数输出为 64+32 X 15 = 544
若不适用 1 X 1 的卷积进行降维,则第16个bottleneck层的参数量是 3 X 3 X 544 X 32 = 156672
若使用1 X 1 的卷积进行降维,则第16个bottleneck层的参数为 1 X 1 X 544 X 128 + 3 X 3 X 128 X 32 = 106496
可以看到参数量减少了1/3。
3.2 Transition层
-
主要作用在于连接两个相邻的Denseblock,并且降低特征图的大小
Transition层包括一个 1 X 1 的卷积与 2 X 2 的AvgPooling的结构。
-
Trasition的主要起到压缩模型的作用
Transition层可以输出[ xm]个特征,其中x是压缩系数。当x=1时,表示无压缩,特征个数经过Transition层没有变化。
当x小于1时,这种结构称为DenseNet-C,一般使用 x=0.5
对于使用Bottleneck层的DenseBlock结构压缩系数小于1的Transition组合结构成为Densenet-BC。
4 DenseNet的主要优点与缺点
优点
1.更强的梯度流动,减轻了梯度消失在网络深度越深的时候越容易出现。
2.减少了参数量
3.保存了低维度的特征
缺点
运行时容易占用显存的大小
5 代码实现
DenseNet的网络结构图,代码主要是实现**DenseNet-121层网络结构**
5.1 Denseblock代码实现
#构建DenseBlock中的内部结构
#通过语法结构,把这个当成一个layer即可.
#bottleneck + DenseBlock == > DenseNet-B
class _DenseLayer(nn.Sequential):
# num_input_features作为输入特征层的通道数, growth_rate增长率, bn_size输出的倍数一般都是4, drop_rate判断是都进行dropout层进行处理
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
def forward(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)
return torch.cat([x, new_features],1)
#定义Denseblock模块
class _DenseBlock(nn.Sequential):
def __init__(self, num_layers, num_input_features, bn_size, growth_rate, drop_rate):
super(_DenseBlock, self).__init__()
for i in range(num_layers):
layer = _DenseLayer(num_input_features+i*growth_rate, growth_rate, bn_size, drop_rate)
self.add_module("denselayer %d" %(i+1), layer)
5.2 Transition代码实现
#定义Transition层
#负责将Denseblock连接起来,一般都有0.5的维道(通道数)的压缩
class _Transition(nn.Sequential):
def __init__(self, num_input_features, num_output_features):
super(_Transition, self).__init__()
self.add_module('norm', nn.BatchNorm2d(num_input_features))
self.add_module('relu',nn.ReLU(inplace=True))
self.add_module('conv',nn.Conv2d(num_input_features,num_output_features,kernel_size=1,stride=1,bias=False))
self.add_module('pool', nn.AvgPool2d(2,stride=2))
5.3 DenseNet-121完整代码实现
import torch
import torch.nn as nn
import torch.nn.functional as F
from torchsummary import summary
from torchstat import stat
from collections import OrderedDict
#构建DenseBlock中的内部结构
#通过语法结构,把这个当成一个layer即可.
#bottleneck + DenseBlock == > DenseNet-B
class _DenseLayer(nn.Sequential):
# num_input_features作为输入特征层的通道数, growth_rate增长率, bn_size输出的倍数一般都是4, drop_rate判断是都进行dropout层进行处理
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
def forward(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)
return torch.cat([x, new_features],1)
#定义Denseblock模块
class _DenseBlock(nn.Sequential):
def __init__(self, num_layers, num_input_features, bn_size, growth_rate, drop_rate):
super(_DenseBlock, self).__init__()
for i in range(num_layers):
layer = _DenseLayer(num_input_features+i*growth_rate, growth_rate, bn_size, drop_rate)
self.add_module("denselayer %d" %(i+1), layer)
#定义Transition层
#负责将Denseblock连接起来,一般都有0.5的维道(通道数)的压缩
class _Transition(nn.Sequential):
def __init__(self, num_input_features, num_output_features):
super(_Transition, self).__init__()
self.add_module('norm', nn.BatchNorm2d(num_input_features))
self.add_module('relu',nn.ReLU(inplace=True))
self.add_module('conv',nn.Conv2d(num_input_features,num_output_features,kernel_size=1,stride=1,bias=False))
self.add_module('pool', nn.AvgPool2d(2,stride=2))
#实现DenseNet网络
class DenseNet(nn.Module):
def __init__(self, growth_rate=32,block_config=(6,12,24,26), num_init_features=64,bn_size=4, comparession_rate=0.5, drop_rate=0,num_classes=1000):
super(DenseNet,self).__init__()
#前面 卷积层+最大池化
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 in enumerate(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 #确定一个DenseBlock输出的通道数
if i!=len(block_config)-1: #判断是不是最后一个Denseblock
transition = _Transition(num_features,int(num_features*comparession_rate))
self.features.add_module("transition%d" %(i+1), transition)
num_features = int(num_features*comparession_rate) #为下一个DenseBlock的输出做准备
#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 initalization
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.bias, 0)
nn.init.constant_(m.weight, 1)
elif isinstance(m, nn.Linear):
nn.init.constant_(m.bias, 0)
def forward(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
def densenet121(pretrained=False, **kwargs):
"""DenseNet121"""
model = DenseNet(num_init_features=64, growth_rate=32, block_config=(6, 12, 24, 16),
**kwargs)
return model
if __name__ == '__main__':
#输出模型的结构
dense = densenet121()
#输出模型每层的输出
device =torch.device('cuda' if torch.cuda.is_available() else 'cpu')
dense= dense.to(device)
# summary(dense,input_size=(3,416,416), batch_size = 3)
# 每层模型的输入输出(这个能同时显示 输入输出)
# stat(dense, (3,416,416))