目标检测模型的性能评估 - mAP
Object Detection[^1]
Ground truth
Class + X coordinate + Y coordinate + Box Width + Box Height
评估架构
1 | {% mermaid graph LR %} |
IoU - Intersection over Union
FP,FN,TP,TN[^2]
为了计算precision和recall,与所有机器学习问题一样,我们必须鉴别出TP、FP、TN、FN。
指标 | 含义 |
---|---|
True Positive | 真正例 被判定为正样本,事实上也是证样本。 |
False Positive | 假正例 被判定为正样本,但事实上是负样本。 |
True Negative | 真负例 被判定为负样本,事实上也是负样本。 |
False Negative | 假负例 被判定为负样本,但事实上是正样本。 |
P(Positive)和N(Negative) 代表模型的判断结果;
T(True)和F(False) 评价模型的判断结果是否正确。
Accuracy,Precision,Recall[^2]
指标 | 含义 | 公式 |
---|---|---|
准确率(Accuracy) | 模型判断正确的数据占总数据的比例 | $Acc=\frac{TP+TN}{TP+TN+FP+FN}$ |
精确率/查准率(Precision) | 针对模型判断出的所有正例而言,其中真正例占的比例 | $Pre=\frac{TP}{TP+FP}$ |
召回率(Recall) | 模型正确判断出的正例(TP)占数据集中所有正例的比例. | $Rec=\frac{TP}{TP+FN}$ |
mAP[^1]
原因
- 目标检测问题中的模型的分类和定位都需要进行评估,每个图像都可能具有不同类别的不同目标,因此,在图像分类问题中所使用的标准度量不能直接应用于目标检测问题。
计算
理解
mAP的计算收到IoU和置信度阈值两个参数的影响
- IoU是一个简单的几何度量,可以很容易标准化,比如在PASCAL VOC竞赛中采用的IoU阈值为0.5,而COCO竞赛中在计算mAP较复杂,其计算了一系列IoU阈值(0.05至0.95)下的mAP。
- 置信度却在不同模型会差异较大,可能在我的模型中置信度采用0.5却等价于在其它模型中采用0.8置信度,这会导致precision-recall曲线变化。
PASCAL VOC提出了一种可以用于任何模型的评估指标。此为2007年提出,比较简单,后续:2010年提出使用所有数据点而非11个recall[^3],COCO数据集中还需计算不同IoU阈值和物体大小下的AP[^4]。
For a given task and class, the precision/recall curve is computed from a method’s ranked output.
The AP summarises the shape of the precision/recall curve, and is defined as the mean precision at a set of eleven equally spaced recall levels [0,0.1,…,1]
- 对模型预测结果进行排序(ranked output,按照各个预测值置信度降序排列)。那么给定一个rank,Recall和Precision仅在高于该rank值的预测结果中计算,改变rank值会改变recall值。这里共选择11个不同的recall([0, 0.1, …, 0.9, 1.0]),可以认为是选择了11个rank,由于按照置信度排序,所以实际上等于选择了11个不同的置信度阈值。
- 那么,AP就定义为在这11个recall下precision的平均值,其可以表征整个precision-recall曲线。
- 对于各个类别,分别按照上述方式计算AP,取所有类别的AP平均值就是mAP。这就是在目标检测问题中mAP的计算方法。
例子
与上一小节中理解的叙述有区别(单图特定类->数据集特定类->数据集多类/整体),该例子的计算方法特别简单(未涉及置信度),记录下来只为加深理解。实际使用上还是要参考code。
1. 对于每个图像,我们都有ground truth的数据(即知道每个图像的真实目标信息),因此也知道了该图像中给定类别的实际目标(B)的数量。因此我们计算该类模型的精度(A/B) ,即给定一张图像的类别C的Precision=图像正确预测(True Positives)的数量除以在图像中这一类的总的目标数量。
$$Precision_C=\frac{N(TruePositives)_C}{N(TotalObjects)_C}$$
2. 假如现在有一个给定的类,验证集中有100个图像,并且我们知道每个图像都有其中的所有类(基于ground truth)。所以我们可以得到100个精度值,计算这100个精度值的平均值,得到的就是该类的平均精度。 即一个C类的平均精度=在验证集上所有的图像对于类C的精度值的和/有类C这个目标的所有图像的数量。
$$AveragePrecision_C=\frac{\sum Precision_C}{N(TotalImages)_C}$$
3. 现在假如我们整个集合中有20个类,则可以获得20个不同的平均精度值。使用这些平均精度值,我们可以轻松的判断任何给定类别的模型的性能。为了选用一个单一的数字来表示一个模型的表现,我们可以取所有类的平均精度值的平均值,即MAP(均值平均精度)。
$$MeanAveragePrecision=\frac{\sum AveragePrecision_C}{N(Classes)}$$
其他
当比较mAP值,记住以下要点:
- mAP通常是在一个数据集上计算得到的。
- 虽然解释模型输出的绝对量化并不容易,但mAP作为一个相对较好的度量指标可以帮助我们。 当我们在流行的公共数据集上计算这个度量时,该度量可以很容易地用来比较目标检测问题的新旧方法。
- 根据训练数据中各个类的分布情况,mAP值可能在某些类(具有良好的训练数据)非常高,而其他类(具有较少/不良数据)却比较低。所以你的mAP可能是中等的,但是你的模型可能对某些类非常好,对某些类非常不好。因此,建议在分析模型结果时查看各个类的AP值。这些值也许暗示你需要添加更多的训练样本。
代码
VOC数据集
Facebook开源的Detectron包含VOC数据集的mAP计算,这里贴出其核心实现,以对mAP的计算有更深入的理解。
首先是precision和recall的计算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48# 按照置信度降序,排序预测框坐标
sorted_ind = np.argsort(-confidence)
BB = BB[sorted_ind, :]
image_ids = [image_ids[x] for x in sorted_ind] # 各个预测框的对应图片id
# 遍历预测框,并统计TPs和FPs
nd = len(image_ids)
tp = np.zeros(nd)
fp = np.zeros(nd)
for d in range(nd):
R = class_recs[image_ids[d]]
bb = BB[d, :].astype(float)
ovmax = -np.inf
BBGT = R['bbox'].astype(float) # ground truth
if BBGT.size > 0:
# intersection
ixmin = np.maximum(BBGT[:, 0], bb[0])
iymin = np.maximum(BBGT[:, 1], bb[1])
ixmax = np.minimum(BBGT[:, 2], bb[2])
iymax = np.minimum(BBGT[:, 3], bb[3])
iw = np.maximum(ixmax - ixmin + 1., 0.) # +1?
ih = np.maximum(iymax - iymin + 1., 0.)
inters = iw * ih
# union
uni = ((bb[2] - bb[0] + 1.) * (bb[3] - bb[1] + 1.) +
(BBGT[:, 2] - BBGT[:, 0] + 1.) *
(BBGT[:, 3] - BBGT[:, 1] + 1.) - inters)
# 计算IoU
overlaps = inters / uni
# 取最大的IoU,并返回对应索引
ovmax = np.max(overlaps)
jmax = np.argmax(overlaps)
if ovmax > ovthresh: # 是否大于阈值
if not R['difficult'][jmax]: # 非difficult物体
if not R['det'][jmax]: # 未被检测
tp[d] = 1.
R['det'][jmax] = 1 # 标记已被检测
else:
fp[d] = 1.
else:
fp[d] = 1.
# 计算precision recall
fp = np.cumsum(fp)
tp = np.cumsum(tp)
rec = tp / float(npos)# avoid divide by zero in case the first detection matches a difficult
# ground truth
prec = tp / np.maximum(tp + fp, np.finfo(np.float64).eps)这里最终得到一系列的precision和recall值,并且这些值是按照置信度降低排列统计的,可以认为是取不同的置信度阈值(或者rank值)得到的。然后据此可以计算AP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30def voc_ap(rec, prec, use_07_metric=False):
"""Compute VOC AP given precision and recall. If use_07_metric is true, uses
the VOC 07 11-point method (default:False).
"""
if use_07_metric: # 使用07年方法
# 11 个点
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 = ap + p / 11.
else: # 新方式,计算所有点
# correct AP calculation
# first append sentinel values at the end
mrec = np.concatenate(([0.], rec, [1.]))
mpre = np.concatenate(([0.], prec, [0.]))
# compute the precision 曲线值(也用了插值)
for i in range(mpre.size - 1, 0, -1):
mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i])
# to calculate area under PR curve, look for points
# where X axis (recall) changes value
i = np.where(mrec[1:] != mrec[:-1])[0]
# and sum (\Delta recall) * prec
ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1])
return ap计算各个类别的AP值后,取平均值就可以得到最终的mAP值了。
COCO数据集[^5]
[^1]: 绝对不容错过:最完整的检测模型评估指标mAP计算指南(附代码)在这里!
[^2]: FP,FN,TP,TN与精确率(Precision),召回率(Recall),准确率(Accuracy)
[^3]: Everingham M, Eslami S M A, Van Gool L, et al. The pascal visual object classes challenge: A retrospective[J]. International journal of computer vision, 2015, 111(1): 98-136. paper
[^4]: Cocodataset / Detection Evaluation
[^5]: https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocotools/cocoeval.py