本文提出了一种能够增强多尺度特征表示的实时目标检测器YOLO-MS,核心设计是基于对不同核大小的卷积在不同尺度下如何影响目标检测性能的一系列研究。
从以下两个角度进行改进:
从局部的角度来看,设计了一个具有简单而有效的分层特征融合策略的MS-Block。受Res2Net的启发,在MS-Block中引入了多个分支来执行特征提取,不同点是,使用具有深度卷积的反向瓶颈块来实现大核的有效使用。
从全局的角度来看,随着网络的深入逐渐增加卷积的核大小。在浅层中使用小核卷积来更有效地处理高分辨率特征,在深层中采用大核卷积来捕获大范围的信息。
1 相关工作
1.1 实时目标检测
大多数实时目标检测网络采用一阶段框架,其中YOLO系列是最典型的代表。作为影响模型性能的关键因素,体系结构设计是YOLO开发过程中关注的重点。从YOLOv1开始,网络架构发生了巨大的变化:YOLOLv4对DarkNet进行了跨阶段部分连接(cross-stage partial connections, CSPNet)的改进;YOLOv6和PPYOLOE探索了YOLO中的重新参数化技术,可以在不增加额外推理成本的情况下获得更高的精度。YOLOv7提出了扩展有效层聚合网络(E-ELAN),该网络可以通过控制最短最长梯度路径有效地学习和收敛。RTMDet在网络中引入了大核卷积(5×5)来提高基本块的特征提取能力,更大的接受域允许更全面的上下文建模,并显着提高准确性。
1.2 多尺度特征表示
强大的多尺度特征表示能力可以有效地提高模型的性能,这已经在许多任务中得到了证明,包括实时目标检测。
1.2.1 实时目标检测中的多尺度特征学习
许多实时目标检测器通过整合颈部不同特征层次的特征来提取多尺度特征,例如YOLOv3和后来的YOLO系列分别引入了FPN和PAFPN,以捕获丰富的多尺度语义。SPP模块也被广泛用于扩大感受野。此外,多尺度数据增强也被广泛用作有效的训练技能。然而,主流的基本构建块忽略了多尺度特征表示的重要性,而专注于如何提高检测效率或如何引入新的训练技术,特别是对于CSP块和ELAN块。不同的是,我们的方法专注于学习更丰富的多尺度特征。
1.2.2 大核卷积
最近,大的核卷积以深度的方式被重新激活。大核卷积提供的更宽的接受场可以成为构建强多尺度特征表示的强大技术。在实时目标检测领域,RTMDet首次尝试将大核卷积引入网络。然而,由于速度限制,内核大小只能达到5 × 5。不同阶段的均匀块设计限制了大核卷积的应用。在本文中,提出了一个基于实证研究结果的HKS协议,在不同阶段采用不同核大小的卷积层,在大核卷积的帮助下实现了速度和精度之间的良好权衡。
2 本文提出的Block
2.1 CSP Block及其变体
CSP Block是一种基于阶段的梯度路径网络,它平衡了梯度组合和计算成本,是YOLO系列中广泛使用的基本构建块。已经提出了几种变体,包括YOLOv4和YOLOv5中的原始版本,Scaled-yolov4中的CSPVoVNet, YOLOv7中的ELAN,以及RTMDet中提出的大型内核单元。下图分别是原始CSP块和ELAN的结构:
上述实时检测器忽略的一个关键方面是如何在基本构建块中编码多尺度特征,而Res2Net能够聚合来自不同层次的特征以增强多尺度表示。然而这一原理并没有彻底探索大核卷积的作用,将大型内核卷积合并到Res2Net中的主要障碍是,当构建块采用标准卷积时计算量太大,本文采用MobileNet中的Inverted Residual取代标准的3×3卷积,以保证在控制计算量的前提下享受大核卷积的好处。
2.2 MS-block
本文n=3,除了X1之外,都要经过一个Inverted Residual结构,数学表达式描述如下:
以前的实时目标检测器在不同的编码器阶段采用具有相同核大小的卷积,但这不是提取多尺度语义信息的最佳选择。在金字塔结构中,从探测器的浅层阶段提取的高分辨率特征通常用于捕获细粒度语义,这些语义将用于检测小物体。相反,来自网络深层阶段的低分辨率特征被用于捕获高级语义,这些语义将用于检测大型对象。如果我们在所有阶段都采用统一的小核卷积,那么深层阶段的有效接受场(ERF)就会受到限制,从而影响在大型对象上的性能。在每个阶段结合大核卷积可以帮助解决这一限制。然而,具有大ERF的大核可以编码更宽的区域,这增加了在小物体之外包含污染信息的概率,并降低了实验部分分析的推理速度。
在不同阶段利用异构卷积来帮助捕获更丰富的多尺度特征,随后逐渐增加中间阶段的内核大小,使其与特征分辨率的增量保持一致,该策略可以同时提取细粒度和粗粒度的语义信息,增强了编码器的多尺度特征表示能力。
如下表所示,将大核卷积应用于高分辨率特征大大增加了计算量,然而HKS协议在低分辨率特征上采用了大核卷积,因此与只使用大核卷积相比,大大降低了计算成本。
3 实现部分
MS-block实现如下:
class MSBlock(nn.Module):
def __init__(self,
in_channel: int,
out_channel: int,
kernel_sizes: Sequence[Union[int, Sequence[int]]],
in_expand_ratio: float = 3.,
mid_expand_ratio: float = 2.,
layers_num: int = 3,
in_down_ratio: float = 1.,
attention_cfg: OptConfigType = None,
conv_cfg: OptConfigType = None,
norm_cfg: OptConfigType = dict(type='BN'),
act_cfg: OptConfigType = dict(type='SiLU', inplace=True),
) -> None:
super().__init__()
self.in_channel = int(in_channel*in_expand_ratio)//in_down_ratio
self.mid_channel = self.in_channel//len(kernel_sizes)
self.mid_expand_ratio = mid_expand_ratio
groups = int(self.mid_channel*self.mid_expand_ratio)
self.layers_num = layers_num
self.in_attention = None
self.attention = None
if attention_cfg is not None:
attention_cfg["dim"] = out_channel
self.attention = MODELS.build(attention_cfg)
self.in_conv = ConvModule(in_channel,
self.in_channel,
1,
conv_cfg=conv_cfg,
act_cfg=act_cfg,
norm_cfg=norm_cfg)
self.mid_convs = []
for kernel_size in kernel_sizes:
if kernel_size == 1:
self.mid_convs.append(nn.Identity())
continue
mid_convs = [MSBlockLayer(self.mid_channel,
groups,
kernel_size=kernel_size,
conv_cfg=conv_cfg,
act_cfg=act_cfg,
norm_cfg=norm_cfg) for _ in range(int(self.layers_num))]
self.mid_convs.append(nn.Sequential(*mid_convs))
self.mid_convs = nn.ModuleList(self.mid_convs)
self.out_conv = ConvModule(self.in_channel,
out_channel,
1,
conv_cfg=conv_cfg,
act_cfg=act_cfg,
norm_cfg=norm_cfg)
def forward(self, x: Tensor) -> Tensor:
"""Forward process
Args:
x (Tensor): The input tensor.
"""
out = self.in_conv(x)
channels = []
for i,mid_conv in enumerate(self.mid_convs):
channel = out[:,i*self.mid_channel:(i+1)*self.mid_channel,...]
if i >= 1:
channel = channel + channels[i-1]
channel = mid_conv(channel)
channels.append(channel)
out = torch.cat(channels, dim=1)
out = self.out_conv(out)
if self.attention is not None:
out = self.attention(out)
return out