敞开成长之旅!这是我参与「日新计划 12 月更文挑战」的第3天,点击检查活动详情。
- 摘要
- 1,介绍
-
2,高效网络规划的影响要素
- 2.1,内存拜访价值
- 2.2,GPU核算功率
-
3,主张的办法
- 3.1,从头考虑密布衔接
- 3.2,One-Shot Aggregation
- 3.3,构建 VoVNet 网络
- 4,试验
- 5,代码解读
- 参考资料
文章同步发于 github、博客园 和 知乎。最新版以
github
为主。假如看完文章有所收成,一定要先点赞后保藏。毕竟,赠人玫瑰,手有余香。
摘要
Youngwan Lee*
作者于2019
年宣布的论文 An Energy and GPU-Computation Efficient Backbone Network for Real-Time Object Detection. 是对DenseNet
网络推理功率低的改善版别。
由于 DenseNet
经过用密布衔接,来聚合具有不同感触野巨细的中心特征,因而它在方针检测使命上体现出杰出的功能。尽管特征重用(feature reuse
)的运用,让 DenseNet
以少数模型参数和 FLOPs
,也能输出有力的特征,可是运用 DenseNet
作为 backbone
的方针检测器却体现出了运转速度慢和功率低下的弊端。作者以为是密布衔接(dense connection
)带来的输入通道线性增长,然后导高内存拜访本钱和能耗。为了提高 DenseNet
的功率,作者提出一个新的更高效的网络 VoVet
,由 OSA
(One-Shot Aggregation
,一次聚合)组成。OSA
仅在模块的终究一层聚合前面一切层的特征,这种结构不只承继了 DenseNet
的多感触野表明多种特征的优点,也处理了密布衔接功率低下的问题。根据 VoVNet
的检测器不只速度比 DenseNet
快 2 倍,能耗也降低了 1.5-4.1 倍。别的,VoVNet
网络的速度和功率还优于 ResNet
,而且其关于小方针检测的功能有了显着提高。
1,介绍
跟着 CNN
模型:VGG
、ResNet
和 DensNet
的巨大进步,它们开端被广泛用作方针检测器的 backbone
,用来提取图画特征。
ResNet
和 DenseNet
主要的区别在于它们聚合特征的办法,ResNet
是经过逐元素相加(element-wise add
)和前面特征聚合,DenseNet
则是经过拼接(concatenation
)的办法。Zhu
等人在论文32 中以为前面的特征图带着的信息将在与其他特征图相加时被清除。换句话说,经过 concatenation
的办法,早期的特征才能传递下去,由于它保存了特征的原始方式(没有改变特征本身)。
最近的一些作业 [25, 17, 13] 表明具有多个感触野的笼统特征能够捕捉各种标准的视觉信息。由于检测使命比分类愈加需求多样化标准去识别方针,因而保存来自各个层的信息关于检测尤为重要,由于网络每一层都有不同的感触野。因而,在方针检测使命上,DenseNet
比 ResNet
有更好更多样化的特征表明。
这是不是阐明关于,多标签分类问题,用 VoVNet 作为 backbone,效果要比 ResNet 要好。由于前者能够完结多感触野表明特征。
尽管运用 DenseNet 的检测器的参数量和 FLOPs 都比 ResNet 小,可是前者的能耗能耗和速度却更慢。这是由于,还有其他要素 FLOPs 和模型标准(参数量)影响能耗。
首先,内存拜访价值 MAC
是影响能耗的关键要素。如图 1(a) 所示,由于 DenseNet 中的一切特征图都被密布衔接用作后续层的输入,因而内存拜访本钱与网络深度成二次方添加,然后导致核算开支和更多的能耗。
从图 (a) 中能够看出,DenseBlock 中的每一层的输入都是前面一切层 feature map 的叠加。而图 (b)只要终究一层的输入是前面一切层 feature map 的叠加。
其次,关于 GPU 的并行核算,DenseNet 有核算瓶颈的限制。一般来说,当操作的张量更大时,GPU 的并行利用率会更高[19,29,13]。
然而,由于为了线性添加输入通道,需求 DenseNet 选用 11 卷积 bottleneck
架构来削减输入维度和 FLOPs
,这导致运用较小的操作数张量添加层数。作为成果便是 GPU 核算变得低效。总结便是,bottleneck
结构中的 111\times 1 卷积会导致 GPU
并行利用率。
本文的意图在于将 DenseNet 改善的更高效,一起,还保存对方针检测有利的衔接聚合(concatenative aggregation
)操作。
作者以为 DenseNet 网络 DenseBlock 中心层的密布衔接(
dense connections
)会导致网络功率低下,并假定相应的密布衔接是剩余的。
作者运用 OSA
模块构建了 VoVNet
网络,为了验证有效性,将其作为 DSOD、RefineDet 和 Mask R-CNN 的 backbone 来做比照试验。试验成果表明,根据 VoVNet 的检测器优于 DenseNet 和 ResNet,速度和能耗都更优。
2,高效网络规划的影响要素
作者以为,MobileNet v1 [8], MobileNet v2 [21], ShuffleNet v1 [31], ShuffleNet v2 [18], and Pelee 模型主要是经过运用 DW
卷积和 带 111\times 1 卷积的 bottleneck
结构来削减 FLOPs
和模型标准(参数量)。
这儿我觉得作者表达不严谨,由于 shufflenetv2 在论文中现已声明过,FLOPs 和模型参数量不是模型运转速度的唯一决定要素。
实践上,削减 FLOPs
和模型巨细并不总能保证削减 GPU 推理时刻和实践能耗,典型的比如便是 DenseNet
和 ResNet
的比照,还有便是在 GPU 平台上, Shufflenetv2 在同等参数条件下,运转速度比 MobileNetv2 更快。这些现象告知咱们,FLOPs
和 模型标准(参数)是衡量模型实用性(practicality
)的直接方针。为了规划更高效的网络,咱们需求运用直接方针 FPS
,除了上面说的 FLOPs
和模型参数量会影响模型的运转速度(FPS
),还有以下几个要素。
2.1,内存拜访价值
这个 Shufflenetv2 作者现已解说得很清楚了,本文的作者的描述根本和 Shufflenetv2 共同。我这儿直接给定论:
-
MAC
对能耗的影响超过了核算量FLOPs
[28]。 - 卷积层输入输出通道数持平时,
MAC
取得最小值。 - 即便模型参数量共同,只需
MAC
不同,那么模型的运转时刻也是不共同的(ShuffleNetv2 有试验证明)。
论文 [28] Designing energy-efficient convolutional neural networks using energyaware pruning.
2.2,GPU核算功率
其实这个内容和 shufflenetv2 论文中的 G3 原则(网络碎片化会降低 GPU 并行度)根本共同。
为提高速度而降低 FLOPs
的网络架构根据这样一种理念,即设备中的每个浮点运算都以相同的速度进行处理。可是,当模型部署在 GPU
上时,不是这样的,由于 GPU 是并行处理机制能一起处理多个浮点运算进程。咱们用 GPU 核算功率来表明 GPU 的运算才能。
- 经过削减
FLOPs
是来加速的条件是,设备中的每个浮点运算都以相同的速度进行处理; -
GPU 特性:
- 拿手
parallel computation
,tensor
越大,GPU
运用功率越高。 - 把大的卷积操作拆分红碎片的小操作将不利于
GPU
核算。
- 拿手
- 因而,规划
layer
数量少的网络是更好的选择。MobileNet
运用额外的 1×1 卷积来削减核算量,不过这不利于 GPU 核算。 - 为了衡量 GPU 利用率,引进有一个新方针:FLOP/s=FLOPsGPUinferencetimeFLOP/s = \frac{FLOPs}{GPU\ inference\ time}(每秒完结的核算量
FLOPs per Second
),FLOP/s 高,则GPU
利用率率也高。
3,主张的办法
3.1,从头考虑密布衔接
1,DenseNet 的优点:
在核算第 ll 层的输出时,要用到之前一切层的输出的 concat 的成果。这种密布的衔接使得各个层的各个标准的特征都能被提取,供后边的网络运用。这也是它能得到比较高的精度的原因,而且密布的衔接更有利于梯度的回传(ResNet shorcut 操作的加强版)。
2,DenseNet 缺点(导致了能耗和推理功率低的):
- 密布衔接会添加输入通道巨细,但输出通道巨细坚持不变,导致的输入和输出通道数都不持平。因而,DenseNet 具有具有较高的 MAC。
- DenseNet 选用了
bottleneck
结构,这种结构将一个 333\times 3 卷积分红了两个核算(1×1+3×3 卷积),这带来了更多的序列核算(sequential computations),导致会降低推理速度。
密布衔接会导致核算量添加,所以不得不选用 111\times 1 卷积的
bottleneck
结构。
图 7 的第 1 行是 DenseNet 各个卷积层之间的相互联系的巨细。第 (s,l)(s,l) 块代表第 ss 层和第 ll 层之间这个卷积权值的均匀 L1L_1 范数(按特征图数量归一化后的 L1 范数)的巨细,也就相当于是表征 XsX_s 和 XlX_l 之间的联系。
图 2. 训练后的 DenseNet(顶部) 和 VoVNet(中心和底部) 中卷积层的滤波器权重的绝对值的均匀值。像素块的色彩表明的是相互衔接的网络层(i, j)的权重的均匀 L1L_1 范数(按特征图数量归一化后的 L1 范数)的值。OSA Module (x/y) 指的是 OSA 模块由 xx 层和 yy 个通道组成。
如图 2 顶部图所示, Hu
等人[9]经过评估每层输入权重归一化后的 L1 范数来阐明密布衔接的连通性(connectivity
),这些值显示了前面一切层对相应层的归一化影响,1 表明影响最大,0 表明没有影响(两个层之间的权重没有联系)。
这儿要点解说下连通性的了解。两层之间的输入权重的绝对值相差越大,即 L1 越大,那么阐明卷积核的权重越不一样,前面层对后边层影响越大(
connectivity
),即连通性越好(大)。从实用性视点讲,咱们肯定期望相互衔接的网络层的连通性越大越好(归一化后是 0~1 范围),这样我的密布衔接才起效果了嘛。不然,耗费了核算量、献身了功率,可是连通性成果又差,那我还有必要规划成密布衔接(dense connection
)。作者经过图 2 后边的两张图也证明了DenseBlock 模块中各个层之间的联系大部分都是没用,只要少部分是有用的,即密布衔接中大部分网络层的衔接是无效的。
在 Dense Block3 中,对角线附近的红色框表明中心层(intermediate layers
)上的聚合处于活动状态,可是分类层(classification layer
)只运用了一小部分中心特征。 比较之下,在 Dense Block1 中,过渡层(transition layer
)很好地聚合了其大部分输入特征,而中心层则没有。
Dense Block3 的分类层和 Dense Block1 的过渡层都是模块的终究一层。
经过前面的观察,咱们先假定中心层的集合强度和终究一层的集合强度之间存在负相关(中心层特征层的聚合才能越好,那么终究层的聚合才能就越弱)。假如中心层之间的密布衔接导致了每一层的特征之间存在相关性,则密布衔接会使后边的中心层发生更好的特征的一起与前一层的特征相似,则假定成立。在这种情况下,由于这两种特征代表冗余信息,所以终究一层不需求学习聚合它们,然后前中心层对终究层的影响变小。
由于终究一层的特征都是经过集合(aggregated
)一切中心层的特征而发生的,所以,咱们当然期望中心层的这些特征能够互补或者相关性越低越好。因而,进一步提出假定,比较于形成的损耗,中心特征层的 dense connection 发生的效果有限。为了验证假定,咱们从头规划了一个新的模块 OSA
,该模块仅在终究一层聚合块中其他层的特征(intermediate features
),把中心的密布衔接都去掉。
3.2,One-Shot Aggregation
为了验证咱们的假定,中心层的聚合强度和终究一层的聚合强度之间存在负相关,而且密布衔接是剩余的,咱们与 Hu 等人进行了相同的试验,试验成果是图 2 中心和底部方位的两张图。
从图 2(中心)能够观察到,跟着中心层上的密布衔接被剪掉,终究层中的聚合变得愈加激烈。一起,蓝色的部分 (联系大部分不严密的部分) 显着削减了很多,也便是说 OSA 模块的每个衔接都是相对有用的。
从图 2(底部)的能够观察到,OSA 模块的过渡层的权重显示出与 DenseNet 不同的模式:来自浅层的特征更多地集合在过渡层上。由于来自深层的特征对过渡层的影响不大,咱们能够在没有显着影响的情况下削减 OSA 模块的层数,得到。令人惊奇的是,运用此模块(5 层网络),咱们完结了 5.44% 的错误率,与 DenseNet-40 (模块里有 12 层网络)的错误率(5.24%)相似。这意味着经过密布衔接构建深度中心特征的效果不如预期(This implies that building deep intermediate feature via dense connection is less effective than expected
)。
One-Shot Aggregation(只集合一次)是指 OSA 模块的 concat 操作只进行一次,即只要终究一层的输入是前面一切层 feature map 的 concat(叠加)。OSA
模块的结构图如图 1(b) 所示。
在 OSA 模块中,每一层发生两种衔接,一种是经过 conv 和下一层衔接,发生 receptive field
更大的 feature map
,另一种是和终究的输出层相连,以聚合足够好的特征。
为了验证 OSA 模块的有效性,作者运用 dense block 和 OSA 模块构成 DenseNet-40网络,使两种模型参数量共同,做比照试验。OSA 模板版别在 CIFAR-10 数据集上的精度达到了 93.6
,和 dense block 版别比较,只下降了 1.2%
。再根据 MAC 的公式,可知 MAC 从 3.7M 削减为 2.5M。MAC
的降低是由于 OSA 中的中心层具有相同巨细的输入输出通道数,然后使得 MAC 能够取最小值(lower boundary
)。
由于 OSA 模块中心层的输入输出通道数共同,所以没必要运用 bottleneck
结构,这又进一步提高了 GPU 利用率。
3.3,构建 VoVNet 网络
由于 OSA
模块的多样化特征表明和功率,所以能够经过仅堆叠几个模块来构建精度高、速度快的 VoVNet
网络。根据图 2 中浅层深度更简单聚合的认识,作者以为能够配置比 DenseNet
具有更大通道数的但更少卷积层的 OSA
模块。
如下图所示,分别构建了 VoVNet-27-slim,VoVNet-39, VoVNet-57。留意,其间downsampling 层是经过 3×3 stride=2 的 max pooling 完结的,conv 表明的是 Conv-BN-ReLU 的次序衔接。
VOVNet 由 5 个阶段组成,各个阶段的输出特征巨细顺次降为本来的一半。VOVNet-27 前 2 个 stage 的衔接图如下所示。
4,试验
GPU 的能耗核算公式如下:
试验1: VoVNet vs. DenseNet. 比照不同 backbone 下的方针检测模型功能(PASCALVOC)
比照方针:
- Flops:模型需求的核算量
- FPS:模型推断速度img/s
- Params:参数数量
- Memory footprint:内存占用
- Enegry Efficiency:能耗
- Computation Efficiency:GPU 核算功率(GFlops/s)
- mAP(方针检测功能点评方针)
现象与总结:
- 现象 1:比较于 DenseNet-67,PeleeNet 削减了 Flops,可是推断速度没有提高,与之相反,VoVNet-27-slim 稍微添加了Flops,而推断速度提高了一倍。一起,VoVNet-27-sli m的精度比其他模型都高。
- 现象 2:VoVNet-27-slim 的内存占用、能耗、GPU 利用率都是最高的。
- 定论 1:比较其他模型,VoVNet做到了准确率和功率的均衡,提高了方针检测的全体功能。
试验2:Ablation study on 11 conv bottleneck.
定论 2:能够看出,1×1 bottleneck 添加了 GPU Inference 时刻,降低了 mAP,尽管它削减了参数数量和核算量。
由于 1×1 bottleneck 添加了网路的总层数,需求更多的激活层,然后添加了内存占用。
试验3: GPU-Computation Efficiency.
- 图3(a) VoVNet 统筹准确率和 Inference 速度
- 图3(b) VoVNet 统筹准确率和 GPU 运用率
- 图3(c) VoVNet 统筹准确率和能耗
- 图3(d) VoVNet 统筹能耗和 GPU 运用率
试验室4:根据RefineDet架构比较VoVNet、ResNet和DenseNet。
定论 4:从 COCO 数据集测试成果看,比较于 ResNet,VoVnet在 Inference 速度,内存占用,能耗,GPU 运用率和准确率上都占据优势。尽管很多时分,VoVNet 需求更多的核算量以及参数量。
- 比照 DenseNet161(k=48) 和 DenseNet201(k=32)能够发现,深且”瘦“的网络,GPU 运用率更低。
- 别的,作者发现比较于 ResNet,VoVNet 在小方针上的体现更好。
试验 5:Mask R-CNN from scratch.
经过替换 Mask R-CNN 的 backbone,也发现 VoVNet 在Inference 速度和准确率上优于 ResNet。
5,代码解读
尽管 VoVNet 在 CenterMask 论文 中衍生出了升级版别 VoVNetv2,可是本文的代码解读仍是针对原本的 VoVNet,代码来历这儿。
1,界说不同类型的卷积函数
def conv3x3(in_channels, out_channels, module_name, postfix,
stride=1, groups=1, kernel_size=3, padding=1):
"""3x3 convolution with padding. conv3x3, bn, relu的次序组合
"""
return [
('{}_{}/conv'.format(module_name, postfix),
nn.Conv2d(in_channels, out_channels,
kernel_size=kernel_size,
stride=stride,
padding=padding,
groups=groups,
bias=False)),
('{}_{}/norm'.format(module_name, postfix),
nn.BatchNorm2d(out_channels)),
('{}_{}/relu'.format(module_name, postfix),
nn.ReLU(inplace=True)),
]
def conv1x1(in_channels, out_channels, module_name, postfix,
stride=1, groups=1, kernel_size=1, padding=0):
"""1x1 convolution"""
return [
('{}_{}/conv'.format(module_name, postfix),
nn.Conv2d(in_channels, out_channels,
kernel_size=kernel_size,
stride=stride,
padding=padding,
groups=groups,
bias=False)),
('{}_{}/norm'.format(module_name, postfix),
nn.BatchNorm2d(out_channels)),
('{}_{}/relu'.format(module_name, postfix),
nn.ReLU(inplace=True)),
]
2,其间 OSA
模块结构的代码如下。
class _OSA_module(nn.Module):
def __init__(self,
in_ch,
stage_ch,
concat_ch,
layer_per_block,
module_name,
identity=False):
super(_OSA_module, self).__init__()
self.identity = identity # 默许不运用恒等映射
self.layers = nn.ModuleList()
in_channel = in_ch
# stage_ch: 每个 stage 内部的 channel 数
for i in range(layer_per_block):
self.layers.append(nn.Sequential(
OrderedDict(conv3x3(in_channel, stage_ch, module_name, i))))
in_channel = stage_ch
# feature aggregation
in_channel = in_ch + layer_per_block * stage_ch
# concat_ch: 11 卷积输出的 channel 数
# 也从 stage2 开端,每个 stage 最开端的输入 channnel 数
self.concat = nn.Sequential(
OrderedDict(conv1x1(in_channel, concat_ch, module_name, 'concat')))
def forward(self, x):
identity_feat = x
output = []
output.append(x)
for layer in self.layers: # 中心一切层的次序衔接
x = layer(x)
output.append(x)
# 终究一层的输出要和前面一切层的 feature map 做 concat
x = torch.cat(output, dim=1)
xt = self.concat(x)
if self.identity:
xt = xt + identity_feat
return xt
3,界说 _OSA_stage
,每个 stage
有多少个 OSA
模块,由 _vovnet
函数的 block_per_stage
参数指定。
class _OSA_stage(nn.Sequential):
"""
in_ch: 每个 stage 阶段最开端的输入通道数(feature map 数量)
"""
def __init__(self,
in_ch,
stage_ch,
concat_ch,
block_per_stage,
layer_per_block,
stage_num):
super(_OSA_stage, self).__init__()
if not stage_num == 2:
self.add_module('Pooling',
nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True))
module_name = f'OSA{stage_num}_1'
self.add_module(module_name,
_OSA_module(in_ch,
stage_ch,
concat_ch,
layer_per_block,
module_name))
for i in range(block_per_stage-1):
module_name = f'OSA{stage_num}_{i+2}'
self.add_module(module_name,
_OSA_module(concat_ch,
stage_ch,
concat_ch,
layer_per_block,
module_name,
identity=True))
4,界说 VOVNet
,
class VoVNet(nn.Module):
def __init__(self,
config_stage_ch,
config_concat_ch,
block_per_stage,
layer_per_block,
num_classes=1000):
super(VoVNet, self).__init__()
# Stem module --> stage1
stem = conv3x3(3, 64, 'stem', '1', 2)
stem += conv3x3(64, 64, 'stem', '2', 1)
stem += conv3x3(64, 128, 'stem', '3', 2)
self.add_module('stem', nn.Sequential(OrderedDict(stem)))
stem_out_ch = [128]
# vovnet-57,in_ch_list 成果是 [128, 256, 512, 768]
in_ch_list = stem_out_ch + config_concat_ch[:-1]
self.stage_names = []
for i in range(4): #num_stages
name = 'stage%d' % (i+2)
self.stage_names.append(name)
self.add_module(name,
_OSA_stage(in_ch_list[i],
config_stage_ch[i],
config_concat_ch[i],
block_per_stage[i],
layer_per_block,
i+2))
self.classifier = nn.Linear(config_concat_ch[-1], num_classes)
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight)
elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
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):
x = self.stem(x)
for name in self.stage_names:
x = getattr(self, name)(x)
x = F.adaptive_avg_pool2d(x, (1, 1)).view(x.size(0), -1)
x = self.classifier(x)
return x
5,VoVNet 各个版别的完结。vovnet57 中有 4
个 stage
,每个 stage 的 OSP 模块数目顺次是 [1,1,4,3],每个 个 stage
内部对应的通道数都是一样的,分别是 [128, 160, 192, 224]。每个 stage
终究的输出通道数分别是 [256, 512, 768, 1024],由 concat_ch
参数指定。
一切版别的 vovnet 的 OSA 模块中的卷积层数都是 5
。
def _vovnet(arch,
config_stage_ch,
config_concat_ch,
block_per_stage,
layer_per_block,
pretrained,
progress,
**kwargs):
model = VoVNet(config_stage_ch, config_concat_ch,
block_per_stage, layer_per_block,
**kwargs)
if pretrained:
state_dict = load_state_dict_from_url(model_urls[arch],
progress=progress)
model.load_state_dict(state_dict)
return model
def vovnet57(pretrained=False, progress=True, **kwargs):
r"""Constructs a VoVNet-57 model as described in
`"An Energy and GPU-Computation Efficient Backbone Networks"
<https://arxiv.org/abs/1904.09730>`_.
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
return _vovnet('vovnet57', [128, 160, 192, 224], [256, 512, 768, 1024],
[1,1,4,3], 5, pretrained, progress, **kwargs)
def vovnet39(pretrained=False, progress=True, **kwargs):
r"""Constructs a VoVNet-39 model as described in
`"An Energy and GPU-Computation Efficient Backbone Networks"
<https://arxiv.org/abs/1904.09730>`_.
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
return _vovnet('vovnet39', [128, 160, 192, 224], [256, 512, 768, 1024],
[1,1,2,2], 5, pretrained, progress, **kwargs)
def vovnet27_slim(pretrained=False, progress=True, **kwargs):
r"""Constructs a VoVNet-39 model as described in
`"An Energy and GPU-Computation Efficient Backbone Networks"
<https://arxiv.org/abs/1904.09730>`_.
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
return _vovnet('vovnet27_slim', [64, 80, 96, 112], [128, 256, 384, 512],
[1,1,1,1], 5, pretrained, progress, **kwargs)
参考资料
- 论文笔记VovNet(专心GPU核算、能耗高效的网络结构)
- An Energy and GPU-Computation Efficient Backbone Network for Real-Time Object Detection
- 实时方针检测的新backbone网络:VOVNet