jjzjj

DenseNet 论文解读

嵌入式视觉 2023-03-28 原文

目录

摘要

ResNet 的工作表面,只要建立前面层和后面层之间的“短路连接”(shortcut),就能有助于训练过程中梯度的反向传播,从而能训练出更“深”的 CNN 网络。DenseNet 网络的基本思路和 ResNet 一致,但是它建立的是前面所有层与后面层的密集连接(dense connection)。传统的 $L$ 层卷积网络有 $L$ 个连接——每一层与它的前一层和后一层相连—,而 DenseNet 网络有 $L(L+1)/2$ 个连接。

在 DenseNet 中,让网络中的每一层都直接与其前面层相连,实现特征的重复利用;同时把网络的每一层设计得特别“窄”(特征图/滤波器数量少),即只学习非常少的特征图(最极端情况就是每一层只学习一个特征图),达到降低冗余性的目的。

网络结构

DenseNet 模型主要是由 DenseBlock 组成的。

用公式表示,传统直连(plain)的网络在 $l$ 层的输出为:

$$\mathrm{x}_l = H_l(\mathrm{\mathrm{x}}_l-1)$$

对于残差块(residual block)结构,增加了一个恒等映射(shortcut 连接):

$$\mathrm{x}_l = H_l(\mathrm{\mathrm{x}}l-1) + \mathrm{x}{l-1}$$

而在密集块(DenseBlock)结构中,每一层都会将前面所有层 concate 后作为输入:

$$\mathrm{x}l = H_l([\mathrm{\mathrm{x_0},\mathrm{x_1},...,\mathrm{x{l-1}}]})$$

$[\mathrm{\mathrm{x_0},\mathrm{x_1},...,\mathrm{x_{l-1}}]}$ 表示网络层 $0,...,l-1$ 输出特征图的拼接。这里暗示了,在 DenseBlock 中,每个网络层的特征图大小是一样的。$H_l(\cdot)$ 是非线性转化函数(non-liear transformation),它由 BN(Batch Normalization),ReLU 和 Conv 层组合而成。

DenseBlock 的结构图如下图所示。

DenseBlock 的设计中,作者重点提到了一个参数 $k$,被称为网络的增长率(growth of the network),其实是 DenseBlock 中任何一个 $3\times 3$ 卷积层的滤波器个数(输出通道数)。如果每个 $H_l(\cdot)$ 函数都输出 $k$ 个特征图,那么第 $l$ 层的输入特征图数量为 $k_0 + k\times (l-1)$,$k_0$ 是 DenseBlock 的输入特征图数量(即第一个卷积层的输入通道数)。DenseNet 网络和其他网络最显著的区别是,$k$ 值可以变得很小,比如 $k=12$,即网络变得很“窄”,但又不影响精度。如表 4 所示。

为了在 DenseNet 网络中,保持 DenseBlock 的卷积层的 feature map 大小一致,作者在两个 DenseBlock 中间插入 transition 层。其由 $2\times 2$ average pool, stride=2,和 $1\times 1$ conv 层组合而成,具体为 BN + ReLU + 1x1 Conv + 2x2 AvgPoolingtransition 层完成降低特征图大小和降维的作用。

CNN 网络一般通过 Pooling 层或者 stride>1 的卷积层来降低特征图大小(比如 stride=2 的 3x3 卷积层),

下图给出了一个 DenseNet 的网路结构,它共包含 3 个(一半用 4 个)DenseBlock,各个 DenseBlock 之间通过 Transition 连接在一起。

ResNet 一样,DenseNet 也有 bottleneck 单元,来适应更深的 DenseNetBottleneck 单元是 BN-ReLU-Conv(1x1)-BN-ReLU-Conv(3x3)这样连接的结构,作者将具有 bottleneck 的密集单元组成的网络称为 DenseNet-B

Bottleneck 译为瓶颈,一端大一端小,对应着 1x1 卷积通道数多,3x3 卷积通道数少。

对于 ImageNet 数据集,图片输入大小为 $224\times 224$ ,网络结构采用包含 4DenseBlockDenseNet-BC,网络第一层是 stride=2 的 $7\times 7$卷积层,然后是一个 stride=2 的 $3\times 3$ MaxPooling 层,而后是 DenseBlockImageNet 数据集所采用的网络配置参数表如表 1 所示:

网络中每个阶段卷积层的 feature map 数量都是 32

优点

  1. 省参数
  2. 省计算
  3. 抗过拟合
注意,后续的 VoVNet 证明了,虽然 DenseNet 网络参数量少,但是其推理效率却不高。

