评估计算recall、precision、AP、F1、mAP(PyTorch-YOLOv3代码解析二)

本文详细介绍了如何使用PyTorch实现YOLOv3目标检测中的评估函数ap_per_class,包括计算AP(Average Precision)、召回率、精确度和F1分数。涉及数据预处理、TP统计以及多幅图像的批量评估过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目标检测评估计算(utils.py)

代码github地址:https://github.com/eriklindernoren/PyTorch-YOLOv3

1. 检测的评估函数

# reference: https://github.com/eriklindernoren/PyTorch-YOLOv3/blob/f917503ffe4a21d2b1148d8cb13b89b834517d76/utils/utils.py

def ap_per_class(tp, conf, pred_cls, target_cls):
    """ 通过召回率与精确度曲线计算mAP
    Source: https://github.com/rafaelpadilla/Object-Detection-Metrics.
    # 参数说明
        tp: True positives (list).
        conf: 置信度[0,1] (list).
        pred_cls: 预测的目标类别 (list).
        target_cls: 真正的目标类别 (list).
    # 返回
          [precision,recall,average precision,f1, classes_num]
    """

    # 按照预测的置信度做降序排列, 得到排序的索引
    i = np.argsort(-conf)
    tp, conf, pred_cls = tp[i], conf[i], pred_cls[i]

    # 去除重复项
    unique_classes = np.unique(target_cls)

    # 为每个类别创建精度-召回曲线, 计算AP
    ap, p, r = [], [], []
    for c in tqdm.tqdm(unique_classes, desc="Computing AP"):
        # 找出等于c的位置
        i = pred_cls == c
        # 类别c的人工标注目标的数量
        n_gt = (target_cls == c).sum()
        # 类别c的预测目标数量
        n_p = i.sum()

        if n_p == 0 and n_gt == 0:
            continue
        elif n_p == 0 or n_gt == 0:
            ap.append(0)
            r.append(0)
            p.append(0)
        else:
            # 累加计算FPs与TPs
            fpc = (1 - tp[i]).cumsum()  #
            tpc = (tp[i]).cumsum()

            # Recall
            recall_curve = tpc / (n_gt + 1e-16)  # TP/(TP + FN) (TP+FN)为当前类人工标注目标数量
            r.append(recall_curve[-1]) # 这个类别的召回率

            # Precision
            precision_curve = tpc / (tpc + fpc)  # TP/(TP + FP) (TP+FP)为预测框的数量
            p.append(precision_curve[-1]) # 这个类别的精确率

            # 从召回率-精确率曲线计算AP
            ap.append(compute_ap(recall_curve, precision_curve))

    # Compute F1 score (harmonic mean of precision and recall)
    p, r, ap = np.array(p), np.array(r), np.array(ap)
    f1 = 2 * p * r / (p + r + 1e-16)

    return p, r, ap, f1, unique_classes.astype("int32")

2. batch预测数据的统计

(1) 数据加载。通过统计非极大值抑制后得到的outputs与人工标注框的targets条目,得到TP(目标预测正确)。统计的数据为一个batch的,保存的数据为statistics_data.pt,输出数据的shape。

# 非极大值抑制后的outputs,与targets
batch_statistics = torch.load('statistics_data.pt',map_location='cpu')
outputs = batch_statistics['outputs']
targets = batch_statistics['targets']
print('outputs_shape: ',[x.shape for x in outputs])
print('targets_size: ', targets.shape,end='\n\n')

输出结果:

数据statistics_data.pt保存的是8幅图像预测框与人工标注框数据,其中outputs的数据由非极大值抑制获取。outputs数据格式[x1,y1,x2,y2,conf,class_score,class_idx],targets数据格式[batch_idx,class_idx,x1,y1,x2,y2]。数据中的坐标是相对于网络中的输入尺寸。

(2) 统计TP。当某幅图像的某个预测框与targets中的真实框的IoU大于某个阈值,则表示该预测框能够作为targets中真实框的预测值。

# output为某幅图像的预测框,初始化tp
true_positives = np.zeros(output.shape[0])
# 找出targets中batch_idx=i的class_idx与标注框坐标
annotations = targets[targets[:, 0] == i][:, 1:]
    
