文章链接
Paper: https://arxiv.org/abs/2301.06719
Code: https://github.com/yh-pengtu/FemtoDet
主要贡献
这篇论文主要提出了三个新东西,
一个是被称为instance boundary enhancement (IBE)的卷积模块。
二是检测网络新轻量级neck结构SharedNeck。
三是提出了一个随着epoch逐渐减少数据增强操作的训练方法,称为re-cursive warm-restart (RecWR)。
下图所示模型与YOLOX在TJU-DHD数据集上的效果对比,所提出模型效果是要好很多的。
在PASCALVOC数据集上性能对比
其他还比较了不同激活函数对于Mobilev2的训练精度与能耗的影响,其中mEPT是作者整的一个新评价指标,不太重要,知道越大越好就行。
在(ResNet和MobileNetV2)上设置了不同卷积(vanCon和DSC)和多尺度卷积核大小(3×3和5×5)对于性能和能耗的影响。可以看到深度经济的mEPT明显高出很多。
贡献一:instance boundary enhancement (IBE)
中文翻译为实例边界增强模块,作者描述该模块可以有效的提升轻量级神经网络对于目标检测学习特征模糊问题。
上述图像是训练特征的可视化,其中(a) 输入:PASCAL VOC的RGB图像;(b) FemtoDet*:由纯深度可分离卷积组成的经过训练的FemtoDet检测器;(c) FemtoDet:经过训练的FemtoDet探测器,使用IBE模块进行训练。可以看到c图明显特征更加具体了。
文中给出了IBE的结构,我个人认为像是Mobilenetv2瓶颈结构以及Repconv结构的一种结合,在加上了一个共享的Batch normalization模块
论文的主要代码是利用mmdet进行编写的,这个库的集成性比较高,会将模型分为backbone、neck、head三个部分构建。我们先从源码中找到
FemtoDet-main\mmdet\models\backbones\femtonet.py
这里可以找到作者定义的IBE模块在FemtoDet-main\mmdet\core\utils\misc.py文件下,可以看到merge_bn定义后却未被使用,核心代码在于forward部分,按照下面的代码(不包括注释部分),实际上与上图并不是很吻合,缺少了最后的Pointwise Conv,并且注意X22与X21的融合操作不是加而是减,即self.bn2(out_normal - theta * out_diff)
。这里的theta * out_diff
是11卷积加注意力机制。
具体的,out_normal对应X23和X21(在代码表示里这俩是一个东西),kernel_diff是由33卷积生成的1*1卷积核,out_diff对应X22,theta为注意力机制。
self.bn(out_normal) + self.bn2(out_normal - theta * out_diff)
就是上图的凝练,新的代码不区分训练和测试阶段了,都是一样。
class IBEConvModule(BaseModule):
def __init__(self, in_channels, out_channels, kernel_size=3, stride=1,
padding=1, dilation=1, groups=1, bias=False, act_cfg=None, **kwargs):
super(IBEConvModule, self).__init__()
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=kernel_size, stride=stride, padding=padding, dilation=dilation, groups=groups, bias=bias)
# self.att = DifferenceAttention(out_channels, out_channels, out_channels)
self.bn = nn.BatchNorm2d(out_channels)
self.bn2 = nn.BatchNorm2d(out_channels)
self.act = build_activation_layer(act_cfg) #nn.ReLU()
self.theta = Parameter(torch.zeros([1]))
def merge_bn(self, conv, bn):
conv_w = conv
conv_b = torch.zeros_like(bn.running_mean)
factor = bn.weight / torch.sqrt(bn.running_var + bn.eps)
weight = nn.Parameter(conv_w *
factor.reshape([conv_w.shape[0], 1, 1, 1]))
bias = nn.Parameter((conv_b - bn.running_mean) * factor + bn.bias)
return weight, bias
def forward(self, x):
# if self.training:
out_normal = self.conv(x)
[C_out, C_in, kernel_size, kernel_size] = self.conv.weight.shape
kernel_diff = self.conv.weight.sum(2).sum(2)
kernel_diff = kernel_diff[:, :, None, None]
out_diff = F.conv2d(input=x, weight=kernel_diff, stride=self.conv.stride, padding=0, groups=self.conv.groups)
theta = F.sigmoid(self.theta)#[None, :, None, None]
# outs = self.att(out_normal, out_normal - theta * out_diff)
outs = self.bn(out_normal) + self.bn2(out_normal - theta * out_diff)
# outs = self.bn(out_normal) - theta * self.bn2(out_diff)
# else:
# weight_conv = self.conv.weight
# theta = F.sigmoid(self.theta)#[:, None, None, None]
# kernel_diff = theta * self.conv.weight.sum(2).sum(2)[:, :, None, None]
# weight_diff = self.conv.weight - nn.ZeroPad2d(1)(kernel_diff)
# weight_conv, bias_conv = self.merge_bn(weight_conv, self.bn)
# weight_diff, bias_diff = self.merge_bn(weight_diff, self.bn2)
# weight_final = weight_conv + weight_diff
# bias_final = bias_conv + bias_diff
# outs = F.conv2d(input=x, weight=weight_final, bias=bias_final, stride=self.conv.stride, padding=self.conv.padding, groups=self.conv.groups)
# # outs = self.act(outs)
return self.act(outs)
贡献二:特征共享颈部模块SharedNeck
我们评估了FemtoDet中不同颈部(包括FPN、PAN和SharedNeck)的效果。实验结果如表3所示,一些观察结果总结如下:
1)虽然FPN使FemtoDet能够获得更好的结果,但它不仅消耗更多的能量,而且参数开销也很大;
2)由于轻量级模型的表示有限,PAN的自上而下和自下而上的特征融合较差;
3)SharedNeck通过执行自适应特征融合,在参数、能量成本、对象检测性能以及平均能量与性能权衡因素等指标方面取得了最佳结果。
在PASCALVOC数据集上不同neck结构的参数量以及准确率如下图所示
主要代码文件在FemtoDet-main\mmdet\models\necks\yolox_pafpn.py文件下,同时我们还需要知道cfg文件中对于Neck的参数指定信息。
#这部分是截取源码与Neck定义有关的参数情况,并非源代码格式与顺序
default_channels=[32, 96, 320]
widen_factor=0.25
neck_in_chanels = [int(ch*widen_factor) for ch in default_channels]
headfeat_channel = 64
neck=dict(
type='SharedNeck',
in_channels=neck_in_chanels,
out_channels=headfeat_channel,
fixed_size_idx=1,
add=True,
norm_cfg=dict(type='BN'),
act_cfg=dict(type='ReLU'),
num_outs=1)
需要解释的是num_outs=1,代表Neck输出的输出只有一个,也就是说后续的Head只会接收到一个输入,不同于yolo系列的多个Neck输入
class SharedNeck(BaseModule):
"""Path Aggregation Network for Instance Segmentation.
This is an implementation of the `PAN in Path Aggregation Network
<https://arxiv.org/abs/1803.01534>`_.
Args:
in_channels (List[int]): Number of input channels per scale.
out_channels (int): Number of output channels (used at each scale)
num_outs (int): Number of output scales.
start_level (int): Index of the start input backbone level used to
build the feature pyramid. Default: 0.
end_level (int): Index of the end input backbone level (exclusive) to
build the feature pyramid. Default: -1, which means the last level.
conv_cfg (dict): Config dict for convolution layer. Default: None.
norm_cfg (dict): Config dict for normalization layer. Default: None.
activation (str): Config dict for activation layer in ConvModule.
Default: None.
"""
def __init__(
self,
in_channels,
out_channels,
num_outs,
add=False,
fixed_size_idx=0,
start_level=0,
end_level=-1,
conv_bias='auto',
conv_cfg=None,
norm_cfg=None,
act_cfg=None,
init_cfg=dict(
type='Xavier', layer='Conv2d', distribution='uniform')
):
super(SharedNeck, self).__init__(init_cfg)
self.num_ins = len(in_channels)
self.num_outs = num_outs
self.fp16_enabled = False
self.in_channels = in_channels
self.fixed_size_idx = fixed_size_idx
self.add = add
self.start_level = start_level
self.end_level = end_level
self.lateral_convs = nn.ModuleList()
#定义深度可分离卷积conv
conv = DepthwiseSeparableConvModule
#构建输入通道数列表长度个conv列表,这里的输入来自于配置文件
#default_channels=[32, 96, 320]
#widen_factor=0.25
#neck_in_chanels = [int(ch*widen_factor) for ch in default_channels]
for i in range(len(in_channels)):
l_conv = ConvModule(
in_channels[i],
out_channels,
1,
conv_cfg=conv_cfg,
norm_cfg=norm_cfg,
act_cfg=act_cfg,
inplace=False,
)
self.lateral_convs.append(l_conv)
# if self.ca:
# self.ca = CoordAttention(out_channels, out_channels, norm_cfg=norm_cfg, act_cfg=dict(type='HSwish'),)
self.stacked_convs = nn.ModuleList()
if num_outs == 1 and add:
#如果输出为1且add为True
mid_channels = out_channels
#前面定义了conv为深度可分离卷积,所以这里也是DWconv
self.stacked_convs = conv(
mid_channels,
out_channels,
3,
stride=1,
padding=1,
conv_cfg=conv_cfg,
norm_cfg=norm_cfg,
act_cfg=act_cfg,
bias=conv_bias)
elif num_outs == 1 and not add:
#如果add为False
mid_channels = int(len(in_channels) * out_channels)
# for i in range(self.start_level, self.backbone_end_level):
self.stacked_convs = conv(
mid_channels,
out_channels,
3,
stride=1,
padding=1,
conv_cfg=conv_cfg,
norm_cfg=norm_cfg,
act_cfg=act_cfg,
bias=conv_bias)
else:
self.stacked_convs = conv(
mid_channels,
out_channels,
3,
stride=1,
padding=1,
conv_cfg=conv_cfg,
norm_cfg=norm_cfg,
act_cfg=act_cfg,
bias=conv_bias)
self.init_weights()
def forward(self, inputs):
"""Forward function."""
assert len(inputs) == len(self.in_channels)
# build laterals
# 统一维度后直接输出
#对于输入的每一个进行上述设定的卷积[32, 96, 320]
outs0 = [
lateral_conv(inputs[i + self.start_level])
for i, lateral_conv in enumerate(self.lateral_convs)
]
#根据指定的输出层特征图大小调整其他输出特征图的大小,使其能够直接stack或concat
fixed_size = outs0[self.fixed_size_idx].shape[-2:]
outs0 = [F.interpolate(x, fixed_size) for x in outs0]
if self.num_outs == 1 and not self.add:
outs0 = torch.cat(outs0, 1)
outs = [self.stacked_convs(outs0)]
elif self.num_outs == 1 and self.add:
outs0 = torch.stack(outs0, 1)
outs0 = torch.sum(outs0, 1)
# if self.ca:
# outs0 = self.ca(outs0)
outs = [self.stacked_convs(outs0)]
else:
outs = [
self.stacked_convs(x)
for x in outs0
]
return tuple(outs)
贡献三:re-cursive warm-restart (RecWR)
中文译为递归热重启,这是一种训练策略,因为作者发现在轻量级网络中,数据增强可能阻碍了模型在有限的参数量下学习到更加广泛的特征,因为模型去尽力拟合不同的数据增强了。可以看下面原文描述,该模型训练策略采用的epoch还是很大的。
原文:我们认为,有限容量检测器在训练过程中尽可能地适应SA生成的各种数据,使得模块没有多余的能力来调整学习的特征,以提高模块对真实验证数据的泛化能力。换句话说,SA产生了与数据偏移相等的各种表示,从而破坏了模块的泛化能力。
整个培训过程可分为四个阶段。从第1阶段训练到第4阶段训练,图像增强的强度逐渐降低。具体来说,在第一个训练阶段,一些SA类型将被组合,如MixUp、Mosaic和RandomAffine。从第2个训练阶段开始,上述SA类型在每个训练阶段逐渐卸载,直到第4个训练阶段。对于最后一个训练阶段,仅对训练数据执行随机翻转和随机量表。此外,在开始每个训练阶段之前,等待的训练检测器加载前一训练阶段的训练权重作为初始化。
我们可以在附录D中看到,在用RecWR训练FemtoDet之后,MixUp也帮助这种极小的检测器获得更好的性能。换句话说,RecWR利用从SA学习的多样性特征,使FemtoDet摆脱子优化。
消融实验
表明我们的IBE模块和RecWR训练策略的有效性。其中FemtoDet*表示FemtoDet由没有IBE增强的纯DSC组成,300e和1200e表示使用300或1200个时期的相同数据增强的检测器训练。