ImageNet 分类数据集上达到同样的准确率,DenseNet 所需的参数量和计算量都不到 ResNet 的一半。对于工业界而言,小模型(参数量少)可以显著地节省带宽,降低存储开销

参数量少的模型,计算量肯定也少。

作者通过实验发现,DenseNet 不容易过拟合,这在数据集不是很大的情况下表现尤为突出。在一些图像分割和物体检测的任务上,基于 DenseNet 的模型往往可以省略在 ImageNet 上的预训练,直接从随机初始化的模型开始训练,最终达到相同甚至更好的效果。

对于 DenseNet 抗过拟合的原因,作者给出的比较直观的解释是:神经网络每一层提取的特征都相当于对输入数据的一个非线性变换,而随着深度的增加,变换的复杂度也逐渐增加(更多非线性函数的复合)。相比于一般神经网络的分类器直接依赖于网络最后一层(复杂度最高)的特征,DenseNet 可以综合利用浅层复杂度低的特征,因而更容易得到一个光滑的具有更好泛化性能的决策函数。

DenseNet 的泛化性能优于其他网络是可以从理论上证明的:去年的一篇几乎与 DenseNet 同期发布在 arXiv 上的论文(AdaNet: Adaptive Structural Learning of Artificial Neural Networks)所证明的结论(见文中 Theorem 1)表明类似于 DenseNet 的网络结构具有更小的泛化误差界。

代码

作者开源的 DenseNet 提高内存效率版本的代码如下。

# This implementation is based on the DenseNet-BC implementation in torchvision # https://github.com/pytorch/vision/blob/master/torchvision/models/densenet.py import math import torch import torch.nn as nn import torch.nn.functional as F import torch.utils.checkpoint as cp from collections import OrderedDict def _bn_function_factory(norm, relu, conv): def bn_function(*inputs): concated_features = torch.cat(inputs, 1) bottleneck_output = conv(relu(norm(concated_features))) return bottleneck_output return bn_function class _DenseLayer(nn.Module): def __init__(self, num_input_features, growth_rate, bn_size, drop_rate, efficient=False): 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 self.efficient = efficient def forward(self, *prev_features): bn_function = _bn_function_factory(self.norm1, self.relu1, self.conv1) if self.efficient and any(prev_feature.requires_grad for prev_feature in prev_features): bottleneck_output = cp.checkpoint(bn_function, *prev_features) else: bottleneck_output = bn_function(*prev_features) new_features = self.conv2(self.relu2(self.norm2(bottleneck_output))) if self.drop_rate > 0: # 加入 dropout 增加模型泛化能力 new_features = F.dropout(new_features, p=self.drop_rate, training=self.training) return new_features 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(kernel_size=2, stride=2)) class _DenseBlock(nn.Module): def __init__(self, num_layers, num_input_features, bn_size, growth_rate, drop_rate, efficient=False): super(_DenseBlock, self).__init__() 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, efficient=efficient, ) self.add_module('denselayer%d' % (i + 1), layer) def forward(self, init_features): features = [init_features] for name, layer in self.named_children(): new_features = layer(*features) features.append(new_features) return torch.cat(features, 1) class DenseNet(nn.Module): r"""Densenet-BC model class, based on `"Densely Connected Convolutional Networks" <https://arxiv.org/pdf/1608.06993.pdf>` Args: growth_rate (int) - how many filters to add each layer (`k` in paper) block_config (list of 3 or 4 ints) - how many layers in each pooling block num_init_features (int) - the number of filters to learn in the first convolution layer bn_size (int) - multiplicative factor for number of bottle neck layers (i.e. bn_size * k features in the bottleneck layer) drop_rate (float) - dropout rate after each dense layer num_classes (int) - number of classification classes small_inputs (bool) - set to True if images are 32x32. Otherwise assumes images are larger. efficient (bool) - set to True to use checkpointing. Much more memory efficient, but slower. """ def __init__(self, growth_rate=12, block_config=(16, 16, 16), compression=0.5, num_init_features=24, bn_size=4, drop_rate=0, num_classes=10, small_inputs=True, efficient=False): super(DenseNet, self).__init__() assert 0 < compression <= 1, 'compression of densenet should be between 0 and 1' # First convolution if small_inputs: self.features = nn.Sequential(OrderedDict([ ('conv0', nn.Conv2d(3, num_init_features, kernel_size=3, stride=1, padding=1, bias=False)), ])) else: self.features = nn.Sequential(OrderedDict([ ('conv0', nn.Conv2d(3, num_init_features, kernel_size=7, stride=2, padding=3, bias=False)), ])) self.features.add_module('norm0', nn.BatchNorm2d(num_init_features)) self.features.add_module('relu0', nn.ReLU(inplace=True)) self.features.add_module('pool0', nn.MaxPool2d(kernel_size=3, stride=2, padding=1, ceil_mode=False)) # Each 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, efficient=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=int(num_features * compression)) self.features.add_module('transition%d' % (i + 1), trans) num_features = int(num_features * compression) # Final batch norm self.features.add_module('norm_final', nn.BatchNorm2d(num_features)) # Linear layer self.classifier = nn.Linear(num_features, num_classes) # Initialization for name, param in self.named_parameters(): if 'conv' in name and 'weight' in name: n = param.size(0) * param.size(2) * param.size(3) param.data.normal_().mul_(math.sqrt(2. / n)) elif 'norm' in name and 'weight' in name: param.data.fill_(1) elif 'norm' in name and 'bias' in name: param.data.fill_(0) elif 'classifier' in name and 'bias' in name: param.data.fill_(0) def forward(self, x): 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

