4、AP的计算方法:VOC 2007的11点插值法、VOC 2012的积分法、COCO的101点插值法
聊完了PR曲线和AP的基本概念,咱们得动真格的了——到底怎么把AP算出来?
说实话,我刚入行那会儿,觉得AP就是个数字。直到有一次,我用不同版本的评估代码跑同一个模型,结果差了3个点。查了半天才发现,原来是VOC 2007和VOC 2012的计算方法不一样。嗯,从那以后,我再也不敢小看这个“计算细节”了。
4.1 VOC 2007:11点插值法
VOC 2007的方法,说白了就是“取11个点,求平均”。
具体怎么做?
- 把Recall从0到1分成11个等分点:0.0, 0.1, 0.2, ..., 1.0
- 在每个Recall点上,取该点右侧(包含该点)所有Precision的最大值
- 把这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这个点只是“暂时表现不好”,应该用后面的最高值来代表它。
4.2 VOC 2012:积分法(面积法)
VOC 2012改进了。它不再只取11个点,而是计算PR曲线下的实际面积。
怎么做?
- 对PR曲线做平滑处理:在每个Recall点,取该点右侧所有Precision的最大值
- 计算平滑后曲线下的面积(用梯形法积分)
你可能会问:“这不还是取最大值吗?跟2007有什么区别?”
区别大了。2007只取了11个点,2012用了所有点。你想想看,如果PR曲线在Recall=0.35处有一个尖峰,2007可能完全错过它(因为0.35不在11个点里),但2012会把它算进去。
| 对比项 | VOC 2007 | VOC 2012 |
|---|---|---|
| 采样方式 | 11个等分点 | 所有检测点 |
| 插值方式 | 取右侧最大值 | 取右侧最大值 + 梯形积分 |
| 精度 | 较粗糙 | 更精细 |
| 对抖动敏感度 | 不敏感 | 较敏感 |
| 典型应用 | VOC 2007挑战赛 | VOC 2012及之后 |
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倍。
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点法,算得快,够用就行
- 正式提交结果前,一定用目标数据集官方指定的方法再跑一遍
ap_version='voc2007' 或 'voc2012' 或 'coco',一键切换。
4.5 三种方法的可视化对比
下面这张图展示了同一个PR曲线,用三种方法算出来的AP差异:
从图上你能看到:
- 红色点是VOC 2007采样的11个位置,它只关心这些点上的值
- 绿色阴影是VOC 2012计算的面积,它考虑了曲线的每一个转折
- 紫色点是COCO的101个采样点,比红色密集得多
同一个模型,用VOC 2007算出来可能是0.75,用VOC 2012算出来是0.73,用COCO算出来可能是0.71。不是模型变了,是尺子不一样。
4.6 代码实现要点
如果你要自己实现AP计算,核心逻辑就三步:
- 排序: 按置信度从高到低排序所有检测结果
- 计算PR: 遍历每个检测结果,累计TP和FP,计算Precision和Recall
- 插值: 根据你选择的方法(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的计算方法就讲到这里。三种方法各有优劣,选哪个取决于你的场景。记住一点:评估协议的一致性比值更重要。下次看到里的mAP,先问一句:“这是用哪个版本算的?”