网易首页 > 网易号 > 正文 申请入驻

YOLOF:速度和效果均超过YOLOv4的检测模型

0
分享至

文末粉丝福利:价值千元《图像分类与图像搜索》特训,限时0.01元拼团,仅限60人!

YOLOF 全称是 You Only Look One-level Feature, 其通过详细的实验指出特征金字塔 FPN 模块的成功在于其对目标优化问题的分治解决方案,而不是我们常说的多尺度特征融合。针对该结论,设计了一个简洁优雅的无需复杂 FPN 的网络结构,仅仅依靠单尺度特征即可取得相匹配的效果,并且具备极快的推理速度,是一个不错的算法。

YOLOF 论文核心可以总结如下:

1、设计了多组实验,深入探讨了 FPN 模块成功的主要因素

2、基于实验结论,设计了无需 FPN 模块,单尺度简单高效的 Neck 模块 Dilated Encoder

3、基于 FPN 分治处理多尺度问题,配合 Neck 模块提出 Uniform Matching 正负样本匹配策略

4、由于不存在复杂且耗内存极多的 FPN 模块,YOLOF 可以在保存高精度的前提下,推理速度快,消耗内存也相对更小

项目地址:github.com/open-mmlab/mmdetection,欢迎 star~

1 FPN 模块分析


首先目标检测算法可以简单按照上述结构进行划分,网络部分主要分为 Backbone、Encoder 和 Decoder,或者按照我们前系列解读文章划分方法分为 Backbone、Neck 和 Head。对于单阶段算法来说,常见的 Backbone 是 ResNet,Encoder 或者 Neck 是 FPN,而 Head 就是对应的输出层结构。


一般我们都认为 FPN 层作用非常大,不可或缺,其通过特征多尺度融合,可以有效解决尺度变换预测问题。而本文认为 FPN 至少有两个主要作用:

  • 多尺度特征融合

  • 分治策略,可以将不同大小的物体分配到不同大小的的输出层上,克服尺度预测问题

作者试图分析上述两个作用中,最核心的部分,故选择最常用的 RetinaNet 进行 FPN 模块深入分析

对 FPN 模块进一步抽象,如上图所示,可以分成 4 种结构 MiMo、SiMo、MiSo 和 SiSo,其中 MiMo 即为标准的 FPN结构,输入和输出都包括多尺度特征图。

将 FPN 替换为上述 4 个模块,然后基于 RetinaNet 重新训练,计算 mAP 、 GFLOPs 和 FPS 指标

  • 从 mAP 角度分析,SiMo 结果和 MiMo 差距不大,说明 C5 (Backbone 输出)包含了足够的检测不同尺度目标的上下文信息;而 MiSo 和 SiSo 则和 MiMo 差距较大,说明 FPN 分治优化作用远远大于 多尺度特征融合

  • 从下表 GFLOPs 和 FPS 可以看出,MiMo 结构由于存在高分辨率特征图 C3 会带来较大的计算量,并且拖慢速度

综上所示,可以得到一些结论:

  • FPN 模块的主要增益来自于其分治优化手段,而不是多尺度特征融合

  • FPN 模块中存在高分辨率特征融合过程,导致消耗内存比较多,训练和推理速度也比较慢,对部署不太优化

  • 如果想在抛弃 FPN 模块的前提下精度不丢失,那么主要问题是提供分治优化替代手段

2 YOLOF 原理简析

作者为了克服 FPN 存在的内存占用多,速度慢问题,采用了 SiSo 结构,但是精度下降比较严重,从 35.9 变成了 24.6,故后续有针对性的改进,主要包括两个部分:Dilated Encoder 和 Uniform Matching。

2.1 Dilated Encoder

虽然 FPN 的主要作用是分治优化思想,但是多尺度融合也有一定作用,从上述实验也可以看出。并且虽然 C5 提供了足够的上下文,但是其感受野所对应的目标尺寸范围是有限的,无法应对目标检测场景中变化剧烈的目标尺寸。简要理解如上图所示,绿色点表示数据集中的多种目标尺寸,粉红色区域代表特征图能够有效表达的目标尺寸范围

  • 如果仅仅使用 C5 特征,会出现图(a)所示的情况

  • 若使用空洞卷积操作来增大 C5 特征图的感受野,则会出现图(b)所示的情况,感受野变大,能够有效地表达尺寸较大的目标,但是对小目标表达能力会变差

  • 如果采用不同空洞率的叠加,则可以有效避免上述问题