if len(annotations):
    # 真实的目标类别代号,标注框
    target_labels = annotations[:, 0]
    target_boxes = annotations[:, 1:]
    print('target_boxes: ',target_boxes,',target_labels: ',target_labels)   
    detected_boxes = []
    # 遍历预测框索引,预测框坐标,预测类别代号 
    for pred_i,(pred_box, pred_cls) in enumerate(zip(output[:,:4],output[:,-1])):
        if len(detected_boxes) == len(target_boxes): break
        if pred_cls not in target_labels: continue 
        # 预测框去拟合目标框(通过最大的IoU加阈值判断)   
        iou, box_index = bbox_iou(pred_box.unsqueeze(0), target_boxes).max(0)
        print('pred_{}'.format(pred_i),iou, ',box_index: ',box_index)
        if iou >= iou_threshold and box_index not in detected_boxes:
            # 预测真实值则赋值相应位置为1
            true_positives[pred_i] = 1
            detected_boxes += [box_index]
   

输出结果

[true_positives, pred_confs, pred_cls]:

多幅图像的统计

# batch统计
batch_metrics = []
for i,output in enumerate(outputs):
    true_positives = np.zeros(output.shape[0])
    annotations = targets[targets[:, 0] == i][:, 1:]
    
    if len(annotations):
        target_boxes = annotations[:, 1:]
        target_labels = annotations[:, 0]
        detected_boxes = []
        
        for pred_i,(pred_box, pred_cls) in enumerate(zip(output[:,:4],output[:,-1])):
            if len(detected_boxes) == len(target_boxes): break
            if pred_cls not in target_labels: continue 
            
            iou, box_index = bbox_iou(pred_box.unsqueeze(0), target_boxes).max(0)
            if iou >= iou_threshold and box_index not in detected_boxes:
                true_positives[pred_i] = 1
                detected_boxes += [box_index]
    # 每幅图像的TP,预测的目标置信度,预测类别代号
    batch_metrics.append([true_positives,output[:,4],output[:,-1]])

3. Recall、Precision、F1、AP、mAP计算

(1) 获取batch统计的结果与排序

batch_metrics的数据结构(list):

[[true_positives1, pred_confs1, pred_cls1],

[true_positives2, pred_confs2, pred_cls2],

[true_positives3, pred_confs3, pred_cls3],

......]

通过解包重组: list(zip(*batch_metrics)),然后得到如下:

[(true_positives1, true_positives2, ......),

(pred_confs1, pred_confs2, ......),

(pred_cls1, pred_cls2, ......) ]

# 解包一个batch的数据
true_positives, pred_confs, pred_cls = [np.concatenate(x, 0) for x in list(zip(*batch_metrics))]

按照置信度降序排列统计的数据

# 按照置信度降序排序,排序tp,conf,cls
idx = np.argsort(-pred_confs)
tp, pre_confs, pred_cls = true_positives[idx],pred_confs[idx],pred_cls[idx]

(2) 评估一个batch的预测结果。通常情况是通过第2步的统计,统计出整个验证图片库的[true_positives, pred_confs, pred_cls]数据,然后再计算相应的评估值。

a. 召回率、精确度曲线

# 人工标注的类别
unique_cls = np.unique(target_cls)

# 每个类别创建精度-召回曲线, 计算AP
ap, p, r = [], [], []
for num, c in enumerate(unique_cls):
    idx = pred_cls==c
    # 类别c的人工标注目标的数量
    n_gt = (target_cls == c).sum().numpy()
    n_p = idx.sum()
    print('n_gt: ',n_gt,',n_p: ',n_p)
    
    if n_p == 0 and n_gt == 0:
        continue
    elif n_p == 0 or n_gt == 0:
        ap.append(0)
        r.append(0)
        p.append(0)
    else:
        # 累加计算FPs与TPs
        fpc = (1 - tp[idx]).cumsum()
        tpc = tp[idx].cumsum()
        
        # 召回率
        recall_curve = tpc/(n_gt + 1e-16) # TP/(TP+FN)
        r.append(recall_curve[-1])
        
        # 精确率
        precision_curve = tpc/(tpc+fpc) # TP/(TP+FP)
        p.append(precision_curve[-1])
        print('recall_curve: ',recall_curve,'\nprecision_curve: ',precision_curve)

