目录

摘要

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. 论文解读丨基于局部特征保留的图卷积神经网络架构(LPD-GCN)

    摘要:本文提出一种基于局部特征保留的图卷积网络架构,与最新的对比算法相比,该方法在多个数据集上的图分类性能得到大幅度提升,泛化性能也得到了改善. 本文分享自华为云社区<论文解读:基于局部特征保留 ...

  2. 从单一图像中提取文档图像:ICCV2019论文解读

    从单一图像中提取文档图像:ICCV2019论文解读 DewarpNet: Single-Image Document Unwarping With Stacked 3D and 2D Regressi ...

  3. itemKNN发展史----推荐系统的三篇重要的论文解读

    itemKNN发展史----推荐系统的三篇重要的论文解读 本文用到的符号标识 1.Item-based CF 基本过程: 计算相似度矩阵 Cosine相似度 皮尔逊相似系数 参数聚合进行推荐 根据用户 ...

  4. CVPR2019 | Mask Scoring R-CNN 论文解读

    Mask Scoring R-CNN CVPR2019 | Mask Scoring R-CNN 论文解读 作者 | 文永亮 研究方向 | 目标检测.GAN 推荐理由: 本文解读的是一篇发表于CVPR ...

  5. AAAI2019 | 基于区域分解集成的目标检测 论文解读

    Object Detection based on Region Decomposition and Assembly AAAI2019 | 基于区域分解集成的目标检测 论文解读 作者 | 文永亮 学 ...

  6. Gaussian field consensus论文解读及MATLAB实现

    Gaussian field consensus论文解读及MATLAB实现 作者:凯鲁嘎吉 - 博客园 http://www.cnblogs.com/kailugaji/ 一.Introduction ...

  7. zz扔掉anchor!真正的CenterNet——Objects as Points论文解读

    首发于深度学习那些事 已关注写文章   扔掉anchor!真正的CenterNet——Objects as Points论文解读 OLDPAN 不明觉厉的人工智障程序员 ​关注他 JustDoIT 等 ...

  8. NIPS2018最佳论文解读:Neural Ordinary Differential Equations

    NIPS2018最佳论文解读:Neural Ordinary Differential Equations 雷锋网2019-01-10 23:32     雷锋网 AI 科技评论按,不久前,NeurI ...

  9. [论文解读] 阿里DIEN整体代码结构

    [论文解读] 阿里DIEN整体代码结构 目录 [论文解读] 阿里DIEN整体代码结构 0x00 摘要 0x01 文件简介 0x02 总体架构 0x03 总体代码 0x04 模型基类 4.1 基本逻辑 ...

  10. 【抓取】6-DOF GraspNet 论文解读

    [抓取]6-DOF GraspNet 论文解读 [注]:本文地址:[抓取]6-DOF GraspNet 论文解读 若转载请于明显处标明出处. 前言 这篇关于生成抓取姿态的论文出自英伟达.我在读完该篇论 ...

随机推荐

  1. 【机器学习】利用 Python 进行数据分析的环境配置 Windows(Jupyter,Matplotlib,Pandas)

    环境配置 安装 python 博主使用的版本是 3.10.6 在 Windows 系统上使用 Virtualenv 搭建虚拟环境 安装 Virtualenv 打开 cmd 输入并执行 pip inst ...

  2. 利用POI遍历出层级结构的excel表格

    import java.util.ArrayList; import java.util.List; import org.apache.poi.ss.util.CellRangeAddress; p ...

  3. Filter 筛选器(三)之 自定义一个启动事务的 TransactionScopeFilter

    如果一个方法内有多个写入操作,比如 写入A表,然后用A表的自增id 去写入B表,假如A表写入成功,但B表因为某种原因写入失败!(这就导致A表写入了脏数据) 这时候 我们可以自定义 一个Filter 进 ...

  4. 带你读AI论文丨ACGAN-动漫头像生成

    摘要:ACGAN-动漫头像生成是一个十分优秀的开源项目. 本文分享自华为云社区<[云驻共创]AI论文精读会:ACGAN-动漫头像生成>,作者:SpiderMan. 1.论文及算法介绍 1. ...

  5. 「JOISC 2022 Day1」京都观光 题解

    Solution 考虑从\((x_1,y_1)\)走到\((x_2,y_2)\)满足只改变一次方向,则容易求出先向南走当且仅当 \[\frac{a_{x_1} - a_{x_2}}{x_1 - x_2 ...

  6. Android自动化测试工具调研

    原文地址:Android自动化测试工具调研 - Stars-One的杂货小窝 Android测试按测试方式分类,可分为两种:一种是传统逻辑单元测试(Junit),另外一种则是UI交互页面测试. 这里详 ...

  7. C#实现生成Markdown文档目录树

    前言 之前我写了一篇关于C#处理Markdown文档的文章:C#解析Markdown文档,实现替换图片链接操作 算是第一次尝试使用C#处理Markdown文档,然后最近又把博客网站的前台改了一下,目前 ...

  8. SQL--插入单引号值

    sql语句中插入单引号的值需要多写一个单引号进行转义 例如:插入一个值为student' name的值 update table set name='student'' name'

  9. (二)Three光线检测-实现摄像机向鼠标点击位置滑动动画

    (二)Three.js光线检测 摘要:使用three.js中的光线检测 Raycaster() ,实现一下效果: 通过点击处的坐标,修改摄像机位置,实现摄像机由远及近的过渡动态效果(由远景到近景) 1 ...

  10. 0基础90分钟会用PS——GenJi笔记

    数码图像的相关基础概念 1.位图和矢量图 位图 也叫点阵图像,位图使用也称像素的一格一格的小点来描述图像,图放大后我们可以看到像素点 矢量图 根据几何特性来绘制图形,用线段和曲线描述图像,可以是一个一 ...