RTMDet原理与代码解析

paper:RTMDet: An Empirical Study of Designing Real-Time Object Detectors

official implementation:https://github.com/open-mmlab/mmdetection/tree/main/configs/rtmdet

本文的创新点

Backbone and Neck

  1. 在backbone的basic building block中采用large-kernel depth-wise convolution,提高了模型捕获全局上下文的能力。
  2. 直接添加depth-wise卷积会增加模型深度,减慢推理速度。因此通过减小building block的个数来减小模型深度,同时通过增加模型宽度来补偿模型容量。
  3. 作者还观察到,在neck部分设置更多参数,使其容量与backbone兼容,可以实现更好的速度-精度的平衡。

论文中提到的增加模型宽度,以及通过增加neck部分的expand ratio来增加neck部分的参数在代码中都没有看到。

block的结构设计中采用大核深度可分离卷积,而没有采用重参数化方法的原因:其它YOLO系列中结构重参数化技术被广泛使用,但有一些副作用,比如训练速度变慢,增加训练占用显存,以及在量化到较低的比特后增加了误差间隙,这需要通过重参数化优化器或量化感知训练来补偿。

Head

  1. 不同尺度的head之间共享卷积参数,但BN层独立。

Label Assignment and Loss

提出在计算matching cost时使用soft label来扩大高质量匹配和低质量配置之间的差异,从而稳定训练加速收敛。基于SimOTA进行的改动。

  1. 分类损失引入soft label,就是GFL
  2. 回归损失添加log,增大了低质量匹配的cost,增大了高质量匹配和低质量匹配之间的差异。
  3. center损失采用soft center region cost。

Data Augmentation

cross-sample数据增强比如MixUp和CutMix有两个缺点:(1)每个iteration需要load多张图片,增加了data loading cost减慢了训练速度。(2)生成的样本带有噪声有可能不属于真实数据的实际分布,影响模型的训练。

  1. 引入caching mechanism改进MixUp和CutMix。
  2. 对于第二点,YOLOX通过使用两阶段的训练策略,第一阶段使用强数据增强,第二阶段使用弱数据增强,由于第一阶段的强数据增强包括随机旋转和剪切,导致输入和转换后的box之间有错位,YOLOX在第二阶段增加L1损失来微调回归分支
    为了解耦数据增强和损失函数的使用,本文去除了这些数据增强,在280个epoch的第一个阶段将混合图片的数量增加到8个来补偿数据增强的强度。在最后20个epoch中,切换到Large Scale Jittering,从而在一个与真实数据分布更更一致的doman中对模型进行微调。

Training Strategy

  1. 为了稳定训练优化器采用AdamW,这个在卷积目标检测模型中很少使用,但在vision transformer中是default。

方法介绍 & 代码解析

骨干网络部分用depth-wise卷积增加了网络深度,因此作者减少了第2个和第3个stage中block的数量,如下表所示,block数量由9减到6延迟降低了20%,但AP也降低了0.5,为了弥补精度的所示,作者在每个stage的最后添加了一个channel attention,实现了更好的精度-速度的权衡。

以RTMDet-s为例,deepen_factor=0.33, widen_factor=0.5,因此每个stage的block数量变成了1-2-2-1。stage2的结构如下

Head部分共享卷积参数,但BN独立。官方实现中在定义head时是分开的,最后将每个head的卷积就赋值为相同。 

if self.share_conv:
    for n in range(len(self.prior_generator.strides)):
        for i in range(self.stacked_convs):
            self.cls_convs[n][i].conv = self.cls_convs[0][i].conv
            self.reg_convs[n][i].conv = self.reg_convs[0][i].conv

