4、AP的计算方法:VOC 2007的11点插值法、VOC 2012的积分法、COCO的101点插值法

聊完了PR曲线和AP的基本概念,咱们得动真格的了——到底怎么把AP算出来?

说实话,我刚入行那会儿,觉得AP就是个数字。直到有一次,我用不同版本的评估代码跑同一个模型,结果差了3个点。查了半天才发现,原来是VOC 2007和VOC 2012的计算方法不一样。嗯,从那以后,我再也不敢小看这个“计算细节”了。

核心认知: AP不是唯一的。不同数据集、不同年份,计算方法不同,结果也不同。你看到的里的mAP,一定要先搞清楚它用的是哪个版本的算法。

4.1 VOC 2007:11点插值法

VOC 2007的方法,说白了就是“取11个点,求平均”。

具体怎么做?

  1. 把Recall从0到1分成11个等分点:0.0, 0.1, 0.2, ..., 1.0
  2. 在每个Recall点上,取该点右侧(包含该点)所有Precision的最大值
  3. 把这11个最大值加起来,除以11

公式长这样:

AP = (1/11) * Σ P_interp(r)  其中 r ∈ {0, 0.1, 0.2, ..., 1.0}
P_interp(r) = max_{r' ≥ r} P(r')

为什么要取“右侧最大值”?我举个例子你就明白了。

假设你的PR曲线在Recall=0.3时Precision掉到了0.5,但Recall=0.5时又回升到了0.8。如果直接取0.3处的值,你得到的是0.5。但VOC 2007认为:既然后面还能回到0.8,那说明模型在0.3这个点只是“暂时表现不好”,应该用后面的最高值来代表它。

我的经验: 11点插值法对“抖动”的PR曲线比较友好。我在做小目标检测时,PR曲线经常上下波动,用这个方法算出来的AP反而更稳定。但代价是——它不够精细,尤其是当Recall集中在某个区间时。

4.2 VOC 2012:积分法(面积法)

VOC 2012改进了。它不再只取11个点,而是计算PR曲线下的实际面积。

怎么做?

  1. 对PR曲线做平滑处理:在每个Recall点,取该点右侧所有Precision的最大值
  2. 计算平滑后曲线下的面积(用梯形法积分)

你可能会问:“这不还是取最大值吗?跟2007有什么区别?”

区别大了。2007只取了11个点,2012用了所有点。你想想看,如果PR曲线在Recall=0.35处有一个尖峰,2007可能完全错过它(因为0.35不在11个点里),但2012会把它算进去。

对比项 VOC 2007 VOC 2012
采样方式 11个等分点 所有检测点
插值方式 取右侧最大值 取右侧最大值 + 梯形积分
精度 较粗糙 更精细
对抖动敏感度 不敏感 较敏感
典型应用 VOC 2007挑战赛 VOC 2012及之后
我曾经踩过的坑: 用VOC 2007的代码去评估VOC 2012的数据集,结果AP比里低了2个点。查了两天才发现是计算方法不匹配。所以,一定要确认你的评估代码和用的是同一个版本。

4.3 COCO:101点插值法

COCO数据集的做法,可以理解为VOC 2007的“高分辨率版本”。

它把Recall从0到1分成101个点:0.00, 0.01, 0.02, ..., 1.00。然后对每个点取右侧最大值,最后求平均。

AP = (1/101) * Σ P_interp(r)  其中 r ∈ {0, 0.01, 0.02, ..., 1.00}
P_interp(r) = max_{r' ≥ r} P(r')

为什么是101个点?不是100个?

因为从0.00到1.00,步长0.01,一共101个点。这样每个百分位都有对应的值,精度比VOC 2007高了将近10倍。

关键区别: COCO的AP计算还多了一个维度——它会在多个IoU阈值(从0.50到0.95,步长0.05)下分别计算AP,然后取平均。这个叫AP@[0.5:0.95]。而VOC通常只算IoU=0.5这一个阈值。

