coco和yolov5 map计算结果不一致的问题

在测试检测benchmark时发现使用coco和yolov5计算出的map结果不一致, yolov5的指标要略高一点, 好奇他们都是如何计算的, 通过阅读源码, 发现了一些端倪, 如有纰漏, 还望指出.

ap概念及计算方式

先说ap(average precision), 翻译过来为平均精度, 顾名思义, 就是精度的平均值. 通常来讲, 一个算法任务在数据集上的测试输出的结果是固定的(TP, FP是固定的), 也就是说, 精度值就一个, 那么何来平均精度一说呢?
事实上, 当正负样本差别较大时, 使用单一指标, 如精度(查准率), 还是召回率(查全率), 都无法评价模型的好坏(想象一下正样本99, 负样本1, 模型将所有目标都预测为正例, 此时tp=99, fp=1, p=0.99, r=1能说明模型很好吗? ), 于是需要一个综合指标来衡量模型好坏, ap就是这个综合指标, 计算方式也很简单, 就是计算平均精度, 问题是, 如何构造多个精度呢?

假设某单一目标检测任务(只有1类)在数据集(假设只有3张图片, 每个图片一个目标)有三个预测结果(每个图片一个, 事实上,计算map时跟几张图片没关系), 每一个预测结果 都有置信度, 将所有结果按照置信度从大到小排列:
在这里插入图片描述
对于整个数据集:
置信度为0.9时, tp = 1, fp=0;
置信度为0.7时, tp = 1, fp=1;
置信度为0.4时, tp = 2, fp=1
由以上结果, 可以得到下表:
在这里插入图片描述
于是, 我们便得到了一系列的precision.
但是有几个细节需要我们注意:

  • 问: 我们如何确定检测结果是tp还是fp?
    答: 靠iou, 预测框和gt框iou>iou_th为tp, 否则为fp, ap50就是在iou_th=0.5的条件下计算得到的map.
  • 问: ap和map什么关系?
    答: 通常来讲, ap是某一类的平均精度, map是所有类别的平均精度的平均, 但在coco中, 就没map这个说法, coco中的ap就是map
    在这里插入图片描述
    值得注意的是, 在coco意义下,
    ap(map)是iou_th从0.5取到0.95共10个iou level计算得到的ap的平均值(由每一类ap得到最终ap, 一共需要取两次均值, 一次是所有的iou level, 一次是所有的class)
    ap50是iou_th取0.5计算得到的map
  • 问: 得到了p和r, 接下来ap如何计算呢? 是不是将p取平均就行了?
    答: 这个问题很关键, coco, yolov5的区别就在这

coco和yolov5的差别

在得到p, r列表后, 可以画出p, r图像(画图只是为了方便理解, 实际计算时, 只要得到p, r列表就行了)
在这里插入图片描述
通常, 在得到以上图像后, 需要将x轴(即r)分为101份(0~1, 0.01)进行插值来计算最终的ap, coco和yolov5的差别体现在得到p, r 列表后, 如何插值以及如何计算ap上.

  • 先看下coco(pycocotools.cocoeval.COCOeval):
    coco的插值方法:
# cocoeval.py, line 402
inds = np.searchsorted(rc, p.recThrs, side='left')

其中, recThrs就是recall的101个点, 熟悉np.searchsorted()的小伙伴应该发现了, 其实coco在计算时并没有真的进行插值, 只是按照101个recall的位置, 从p列表中取对应位置的p而已, 对应图像是这样:
在这里插入图片描述

细心的小伙伴会发现np.searchsorted()得到的索引可能超过p的长度, 那超过r最大值的点(右侧红框部分)怎么取呢?答案是直接置为0, 也就是说"插值坐标点"取到r能取到的最大索引后就结束了, 代码对应于这部分:

# cocoeval.py, line 402~408
try:
     for ri, pi in enumerate(inds):
         q[ri] = pr[pi]
         ss[ri] = dtScoresSorted[pi]
except:
    pass

q初始化的时候都是0, 当pi超过pr的索引时会发生错误, 于是超过索引的p都是0.
再来看最终如何计算ap值, coco直接简单粗暴, 取均值:

# cocoeval.py, line 455
mean_s = np.mean(s[s>-1])
  • 再看yolov5
    首先, yolov5是老老实实对p列表进行了101个点的插值, 插值完大概是这样:
    在这里插入图片描述

对应代码:

# Append sentinel values to beginning and end
mrec = np.concatenate(([0.0], recall, [1.0]))
mpre = np.concatenate(([1.0], precision, [0.0]))

# Compute the precision envelope
mpre = np.flip(np.maximum.accumulate(np.flip(mpre)))

# Integrate area under curve
method = 'interp'  # methods: 'continuous', 'interp'
if method == 'interp':
   x = np.linspace(0, 1, 101)  # 101-point interp (COCO)
   ap = np.trapz(np.interp(x, mrec, mpre), x)  # integrate

从代码可以看出, 首先在p, r列表左右两侧各查了0,1两个值, 方便后面查值, 之后通过np.interp()插101个点, 最后通过
np.trapz 积分得到最终的ap
, 大概就是这样:
在这里插入图片描述

结论

coco和yolov5在计算map(coco叫ap)的不同之处有两点:

  • coco插值用的是np.searchsorted(), yolov5用的是np.interp();
  • coco用平均得到ap, yolov5积分得到ap;
  • 12
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值