为此,作者设计了 Dilated Encoder 结构,串联多个不同空洞率的模块以覆盖不同大小物体,改善感受野单一问题,如下所示:

对 C5 特征先进行压缩通道,然后串联 4 个不同空洞率的残差模块,从而得到不同感受野的特征图。其实这种做法非常场景,在语义分割算法 ASPP 中和目标检测算法 RFBNet 都采用了类似思想,只不过这两个都是并联结构,而本文是串联, RFBNet 结构如下所示:

2.2 Uniform Matching


前面说过 FPN 的核心功能是分治手段,但是我们知道虽然其输出多个尺度特征图,但是要想发挥分治功能则主要依靠 bbox 正负样本分配策略,也就是说 FPN 和优异的 bbox 正负样本分配策略结合才能最大程度发挥功效,大部分最新的单阶段目标检测算法都在 bbox 分配策略上面做文章,可以借用 AutoAssign 论文中的图说明:

为了充分发挥 FPN 功效,一般会从 scale 和 spatial 两个方面着手进行设计,scale 用于处理不同尺度大小的 gt bbox 应该属于哪些输出层负责,而 spatial 用于处理在某个输出特征图上哪些位置才是最合适的正样本点。不同的 bbox 正负样本分配策略对最终性能影响极大。


一般来说,由于自然场景中,大小物体分布本身就不均匀,并且大物体在图片中所占区域较大,如果不设计好,会导致大物体的正样本数远远多于小物体,最终性能就会偏向大物体,导致整体性能较差。YOLOF 算法采用单尺度特征图输出,锚点的数量会大量的减少(比如从 100K 减少到 5K),导致了稀疏锚点,如果不进行重新设计,会加剧上述现象。为此作者提出了新的均匀匹配策略,核心思想就是不同大小物体都尽量有相同数目的正样本。

所提两个模块的作用如下所示:

  • Uniform Matching 作用非常大,说明该模块其实发挥了 FPN 的分治作用

  • Dilated Encoder 配合 Uniform Matching 可以提供额外的变感受野功能,有助于多尺度物体预测

需要特别注意:论文中所描述的 Uniform Matching 和代码中实现的 Uniform Matching 有一定差距,在下一节源码解读时会详细说明。

3 YOLOF 源码解析

和前系列解读一样,依然按照 Backbone、Neck、Head、Bbox Coder、Bbox Assigner 和 Loss 顺序解读。

3.1 BackboneBackbone

采用了 ResNet50,caffe 模式,和 FCOS 算法配置相同,只不过这里只需要输出 C5 特征图即可,不需要多尺度。

pretrained='open-mmlab://detectron/resnet50_caffe',
backbone=dict(
type='ResNet',
depth=50,
num_stages=4,
out_indices=(3, ),
frozen_stages=1,
norm_cfg=dict(type='BN', requires_grad=False),
norm_eval=True,
style='caffe'),

3.2 NeckNeck

模块是本文新提出的 Dilated Encoder 模块,包括一个通道压缩模块,然后串联 4 个不同空洞率的残差模块,提供灵活变尺度的感受野。

neck=dict(
type='DilatedEncoder',
in_channels=2048,
out_channels=512,
block_mid_channels=128,
num_residual_blocks=4),

由于比较简单,就不展开分析了。


3.3 Head

Head 模块即上图的 Decoder 结构,包括分类和回归分支,借鉴 AutoAssign 算法,在回归分支上并行引入一个 Objectness 分支,用于抑制背景区域的高响应,然后将其和分类分支相乘。故对外实际上两个分支,Objectness是没有监督 label 的。其 Head forward 如下所示

def forward_single(self, feature):
# 分类分支
cls_score = self.cls_score(self.cls_subnet(feature))
N, _, H, W = cls_score.shape
cls_score = cls_score.view(N, -1, self.num_classes, H, W)

# 回归分支
reg_feat = self.bbox_subnet(feature)
bbox_reg = self.bbox_pred(reg_feat)
objectness = self.object_pred(reg_feat)
# implicit objectness
objectness = objectness.view(N, -1, 1, H, W)

normalized_cls_score = cls_score + objectness - torch.log(
1. + torch.clamp(cls_score.exp(), max=INF) +
torch.clamp(objectness.exp(), max=INF))
normalized_cls_score = normalized_cls_score.view(N, -1, H, W)
return normalized_cls_score, bbox_reg

上述得到 normalized_cls_score 的计算过程看起来非常复杂,但是其实是为了能够对融合后的 normalized_cls_score 采用 sigmoid 函数而已,其对应的公式是:

也就是说 normalized_cls_score 是不含 sigmoid 的包括 cls_score 和 objectness 融合的值。可以通过如下简单代码验证:

import torch

if __name__ == '__main__':
INF = 1e8
N = 1
num_classes = 2
H = W = 3
cls_score = torch.rand((N, 1, num_classes, H, W))
objectness = torch.rand(N, 1, 1, H, W)

normalized_cls_score = cls_score + objectness - torch.log(
1. + torch.clamp(cls_score.exp(), max=INF) +
torch.clamp(objectness.exp(), max=INF))

cls_score_s = torch.sigmoid(cls_score) * torch.sigmoid(objectness)

assert torch.allclose(cls_score_s, torch.sigmoid(normalized_cls_score))


3.4 Bbox

CoderYOLOF 输出格式采用 RetinaNet 算法中定义的 deltaXYWH ,即回归分支输出的 4 个值表示相对于 anchor 的偏移

anchor_generator=dict(
type='AnchorGenerator',
ratios=[1.0],
scales=[1, 2, 4, 8, 16],
strides=[32]),
bbox_coder=dict(
type='DeltaXYWHBBoxCoder',
target_means=[.0, .0, .0, .0],
target_stds=[1., 1., 1., 1.],
add_ctr_clamp=True,
ctr_clamp=32),

只有一个输出特征图,每个位置铺设了 5 个 anchor,宽高比是 1,设置了 5 种 scale。为了稳定训练过程,作者在 DeltaXYWHBBoxCoder 中引入了 add_ctr_clamp 参数即当中心坐标预测相比 anchor 偏离大于 32 个像素,则强制裁剪为 32,防止产生较大的梯度。


3.5 Bbox Assigner

这个部分是 YOLOF 的核心,需要重点分析。首先分析论文中描述,然后再基于代码说明代码和论文的差异。论文中描述的非常简单,核心目的是保证不同尺度物体都尽可能有相同数目的正样本

  • 遍历每个 gt bbox,然后选择 topk 个距离最近的 anchor 作为其匹配的正样本

  • 由于存在极端比例物体和小物体,上述强制 topk 操作可能出现 anchor 和 gt bbox 的不匹配现象,为了防止噪声样本影响,在所有正样本点中,将 anchor 和 gt bbox 的 iou 低于 0.15 的正样本(因为不管匹配情况,topk 都会选择出指定数目的正样本)强制认为是忽略样本,在所有负样本点中,将 anchor 和 gt bbox 的 iou 高于 0.75 的负样本(可能该物体比较大,导致很多 anchor 都能够和该 gt bbox 很好的匹配,这些样本就不适合作为负样本了)强制认为是忽略样本

实际上作者代码的写法如下所示

  • 遍历每个 gt bbox,然后选择 topk 个距离最近的 anchor 作为其匹配的正样本

  • 遍历每个 gt bbox,然后选择 topk 个距离最近的预测框作为补充的匹配正样本

  • 计算 gt bbox 和预测框的 iou,在所有负样本点中,将 iou 高于 0.75 的负样本强制认为是忽略样本

  • 计算 gt bbox 和 anchor 的 iou,在所有正样本点中,将 iou 低于 0.15 的正样本强制认为是忽略样本

可以发现相比于论文描述,实际上代码额外动态补充了一定量的正样本,同时也额外考虑了一些忽略样本。相比于纯粹采用 anchor 和 gt bbox 进行匹配,额外引入预测框,可以动态调整正负样本,理论上会更好。

# 全部任务是负样本
assigned_gt_inds = bbox_pred.new_full((num_bboxes, ),
0,
dtype=torch.long)

