Densely Connected Convolutional Networks 密集连接卷积网络

什么是DenseNet?

DenseNet是由清华大学的Zhuang Liu、康奈尔大学的Gao Huang和Kilian Q.Weinberger,以及Facebook研究员Laurens van der Maaten在CVPR 2017所作,并且还获得了2017CVPR最佳论文!下面我们就来看看DenseNet是何方神圣吧。

我们知道,当靠近输入的层和靠近输出的层之间的连接越短,卷积神经网络就可以做得更深,精度更高且可以更加有效的训练。而DenseNet在此基础上将每一层与之前所有层相连接。传统的L层卷积神经网络有L个连接——位于每一层和其后一层之间—,而我们的神经网络有L*(L+1)/2个直接链接。对于每一层,其输入的特征是之前所有的层,而它自己的特征图作为之后所有层的输入。

下图展示了具体的密集连接方式:

DenseNet有以下几个引人注目的优点:缓解梯度消失问题,加强特征传播,鼓励特征复用,极大的减少了参数量。DenseNets需要更少的计算来实现高性能

ResNet

训练深层的神经网络,会遇到梯度消失和梯度爆炸(vanishing/exploding gradients)的问题,影响了网络的收敛,但是这很大程度已经被标准初始化(normalized initialization)和BN(Batch Normalization)所处理

当深层网络能够开始收敛,会引起网络退化(degradation problem)问题,即随着网络深度增加,准确率会饱和,甚至下降。这种退化不是由过拟合引起的,因为在适当的深度模型中增加更多的层反而会导致更高的训练误差
ResNet就通过引入深度残差连接来解决网络退化的问题,建立前面层与后面层之间的短链接(shortcuts),从而解决深度CNN模型难训练的问题

具体表达式如下:
在这里插入图片描述
在ResNet中前面层与后面层建立的短链接(shortcuts)方式是add相加操作,因此要求输入与输出的shape完全一样!这主要是由ResNet结构中的Identity Block来完成的,更大程度地去加深网络的深度,实现了深度残差网络。但是add操作可能会阻碍网络中的信息流。

DenseNets和Resnet之间的主要区别,DenseNets通过特征重用来挖掘网络的潜力,产生易于训练且参数效率高的精简模型。将不同层学习到的特征映射进行堆叠操作,可以增加后续层的输入变化,并提高效率。

DenseNet

DenseNet的整体网络架构如下图所示:

一个有三个Dense Block的DenseNet网络。每个Dense Block中含有多个conv blocks,conv blocks中特征图的高度和宽度不发生变化,进行的是通道数的变化。 两个相邻Dense Block之间的层称为Transition Layer过渡层,通过卷积和池化改变特征图的大小

Dense connectivity 紧密连接

为了进一步改善层与层之间的信息流,提出了一种不同的连接模式:引入从任何层到所有后续层的直接连接。正如上图所展示的那样,例如第l层接收前面所有层的特征映射,即x0,x1,…,xl-1作为输入:
在这里插入图片描述
H函数实现每层的非线性变换,主要由BN,Relu和3x3Conv组成,式中[x0,x1,…,xl-1]指的是第0,…,l-1层中生成的特征图进行串联。

Bottleneck layers

每一个Dense Block中的conv block经过H非线性输出k个特征图,但是它通常是输入特征图更多。因此在3x3卷积之前引入1x1卷积作为瓶颈层,以减少特征映射的数量,从而提高计算效率。

Growth rate

如果每个函数H生成k个特征映射,则第l层具有k0+k×(l-1)个输入特征映射,其中k0是输入的通道数。DenseNet与现有网络体系结构的一个重要区别是,DenseNet可以有非常窄的层,例如k=12。我们把超参数k称为网络的growth rate。相对较小的growth rate足以获得最新的结果。
实现代码为:

class Bottleneck(nn.Module):
    def __init__(self, nChannels, growthRate):
        super(Bottleneck, self).__init__()
        interChannels = 4*growthRate
        # 瓶颈结构1x1卷积减少输入的通道数
        self.bn1 = nn.BatchNorm2d(nChannels)
        self.conv1 = nn.Conv2d(nChannels, interChannels, kernel_size=1,bias=False)
        # H函数实现非线性输出BN+Relu+BN
        self.bn2 = nn.BatchNorm2d(interChannels)
        self.conv2 = nn.Conv2d(interChannels, growthRate, kernel_size=3,padding=1, bias=False)

    def forward(self, x):
        out = self.conv1(F.relu(self.bn1(x)))
        out = self.conv2(F.relu(self.bn2(out)))
        out = torch.cat((x, out), 1)
        return out

Pooling layers 池化层

当特征映射的大小发生变化时,上面式子中使用的连接操作是不可行的。然而,卷积网络的一个重要组成部分是向下采样层来改变特征映射的大小。因此在Dense Block之间添加Transition Layer来进行特征图大小的压缩,而Transition Layer主要由一个1x1卷积块以及步长为2的AveragePooling2D来进行特征图大小压缩。

Compression 压缩

为了进一步提高模型的紧凑性,在Transition Layer中减少特征映射的数量。如果一个Desne Block中包含m个特征映射,在Transition Layer中生成的特征图数量为θm,其中0<θ≤1被称为压缩因子。当θ=1时,Transition Layer的特征图数量保持不变,论文中将θ设置为0.5。
Transition Layer实现代码如下:

# reduction即为压缩因子
nOutChannels = int(math.floor(nChannels*reduction))
class Transition(nn.Module):
    def __init__(self, nChannels, nOutChannels):
        super(Transition, self).__init__()
        self.bn1 =
### Densely Connected Convolutional Networks (DenseNet) 的架构与实现 #### 架构解释 DenseNet 是一种密集连接卷积神经网络,其核心思想在于通过层间的密集连接来增强特征重用并减少冗余参数。在传统卷积网络中,每一层仅与其相邻的一层相连;而在 DenseNet 中,每层不仅接受前一层的输入,还接受之前所有层的输出作为额外输入[^3]。 这种设计使得 DenseNet 能够显著降低模型复杂度和计算成本,因为各层无需重复学习相同的特征图。具体而言,DenseNet 将每一层的输出视为全局状态的一部分,并允许后续层访问这些状态。因此,最终分类器能够利用整个网络中的所有特征图进行预测[^5]。 #### 实现方法 以下是 DenseNet 的基本构建单元及其 Python 实现: 1. **Dense Block**: 密集块由多个卷积层组成,其中每一层都将自身的输出与其他层的输出拼接在一起。 2. **Transition Layer**: 这些层用于控制特征图的数量以及缩小图像尺寸,通常包括批量归一化(Batch Normalization)、ReLU 和平均池化操作。 下面是基于 PyTorch 的简单实现示例: ```python import torch.nn as nn import torch class Bottleneck(nn.Module): """Bottleneck 层""" def __init__(self, in_channels, growth_rate): super(Bottleneck, self).__init__() inter_channel = 4 * growth_rate self.bn1 = nn.BatchNorm2d(in_channels) self.conv1 = nn.Conv2d(in_channels, inter_channel, kernel_size=1, bias=False) self.relu = nn.ReLU(inplace=True) self.bn2 = nn.BatchNorm2d(inter_channel) self.conv2 = nn.Conv2d(inter_channel, growth_rate, kernel_size=3, padding=1, bias=False) def forward(self, x): out = self.conv1(self.relu(self.bn1(x))) out = self.conv2(self.relu(self.bn2(out))) out = torch.cat((x, out), dim=1) # 特征图拼接 return out class Transition(nn.Module): """过渡层""" def __init__(self, in_channels, out_channels): super(Transition, self).__init__() self.bn = nn.BatchNorm2d(in_channels) self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False) self.avg_pool = nn.AvgPool2d(kernel_size=2, stride=2) def forward(self, x): out = self.conv(self.bn(x)) out = self.avg_pool(out) return out class DenseNet(nn.Module): """DenseNet 主体结构""" def __init__(self, num_init_features, growth_rate, block_config, num_classes=1000): super(DenseNet, self).__init__() self.features = nn.Sequential( nn.Conv2d(3, num_init_features, kernel_size=7, stride=2, padding=3, bias=False), nn.BatchNorm2d(num_init_features), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=3, stride=2, padding=1) ) num_features = num_init_features for i, num_layers in enumerate(block_config): dense_block = self._make_dense_block(growth_rate, num_layers, num_features) self.features.add_module(f"denseblock{i + 1}", dense_block) num_features += num_layers * growth_rate if i != len(block_config) - 1: trans_layer = Transition(num_features, num_features // 2) self.features.add_module(f"transition{i + 1}", trans_layer) num_features //= 2 self.classifier = nn.Linear(num_features, num_classes) def _make_dense_block(self, growth_rate, n_layers, input_channels): layers = [] for _ in range(n_layers): layers.append(Bottleneck(input_channels, growth_rate)) input_channels += growth_rate return nn.Sequential(*layers) def forward(self, x): features = self.features(x) out = nn.functional.adaptive_avg_pool2d(features, (1, 1)) out = torch.flatten(out, 1) out = self.classifier(out) return out ``` 上述代码定义了一个基础版本的 DenseNet 模型,其中包括瓶颈层(`Bottleneck`)和过渡层(`Transition`)。通过调整 `growth_rate` 和 `block_config` 参数,可以灵活配置不同的 DenseNet 变种。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值