一个类别召回率、精确度曲线输出:

b. AP计算

AP是召回率-精确度曲线的面积,采用的方法为积分求面积。通过plot绘制的曲线如下图所示:

积分的方法需要找出图中recall存在梯度的位置,即上图红色线与红色线之间在recall方向存在梯度(也就是相邻的两个点的recall值不同)。把整个面积区域划分成坐标点-1个条形图(图中8个点,则有7个条形图),然后求和所有条形图的面积得到AP的面积。实现代码如下:

# 计算AP,检测评估函数compute_ap
mrec = np.concatenate(([0.0], recall_curve, [1.0]))
mpre = np.concatenate(([1.0], precision_curve, [0.0]))
        
for i in range(mpre.size - 1, 0, -1):
    mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i])
        
# 找出召回率有梯度的位置
idx = np.where(mrec[1:] != mrec[:-1])[0]
# 计算面积 sum(delta_recall*precision)
ap_c = np.sum((mrec[idx+1] - mrec[idx])*mpre[idx + 1])

c. 计算F1

f1 = 2 * p * r / (p + r + 1e-16)

d. 总体代码

# 评估一个batch的预测结果
true_positives, pred_confs, pred_cls = [np.concatenate(x, 0) for x in list(zip(*batch_metrics))]
target_cls = targets[:,1]

# 按照置信度降序排序,排序tp,conf,cls
idx = np.argsort(-pred_confs)
tp, pre_confs, pred_cls = true_positives[idx],pred_confs[idx],pred_cls[idx]

# 得到人工标注的类别
unique_cls = np.unique(target_cls)

# 为每个类别创建精度-召回曲线, 计算AP
ap, p, r = [], [], []
for num, c in enumerate(unique_cls):
    idx = pred_cls==c
    # 类别c的人工标注目标的数量
    n_gt = (target_cls == c).sum().numpy()
    n_p = idx.sum()
    
    if n_p == 0 and n_gt == 0:
        continue
    elif n_p == 0 or n_gt == 0:
        ap.append(0)
        r.append(0)
        p.append(0)
    else:
        # 累加计算FPs与TPs
        fpc = (1 - tp[idx]).cumsum()
        tpc = tp[idx].cumsum()
        
        # 召回率
        recall_curve = tpc/(n_gt + 1e-16) # TP/(TP+FN)
        r.append(recall_curve[-1])
        
        # 精确率
        precision_curve = tpc/(tpc+fpc) # TP/(TP+FP)
        p.append(precision_curve[-1])
        
        # 计算AP
        mrec = np.concatenate(([0.0], recall_curve, [1.0]))
        mpre = np.concatenate(([1.0], precision_curve, [0.0]))
        
        for i in range(mpre.size - 1, 0, -1):
            mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i])
        
        # 找出召回率有梯度的位置
        idx = np.where(mrec[1:] != mrec[:-1])[0]
        
        # 计算面积
        ap_c = np.sum((mrec[idx+1] - mrec[idx])*mpre[idx + 1])
        ap.append(ap_c)

# batch中所有类别       
p, r, ap = np.array(p), np.array(r), np.array(ap)
f1 = 2 * p * r / (p + r + 1e-16)

#[类别,召回率,精确率,F1, AP]
str_list = ['%d \t %.3f \t %.3f \t %.3f \t %.3f'%(x[0],x[1],x[2],x[3],x[4]) for x in np.array([unique_cls,r,p,f1,ap]).T]
print('cls_num | recall | precision | F1 | AP')
for x in str_list:
    print(x)

print('mAP:', np.mean(ap))

输出结果