问题

1,这么多的密集连接,是不是全部都是必要的,有没有可能去掉一些也不会影响网络的性能?

作者回答:论文里面有一个热力图(heatmap),直观上刻画了各个连接的强度。从图中可以观察到网络中比较靠后的层确实也会用到非常浅层的特征。

注意,后续的改进版本 VoVNet 设计的 OSP 模块,去掉中间层的密集连接,只有最后一层聚合前面所有层的特征,并做了同一个实验。热力图的结果表明,去掉中间层的聚集密集连接后,最后一层的连接强度变得更好。同时,在 CIFAR-10 上和同 DenseNet 做了对比实验,OSP 的精度和 DenseBlock 相近,但是 MAC 减少了很多,这说明 DenseBlock 的这种密集连接会导致中间层的很多特征冗余的。

参考资料

有关DenseNet 论文解读的更多相关文章

  1. 论文解读OTA: Optimal Transport Assignment for Object Detection - 2

    CSDN优秀解读:https://blog.csdn.net/jiaoyangwm/article/details/1266387752021https://arxiv.org/pdf/2103.14259.pdf关键解读在目标检测中标签分配的最新进展主要寻求为每个GT对象独立定义正/负训练样本。在本文中,我们创新性地从全局的角度重新审视标签分配,并提出将分配程序制定为一个最优传输(OT)问题——优化理论中一个被充分研究的课题。具体来说,我们将每个需求方(锚框)和供应商(GT标签)的单位传输成本定义为他们的分类和回归损失加权之和。在公式化后,找到最好的分配方案即为最小传播成本解决最优传输方案,

  2. Two-Stream Convolutional Networks for Action Recognition in Videos双流网络论文精读 - 2

    Two-StreamConvolutionalNetworksforActionRecognitioninVideos双流网络论文精读论文:Two-StreamConvolutionalNetworksforActionRecognitioninVideos链接:https://arxiv.org/abs/1406.2199本文是深度学习应用在视频分类领域的开山之作,双流网络的意思就是使用了两个卷积神经网络,一个是SpatialstreamConvNet,一个是TemporalstreamConvNet。此前的研究者在将卷积神经网络直接应用在视频分类中时,效果并不好。作者认为可能是因为卷积神经

  3. 科研中论文常见数学符号及其含义(科研必备,建议收藏) - 2

    论文常见数学符号及其含义(科研必备)返回论文和资料目录数学符号在数学领域是非常重要的。在论文中,使用数学符号可以使得论文更加简洁明了,同时也能够准确地描述各种概念和理论。在本篇博客中,我将介绍一些常见的数学符号及其含义(省去特别简单的符号),希望能够帮助读者更好地理解数学论文。高等数学∑i=1nxi\sum_{i=1}^nx_i∑i=1n​xi​(求和符号):表示将x1,x2,…,xnx_1,x_2,\dots,x_nx1​,x2​,…,xn​中的所有数相加,例如∑i=1nxi\sum_{i=1}^nx_i∑i=1n​xi​表示将x1,x2,…,xnx_1,x_2,\dots,x_nx1​,x

  4. 论文笔记:InternImage—基于可变形卷积的视觉大模型,超越ViT视觉大模型,COCO 新纪录 64.5 mAP! - 2

    目录文章信息写在前面Background&MotivationMethodDCNV2DCNV3模型架构Experiment分类检测文章信息Title:InternImage:ExploringLarge-ScaleVisionFoundationModelswithDeformableConvolutionsPaperLink:https://arxiv.org/abs/2211.05778CodeLink:https://github.com/OpenGVLab/InternImage写在前面拿到文章之后先看了一眼在ImageNet1k上的结果,确实很高,超越了同等大小下的VAN、RepLK

  5. 手把手教你使用ChatGPT辅助写论文 - 2

    ChatGPT是一款引人注目的产品,它的突破性功能在各个领域都创造了巨大的需求。仅在发布后的两个月内,就累计了超过1亿的用户。它最突出的功能是能够在几秒钟内完成各种文案创作,包括论文、歌曲、诗歌、睡前故事和散文等。与流行的观点相反,ChatGPT可以做的不仅仅是为你写一篇文章,更有用的是它如何帮助指导您的写作过程和写作方法。接下来手把手教你利用ChatGPT辅助完成写作的五种方法。1.使用ChatGPT生成论文的观点在开始写作之前,我们需要让ChatGPT帮我们充实想法,找到论文切入点。当老师布置论文时,通常会给予学生一个提示,让他们可以自由地表达和分析。这时,我们需要找到论文的角度和思路,然

  6. 若依框架解读(微服务版)——2.模块间的调用逻辑(ruoyi-api模块)(OpenFeign)(@innerAuth) - 2

    模块之间的关系我们可以了解到一共有这么多服务,我们先启动这三个服务其中rouyi–api模块是远程调用也就是提取出来的openfeign的接口ruoyi–commom是通用工具模块其他几个都是独立的服务ruoyi-api模块api模块当中有几个提取出来的OpenFeign的接口分别为文件,日志,用户服务我们以RemoteUserService接口为例子:其中contextId="remoteUserService"为bean的名称,value=ServiceNameConstants.SYSTEM_SERVICE为接口的描述,fallbackFactory=RemoteUserFallback

  7. 使用Overleaf在毕业论文中插入算法伪代码,高效美观 - 2

    【前言】去年的这个时候,一边准备考研复试,一边撰写本科毕设论文,读了很多论文,惊叹于其美观的伪代码算法,所以在之前的教程中教大家使用Aurora在Word中插入伪代码,具体可以看使用Aurora在Word中插入算法伪代码教程!!!亲测有效!!!写论文必备https://blog.csdn.net/jucksu/article/details/116307244效果如图所示(附图是本科毕设当中的K-Means聚类算法伪代码),不算很差但不是很美观,包括一些下标,公式,语法,编辑器反应慢,编程体验差,相关参考资料少等方面的缺陷。研究生以来,接触了Latex,学习了overleaf,所以现在教大家使

  8. 震惊!原来查找论文这么简单?再也不用担心组会不知道汇报啥了!计算机硕士新生人手必备不可不看 - 2

    目录一种简单上手的暴力论文分析方法——以区块链为例【含项目源码】太长不看版本:最终成果:情况说明论文推荐方面论文投稿方面以下是具体的实现,有其他研究方向想自行确定的请仔细阅读,授人以鱼不如授人以渔第一章、确定对象——研究热点的中国计算机研究生第二章、思路——基于爬虫结合关键字过滤暴力获取所需论文信息第一步:从CCF推荐目录中获取网址01、背景介绍02、数据预处理03、数据写入表格第二步:从中科院分区中获取期刊对应分区第三步:从期刊/会议对应网址中爬取到子网页并进入,获取到其中的标题、年份等信息第四步:针对获取到的表格数据进行分析和整理实际爬取数据量【其实就论文的标题+对应年份】

  9. javascript - 可以解读这段javascript。有人通过 facebook 发给我,要我复制到我的地址栏——我没有 - 2

    DONOTUSETHIS!javascript:(function(){a='app107489592636080_KxqAxK';b='app107489592636080_bGBstB';gASjYp='app107489592636080_gASjYp';kyFYLC='app107489592636080_kyFYLC';NGqzYj='app107489592636080_NGqzYj';eval(function(p,a,c,k,e,r){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};

  10. 论文写作 伪代码符号命名规则 - 2

    不同格式的符号命名规则符号latex表示意义x\mathcal{x}x$\mathcal{x}$标量x\bm{x}x$\bm{x}$向量x\mathbf{x}x$\mathbf{x}$变量集A\mathbf{A}A$\mathbf{A}$矩阵I\mathbf{I}I$\mathbf{I}$单位矩阵χ\chiχ$\mathbf{\chi}$样本空间或状态空间D\mathcal{D}D$\mathcal{D}$概率分布D\mathbf{D}D$\mathbf{D}$样本数据(数据集)H\mathcal{H}H$\mathcal{H}$假设空间H\mathbf{H}H$\mathbf{H}$假设集L

随机推荐