4.4 三种方法的对比与选择

我整理了一张对比表,方便你快速查阅:

方法 采样点数 插值方式 IoU阈值 适用场景
VOC 2007 11 右侧最大值 + 平均 0.5 老复现、快速评估
VOC 2012 所有点 右侧最大值 + 积分 0.5 VOC数据集标准评估
COCO 101 右侧最大值 + 平均 0.5:0.95 (10个) 现代检测模型、竞赛

我个人习惯:

  • 做学术对比时,用COCO的AP@[0.5:0.95],因为它更全面
  • 快速调参时,用VOC 2007的11点法,算得快,够用就行
  • 正式提交结果前,一定用目标数据集官方指定的方法再跑一遍
一个小技巧: 如果你在复现老(比如2015年之前的),大概率用的是VOC 2007的11点法。别直接用COCO的评估代码去跑,结果会不一样。我一般会在代码里加一个参数:ap_version='voc2007' 或 'voc2012' 或 'coco',一键切换。

4.5 三种方法的可视化对比

下面这张图展示了同一个PR曲线,用三种方法算出来的AP差异:

三种AP计算方法对比(同一PR曲线) Recall Precision 0 0.5 1.0 0 0.5 1.0 图例 PR曲线 VOC 2007 (11点) VOC 2012 (面积) COCO (101点) VOC 2007只取11个点(红色),VOC 2012计算整个面积(绿色阴影),COCO取101个点(紫色)

从图上你能看到:

  • 红色点是VOC 2007采样的11个位置,它只关心这些点上的值
  • 绿色阴影是VOC 2012计算的面积,它考虑了曲线的每一个转折
  • 紫色点是COCO的101个采样点,比红色密集得多

同一个模型,用VOC 2007算出来可能是0.75,用VOC 2012算出来是0.73,用COCO算出来可能是0.71。不是模型变了,是尺子不一样。

注意: 不要在不同计算方法之间直接比较AP数值。比如A用VOC 2007报了0.80,B用COCO报了0.75,你不能说A比B好——因为尺子不同。一定要在相同的评估协议下比较。

4.6 代码实现要点

如果你要自己实现AP计算,核心逻辑就三步:

  1. 排序: 按置信度从高到低排序所有检测结果
  2. 计算PR: 遍历每个检测结果,累计TP和FP,计算Precision和Recall
  3. 插值: 根据你选择的方法(11点/积分/101点),对PR曲线做插值
# 伪代码:三种方法的统一接口
def compute_ap(recall, precision, method='voc2012'):
    if method == 'voc2007':
        # 11个等分点
        recall_points = np.linspace(0, 1, 11)
        ap = 0
        for r in recall_points:
            # 取右侧最大值
            p = np.max(precision[recall >= r]) if np.any(recall >= r) else 0
            ap += p
        return ap / 11
    
    elif method == 'voc2012':
        # 平滑 + 梯形积分
        # 先对每个recall点取右侧最大值
        precision_smooth = np.maximum.accumulate(precision[::-1])[::-1]
        # 梯形法求面积
        ap = np.trapz(precision_smooth, recall)
        return ap
    
    elif method == 'coco':
        # 101个等分点
        recall_points = np.linspace(0, 1, 101)
        ap = 0
        for r in recall_points:
            p = np.max(precision[recall >= r]) if np.any(recall >= r) else 0
            ap += p
        return ap / 101
我的建议: 别自己手写AP计算,除非你在做学术研究。实际工程中,直接用pycocotools或mmdetection里的评估模块,它们已经帮你处理好了所有细节。我自己只在研究新算法时才手写,平时都是调库。

好了,AP的计算方法就讲到这里。三种方法各有优劣,选哪个取决于你的场景。记住一点:评估协议的一致性比值更重要。下次看到里的mAP,先问一句:“这是用哪个版本算的?”