# 计算两两直接的距离,包括 预测框和 gt bbox,以及 anchor 和 gt bbox
cost_bbox = torch.cdist(
bbox_xyxy_to_cxcywh(bbox_pred),
bbox_xyxy_to_cxcywh(gt_bboxes),
p=1)
cost_bbox_anchors = torch.cdist(
bbox_xyxy_to_cxcywh(anchor), bbox_xyxy_to_cxcywh(gt_bboxes), p=1)

# 分别提取 topk 个样本点作为正样本,此时正样本数会加倍
index = torch.topk(
C,
k=self.match_times,
dim=0,
largest=False)[1]
# self.match_times x n
index1 = torch.topk(C1, k=self.match_times, dim=0, largest=False)[1]
# (self.match_times*2) x n
indexes = torch.cat((index, index1),
dim=1).reshape(-1).to(bbox_pred.device)

# 计算 iou 矩阵
pred_overlaps = self.iou_calculator(bbox_pred, gt_bboxes)
anchor_overlaps = self.iou_calculator(anchor, gt_bboxes)
pred_max_overlaps, _ = pred_overlaps.max(dim=1)
anchor_max_overlaps, _ = anchor_overlaps.max(dim=0)

# 计算 gt bbox 和预测框的 iou,在所有负样本点中,将 iou 高于 0.75 的负样本强制认为是忽略样本
ignore_idx = pred_max_overlaps > self.neg_ignore_thr
assigned_gt_inds[ignore_idx] = -1

# 计算 gt bbox 和 anchor 的 iou,在所有正样本点中,将 iou 低于 0.15 的正样本强制认为是忽略样本
pos_gt_index = torch.arange(
0, C1.size(1),
device=bbox_pred.device).repeat(self.match_times * 2)
pos_ious = anchor_overlaps[indexes, pos_gt_index]
pos_ignore_idx = pos_ious < self.pos_ignore_thr
pos_gt_index_with_ignore = pos_gt_index + 1
pos_gt_index_with_ignore[pos_ignore_idx] = -1
assigned_gt_inds[indexes] = pos_gt_index_with_ignore


3.6 Loss

在确定了每个特征点位置哪些是正样本和负样本后,就可以计算 loss 了,分类采用 focal loss,回归采用 giou loss,都是常规操作。

loss_cls=dict(
type='FocalLoss',
use_sigmoid=True,
gamma=2.0,
alpha=0.25,
loss_weight=1.0),
loss_bbox=dict(type='GIoULoss', loss_weight=1.0))

上述就是整个 YOLOF 核心实现过程。至于推理过程和 RetinaNet 算法完全相同。

4 YOLOF 复现心得和体会

如果不仔细思考,可能看不出上述代码有啥问题,实际上在 Bbox Assigner 环节会存在重复索引分配问题,这个问题会带来几个影响。具体代码是:

# 对应 3.5 小节的源码分析第 44 行
assigned_gt_inds[indexes] = pos_gt_index_with_ignore

前面说过,YOLOF 会引入额外的预测框点作为补充正样本,当 2 次 topk 选择的位置相同时候就会出现意想不到问题。


举个简单例子,当前图片中仅仅有一个 gt bbox,且预测输出特征图大小是 10x10,设置 anchor 个数是 1,那么说明输出特征图上只有 10x10 个anchor,并且对应了 10x10 个预测框,topk 设置为 4

  • 计算该 gt bbox 和 100 个 anchor 的距离,然后选择最近的前 4 个位置作为正样本

  • 计算该 gt bbox 和 100 个预测框的距离,然后选择最近的前 4 个位置作为正样本,注意这里选择的 4个位置很可能和前面选择的 4 个位置有重复

  • 计算该 gt bbox 和预测框的 iou,在所有负样本点中,将 iou 高于 0.75 的负样本强制认为是忽略样本

  • 计算该 gt bbox 和 anchor 的 iou,在所有正样本点中,将 iou 低于 0.15 的正样本强制认为是忽略样本,注意和上一步的区别,由于 iou 计算的输入是不一样的,可能导致某个被重复计算的正样本位置出现 2 种情况:1. 两个步骤都认为是忽略样本;2. 一个认为是忽略样本,一个认为是正样本,而一旦出现第二种情况则在 CUDA 并行计算中出现不确定输出

