1、研究背景
(1)最近的工作表明,如果卷积网络在靠近输入的层和靠近输出的层之间包含较短的连接,则可以进行更深入,更准确和有效的训练。在本文中,我们接受了这一观察,并介绍了密集卷积网络(DenseNet),该网络以前馈方式将每一层连接到其他每一层。具有L层的传统卷积网络具有L(L+1)/2个连接(每层与其后续层之间有一个连接),而我们的网络具有L个直接连接。对于每一层,所有先前层的特征图都用作输入,而其自身的特征图则用作所有后续层的输入。 DenseNets具有几个引人注目的优点:它们减轻了消失梯度的问题,增强了特征传播,鼓励了特征重用,并大大减少了参数数量。
(2)卷积神经网络(CNN)已成为视觉对象识别的主要机器学习方法。 尽管它们最初是在20多年前引入的[18],但计算机硬件和网络结构的改进才使真正的深度CNN训练成为可能。 最初的LeNet5 [19]由5层组成,VGG具有19层[29],仅去年的高速公路网[34]和残差网络(ResNets)[11]才突破了100层障碍。
(3)ResNets 和 DenseNets 之间的主要区别在于 DenseNets 将层的输出特征图与下一层连接而不是求和。DenseNets网络架构与ResNet有点相像,都是把L-1层的输出作为L层的输入,DenseNets作为L+1,...,L+n的输入,这也就是dense含义的所在了。
(4)随着CNN越来越深入,出现了一个新的研究问题:随着有关输入或渐变的信息穿过许多层,当到达网络的末端(或起点)时,它可能消失并“洗掉”。 最近的许多出版物都解决了这一问题或相关问题。 ResNets [11]和Highway Networks [34]通过身份连接将信号从一层旁路到另一层。 随机深度[13]通过在训练过程中随机放置各层来缩短ResNets,以提供更好的信息和梯度流。 FractalNets [17]反复将几个并行层序列与不同数量的卷积块组合在一起,以获得较大的标称深度,同时保持网络中的许多短路径。 尽管这些不同的方法在网络拓扑和培训过程方面有所不同,但是它们都具有一个关键特征:它们创建了从早期层到后续层的短路径。在本文中,我们提出了一种架构,该架构将这种见解提炼为简单的连通性模式:为了确保网络中各层之间的最大信息流,我们将所有层(具有匹配的特征图大小)直接相互连接。为了保留前馈特性,每个层都从所有先前的层中获取其他输入,并将其自身的特征图传递给所有后续层。图1示意性地说明了这种布局。至关重要的是,与ResNets相比,我们永远不会在将特征传递到图层之前通过求和来组合特征。相反,我们通过串联功能来组合它们。因此,“第l层具有”输入,由所有前面的卷积块的特征图组成。它自己的特征图将传递到所有L-′后续层。这将在L层网络中引入L(L + 1)/ 2个连接,而不是像传统体系结构那样仅引入L个。由于其密集的连接模式,我们将这种方法称为密集卷积网络(DenseNet)。
(5)这种密集的连接模式的可能与直觉相反的效果是,与传统的卷积网络相比,它需要的参数更少,因为不需要重新学习冗余的特征图。可以将传统的前馈体系结构视为具有状态的算法,该状态会逐层传递。每一层都从其上一层读取状态并写入下一层,它不仅改变状态,还传递需要保留的信息。 ResNets [11]通过附加身份转换使信息保存变得明确。ResNets[13]的最新变化表明,许多层的贡献很小,实际上在训练过程中可以随意丢弃。这使得ResNet的状态类似于(展开的)递归神经网络[21],但是ResNet的参数数量实质上更大,因为每一层都有其自己的权重。我们提出的DenseNet体系结构明确区分了添加到网络中的信息和保留的信息。DenseNet层非常狭窄(例如,每层12个过滤器),仅向网络的“集体知识”添加少量特征图集网络并保持其余特征图不变-最终分类器根据网络中的所有特征图做出决定。
(6)除了更好的参数效率外,DenseNets的一大优势是它们改善了整个网络中的信息流和梯度,这使得它们易于训练。每层都可以直接从损耗函数和原始输入信号访问梯度,从而导致隐式的深度监控[20]。这有助于训练更深层次的网络体系结构。此外,我们还观察到密集的连接具有正则化效果,从而减少了训练集大小较小的任务的过度拟合。
2、网络架构
由上图的网络架构可以明显的看出,DenseNet是把第0,...,L-1层输出的feature maps(第0层表示输入图像)拼接层一个张量(tensor)feature maps,作为L层的输入。
1】、密集连接:为了提高信息流也就是个人理解的特征在层与层之间的连接。密集连接属于一个DenseBlock块里面的,不同的DenseBlock块不存在密集连接。
2】、Composite function(复合功能):指的是上图中H1 H2 H3,这里面的操作包含:BN,Relu,3*3conv(padding=1,不改变feature map的尺寸)
3】、Pooling layers:整个网络分为多个dense block,在一个dense block结束,连接下一个dense block之前,需要经过一个transition layers层(黄框部分),它包含了BN,1*1con pooling层
3、模型解析
那么我们就会有一个疑问,每个卷积层的输出的feature maps可能大小不同,如经过采样和pooling的输出,这样拼接的feature maps大小就会不一致,这该怎么办呢?有办法,DenseNet把该网络架构封装成DenseBlocks的形式(如下图),在每个DenseBlocks里进行拼接的feature maps大小都是一致的,尽管不同DenseBlocks内的feature maps的大小不一致。如下图所示:
3、代码
import re
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.utils.checkpoint as cp
from collections import OrderedDict
# from .utils import load_state_dict_from_url
from torch import Tensor
from typing import Any, List, Tuple
from torchsummary import summary
# 可选择的densenet模型
__all__ = ['DenseNet', 'densenet121', 'densenet169', 'densenet201', 'densenet161']
# 可下载的densenet预训练权重
model_urls = {
'densenet121': 'https://download.pytorch.org/models/densenet121-a639ec97.pth',
'densenet169': 'https://download.pytorch.org/models/densenet169-b2777c0a.pth',
'densenet201': 'https://download.pytorch.org/models/densenet201-c1103571.pth',
'densenet161': 'https://download.pytorch.org/models/densenet161-8d451a50.pth',
}
# 定义一个denseblock(dense layer),其中growth_rate的意思是一层产生多少个特征图
class _DenseLayer(nn.Module):
def __init__(
self,
num_input_features: int,
growth_rate: int,
bn_size: int,
drop_rate: float,
memory_efficient: bool = False
) -> None:
super(_DenseLayer, self).__init__()
# 首先对输入做一次bn、激活、卷积
self.norm1: nn.BatchNorm2d
self.add_module('norm1', nn.BatchNorm2d(num_input_features))
self.relu1: nn.ReLU
self.add_module('relu1', nn.ReLU(inplace=True))
self.conv1: nn.Conv2d
# 输出特征图的数量为bn_size*growth_rate,卷积、bn、激活
self.add_module('conv1', nn.Conv2d(num_input_features, bn_size *growth_rate, kernel_size=1, stride=1,bias=False))
self.norm2: nn.BatchNorm2d
self.add_module('norm2', nn.BatchNorm2d(bn_size * growth_rate))
self.relu2: nn.ReLU
self.add_module('relu2', nn.ReLU(inplace=True))
self.conv2: nn.Conv2d
self.add_module('conv2', nn.Conv2d(bn_size * growth_rate, growth_rate,kernel_size=3, stride=1, padding=1,bias=False))
self.drop_rate = float(drop_rate)
self.memory_efficient = memory_efficient
def bn_function(self, inputs: List[Tensor]) -> Tensor:
concated_features = torch.cat(inputs, 1)
bottleneck_output = self.conv1(self.relu1(self.norm1(concated_features))) # noqa: T484
return bottleneck_output
# 判断当前tensor是否参与梯度传播
def any_requires_grad(self, input: List[Tensor]) -> bool:
for tensor in input:
if tensor.requires_grad:
return True
return False
# @torch.jit.unused # noqa: T484
def call_checkpoint_bottleneck(self, input: List[Tensor]) -> Tensor:
def closure(*inputs):
return self.bn_function(inputs)
return cp.checkpoint(closure, *input)
# @torch.jit._overload_method # noqa: F811
def forward(self, input: List[Tensor]) -> Tensor:
pass
# @torch.jit._overload_method # noqa: F811
def forward(self, input: Tensor) -> Tensor:
pass
# torchscript does not yet support *args, so we overload method
# allowing it to take either a List[Tensor] or single Tensor
def forward(self, input: Tensor) -> Tensor: # noqa: F811
if isinstance(input, Tensor):
prev_features = [input]
else:
prev_features = input
if self.memory_efficient and self.any_requires_grad(prev_features):
if torch.jit.is_scripting():
raise Exception("Memory Efficient not supported in JIT")
bottleneck_output = self.call_checkpoint_bottleneck(prev_features)
else:
bottleneck_output = self.bn_function(prev_features)
new_features = self.conv2(self.relu2(self.norm2(bottleneck_output)))
# 加上dropout
if self.drop_rate > 0:
new_features = F.dropout(new_features, p=self.drop_rate,training=self.training)
return new_features
class _DenseBlock(nn.ModuleDict):
_version = 2
def __init__(
self,
num_layers: int,
num_input_features: int,
bn_size: int,
growth_rate: int,
drop_rate: float,
memory_efficient: bool = False
) -> None:
super(_DenseBlock, self).__init__()
# 随着layer层数的增加,每增加一层,输入的特征图就增加一倍growth_rate
for i in range(num_layers):
layer = _DenseLayer(
num_input_features + i * growth_rate,
growth_rate=growth_rate,
bn_size=bn_size,
drop_rate=drop_rate,
memory_efficient=memory_efficient,
)
# 添加一层layer
self.add_module('denselayer%d' % (i + 1), layer)
def forward(self, init_features: Tensor) -> Tensor:
# 提取特征
features = [init_features]
for name, layer in self.items():
new_features = layer(features)
features.append(new_features)
# 将特征图concat在一起
return torch.cat(features, 1)
class _Transition(nn.Sequential):
def __init__(self, num_input_features: int, num_output_features: int) -> None:
super(_Transition, self).__init__()
# transition层使用的是1 x 1卷积核,作用是用来改变通道数
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(kernel_size=2, stride=2))
class DenseNet(nn.Module):
def __init__(
self,
growth_rate: int = 32,
block_config: Tuple[int, int, int, int] = (6, 12, 24, 16),
num_init_features: int = 64,
bn_size: int = 4,
drop_rate: float = 0,
num_classes: int = 1000,
memory_efficient: bool = False
) -> None:
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(kernel_size=3, stride=2, padding=1)),
]))
# 构建每一个denseblock
num_features = num_init_features
for i, num_layers in enumerate(block_config):
block = _DenseBlock(
num_layers=num_layers,
num_input_features=num_features,
bn_size=bn_size,
growth_rate=growth_rate,
drop_rate=drop_rate,
memory_efficient=memory_efficient
)
self.features.add_module('denseblock%d' % (i + 1), block)
num_features = num_features + num_layers * growth_rate
if i != len(block_config) - 1:
trans = _Transition(num_input_features=num_features,
num_output_features=num_features // 2)
self.features.add_module('transition%d' % (i + 1), trans)
num_features = num_features // 2
# 最后一个bn层
self.features.add_module('norm5', nn.BatchNorm2d(num_features))
# 分类器
self.classifier = nn.Linear(num_features, 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: Tensor) -> Tensor:
# 提取特征、激活、池化、摊平、分类
features = self.features(x)
out = F.relu(features, inplace=True)
out = F.adaptive_avg_pool2d(out, (1, 1))
out = torch.flatten(out, 1)
out = self.classifier(out)
return out
# 加载训练权重
def _load_state_dict(model: nn.Module, model_url: str, progress: bool) -> None:
# '.'s are no longer allowed in module names, but previous _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 = load_state_dict_from_url(model_url, progress=progress)
for key in list(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)
def _densenet(
arch: str,
growth_rate: int,
block_config: Tuple[int, int, int, int],
num_init_features: int,
pretrained: bool,
progress: bool,
**kwargs: Any
) -> DenseNet:
model = DenseNet(growth_rate, block_config, num_init_features, **kwargs)
if pretrained:
_load_state_dict(model, model_urls[arch], progress)
return model
# 预训练权重,其中第一个参数'densenet121'代表densenet的模型名称,32代表每一层添加32个特征图,(6, 12, 24, 16)表示4个denselayer重复的次数,64表示初始特征数
def densenet121(pretrained: bool = False, progress: bool = True, **kwargs: Any) -> DenseNet:
return _densenet('densenet121', 32, (6, 12, 24, 16), 64, pretrained, progress,
**kwargs)
def densenet161(pretrained: bool = False, progress: bool = True, **kwargs: Any) -> DenseNet:
return _densenet('densenet161', 48, (6, 12, 36, 24), 96, pretrained, progress,
**kwargs)
def densenet169(pretrained: bool = False, progress: bool = True, **kwargs: Any) -> DenseNet:
return _densenet('densenet169', 32, (6, 12, 32, 32), 64, pretrained, progress,
**kwargs)
def densenet201(pretrained: bool = False, progress: bool = True, **kwargs: Any) -> DenseNet:
return _densenet('densenet201', 32, (6, 12, 48, 32), 64, pretrained, progress,
**kwargs)
参考: