NanoDet代码逐⾏精读与修改(⼀)Backbone
--neozng1@
笔者已经为nanodet增加了⾮常详细的注释,代码请戳此仓库:。
此仓库会跟着⽂章推送的节奏持续更新!
⽬录
ne
作为⼀个着眼于边缘平台部署,尤其是针对CPU型设备的⽹络,NanoDet之前⾃然选择的是使⽤深度可分离卷积的轻量⾻⼲⽹络。
这⾥我们主要介绍默认的Backbone:,这是⼀个由华为提出的轻量⾻⼲⽹络,关于GhostNet的详解请戳:。此模块提供了预训练权重下载,并
将结构封装成了⼀个类。
这个⽂件被放在仓库中的nanodet/model/backbone下。
1.0._make_divisible()
#_make_divisible()是⼀个⽤于取整的函数,确保ghostmodule的输⼊输出都可以被组卷积数整除
#这是因为2d中要求groups参数必须能被输⼊输出整除,具体请参考深度可分离卷积相关的资料
def_make_divisible(v,divisor,min_value=None):
"""
Thisfunctionistakenfromtheoriginaltfrepo.
Itensuresthatalllayershaveachannelnumberthatisdivisibleby8
Itcanbeenhere:
/tensorflow/models/blob/master/rearch/slim/nets/mobilenet/
"""
ifmin_valueisNone:
min_value=divisor
new_v=max(min_value,int(v+divisor/2)//divisor*divisor)
#Makesurethatrounddowndoesnotgodownbymorethan10%.
ifnew_v<0.9*v:
new_v+=divisor
returnnew_v
eExcite
classSqueezeExcite():
def__init__(
lf,
in_chs,
_ratio=0.25,
reduced_ba_chs=None,
activation="ReLU",
gate_fn=hard_sigmoid,
divisor=4,
**_
):
super(SqueezeExcite,lf).__init__()
_fn=gate_fn
reduced_chs=_make_divisible((reduced_ba_chsorin_chs)*_ratio,divisor)
#channel-wi的全局平均池化
_pool=veAvgPool2d(1)
#1x1卷积,得到⼀个维度更⼩的⼀维向量
_reduce=2d(in_chs,reduced_chs,1,bias=True)
#送⼊激活层
1=act_layers(activation)
#再加上⼀个1x1conv,使得输出长度还原回通道数
_expand=2d(reduced_chs,in_chs,1,bias=True)
defforward(lf,x):
x_=_pool(x)
x_=_reduce(x_)
x_=1(x_)
x_=_expand(x_)
#⽤刚得到的权重乘以原输⼊
x=x*_fn(x_)
returnx
这个模块来⾃,介绍请戳笔者之前介绍visionattention的博客:。利⽤额外的全局池化+FC+channel-wimultiply构建SE分⽀,这能够⽤
来捕捉通道之间的相关性,给予重要的通道更⼤的权重。
Act
classConvBnAct():
def__init__(lf,in_chs,out_chs,kernel_size,stride=1,activation="ReLU"):
super(ConvBnAct,lf).__init__()
=2d(
in_chs,out_chs,kernel_size,stride,kernel_size//2,bias=Fal
)
1=orm2d(out_chs)
1=act_layers(activation)
defforward(lf,x):
x=(x)
x=1(x)
x=1(x)
returnx
这其实就是卷积、批归⼀化和激活函数的叠加,这三个结构⼏乎是现在深度⽹络的构成单元的标准配置了,写成⼀个模块⽅便后⾯多次调
⽤。
odule
classGhostModule():
def__init__(
lf,inp,oup,kernel_size=1,ratio=2,dw_size=3,stride=1,activation="ReLU"
):
super(GhostModule,lf).__init__()
=oup
#确定特征层减少的⽐例,init_channels是标准卷积操作得到
init_channels=(oup/ratio)
#new_channels是利⽤廉价操作得到的
new_channels=init_channels*(ratio-1)
#标准的convBNactivation层,注意conv是point-wiconv的1x1卷积
y_conv=tial(
2d(
inp,init_channels,kernel_size,stride,kernel_size//2,bias=Fal
),
orm2d(init_channels),
act_layers(activation)tial(),
)
#ghostNet的核⼼,⽤廉价的线性操作来⽣成相似特征图
#关键在于groups数为init_channels,则说明每个init_channel都对应⼀层conv
#输出的通道数是输⼊的ratio-1倍,输⼊的每⼀个channel会有ratio-1组参数
_operation=tial(
2d(
init_channels,
new_channels,
dw_size,
1,
dw_size//2,
groups=init_channels,
bias=Fal,
),
#BN和AC操作
orm2d(new_channels),
act_layers(activation)tial(),
)
defforward(lf,x):
x1=y_conv(x)
x2=_operation(x1)
#new_channel和init_channel是并列的关系,拼接在⼀起形成新的输出
out=([x1,x2],dim=1)
returnout
这个模块就是GhostNet的关键了,在了解GhostNet所谓的”廉价操作“即cheap_operation之前,你需要知道组卷积(groupconv)和深
度可分离卷积(depth-wiparableconv)的概念。⾸先对上⼀个特征层的输⼊进⾏标准卷积,⽣成init_channels的特征;随后将此特征进
⾏分组卷积,并将groups数取得和输⼊的channel数相同(每⼀个channel都对应⼀个单独的卷积核),这样就可以尽可能的降低参数量和运
算量,开销⾮常⼩.
ottleneck
GhostBottleneck就是GhostNet的基本架构了,GhostNet就由数个GhostBottleneck堆叠⽽成,对于Stride=2的bottleneck在两个Ghost
module之间增加了⼀个深度可分离卷积作为连接。
ghostbottlenect的结构,分为stage内的stride=1和stage间的stride=2
classGhostBottleneck():
"""Ghostbottleneckw/optionalSE"""
def__init__(
lf,
in_chs,
mid_chs,
out_chs,
dw_kernel_size=3,
stride=1,
activation="ReLU",
_ratio=0.0,
):
super(GhostBottleneck,lf).__init__()
#可以选择是否加⼊SEmodule
has_=_ratioisnotNoneand_ratio>0.0
=stride
#Point-wiexpansion
#第⼀个ghost将会有较⼤的mid_chs即输出通道数
1=GhostModule(in_chs,mid_chs,activation=activation)
#Depth-wiconvolution
#对于stride=2的版本(或者你⾃⼰选择添加更⼤的Stride),两个GhostModule中间增加DW卷积
>1:
_dw=2d(
mid_chs,
mid_chs,
dw_kernel_size,
stride=stride,
padding=(dw_kernel_size-1)//2,
groups=mid_chs,
bias=Fal,
)
_dw=orm2d(mid_chs)
#Squeeze-and-excitation
ifhas_:
=SqueezeExcite(mid_chs,_ratio=_ratio)
el:
=None
=None
#Point-wilinearprojection
#最后的输出不添加激活函数层,并且会使⽤⼀个较⼩的out_chs以匹配shortcut连接的通道数
2=GhostModule(mid_chs,out_chs,activation=None)
#shortcut
#最后的跳连接,如果in_chs等于out_chs则直接执⾏element-wiadd
ifin_chs==out_==1:
ut=tial()
#如果不相等,则使⽤深度可分离卷积使得featuremap的⼤⼩对齐
el:
ut=tial(
2d(
in_chs,
in_chs,
dw_kernel_size,
stride=stride,
padding=(dw_kernel_size-1)//2,
groups=in_chs,
bias=Fal,
),
orm2d(in_chs),
2d(in_chs,out_chs,1,stride=1,padding=0,bias=Fal),
orm2d(out_chs),
)
defforward(lf,x):
#保留identityfeature,稍后进⾏连接
residual=x
#1stghostbottleneck
x=1(x)
#如果stride>1则加⼊Depth-wiconvolution
>1:
x=_dw(x)
x=_dw(x)
#Squeeze-and-excitation
tNone:
x=(x)
#2ndghostbottleneck
x=2(x)
x+=ut(residual)
returnx
Ghostmodule中⽤于⽣成复杂特征的卷积是1x1的point-wiconv,对于Stride=2的bottleneck来说⼜有⼀个stride=2的DW,那么就
可以将前者就和后者看作是构成了⼀组深度可分离卷积,只不过Ghostmodule⽣成ghostfeature的操作⼤⼤降低了参数量和运算量。若
启⽤了has_的选项,则会在两个ghostmodule之间加⼊⼀个SE分⽀。
et
讲解完了基本的模块之后,我们就可以利⽤上述的GhostBottleneck来构建GhostNet了:
GhostNet原⽂中整个backbone的结构,#exp是bottleneck中通道扩展的倍数,#out是当前层的输出通道数
#exp代表了在经过bottleneck中的第⼀个Ghostmodule后通道扩展的倍数,通道数随后会在同⼀个bottleneck中的第⼆个ghost
module被减少到和该bottleneck中最开始的输⼊相同,以便进⾏res连接。#out是输出的通道数。可以发现,Stride=2的bottleneck被
⽤在两个不同的stage之间以改变featuremap的⼤⼩。
为了⽤作检测⽹络,删除最后⽤于分类的FC,并从stage4、6、9分别取出stage的输出作为FPN的输⼊。若需要追求速度,可以考虑进⼀
步减少每个stage的层数或是直接砍掉⼏个stage也⽆妨。
本文发布于:2022-12-28 13:35:02,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/90/46937.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |