本文参考:
https://blog.csdn.net/weixin_46142822/article/details/124074168
该作者写的细节我认为应该是 SimOTA 的细节。
https://zhuanlan.zhihu.com/p/397993315 果然是 SimOTA,其实是抄的这篇的。
OTA 论文回顾: https://www.cnblogs.com/odesey/p/16740854.html
SimOTA 来自于 YOLOX 论文:https://arxiv.org/pdf/2107.08430.pdf ,是 OTA 的简化, OTA 使用 Sinkhorn-Knopp Iteration 来求解 cost 矩阵。 OTA 是直接基于规则,直接用 k 个最小 cost 值的候选框作为正样本。
为了便于理解,我们假设:
3个目标框
,即 3个ground truth
2个检测类别
,比如 cat/dog1000个预测框
,其中只有少部分是正样本,绝大多数是负样本bboxes_preds_per_image
是候选检测框
的信息,维度是[1000,4]
。预测框的四个坐标。
obj_preds
是目标分数(object score),维度是 [1000,1]
。预测框是前景还是背景。
cls_preds
是类别分数,维度是 [1000,2]
。预测框的2个检测类别的one-hot。
训练网络需要知道这 1000个预测框的标签,而如何分配标签呢?
使用OTA方法,分为4步,具体做法如下:
OTA 方法分配标签是基于 cost
的,因为有 3个目标框和1000个预测框
,所以需要生成 3 × 1000 的 cost matrix
(也就是每个真实框都要和1000个预测框计算下 cost
)。
那么 cost 怎么计算?
对于目标检测任务,cost 由定位损失和分类损失组成
,计算方法如下:
计算 3个目标框,和 1000个候选框
,得到每个目标框和 1000 个预测框之间的 iou
(pair_wise_ious)。
再通过 -torch.log
计算得到定位损失,即 pair_wise_iou_loss,向量维度为[3, 1000]
。3 是 3个真实框,每个都计算1000个值。
pair_wise_ious=bboxes_iou(gt_bboxes_per_image,bboxes_perds_per_image,False)
pair_wise_ious_loss=-torch.log(pair_wise_ious+1e-8)
通过第一行代码,将类别的条件概率(cls_preds:表示分类的概率)和目标的先验概率(obj_preds:是前景的概率)做乘积,得到目标的类别分数
(两个乘积得到的)。
再通过第二行代码,F.binary_cross_entroy 的处理,得到 3个目标框和1000个预测框 的综合loss值,得到类别损失,即 pair_wise_cls_loss,向量维度为 [3,1000]。3也是 3个真实框。其实这里就是算一个2分类交叉熵,cls_preds 和 真实框的 1 算下。每个真实框算1000次。
cls_preds=(cls_preds_.float().unsqueeze(0).repeat(num_gt,1,1).sigmoid_()
*obj_preds_.unsqueeze(0).repeat(num_gt,1,1).sigmoid_())
pair_wise_cls_losss=F.binary_cross_entropy(cls_pres_.sqrt_(),gt_cls_per_image,reduction='none').sum(-1)
有了reg_loss和 cls_loss,将两个损失函数加权相加
,就可以得到cost成本函数
了。
cost 计算公式如下:
C i j = L i j c l s + λ L i j r e g C_{ij} = L_{ij}^{cls} + \lambda L_{ij}^{reg} Cij=Lijcls+λLijreg
加权系数 λ = 3 \lambda=3 λ=3,计算代码如下:
cost=pair_wise_cls_loss
+3.0*pair_wise_ious_loss
+100000.0*(~is_in_boxes_and_center)
每个 gt 提供多少正样本,可以理解为“ 这个 gt 需要多少个正样本才能让网络更好的训练收敛 ”。
直觉上,每个 gt 的大小、尺度和遮挡条件不同,所以其提供的 positive albel 数量也应该是不同的,如何确定每个 gt 的正样本数 k 值呢?论文提供了一个简单的方案,该方法称之为:Dynamic k Estimation,具体做法如下:
从前面的 pair_wise_ious 中,给每个真实框,挑选 10个iou 最大的预测框。因为前面假定有3个目标,因此这里topk_ious的维度为[3,10]。 其实这里就是对于每个真实框选出来的 1000 个 IOU 值中选出来十个。
topk_ious 计算代码如下:
ious_in_boxes_matrix = pair_wise_ious
n_candidate_k = min(10, ious_in_boxes_matrix.size(1))
topk_ious, _ = torch.topk(ious_in_boxes_matrix, n_candidate_k, dim=1)
下面通过topk_ious的信息,动态选择预测框。**dynamic_k_matching 代码如下:
dynamic_ks = torch.clamp(topk_ious.sum(1).int(), min=1)
针对每个目标框,计算所有 anchor 的 iou 值之和,再经过 torch.clamp 函数,得到最终左边的dynamic_ks值,给目标框 1 和 3 各分配 3 个候选框,给目标框 2 分配 4个候选框。
torch.clamp 就是把每个真实框最少分配一个目标(min=1),参考:
https://blog.csdn.net/hb_learing/article/details/115696206
Sinkhorn-Knopp Iteration
求解 cost 矩阵获得 标签分配方案有了cost 矩阵 c,supplying vector s ∈ R m + 1 s∈R^{m+1} s∈Rm+1, demanding vector d ∈ R n d∈R^n d∈Rn,因此可以通过现有的 Sinkhorn-Knopp Iteration
[5]来求解该 OT 问题,从而得到最优运输方案 π ∗ ∈ R ( m + 1 ) × n π^∗∈R^{(m+1)×n} π∗∈R(m+1)×n。
在得到 π ∗ π^∗ π∗ 之后,将每个 anchor 分配给运送 labels 量最大的 supplier(真实框) ,从而解码出相应的标签分配方案。
for gt_idx in range(num_gt):
_, pos_idx = torch.topk(cost[gt_idx], k=dynamic_ks[gt_idx], largest=False)
matching_matrix[gt_idx][pos_idx] = 1
针对每个目标框挑选相应的 cost 值最低的一些候选框
。比如右面的matching_matrix
中,cost值最低
(largest=False)的一些位置,数值为1
,其余位置都为0。
因为目标框1和3,dynamic_ks 值都为3,因此matching_matrix的第一行和第三行,有3个1。而目标框2,dynamic_ks值为4,因此matching_matrix的第二行,有4个1。
这一步就是论文中所谓的考虑了全局信息的意思吧。
anchor_matching_gt = matching_matrix.sum(0)
if (anchor_matching_gt > 1).sum() > 0:
_, cost_argmin = torch.min(cost[:, anchor_matching_gt > 1], dim=0)
matching_matrix[:, anchor_matching_gt > 1] *= 0
matching_matrix[cost_argmin, anchor_matching_gt > 1] = 1
matching_matrix 中第5列有两个1,这说明第5列所对应的候选框,被目标检测框1和2 都进行关联。
因此对这两个位置,还要使用 cost值 进行对比,选择较小的值,再进一步筛选。假设第5列两个位置的值分别为0.4和0.3。
经过第三行代码,可以找到最小的值是0.3,即cost_min为0.3,所对应的行数,cost_argmin为2。
经过第四行代码,将matching_matrix第5列都置0。
再利用第五行代码,将matching_matrix第2行,第5列的位置变为1。
最终我们可以得到3个目标框,最合适的一些候选框,即matching_matrix中,所有1所对应的位置。
看来 cost 最低是这个算法的核心。并且也解决了冲突问题。太强了
从上面描述可知,OTA 和 SimOTA 都是在经过 step2:dynamic_k_estimation 后,OTA 使用的是 Sinkhorn-Knopp Iteration
求解 cost 矩阵来获得最优的标签分配结果。SimOTA 是采用定义的规则使用 torch.topk 根据 dynamic_k_estimation 得到的 k ,选择 k 个 cost 最小的值,作为分配给真实框的 候选框。
因此,SimOTA 有两个 topk :1. step2:dynamic_k_estimation 中的选择比如 10 个 topk_ious。 2. 根据动态获得的 k 选择 k 个候选框。
OTA 只有 step2:dynamic_k_estimation 中的选择比如 10 个 topk_ious。
据我目前的认识,第 4 步两者都是需要的。目前的疑问:OTA 是否需要第 5 步 去掉歧义的候选框还不太清楚。需要调试源码,但是我也不想调。
参考:
OTA的topk用来筛选candidates,是用来求dynamic k过程中使用的,最终的正样本划分有sinkhorn-knopp iter求解得到。SimOTA拿到dynamic k之后,最终正负样本划分依然是greedy的topk
https://github.com/Megvii-BaseDetection/YOLOX/issues/859
代码细节:https://blog.csdn.net/jiaoyangwm/article/details/126638775
OTA方法通常用于精筛选正样本,在精筛选正样本前,可以增加一步粗筛选,有2种方式:
在粗筛选的结果基础上,再使用OTA方法,可以减少运算量和提高精度。
将 r 设置成 3, 5, 7 后,得到粗筛选候选框数量分别为 45,125 和 245。将 OTA
和 ATSS
、PAA
方法作比较,得到2个结论:
上面说明 OTA 比 ATSS 和 PAA 要好!
在使用 Sinkhorn-Knopp
算法前,需要知道每个 gt 需要提供多少 positive label,posivtive label 的数量就是 k ,如下比较了将 k 设置成固定值和动态值的情形,论文提出的 dynamic k
方法可有效提高 AP值。
OTA 论文的主要贡献包括以下几点:
dynamic k