开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情
看目标检测方面的论文时,都把mAP(mean Average Precision) 当作最终的比较不同模型性能的指标。
平时要用的时候,就调用个开源代码,然后屁颠屁颠地给老板看结果,没想到这次要自己写,找了好几份资料和代码,才把这个概念看懂了大概,看来想在这行有长进,还是得踏踏实实下苦功。
为了理解mAP的含义,先列出这几个目标检测的基本指标:
- TP:IoU>IoU_Threshold的真值检测框数量。
- FP:IoU≤IoU_Threshold的检测框数量,或者是检测到同一个真值框的多余检测框的数量。
- FN:没有检测到的真值框的数量。
- Precision(精确率):TP/(TP+FP)
- Recall(召回率):TP/(TP+FN)
- F1-Score:2×(presicion×recall)/(presicion+recall) 用于衡量precision和recall间的大小关系
相信了解目标检测的同学们都已经对Presicion和Recall滚瓜烂熟了:
Precision就是想确保没有错检,Recall就是为了确保没有漏检,若提升阈值,错检的个数会减少,但同时漏检的个数会增多,这两个指标不会同时增高,会相互直接拉扯。F1-Score则是为了衡量Recall和Precision间的平衡而提出的指标。
已经有Precision、Recall、F1 score了,为什么还需要mAP呢?
- 目标检测任务里常常为多分类问题,上述的指标并不能反映多分类的性能
- IoU_threshold和置信度都会影响Precision和 recall的值
我们常常还会在论文里看到mAP@后面带着一个小数,可以分开三部分来看。
-
“average precision”(AP)指的是PR曲线下的面积
-
“mean”指的是所有类别的AP平均值
-
@后面的小数指的是不同iou阈值,即IoU_Threshold,用于衡量定位精度
如何得出PR曲线呢?
一种常见的方法,就是将置信度分数按从大到小排列,取第一个框,计算第一对recall和precision值,再取前两个框,计算recall和precision,再取前三个框...以此类推,直到所有的检测框都计算完。
关于计算PR曲线下的面积,VOC给出了两种不一样的方法:
- voc2007里,对于每个点,取其右边的极大Precision值;每隔0.1个recall取11个点,最后算11个点的precision的平均值作为AP。
(图片来自网络)
- voc2012里是直接计算PR曲线围成的面积大小。
(图片来自网络)
代码计算步骤
Step1: 将检测框按置信度从高到低排列,设定IoU_Threshold,计算Recall-Precision曲线
def calc_detection_mAP(all_gt_dict, all_pred_dict,iou_threshold=0.8,use_07_metric=True):
mAP_dict = {}
for class_name in all_gt_dict.keys():
gt_boxes_list = all_gt_dict[class_name]
pred_boxes_list = all_pred_dict[class_name]
npos = len(gt_boxes_list)
if npos == 0:
continue
pred_boxes_list = sorted(pred_boxes_list, key=lambda x: x[4], reverse=True)
tp = np.zeros(len(pred_boxes_list))
fp = np.zeros(len(pred_boxes_list))
for idx, pred_box in enumerate(pred_boxes_list):
pred_box_restored = list(map(int, pred_box[:4]))
max_overlap = -1
for gt_box in gt_boxes_list:
overlap = iou(pred_box_restored, gt_box)
if overlap > max_overlap:
max_overlap = overlap
if max_overlap >= iou_threshold:
tp[idx] = 1
else:
fp[idx] = 1
fp = np.cumsum(fp)
tp = np.cumsum(tp)
rec = tp / float(npos)
prec = tp / np.maximum(tp+fp, np.finfo(np.float64).eps)
ap = get_ap(rec, prec, use_07_metric)
mAP_dict[class_name] = ap
return mAP_dict
Step2:计算AP的两种不同方法
def get_ap(rec, prec, use_07_metric=False):
if use_07_metric:
ap = 0
for t in np.arange(0., 1.1, 0.1):
if np.sum(rec >= t) == 0:
p = 0
else:
p = np.max(prec[rec >=t])
ap += p/11
else:
mrec = np.concatenate(([0.], rec, [1.]))
mpre = np.concatenate(([0.], prec, [0.]))
for i in range(mpre.size - 1, 0, -1):
mpre[i-1] = np.maximum(mpre[i-1], mpre[i])
i = np.where(mrec[1:] != mrec[:-1])[0]
ap = np.sum((mrec[i+1]-mrec[i]) * mpre[i+1])
return ap
Step3:计算mAP
def calc_mAP(all_gt_dict, all_pred_dict, iou_threshold=0.5, use_07_metric=True):
mAP_dict = calc_detection_mAP(all_gt_dict, all_pred_dict, iou_threshold, use_07_metric)
mAP_list = list(mAP_dict.values())
mAP = np.mean(mAP_list)
return mAP
写在后面
这些基础的指标计算说难不难,说容易,又会写错细节。搞算法,花里胡哨的神经网络层出不穷,但更重要的是牢牢掌握基础知识,思路清晰的思考才能够更快速高效地解决问题。