一、前言
由于之前工作中,训练数据集普遍较小 以及 开发板对模型的限制,所以对
SE
模块的使用较少,对它的插入位置不是很清楚,这样不利于日后对它的使用。故最近查了下使用案例,记录总结如下。
二、正文
(一)
plain
模型
SE
作者对
SE
模块在
plain
模型插入位置的建议是:在每个卷积的激活函数后面插入。这样一看会误以为在每个卷积层后面加个
SE
模块,一般是在**每个
block
后面插入**,下面结合实际的案例来做说明。
1. SE-Inception 模型
2. PP-LCNet 模型
由上面两张图可见,
SE
模块在
plain
模型的插入位置,一般在上个block的结尾 下一个block之前的位置插入。
(二)
skip connection
模型
skip connection
模型指
ResNet
、
MobileNet v2/v3
这种具有
shortcut
操作的模型。现在的模型基本是这个结构,它与
plain
模型
block
最大的不同就是多了个恒等映射的分支(一些变种可能不是恒等映射分支,意思明白就好)。
1.
(类)residual unit
外部,
SE
的插入位置
SE
作者做了个实验,验证
SE
模块在
residual unit
外部时,放在哪个位置效果最好。这个实验虽然是用残差网络来做的,但是其他模型如
MobileNet
也可以借鉴,毕竟二者的思路是一致的。
验证结果如下,下图中的
SE
就是上图的
Standard SE block
,其他名词含义与上图一致。
由上图可见,
SE-POST block
误差相对最大,所以作者建议:**
SE
模块要加在两个分支汇合之前**。
至于
SE-PRE block
、
SE-Identity block
的
top1
误差比
Standard SE block
还小,但
SE
作者最后并没有采用这种形式,而是用了
Standard SE block
(也即上图的
SE
)形式。我猜想可能是
plain
模型的思维惯性,即放在卷积后面。
在此还要说明一点,
SE
作者自己说过,这些插入位置什么的,不是
SE
论文的核心,所以他没做很多实验。他建议针对特定网络结构,针对性地插入
SE
模块,可能会得到更好的结果。所以
SE-PRE block
、
SE-Identity block
甚至
SE-POST block
都可以尝试一下。(反正深度学习是拿实验数据说话)
2.
(类)residual unit
内部,
SE
的插入位置
SE
作者还实验了下,把插入位置由下图的 “
SE
模块” 换到 "
SE_3X3
"处(
3x3
指的是
block
中间那个
3x3
卷积)。另外说下,下图就是
SE-ResNet50
的模型图,也就是作者最终选定的结构样式。
上面实验结果如下,可以发现二者的性能没什么差别,但因为
ResNet
的
3x3
卷积比下面
1x1
卷积的通道数更低,所以
SE_3X3
的参数量、计算量也更低。
MobileNet v3
也借鉴了
SE_3X3
的添加位置,由下图可见,也是放在
3x3
卷积与
1x1
卷积之间。
此外,看
git
上MobileNet v3代码,会发现
SE
的插入位置还有个版本,该版本插入位置与
SE-ResNet50
一致,在
unit
最后一个卷积后面。这里估计是想减少点参数量及计算复杂度,毕竟
MobileNet v3
的
3x3
卷积比下面
1x1
卷积的通道数更高。实际使用时,两个位置都可以试试。
classBlock(nn.Module):'''expand + depthwise + pointwise'''def__init__(self, kernel_size, in_size, expand_size, out_size, nolinear, semodule, stride):"""
这块代码不重要,就不贴出来了,免得不好看博客
"""defforward(self, x):
out = self.nolinear1(self.bn1(self.conv1(x)))
out = self.nolinear2(self.bn2(self.conv2(out)))
out = self.bn3(self.conv3(out))if self.se !=None:# 在 1x1卷积后面插入SE模块
out = self.se(out)
out = out + self.shortcut(x)if self.stride==1else out
return out
自
YOLO V5
面世以来,针对其的改进也有添加
SE
模块的方式。纵观网上的博客,发现
V5
添加
SE
模块一般是在两个位置:
① 在
C3-bottleneck
中添加
SE
模块的,这样添加主要为了更好的做实验,参考博客;
另外,目前一般是加在
bottleneck
中第一个卷积
block
后面,参考上面的博客内容,也可以试试放在第二个卷积
block
后面。最后我们可以看到,无论是
YOLO V5
、
MobileNet v3
还是
SE-ResNet50
,**添加
SE
模块都是以block为单位目标来添加的**,这点与我们在博文开头处的观点倒是不谋而合。
② 在
V5-backbone
结尾处添加
SE
模块。
这个添加位置比较少见,我也是看这个参考博客才知道,博主表示 backbone结尾添加一个注意力机制 会好点。
# YOLOv5 backbone
backbone:# [from, number, module, args][[-1,1, Focus,[64,3]],# 0-P1/2 #1[-1,1, Conv,[128,3,2]],# 1-P2/4 #2[-1,3, C3,[128]],#3[-1,1, Conv,[256,3,2]],# 3-P3/8 #4[-1,9, C3,[256]],#5[-1,1, Conv,[512,3,2]],# 5-P4/16 #6[-1,9, C3,[512]],#7[-1,1, Conv,[1024,3,2]],# 7-P5/32 #8[-1,1, SPP,[1024,[5,9,13]]],#9[-1,3, C3,[1024,False]],# 9 #10[-1,1, SELayer,[1024,4]],# SE模块加在block的外面,即 SE-POST block方式]
想了一下,这点在
SE
论文里的
SE-Inception
也有体现。如下图:
3. 在模型哪几层插入
SE
模块
这对我们使用
SE
模块,确实是个问题,每层都用不合适,选一层的话又不知道应该选哪层。
SE
作者为此做了个实验。
先看看作者做实验的模型,绿色的字体是我打的,让各位更明白模型每个
stage
在模型中的位置,其中每个
feature map
尺寸就是一个
stage
。中间与右边两列中括号里的
fc,[xx, xx]
就是
SE
模块。
作者做了组对比实验,分别只在
SE_stage_2
,
SE_stage_3
,
SE_stage_4
插入
SE
模块,最后又给出所有
stage
(
SE_ALL
)插入
SE
模块的实验结果。可以看见,每层都加的效果是最好的(其参数量与计算量也最高),所以作者最后也是每个
block
都添加了
SE
模块。
但要注意的是,在模型的最后三个
block
(
SE_stage_4
)添加
SE
模块,会发现 性能-计算复杂度取到一个比较好的平衡,这点在
PP-LCNet
里也得到了呼应。如下图所示,
PP-LCNet
在模型最后的两个
block
添加
SE
模块(也是最后一个
stage
),也是取得了性能-计算复杂度平衡,故以后部署平台算力紧张时,可以考虑这种策略。
查看
MobileNet v3
论文,发现其对
SE
的用法也有点类似于前两者。据下图可知,
MobileNet v3
在其
large
与
small
版本的最后两个
stage
中都插入了
SE
模块。以后使用时,**在模型最后两个
stage
添加
SE
模块性价比更高点,如果算力限制大,也可以试着只在最后一个
stage
添加
SE
模块**。
三、后语
抛砖引玉之作,如有遗漏、补充,还请各位看官不吝指出,谢谢。
版权归原作者 tang-shopping 所有, 如有侵权,请联系我们删除。