【4】卷积神经网络之DenseNets

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)

参考:

densenet代码解读_行者无疆哇的博客-CSDN博客_densenet代码

DenseNets_鹊踏枝-码农的博客-CSDN博客_densenets

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值