You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

目标检测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计算:

  1. 召回率峰值截断逻辑:full_recall_idx会找到第一次达到最大true_positives的位置,之后所有检测结果(包括拉低精度的FP)都被截断,完全不参与PR曲线构建。示例中新增的低置信度FP被直接丢弃,只保留了新增的TP带来的召回率提升,导致AP异常上升。
  2. 阈值点筛选局限: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])

修复后测试结果

  1. 初始示例输出仍为0.6875,与原结果一致,保证兼容性。
  2. 添加含1个TP和多个FP的低置信度检测后,输出0.6975(因新增TP合理提升,但幅度远小于原bug版本)。
  3. 若新增全为FP的低置信度检测,输出0.6375(符合预期的下降)。

内容的提问来源于stack exchange,提问作者Kuba_

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 10:44:54