简单来说:indexes 中可能存在重复值,并且重复值位置对应的 pos_gt_index_with_ignore 可能相同也可能不同,注意重复现象可能出现在两个不同类别物体有重叠的情况下,那么上述赋值操作在 CUDA 中是不可预知的,可能会出现同一份数据跑两次输出结果不一样。

这个操作会给后面的回归分支带来歧义,因为回归分支仅仅处理正样本,那么会出现以下几个情况:

  • 如果两个重复索引处对应的 gt bbox 是同一个,那么相当于该 gt bbox 对应的正样本 loss 权重加倍

  • 如果两个重复索引处对应的 gt bbox 不是同一个,那么就会出现歧义,因为特征图上同一个预测点,被同时分配给了两个不同的 gt bbox

总的来说,对于上述重复索引分配现象,会带来几个影响:

  • 读者理解代码运行流程会比较困惑

  • 同一个程序跑多次,可能输出结果不一致

  • 训练过程不稳定

  • 当重复索引出现时候,回归分支 loss 计算过程非常奇怪,难以理解

  • 低版本 CUDA 上会出现非法内存越界错误, 实验发现 CUDA9.0 会出现非法内存越界错误,但是 CUDA10.1 则正常,其余版本没有进行测试

关于第5点,原因暂时不清楚,但是现象是 pos_gt_index_with_ignore、indexes 和 assigned_gt_inds 都不存在越界情况,只不过 indexes 如果存在相同值,在赋值后会出现 4294967295(2^32 -1) 和 -4294967295 (-2^32 +1) 异常值,然后后续基于 assigned_gt_inds 取值后就出现出现 RuntimeError: CUDA error: an illegal memory access was encountered. 经过多次实验发现,错误是必现的。

当时也试过其他几个方案,例如 1. 将上述赋值操作放置到 cpu 上进行;2. 将赋值后异常值全部设置为忽略样本,虽然可以避免报错,但是实验结果显示会存在一定程度的掉点,所以最终没有修改。这个问题我们也会持续关注,直到找到一个更加合适的方式以避免上述报错问题。


上述这个写法,给代码复现带来了些问题,并且由于 YOLOF 学习率非常高 lr=0.12,训练过程偶尔会出现 Nan 现象,训练不太稳定,可能对参数设置例如 warmup 比较敏感。

最后还是要感谢作者 Qiang Chen,在复现过程中解答了一些疑问。通过针对训练不稳定的问题,也指明了可能解决的方案。

本文素材来源于网络,如有侵权,联系删除!

(粉丝福利来啦)

七月在线【图像分类与图像搜索】特训0.01元秒杀

仅限60人!

课程详情如下:

本课程是CV高级小班的前期预习课之一,主要内容包含卷积神经网络基础知识、卷积网络结构、反向传播、图像特征提取、三元组损失等理论,以及目标检测和图像搜索实战项目,理论和实战结合,打好计算机视觉基础。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
女同事吃饭没带钱,我垫了1320,她说:送我回家,我用自己抵债

女同事吃饭没带钱,我垫了1320,她说:送我回家,我用自己抵债

农村情感故事
2024-12-21 16:28:41
悲报:你们的老师「桥本有菜」要引退了……

悲报:你们的老师「桥本有菜」要引退了……

日本物语
2024-12-23 20:51:31
四川男子留宿岳母家,半夜意外闯进岳母卧室,竟发现她的另一面

四川男子留宿岳母家,半夜意外闯进岳母卧室,竟发现她的另一面

莉雅细细谈
2024-08-05 22:29:22
《黑神话》轮椅打法开发,零操作过连战Boss!玩家:毫无交互感

《黑神话》轮椅打法开发,零操作过连战Boss!玩家:毫无交互感

叶子猪游戏网
2024-12-23 14:56:58
一夜间马丽被降咖!工作室评论区沦陷,网友:200亿影后沦落了?

一夜间马丽被降咖!工作室评论区沦陷,网友:200亿影后沦落了?

小咪侃娱圈
2024-12-23 08:57:37
父亲和叔叔轮流给奶奶养老,临终前却非来我家,去世后才明白

父亲和叔叔轮流给奶奶养老,临终前却非来我家,去世后才明白

