史上最易懂AP、mAP计算解析

        博主最近也有在接触目标检测相关的相关研究,其中有一个环节博主卡了很久,那就是AP的计算过程。相信大家都看到过很多关于AP的介绍,但是都很空泛,而且大家的答案都是千篇一律,我们只能看个大概,但是一到看代码或者是要自己写代码的时候具体怎么操作就不会了。博主在学习的时候看到一篇英文的关于AP的介绍十分的详细,最最最关键的是它还有例子。这里放上原文的链接:https://github.com/rafaelpadilla/Object-Detection-Metrics 然后博主在这里结合自己的理解给大家通俗易懂的讲讲AP到底怎么算不是想其他资料一样讲写套话,看的大家云里雾里的。

一、IOU

简单一点说呢就是我们要求两个框的IOU就是用两个框的重合部分除以连个框的并集,可以看上面的第二个公式就很明了了,计算的时候要注意两个框的并集的面积等于二者的面积之和减去二者的交集的面积。

二、True Positive, False Positive, False Negative and True Negative

True Positive (TP):一个正确的定位结果,就是你预测的框和我们的groudtruth之间的IOU是可以大于我们规定的阈值的,我们一般取这个阈值都是0.5

False Positive (FP):就是一个错误的结果,就是你预测的这个框和groundtruth的IOU是小于阈值的

False Negative (FN): 就是我们本来这里有一个物体的,所以我们这个地方应该有个框,但是你没预测出来,那么这个groundtruth对于你的模型来讲就是一个FN

True Negative (TN):这个就是我们这个groundtru找到了一个和它的IOU大于阈值的预测框,那么对于这个ground就是认为被成功的检测出来了。

三、Precision和Recall

首先是precision就是用TP除以总的detection的数目(你预测处来的框的数目),注意我们在计算AP的时候要首先要计算precision。在我们实际计算的时候是一个序列,如果我们一共有N个detections那么它就是一个长度维N的序列,我们计算Precision的时候是针对某一个类别的,首先确定一个类别,然后对这个类别的所有的检测结果按他们得到的分数也就是confidence进行从大大小排序,然后我们每次取一个结果出来不管这个detection是TP还是FP,all detections都要加1也就是在实际计算的过程中上述的公式中的all detections是一个递增变化的数,每次取一个框就加1,然后每次取一个框出来如果这个框是TP那么TP的数目就加1,然后TP和all detections比值作为当前的precision,这样取完所有的框就有了N个precision组成的一个序列。这个地方如果没有看太懂没有关系,下面我们会举一个简单的例子来解释具体的操作的。然后我们来看下什么是recall.

与precision对应的recall也是一个长度维N的序列,其中N是我们得到detections的数目,我们没取出来一个框如果这个框是TP,那么TP的数目就加1,然后我们用当前的TP数目来除以all ground truths来得到当前的recall,这里要注意all ground truths就是我们这数据集中所有的这个类别的目标的数目,对于一个特定的数据集的一个特定的类别这是一个定值。所以我么把所有的框都取完之后我们就得到两个长度为N的序列,一个是precision一个是recall。这两个数据分别表示了我们的定位效果的两个方面,precision表示的是我们的定位的准确率,就是我们得到了这么多框到底有多少个是好的,比如我们一共有5个目标你给出了2000个框,好吧这2000个框中都包含了这5个目标,但是其他的1995个框都是没用的,这样无疑是很蠢的,因为只要我们取的框足够多我们总是可以把这些东西找的出来的,但这样就没有意义了。那么recall怎么解释呢,recall是表征我们到底找到了多少个目标物的量,比如我们一共有200个目标,我们最后给出的detections是20个而且这20个里面竟然有19个都是TP那么无疑我们的precision是很高的有95%,但是我们发现我们还有181个目标没有找到,也就是我们的recall是很低的,这肯定不行啊,比如我么要在雷达里面找导弹,一共12个导弹你就找到3个,好了够你死好几回了。所以我们可以看到无论是precision还是recall在目标检测中都是很重要的,但是我们发现我们多取一些框那么recall就可能比较的高,但是这样就可能会使得precision降低,所以在模型如果不是特别特别精确的情景下,这两个量是相互矛盾的,所以我们对于不同的场景可能就必须要选择到底要照顾那一边。那么对于普适的情况下我们就要综合考虑二者的取一个平衡,也就有了我们的AP这个指标。好吧下面来介绍AP的计算过程。

四、AP的计算

AP的计算有两套标准,一套是07年的标准,一套是后面的标准我们一般现在使用的都是新的标准。

第一种是根据recall将我们的得到的那个序列分成11段

就是首先我们取出来所有的recall在0到0.1这个区间内的所有的recall以及对应的那些precision,然后我们从这些precision中找到最大值作为这个区间内precision的代表,然后我们在剩下的10个区间内也分别找到最大的precision,最后把这11个数求均值就作为我们的AP。

好了我们可以看到我们上面只是取了11个recall的区间,这样做其实也可以但是总是感觉太粗糙了些,所以后面大家就提出了一种新的AP的计算方法,我们可以把所有的recall取值都取一遍,仔细观察下我们会发现其实在recall序列中有些位置都是相等的,因为只有我们取到了一个TP的时候recall的数值才会发生变化。

好了我们结合上面的公式来看看到底怎么算,首先我们取到了第n种recal的取值,然后我们我们在往后看直到发现出现了不同的recall我们然后我们在这个区间里找到最大的precision,然后用这个最大的precision和这两个recall相乘作为这段区间的AP然后我们遍历所有的区间然后把每段的AP加起来就得到了最后的AP,好吧,如果还是没有看懂我们看个例子,看完这个例子你一定就懂了。

首先我们假设这个数据集中的某个类别一共有7张图像,然后这些图像中绿色的框是ground truths然后红色的框是我们得到的detections。

然后我们首先我们按分数来给这下框排个序就像下面这样然后我们要看下这些框到底是TP还是FP,这写怎么判断我们上都有解释过,但是这里有一个问题就是对于同一个ground truth我们可能同时有多个框都和它的IOU都大于阈值,那么我么把它们都当做TP吗?肯定不是啊,这个时候我们就从这些框里面选一个和我们的ground truth重合率最大的一个框作为TP剩下的作为FP,注意这里是重合率大的留下而不是confidence大的留下。其实我感觉这样也不太合理重合率大的作为TP没问题但是小的作为FP就不合理了,应该直接跳过这个框就好了不是TP也不是FP。好吧反正就是这么算。

然后得到了每个框是TP还是FP之后我们来计算 precision和recal序列,我们先看下结果好吧

 

然后怎么算我们已经说过了现在我们在简单提下看最前面的两个框。取到第一个框的时候我们的all detections就是1正好我我们这个框就是TP,所以我们这里的precision就是1咯,然后在取一个框,但是这个框是FP,所有我们的TP数目还是1,但是我们的all detections就变成2了所以我们的precision这个时候变成了0.5,然后我们看下recall我们一共有15个groundtrus然后我们的recall只有在我们取到了一个TP之后才会变化对吧,好了这个很明显了我不想写了。

恩然后有了recall和precision序列了然后我们怎么搞:

首先是11点法:这个就不解释了前面有解释过结合着我们上面的那个表大家肯定看得懂的。

然后是我们现在常用的算法:就是每个recall区间做相应的计算,即每个recall的区间内我们只取这个区间内precision的最大值然后和这个区间的长度做乘积,所以最后体现出来就是一系列的矩形的面积,还是以上面的那个例子为例,我们一共有recall一共变化了7次,我们就有7个recall区间要做计算,然后实际我们计算的时候人为的要把这个曲线变化成单调递减的,也就是对现有的precision序列要做一些处理,后来有些同学留言说在AP的计算这块有疑问,我最初写的时候也确实有写问题,有些地方也没有交代清楚,所以现在在这里重新结合代码写了一下。我们先放上代码:

def voc_ap(rec, prec, use_07_metric=False):
  """ ap = voc_ap(rec, prec, [use_07_metric])
  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:
    # 11 point 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 = 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.]))
    print(mpre)
    # compute the precision envelope
    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]

    ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1])
    print(mpre)
  return ap

这个代码在任何一个目标检测的代码里应该都可以找到的,下面我们看下这个代码做了哪些事情:首先我们输入的序列是我们得到的:

rec:[0.0666,0.0666,0.1333,0.1333,0.1333,0.1333,0.1333,0.1333,0.1333,0.2,0.2,0.2666,0.3333 ,0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.4,0.4666,0.4666],

然后是我们的pre:[1,0.5,0.6666,0.5,0.4,0.3333,0.2857,0.25,0.2222,0.3,0.2727,0.3333,0.3846,0.4285,0.4,0.375,0.3529, 0.3333,0.3157,0.3,0.2857,0.2727,0.3043,0.2916]。

代码里首先是判断是不是用11点的方法,我们这里不用所以跳转到下面的代码:

mrec = np.concatenate(([0.], rec, [1.]))
mpre = np.concatenate(([0.], prec, [0.]))

 那么这一步呢就是相当于把我们的recall开放的区间给补上了,补成了闭合的区间。mpre也是做了对应的补偿。

    for i in range(mpre.size - 1, 0, -1):
      mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i])

 

那这一步呢就是在做我们上面说到的人为地把这个pre-rec曲线变成单调递减的,为什么这么做呢,我也不太清楚。我们这里可以看到他就是在做一个比较,就是后一项要是比前一项大的话那么就把这个大的值赋值给前一项。做完这一步后的pre就变成了:[1.     1.     0.6666 0.6666 0.5    0.4285 0.4285 0.4285 0.4285 0.4285 0.4285 0.4285 0.4285 0.4285 0.4285 0.4    0.375  0.3529 0.3333 0.3157 0.3043 0.3043 0.3043 0.3043 0.2916 0.    ]我们发现与之前的[0.     1.     0.5    0.6666 0.5    0.4    0.3333 0.2857 0.25   0.2222  0.3    0.2727 0.3333 0.3846 0.4285 0.4    0.375  0.3529 0.3333 0.3157 0.3    0.2857 0.2727 0.3043 0.2916 0.    ]对比那些在0.4285之前出现的但是比0.4285小的数值都被替换成了0.4285.,相当于强行把这个曲线给填的鼓了起来。

做完这个操作后,我们就要按rec区间去计算ap了,准确来说是用rec的区间长度乘以这个区间上的最大的pre(注意这里是处理后的pre)。代码实现起来就是:

i = np.where(mrec[1:] != mrec[:-1])[0]#获取rec区间变化的点
ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1])#(mrec[i + 1] - mrec[i])这里得到rec区间的
#长度,mpre[i + 1]这里是这个区间上对应的pre数值(处理后的)

我们这里分别输出一下:

print((mrec[i + 1] - mrec[i]))
print(mpre[i + 1])

 结果是:[0.0666 0.0667 0.0667 0.0666 0.0667 0.0667 0.0666 0.5334]和[1.     0.6666 0.4285 0.4285 0.4285 0.4285 0.3043 0.    ]我们看一下首先是0.0666他代表第一个rec0.0666到0的区间长度,然后呢这个区间上出现的最大的pre是1,第二项0.0667代表0.0666到0.1333的区间长度是0.0667,在rec取0.1333这段区间上最大额pre是0.4285(变化后的),后面的以此类推这块还有疑问的建议结合这上面的那个表和pre处理后的序列看一下哈,。为啥pre输出的最后一项是0呢,是因为这段区间实际上recall是没有达到过的我们最大的rec也就只到了0.4666就结束了。

那么对于这个例子就是通过上面的就可以算出来了,其实还是蛮直观的。恩到这里我们就把怎么算AP讲完了。

那么mAP又是啥呢,其实m就是mean均值的意思我们AP不是算个一个类别的吗,mAP就是把所有的类别的AP都算出来然后求个均值就可以了,然后至于代码github上面或者你看faster-RCNN这些都有现成的,恩希望大家看完这个就能理解AP咋算的啦,有不懂的地方好好看看这个例子肯定是可以看懂的。

 

 

### 回答1: "Java自学教程(史上最全)文库"是一个提供丰富资料的自学教程文库,旨在帮助那些希望自学Java编程的人们。这个文库包含了大量的Java知识和编程技巧,从基础知识到高级应用涵盖了各个方面。 首先,Java自学教程提供了一个系统而完整的学习路径。它以浅显易懂的方式介绍了Java的核心概念,如变量、数据类型、控制结构和面向对象编程等。然后,它逐渐深入到更复杂的主题,例如异常处理、线程编程和网络编程等。这样的学习路径使初学者能够逐步掌握Java的知识,并逐渐提高他们的编程技能。 其次,Java自学教程提供了大量的实例和实践项目。这些实例展示了Java在实际应用中的使用,包括图形用户界面、数据库连接和Web开发等领域。通过实际的实例,读者可以更好地理解和掌握Java的应用,并能够将所学知识应用到自己的项目中。 此外,Java自学教程还提供了丰富的学习资源。除了文字教程外,它还包括了视频教程、在线编程环境和交流社区等。这样的学习资源不仅能够满足不同类型学习者的需求,还能够帮助读者更好地与其他学习者互动交流,加深对Java编程的理解和掌握。 总之,Java自学教程(史上最全)文库是一个宝贵的学习资源,适合想要自学Java编程的初学者或者进一步提升Java技能的开发者。它提供了全面的教程,丰富的实例和实践项目,以及多样化的学习资源,帮助读者深入学习和应用Java编程。 ### 回答2: Java自学教程是一个为那些希望自学Java编程的人提供的宝贵资源。这个文库被称为史上最全,是因为它提供了从基础知识到高级概念的全面指导,并涵盖了Java编程的各个方面。 首先,该教程首先介绍了Java的背景和发展历程,帮助读者了解Java的起源和其在软件开发中的重要性。接下来,它详细解释了Java的基本语法、数据类型、运算符和控制流程,帮助读者建立起对Java编程的基础理解。 随后,教程深入讲解了Java面向对象编程的核心概念,如类、对象、封装、继承和多态。此外,它还介绍了Java的异常处理机制和文件输入/输出操作,提供了有关如何处理潜在错误和管理文件的重要指导。这些内容有助于读者构建健壮的Java应用程序。 该教程还包括关于Java集合框架、多线程编程和网络编程的详细指南。这些主题涉及到Java编程中的高级概念和技术,对于希望进一步扩展他们的Java知识的读者来说,是必不可少的。 此外,这个文库提供了大量的实例和练习,帮助读者巩固他们所学的知识,并帮助他们实践和掌握Java编程技巧。教程还介绍了一些常见的Java开发工具和技术,如Eclipse和Maven,以帮助读者优化他们的Java开发环境。 总之,Java自学教程(史上最全)文库是一个为那些希望自学Java编程的人提供全面指导的宝贵资源。它涵盖了Java编程的各个方面,从基础知识到高级概念,为读者提供了一个全面而系统的学习路径。无论是初学者还是有经验的开发者,都可以从中受益,并在Java编程领域取得成功。 ### 回答3: Java自学教程(史上最全)文库是一个综合性的教程资源库,提供了全面且详细的Java学习资料。以下是对其的一些介绍: Java自学教程(史上最全)文库是由一群经验丰富的Java开发者所创建的,他们将自己多年的经验和知识总结、整理,并以此为基础编写了一系列适合自学的教程。 这个文库首先从Java的基础知识开始讲解,包括Java语言的特点、基本语法、数据类型、流程控制等内容。然后逐步深入,介绍了Java面向对象的特性、类和对象、继承、多态等,并通过大量的实例演示和练习来帮助读者更好地理解和运用。 此外,文库还涵盖了Java核心技术,包括异常处理、多线程、网络编程、IO流、集合框架等,它们是Java开发中必备的技能。 除了核心技术,文库还介绍了一些常用的Java开发工具和框架,如Eclipse、Maven、Spring等,以帮助读者更好地进行Java项目的开发与管理。 最重要的是,文库中的内容都经过仔细筛选和验证,确保了教程的准确性和实用性,因此它可以作为一个可靠的学习资料供读者参考。 总之,Java自学教程(史上最全)文库是一个内容丰富、体系完整的学习资源库,通过它你可以系统地学习并掌握Java开发所需的知识和技能。无论你是初学者还是有一定经验的开发者,都可以从中获得帮助和提升。
评论 55
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值