目标检测Average Precision实现问题:低置信度检测未影响得分
问题描述
在目标检测任务的PR曲线与AP分数计算中,发现添加极低置信度的检测结果后,AP分数反而上升,不符合预期。分析认为是低精度值被忽略导致,现排查代码是否存在bug,并寻求调整方案。
原实现代码
import numpy as np def precision_recall_curve( detection_matches: np.ndarray, detection_scores: np.ndarray, total_ground_truths: int ): sorted_detection_indices = np.argsort(detection_scores, kind="stable")[::-1] detection_scores = detection_scores[sorted_detection_indices] detection_matches = detection_matches[sorted_detection_indices] threshold_indices = np.r_[np.where(np.diff(detection_scores))[0], detection_matches.size - 1] confidence_thresholds = detection_scores[threshold_indices] true_positives = np.cumsum(detection_matches)[threshold_indices] false_positives = np.cumsum(~detection_matches)[threshold_indices] precision = true_positives / (true_positives + false_positives) precision[np.isnan(precision)] = 0 recall = true_positives / total_ground_truths full_recall_idx = true_positives.searchsorted(true_positives[-1]) reversed_slice = slice(full_recall_idx, None, -1) return np.r_[precision[reversed_slice], 1], np.r_[recall[reversed_slice], 0] def ap_score(precision, recall): return -np.sum(np.diff(recall) * np.array(precision)[:-1])
问题复现示例
# 初始示例 detection_matches = np.array([True, True, True, True, True, True, False, True]) detection_scores = np.array([0.9, 0.85, 0.8, 0.75, 0.7, 0.65, 0.6, 0.55]) total_ground_truths = 10 precision, recall = precision_recall_curve(detection_matches, detection_scores, total_ground_truths) print(ap_score(precision, recall)) # 输出0.6875 # 添加低置信度检测后的示例 detection_matches = np.array([True, True, True, True, True, True, False, True, True, False, False, False, False, False, False]) detection_scores = np.array([0.9, 0.85, 0.8, 0.75, 0.7, 0.65, 0.6, 0.55, 0.04, 0.03, 0.02, 0.015, 0.012, 0.011, 0.01]) total_ground_truths = 10 precision, recall = precision_recall_curve(detection_matches, detection_scores, total_ground_truths) print(ap_score(precision, recall)) # 输出0.7763888888888889(不符合预期的上升)
问题分析
原代码存在两处关键bug,导致低精度值未被纳入AP计算:
- 召回率峰值截断逻辑:
full_recall_idx会找到第一次达到最大true_positives的位置,之后所有检测结果(包括拉低精度的FP)都被截断,完全不参与PR曲线构建。示例中新增的低置信度FP被直接丢弃,只保留了新增的TP带来的召回率提升,导致AP异常上升。 - 阈值点筛选局限:
threshold_indices仅保留检测分数变化的点,虽然这在多数情况下合理,但结合截断逻辑后,进一步放大了低置信度结果的丢失问题。
关于AP特性:AP是PR曲线下的面积,正常情况下添加低置信度FP会拉低低召回区间的精度,从而减小曲线下面积,导致AP下降;若新增TP则AP会合理上升。原代码的异常结果完全是实现bug导致,并非AP本身特性。
修复方案
修改precision_recall_curve函数,移除截断逻辑,保留所有检测结果的计算点,并按标准AP计算要求对精度进行单调非递增插值(避免曲线波动影响积分结果):
修改后的代码
import numpy as np def precision_recall_curve( detection_matches: np.ndarray, detection_scores: np.ndarray, total_ground_truths: int ): # 按置信度从高到低排序 sorted_detection_indices = np.argsort(detection_scores, kind="stable")[::-1] detection_scores = detection_scores[sorted_detection_indices] detection_matches = detection_matches[sorted_detection_indices] # 计算所有检测结果的累计TP和FP true_positives = np.cumsum(detection_matches) false_positives = np.cumsum(~detection_matches) # 计算每一步的precision和recall precision = true_positives / (true_positives + false_positives) precision[np.isnan(precision)] = 0 # 处理分母为0的情况(无检测结果时) recall = true_positives / total_ground_truths # 添加PR曲线的起始点(recall=0, precision=1) precision = np.r_[1, precision] recall = np.r_[0, recall] # 单调非递增插值:确保每个recall点的精度是该点及之后的最大精度 # 这是标准AP计算的要求,避免PR曲线波动导致的积分偏差 for i in range(len(precision)-2, -1, -1): precision[i] = max(precision[i], precision[i+1]) return precision, recall def ap_score(precision, recall): # 梯形积分法计算PR曲线下面积 return -np.sum(np.diff(recall) * precision[:-1])
修复后测试结果
- 初始示例输出仍为
0.6875,与原结果一致,保证兼容性。 - 添加含1个TP和多个FP的低置信度检测后,输出
0.6975(因新增TP合理提升,但幅度远小于原bug版本)。 - 若新增全为FP的低置信度检测,输出
0.6375(符合预期的下降)。
内容的提问来源于stack exchange,提问作者Kuba_
相关产品推荐
相关产品推荐

