NanoDet代码逐行精读与修改(一)Backbone

更新时间:2023-06-06 04:00:27 阅读: 评论:0

NanoDet代码逐⾏精读与修改(⼀)Backbone
--neozng1@hnu.edu
笔者已经为nanodet增加了⾮常详细的注释,代码请戳此仓库: 。
此仓库会跟着⽂章推送的节奏持续更新!
⽬录
1. Backbone
作为⼀个着眼于边缘平台部署,尤其是针对CPU型设备的⽹络,NanoDet之前⾃然选择的是使⽤深度可分离卷积的轻量⾻⼲⽹络。
这⾥我们主要介绍默认的Backbone:,这是⼀个由华为提出的轻量⾻⼲⽹络,关于GhostNet的详解请戳:。此模块提供了预训练权重下载,并将结构封装成了⼀个类。
ghostnet.py这个⽂件被放在仓库中的nanodet/model/backbone下。
1.0. _make_divisible()
# _make_divisible()是⼀个⽤于取整的函数,确保ghost module的输⼊输出都可以被组卷积数整除
# 这是因为nn.Conv2d中要求groups参数必须能被输⼊输出整除,具体请参考深度可分离卷积相关的资料
def _make_divisible(v, divisor, min_value=None):
"""
This function is taken from the original tf repo.
It ensures that all layers have a channel number that is divisible by 8
It can be en here:
"""
if min_value is None:
大鼠翻译min_value = divisor
new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
# Make sure that round down does not go down by more than 10%.
if new_v < 0.9 * v:
new_v += divisor
return new_v
1.1. SqueezeExcite
lf,
in_chs,
_ratio=0.25,
reduced_ba_chs=None,
activation="ReLU",
gate_fn=hard_sigmoid,
divisor=4,
**_
):
super(SqueezeExcite, lf).__init__()
lf.gate_fn = gate_fn
reduced_chs = _make_divisible((reduced_ba_chs or in_chs) * _ratio, divisor)
# channel-wi的全局平均池化
lf.avg_pool = nn.AdaptiveAvgPool2d(1)
# 1x1卷积,得到⼀个维度更⼩的⼀维向量
# 送⼊激活层
lf.act1 = act_layers(activation)action是什么意思
# 再加上⼀个1x1 conv,使得输出长度还原回通道数
def forward(lf, x):
x_ = lf.avg_pool(x)
x_ = lf.conv_reduce(x_)
x_ = lf.act1(x_)
x_ = lf.conv_expand(x_)
# ⽤刚得到的权重乘以原输⼊
x = x * lf.gate_fn(x_)
return x
这个模块来⾃,介绍请戳笔者之前介绍vision attention的博客:。利⽤额外的全局池化+FC+channel-wi multiply构建SE分⽀,这能够⽤来捕捉通道之间的相关性,给予重要的通道更⼤的权重。
1.2. ConvBnAct
class ConvBnAct(nn.Module):
def __init__(lf, in_chs, out_chs, kernel_size, stride=1, activation="ReLU"):
super(ConvBnAct, lf).__init__()
in_chs, out_chs, kernel_size, stride, kernel_size // 2, bias=Fal
)diff
lf.bn1 = nn.BatchNorm2d(out_chs)
lf.act1 = act_layers(activation)
def forward(lf, x):
x = lf.conv(x)
x = lf.bn1(x)
x = lf.act1(x)
return x
这其实就是卷积、批归⼀化和激活函数的叠加,这三个结构⼏乎是现在深度⽹络的构成单元的标准配置了,写成⼀个模块⽅便后⾯多次调⽤。
1.3.GhostModule
lf, inp, oup, kernel_size=1, ratio=2, dw_size=3, stride=1, activation="ReLU"
):
super(GhostModule, lf).__init__()
lf.oup = oup
# 确定特征层减少的⽐例,init_channels是标准卷积操作得到
init_channels = il(oup / ratio)
# new_channels是利⽤廉价操作得到的
new_channels = init_channels * (ratio - 1)
# 标准的conv BN activation层,注意conv是point-wi conv的1x1卷积
lf.primary_conv = nn.Sequential(
nn.Conv2d(
inp, init_channels, kernel_size, stride, kernel_size // 2, bias=Fal
),
nn.BatchNorm2d(init_channels),
act_layers(activation) if activation el nn.Sequential(),
)
# ghostNet的核⼼,⽤廉价的线性操作来⽣成相似特征图
# 关键在于groups数为init_channels,则说明每个init_channel都对应⼀层conv
家人英文# 输出的通道数是输⼊的ratio-1倍,输⼊的每⼀个channel会有ratio-1组参数
lf.cheap_operation = nn.Sequential(
nn.Conv2d(
init_channels,
new_channels,
dw_size,
1,
dw_size // 2,
groups=init_channels,
bias=Fal,
),
# BN和AC操作
nn.BatchNorm2d(new_channels),
act_layers(activation) if activation el nn.Sequential(),
)
def forward(lf, x):
dhfx1 = lf.primary_conv(x)
x2 = lf.cheap_operation(x1)
# new_channel和init_channel是并列的关系,拼接在⼀起形成新的输出
out = torch.cat([x1, x2], dim=1)
return out
这个模块就是GhostNet的关键了,在了解GhostNet所谓的”廉价操作“即cheap_operation之前,你需要知道组卷积(group conv)和深度可分离卷积(depth-wi parable conv)的概念。⾸先对上⼀个特征层的输⼊进⾏标准卷积,⽣成init_channels的特征;随后将此特征进⾏分组卷积,并将groups数取得和输⼊的channel数相同(每⼀个channel都对应⼀个单独的卷积核),这样就可以尽可能的降低参数量和运算量,开销⾮常⼩.
1.4. GhostBottleneck
GhostBottleneck就是GhostNet的基本架构了,GhostNet就由数个GhostBottleneck堆叠⽽成,对于Stride=2的bottleneck在两个Ghost module之间增加了⼀个深度可分离卷积作为连接。
ghost bottle nect的结构,分为stage内的stride=1和stage间的stride=2
class GhostBottleneck(nn.Module):
"""Ghost bottleneck w/ optional SE"""
def __init__(
movie
lf,
in_chs,
mid_chs,
out_chs,
dw_kernel_size=3,
stride=1,
activation="ReLU",
_ratio=0.0,
):
super(GhostBottleneck, lf).__init__()
# 可以选择是否加⼊SE module
has_ = _ratio is not None and _ratio > 0.0
lf.stride = stride
# Point-wi expansion
# 第⼀个ghost将会有较⼤的mid_chs即输出通道数
lf.ghost1 = GhostModule(in_chs, mid_chs, activation=activation)
# Depth-wi convolution
# 对于stride=2的版本(或者你⾃⼰选择添加更⼤的Stride),两个GhostModule中间增加DW卷积
if lf.stride > 1:
mid_chs,
mid_chs,
dw_kernel_size,
stride=stride,
padding=(dw_kernel_size - 1) // 2,
groups=mid_chs,
bias=Fal,
)
lf.bn_dw = nn.BatchNorm2d(mid_chs)
# Squeeze-and-excitation
if has_:
lf. = SqueezeExcite(mid_chs, _ratio=_ratio)
el:
lf. = None
lf. = None
# Point-wi linear projection
# 最后的输出不添加激活函数层,并且会使⽤⼀个较⼩的out_chs以匹配short cut连接的通道数
lf.ghost2 = GhostModule(mid_chs, out_chs, activation=None)
# shortcut
# 最后的跳连接,如果in_chs等于out_chs则直接执⾏element-wi add
if in_chs == out_chs and lf.stride == 1:
lf.shortcut = nn.Sequential()
# 如果不相等,则使⽤深度可分离卷积使得feature map的⼤⼩对齐
el:
祖国在我心中的演讲稿
lf.shortcut = nn.Sequential(
nn.Conv2d(
in_chs,
in_chs,
dw_kernel_size,
stride=stride,
padding=(dw_kernel_size - 1) // 2,
groups=in_chs,
bias=Fal,
),
nn.BatchNorm2d(in_chs),
nn.Conv2d(in_chs, out_chs, 1, stride=1, padding=0, bias=Fal),
nn.BatchNorm2d(out_chs),
)
def forward(lf, x):
# 保留identity feature,稍后进⾏连接
residual = x
# 1st ghost bottleneck
x = lf.ghost1(x)
# 如果stride>1则加⼊Depth-wi convolution
if lf.stride > 1:
x = lf.conv_dw(x)
x = lf.bn_dw(x)
# Squeeze-and-excitation
if lf. is not None:
jenna jameson>grenoble
x = lf.(x)
# 2nd ghost bottleneck
x = lf.ghost2(x)
x += lf.shortcut(residual)
return x
Ghost module中⽤于⽣成复杂特征的卷积是1x1的point-wi conv,对于Stride=2的bottleneck来说⼜有⼀个stride=2的DW,那么就可以将前者就和后者看作是构成了⼀组深度可分离卷积,只不过Ghost module⽣成ghost feature的操作⼤⼤降低了参数量和运算量。若启⽤了has_的选项,则会在两个ghost module之间加⼊⼀个SE分⽀。
1.5. GhostNetrua是什么意思
讲解完了基本的模块之后,我们就可以利⽤上述的GhostBottleneck来构建GhostNet了:

本文发布于:2023-06-06 04:00:27,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/78/879920.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:卷积   深度   分离   通道   输出
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图