农村情感故事
2024-12-15 08:34:06
博主:原甲府风林日本籍主帅筱田善之即将执教南通支云

博主:原甲府风林日本籍主帅筱田善之即将执教南通支云

懂球帝
2024-12-23 16:56:16
他是浙大副校长,因贪污1.3亿入狱3年,出狱却获得国家支持2758万

他是浙大副校长,因贪污1.3亿入狱3年,出狱却获得国家支持2758万

八斗小先生
2024-12-18 16:37:10
8只股票暴雷!含违规减持、违规保荐、内幕交易,新易盛与中金

8只股票暴雷!含违规减持、违规保荐、内幕交易,新易盛与中金

小佩棋不蹦迪
2024-12-23 17:48:06
全剧终!开盘直接20厘米跌停,140万手巨量封单,股民全被一锅端

全剧终!开盘直接20厘米跌停,140万手巨量封单,股民全被一锅端

芯怡飞
2024-12-24 00:00:32
潘石屹成房地产最后赢家?套现400亿移居美国后,又干起了老本行

潘石屹成房地产最后赢家?套现400亿移居美国后,又干起了老本行

担扑
2024-12-21 11:01:04
ESPN:奥斯卡和家人原本决定不回巴西,但假期回国后情况发生变化

ESPN:奥斯卡和家人原本决定不回巴西,但假期回国后情况发生变化

直播吧
2024-12-23 12:31:08
因需求过高,ASML 取消非员工订购的 High-NA EUV 光刻机乐高模型

因需求过高,ASML 取消非员工订购的 High-NA EUV 光刻机乐高模型

IT之家
2024-12-23 08:24:36
2024年,被两市严重低估的10只潜力股,会不会有妖股?

2024年,被两市严重低估的10只潜力股,会不会有妖股?

季第八月
2024-12-23 14:34:23
中国最大汽车集团,被迫下跪求生

中国最大汽车集团,被迫下跪求生

象视汽车
2024-12-21 07:00:03
上海地铁事故施工单位曝光,集团所属工地停工,11号线已恢复通车

上海地铁事故施工单位曝光,集团所属工地停工,11号线已恢复通车

阿凫爱吐槽
2024-12-23 10:52:33
有“性瘾”的女人一般有什么表现?

有“性瘾”的女人一般有什么表现?

番茄健康
2024-11-14 18:28:06
太恶心了!瑞幸咖啡被曝!恶心得一夜没睡,结果只返5张优惠券?

太恶心了!瑞幸咖啡被曝!恶心得一夜没睡,结果只返5张优惠券?

乌娱子酱
2024-12-23 15:26:44
如何看出男女同事有暧昧?网友:拿过她的杯子喝水还回去就露馅了

如何看出男女同事有暧昧?网友:拿过她的杯子喝水还回去就露馅了

美好客栈大掌柜
2024-12-24 00:10:02
跨省份调任安徽省委常委后,孙红梅已任省委组织部部长

跨省份调任安徽省委常委后,孙红梅已任省委组织部部长

澎湃新闻
2024-12-23 21:54:27
2024-12-24 03:40:49
七月在线
七月在线
AI与智能网联汽车职教平台
813文章数 37关注度
往期回顾 全部

科技要闻

京东公布年终奖:绝大多数员工5-8个月月薪

头条要闻

重庆一女子刚卖车便反悔 吃住在车上长达90个小时

头条要闻

重庆一女子刚卖车便反悔 吃住在车上长达90个小时

体育要闻

年终进球盛宴!这法老冲着金球奖来的?

娱乐要闻

影版《射雕》最新预告!肖战版郭靖大获好评

财经要闻

网传36家公司将被退市 证监会回应

汽车要闻

柴油才对味 大通星际X 2.5T舒适得不像皮卡

态度原创

家居
艺术
旅游
教育
数码

家居要闻

现代简约 注重精神空间

艺术要闻

故宫珍藏的墨迹《十七帖》,比拓本更精良,这才是地道的魏晋写法

旅游要闻

哈尔滨再成顶流:冰雪大世界门票被炒至7000

教育要闻

爆火的殡葬专业真的好吗?

数码要闻

2024年度数码好物大赏【年度评选】

无障碍浏览 进入关怀版