# print(self.cls_convs[n][0])  # 共享conv,但BN独立
ConvModule(
  (conv): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
  (bn): SyncBatchNorm(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (activate): SiLU(inplace=True)
)

标签分配的实现在dynamic_soft_label_assigner.py中,基于SimOTA,只不过对cost的计算进行了改进,具体如下,其值为分类cost、回归cost、区域cost的加权和

cost_matrix = soft_cls_cost + iou_cost + soft_center_prior

 其中分类cost采用了Generalized focal loss中的quality focal loss

pairwise_ious = self.iou_calculator(valid_decoded_bbox, gt_bboxes)
soft_label = gt_onehot_label * pairwise_ious[..., None]
scale_factor = soft_label - valid_pred_scores.sigmoid()
soft_cls_cost = F.binary_cross_entropy_with_logits(
    valid_pred_scores, soft_label,
    reduction='none') * scale_factor.abs().pow(2.0)
soft_cls_cost = soft_cls_cost.sum(dim=-1)

其中 \(Y_{soft}\) 是预测框与gt框之间的IoU,作为soft label取来原始的标签1。

当用IoU以及相关变体作为回归损失时,最佳匹配和最差匹配之间的差值小于1,这使得区分高质量匹配和低质量匹配变得困难,因此作者使用IoU的对数作为回归的代价,这增大了低质量匹配即IoU较小匹配的cost

iou_cost = -torch.log(pairwise_ious + EPS) * self.iou_weight

至于区域代价 \(C_{region}\),和FCOS、YOLOX等采用的fixed center prior方法不同,本文采用了一种soft center region cost来稳定dynamic cost的匹配

distance = (valid_prior[:, None, :2] - gt_center[None, :, :]
            ).pow(2).sum(-1).sqrt() / strides[:, None]
soft_center_prior = torch.pow(10, distance - self.soft_center_radius)

数据增强部分,Mosaic和MixUp中引入缓存机制。这一部分实现在mmdet/datasets/transforms/transforms.py中。

在mmdet中要使用Mosaic,需要同时使用MultiImageMixDataset。原本results字典中保存的是一张图的相关信息包括img、gt_bboxes、gt_labels等,在MultiImageMixDataset类中调用Mosaic类中的get_indexes方法,随机再挑出其它三张图的索引。然后将这3张图的信息放到列表中作为key 'mix_results'的value加到原始的results中,这样results就包含了4张图的信息。

而在CachedMosaic中,是维护了一个缓存列表self.results_cache,max_cached_images指定最大缓存数量,默认为40,作者指出10张缓存就可以满足随机的要求了。

def __init__(self,
             *args,
             max_cached_images: int = 40,
             random_pop: bool = True,
             **kwargs) -> None:
    super().__init__(*args, **kwargs)
    self.results_cache = []
    self.random_pop = random_pop
    assert max_cached_images >= 4, 'The length of cache must >= 4, ' \
                                   f'but got {max_cached_images}.'
    self.max_cached_images = max_cached_images

在原始mosaic中,每个iteration需要从整个训练集中随机挑选3张与当前张进行combine,而在cachedmosaic中是从cache中挑选3张,因此挑选索引的原始大小不同,如下

# mosaic
def get_indexes(self, dataset: BaseDataset) -> int:
    """Call function to collect indexes.

    Args:
        dataset (:obj:`MultiImageMixDataset`): The dataset.

    Returns:
        list: indexes.
    """

    indexes = [random.randint(0, len(dataset)) for _ in range(3)]
    return indexes

# cachedmosaic
def get_indexes(self, cache: list) -> list:
    """Call function to collect indexes.

    Args:
        cache (list): The results cache.

    Returns:
        list: indexes.
    """

    indexes = [random.randint(0, len(cache) - 1) for _ in range(3)]
    return indexes

首先进行append和pop更新缓存列表

# cache and pop images
self.results_cache.append(copy.deepcopy(results))
if len(self.results_cache) > self.max_cached_images:
    if self.random_pop:
        index = random.randint(0, len(self.results_cache) - 1)
    else:
        index = 0
    self.results_cache.pop(index)

然后根据get_indexes方法得到的索引从缓存列表中得到mix_results,其中包含3张图片的信息用于与当前图片进行组合,当前图片保存在results中。

indices = self.get_indexes(self.results_cache)
mix_results = [copy.deepcopy(self.results_cache[i]) for i in indices]

而在原始的mosaic中,results中除了当前图片还包含从整个训练集中挑选的3张图片,即mix_results包含在results中传进函数的。

assert 'mix_results' in results
results_patch = copy.deepcopy(results['mix_results'][i - 1])

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

00000cj

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值