<think>好的,我现在要解决用户的问题,关于Python中`ap_per_class`函数的作用以及`.mean`方法属于NumPy还是PyTorch。首先,我需要确认用户提到的`ap_per_class`函数的来源。通常在目标检测任务中,比如YOLO或类似的模型评估中,会用到计算每个类别的平均精度(Average Precision, AP),然后取所有类别的平均值得到mAP(mean Average Precision)。因此,`ap_per_class`很可能是一个用于计算每个类别AP的函数。 接下来,关于`.mean`方法,用户想知道这是NumPy还是PyTorch的方法。我需要回忆这两个库中计算均值的方法。NumPy的数组对象有`.mean()`方法,同样,PyTorch的张量也有`.mean()`方法。所以,具体属于哪个库取决于调用对象的数据类型。如果结果是一个NumPy数组,那么`.mean()`就是NumPy的方法;如果是PyTorch张量,就是PyTorch的方法。 然后,我需要检查用户提供的引用内容,看是否有相关线索。用户引用了环境配置的信息,包括安装了PyTorch 1.13.0和flash_attn,以及requirements.txt。这可能意味着代码中使用了PyTorch,但也不能排除同时使用NumPy的情况。不过,在深度学习模型中,数据通常以张量形式处理,但评估指标可能会转换为NumPy数组进行计算,因为NumPy在数值计算上更为方便。 接下来,我需要详细解释`ap_per_class`函数的功能。这个函数通常接收模型的预测结果、真实标签、类别数等参数,计算每个类别的精确率-召回率曲线下的面积(AP),然后可能对所有类别的AP取平均得到mAP。具体的实现可能包括匹配预测框和真实框,计算TP(True Positive)和FP(False Positive),按置信度排序,计算精确率和召回率,然后积分得到AP。 对于`.mean()`方法,需要说明其作用是对数组或张量中的元素取平均值,并指出在两种库中的使用方式。例如,NumPy中的`np.array([1,2,3]).mean()`会返回2.0,而PyTorch中的`torch.tensor([1,2,3]).float().mean()`也会返回同样的结果。同时,提醒用户注意数据类型,确保在计算前数据已经转换为正确的类型,避免错误。 可能用户的问题来源于阅读或编写目标检测评估代码,需要理解AP计算过程以及如何利用.mean方法得到mAP。用户可能对评估指标的具体实现细节不熟悉,需要明确函数的作用和方法的来源,以便调试或修改代码。 最后,生成相关问题,需要考虑用户可能进一步想知道如何实现AP计算,或处理多类别的评估,以及如何在不同框架下进行均值计算。相关问题需要围绕这些主题展开,帮助用户深入理解。</think>### `ap_per_class`函数解析 `ap_per_class`是目标检测任务中常用的评估指标计算函数,主要用于计算每个类别的平均精度(Average Precision, AP)。该函数通常出现在YOLO系列等目标检测框架的评估模块中,其核心作用包括: 1. 统计每个类别的预测置信度和真实标签 2. 计算精确率(precision)和召回率(recall)曲线 3. 通过积分运算求取PR曲线下面积(即AP值) 典型函数参数结构: ```python def ap_per_class(tp, conf, pred_cls, target_cls): # tp: 预测是否为真正例的布尔数组 # conf: 预测置信度数组 # pred_cls: 预测类别数组 # target_cls: 真实类别数组 ``` ### `.mean()`方法归属说明 `.mean()`方法同时存在于NumPy和PyTorch中,其归属取决于调用对象的数据类型: | 框架 | 调用示例 | 特点 | |---------|-------------------------------|---------------------------| | NumPy | `np.array([1,2,3]).mean()` | 默认计算算术平均值 | | PyTorch | `torch.tensor([1,2,3]).mean()`| 支持指定维度和数据类型[^1] | ### 典型实现代码段 ```python # 计算每个类别的AP aps = [] for cls in unique_classes: # 获取当前类别的预测结果 cls_mask = (pred_cls == cls) tp_cls = tp[cls_mask] conf_cls = conf[cls_mask] # 按置信度降序排序 sorted_indices = np.argsort(-conf_cls) tp_sorted = tp_cls[sorted_indices] # 计算累积TP/FP tp_cumsum = np.cumsum(tp_sorted) fp_cumsum = np.cumsum(~tp_sorted) # 计算精确率和召回率 precision = tp_cumsum / (tp_cumsum + fp_cumsum + 1e-16) recall = tp_cumsum / (n_gt + 1e-16) # 计算AP(使用11点插值法) aps.append(compute_ap(recall, precision)) # 计算mAP mAP = np.array(aps).mean() ``` ### 使用注意事项 1. 输入数据需要预先完成预测与真实标签的匹配 2. 置信度排序应采用稳定排序算法 3. 需要处理除零情况(如添加epsilon) 4. AP计算方式需与评估标准一致(如PASCAL VOC或COCO标准)
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值