WinClip非官方复现代码学习笔记10

一、程序结构展示

这次的笔记主要介绍utils文件下的“metrics.py”文件的内容。 

二、代码功能介绍

这段代码是一个用于评估机器学习模型性能的工具函数集合。

  • calculate_max_f1(gt, scores): 这个函数用于计算给定真实标签和预测分数的最大 F1 分数及其对应的阈值。它首先使用 precision_recall_curve 函数计算出一系列不同阈值下的精确率和召回率,然后根据 F1 分数的定义计算 F1 分数,最后返回最大 F1 分数和对应的阈值。

  • metric_cal(scores, gt_list, gt_mask_list, cal_pro=False): 这个函数用于计算模型的多个性能指标,包括图像级 ROC AUC 分数、图像级 F1 分数、像素级 ROC AUC 分数、像素级 F1 分数等。根据参数 cal_pro 的取值,它还可以计算额外的指标。该函数的返回结果是一个包含各项性能指标值的字典。

  • rescale(x): 这个函数用于对输入数组进行归一化,将数组中的值缩放到 [0, 1] 的范围内。

  • cal_pro_metric(labeled_imgs, score_imgs, fpr_thresh=0.3, max_steps=200): 这个函数用于计算基于像素级别预测结果的平均 IoU 分数(Intersection over Union)以及 Pro AUC 分数。它通过比较预测结果和真实标签之间的重叠来计算 IoU 分数,并根据不同的 FPR 阈值计算 Pro AUC 分数。

  • calculate_max_f1_region(labeled_imgs, score_imgs, pro_thresh=0.6, max_steps=200): 这个函数用于计算基于区域级别预测结果的最大 F1 分数。它首先将预测结果二值化,然后对每个区域计算 F1 分数,并返回最大 F1 分数。

这些函数主要用于评估二分类模型在图像分割任务中的性能,涉及到常见的评价指标如 ROC AUC、F1 分数、IoU 等。

三、代码逐行注释

import numpy as np
from skimage import measure  # scikit-image是Python中的图像处理库,提供了丰富的图像处理功能。这行代码引入了包含了一些用于测量图像特征和属性的函数
from sklearn.metrics import auc  # scikit-learn是Python中用于机器学习的库,其中metrics模块包含了各种用于评估模型性能的函数。auc函数用于计算ROC曲线下的面积(AUC,Area Under Curve)
from sklearn.metrics import precision_recall_curve  # precision_recall_curve函数用于计算在不同阈值下的精确度-召回率曲线。
from sklearn.metrics import roc_auc_score  # roc_auc_score函数用于计算ROC曲线下的面积,即ROC AUC
from sklearn.metrics import roc_curve  # roc_curve函数用于计算ROC曲线上的点,即假阳率和真阳率。

def calculate_max_f1(gt, scores):  # 实现了计算F1值的函数,F1值是精确率(Precision)和召回率(Recall)的调和平均,用于评估二分类模型的性能。
    precision, recall, thresholds = precision_recall_curve(gt, scores)  # 这一行计算了给定真实标签(gt)和模型预测得分(scores)的精确率、召回率以及相应的阈值。precision_recall_curve是一个scikit-learn中的函数,用于计算不同阈值下的精确率和召回率。
    a = 2 * precision * recall  # F1值的分子(2 * precision * recall)
    b = precision + recall  # 分母(precision + recall)
    f1s = np.divide(a, b, out=np.zeros_like(a), where=b != 0)  # 使用NumPy的divide函数计算F1值。它首先进行了分母非零的检查(where=b != 0),以避免除以零的情况。如果分母为零,则将F1值设置为零。这里使用out=np.zeros_like(a)参数指定了输出数组的形状和类型,保证了结果的一致性。
    index = np.argmax(f1s)  # 这一行找到了F1值数组中最大值的索引,即找到了最佳F1值所对应的阈值的索引
    max_f1 = f1s[index]  # 获取了最佳F1值和对应的阈值
    threshold = thresholds[index]
    return max_f1, threshold

def metric_cal(scores, gt_list, gt_mask_list, cal_pro=False):  # 实现了计算评估指标的函数,主要用于评估二分类模型的性能
    # calculate image-level ROC AUC score
    img_scores = scores.reshape(scores.shape[0], -1).max(axis=1)  # 将模型的输出得分(scores)转换为一维数组,并且对每个样本取最大值,得到了图像级别的预测得分(img_scores)
    gt_list = np.asarray(gt_list, dtype=int)  # 将真实标签(gt_list)转换为NumPy数组,并确保其数据类型为整数型
    fpr, tpr, _ = roc_curve(gt_list, img_scores)  # 使用roc_curve函数计算了图像级别的ROC曲线的假阳率(fpr)和真阳率(tpr),以及对应的阈值(_)
    img_roc_auc = roc_auc_score(gt_list, img_scores)  # 计算了图像级别的ROC曲线下的面积(ROC AUC),用于评估模型在整个数据集上的性能
    # print('INFO: image ROCAUC: %.3f' % (img_roc_auc))

    img_f1, img_threshold = calculate_max_f1(gt_list, img_scores)  # 调用了calculate_max_f1函数,计算了图像级别的最佳F1值和对应的阈值

    gt_mask = np.asarray(gt_mask_list, dtype=int)  # 将真实标签的掩码列表(gt_mask_list)转换为NumPy数组,并确保其数据类型为整数型
    pxl_f1, pxl_threshold = calculate_max_f1(gt_mask.flatten(), scores.flatten())  # 调用了calculate_max_f1函数,计算了像素级别的最佳F1值和对应的阈值。首先将真实标签的掩码展平为一维数组,然后与模型的输出得分也展平后一起传入函数

    # calculate per-pixel level ROCAUC
    fpr, tpr, _ = roc_curve(gt_mask.flatten(), scores.flatten())  # 再次使用roc_curve函数计算了像素级别的ROC曲线的假阳率(fpr)和真阳率(tpr),以及对应的阈值(_)
    per_pixel_rocauc = roc_auc_score(gt_mask.flatten(), scores.flatten())  # 计算了像素级别的ROC曲线下的面积(ROC AUC),用于评估模型在像素级别上的性能
    # 两个条件判断语句用于决定是否计算像素级别的Pro AUC和最大F1值区域。如果cal_pro参数为True,则计算Pro AUC和最大F1值区域,否则将它们的值设置为0
    if cal_pro:
        pro_auc_score = cal_pro_metric(gt_mask_list, scores, fpr_thresh=0.3)
        # calculate max-f1 region
        max_f1_region = calculate_max_f1_region(gt_mask_list, scores)

    else:
        pro_auc_score = 0
        # calculate max-f1 region
        max_f1_region = 0
    # 这一行创建了一个字典,其中包含了计算得到的评估指标的值。具体包括图像级别的ROC AUC('i_roc')、像素级别的ROC AUC('p_roc')、像素级别的Pro AUC('p_pro')、图像级别的F1值('i_f1')、像素级别的F1值('p_f1')和最大F1值区域('r_f1')
    result_dict = {'i_roc': img_roc_auc * 100, 'p_roc': per_pixel_rocauc * 100, 'p_pro': pro_auc_score * 100,
     'i_f1': img_f1 * 100, 'p_f1': pxl_f1 * 100, 'r_f1': max_f1_region * 100}

    return result_dict


def rescale(x):  # 用于归一化的函数,将输入数组x中的值重新缩放到[0, 1]的范围内
    return (x - x.min()) / (x.max() - x.min())


def cal_pro_metric(labeled_imgs, score_imgs, fpr_thresh=0.3, max_steps=200):
    # 这是一个用于计算pro_auc_score的函数。它接受以下参数:
    # labeled_imgs:标记的图像,通常是二进制掩模或标签图像。
    # score_imgs:模型对图像进行预测后生成的得分图像。
    # fpr_thresh:false positive rate(FPR)的阈值,默认为0.3。
    # max_steps:计算过程中的最大步数,默认为200。
    labeled_imgs = np.array(labeled_imgs)  # 将labeled_imgs转换为NumPy数组,并将其二值化(小于等于0.45的值设为0,大于0.45的值设为1)
    labeled_imgs[labeled_imgs <= 0.45] = 0
    labeled_imgs[labeled_imgs > 0.45] = 1
    labeled_imgs = labeled_imgs.astype(np.bool)

    max_th = score_imgs.max()  # 计算得分图像的最大值和最小值,然后根据最大值和最小值以及max_steps计算步长delta
    min_th = score_imgs.min()
    delta = (max_th - min_th) / max_steps

    ious_mean = []  # 初始化一些空列表来存储计算结果
    ious_std = []  # iou(intersection over union)、pros(每个区域的重叠比例)、fprs(false positive rates)
    pros_mean = []
    pros_std = []
    threds = []
    fprs = []
    binary_score_maps = np.zeros_like(score_imgs, dtype=bool)
    for step in range(max_steps):  # 在每个步骤中,根据当前阈值将得分图像二值化,并计算出每个区域的重叠比例和每个图像的iou
        thred = max_th - step * delta
        # segmentation
        binary_score_maps[score_imgs <= thred] = 0
        binary_score_maps[score_imgs > thred] = 1

        pro = []  # per region overlap
        iou = []  # per image iou
        # pro: find each connected gt region, compute the overlapped pixels between the gt region and predicted region
        # iou: for each image, compute the ratio, i.e. intersection/union between the gt and predicted binary map
        for i in range(len(binary_score_maps)):  # for i th image
            # pro (per region level)
            label_map = measure.label(labeled_imgs[i], connectivity=2)
            props = measure.regionprops(label_map)
            for prop in props:
                x_min, y_min, x_max, y_max = prop.bbox
                cropped_pred_label = binary_score_maps[i][x_min:x_max, y_min:y_max]
                # cropped_mask = masks[i][x_min:x_max, y_min:y_max]
                cropped_mask = prop.filled_image  # corrected!
                intersection = np.logical_and(cropped_pred_label, cropped_mask).astype(np.float32).sum()
                pro.append(intersection / prop.area)
            # iou (per image level)
            intersection = np.logical_and(binary_score_maps[i], labeled_imgs[i]).astype(np.float32).sum()
            union = np.logical_or(binary_score_maps[i], labeled_imgs[i]).astype(np.float32).sum()
            if labeled_imgs[i].any() > 0:  # when the gt have no anomaly pixels, skip it
                iou.append(intersection / union)
        # against steps and average metrics on the testing data
        ious_mean.append(np.array(iou).mean())
        #             print("per image mean iou:", np.array(iou).mean())
        ious_std.append(np.array(iou).std())
        pros_mean.append(np.array(pro).mean())
        pros_std.append(np.array(pro).std())
        # fpr for pro-auc
        masks_neg = ~labeled_imgs
        fpr = np.logical_and(masks_neg, binary_score_maps).sum() / masks_neg.sum()
        fprs.append(fpr)
        threds.append(thred)

    # as array
    threds = np.array(threds)
    pros_mean = np.array(pros_mean)
    pros_std = np.array(pros_std)
    fprs = np.array(fprs)

    # default 30% fpr vs pro, pro_auc
    idx = fprs <= fpr_thresh  # find the indexs of fprs that is less than expect_fpr (default 0.3)
    fprs_selected = fprs[idx]  # 根据设定的fpr_thresh,筛选出相应的FPR和pros_mean,并将FPR进行归一化
    fprs_selected = rescale(fprs_selected)  # rescale fpr [0,0.3] -> [0, 1]
    pros_mean_selected = pros_mean[idx]
    pro_auc_score = auc(fprs_selected, pros_mean_selected)  # 使用auc函数计算归一化后的FPR和pros_mean之间的曲线下面积,得到pro_auc_score
    # print("pro auc ({}% FPR):".format(int(expect_fpr * 100)), pro_auc_score)
    return pro_auc_score

def calculate_max_f1_region(labeled_imgs, score_imgs, pro_thresh=0.6, max_steps=200):
    # 计算模型在不同阈值下的最大 F1 分数及其对应的召回率和精确度,以评估模型在区域级别上的性能
    # labeled_imgs:标记的图像,通常是二进制掩模或标签图像。
    # score_imgs:模型对图像进行预测后生成的得分图像。
    # pro_thresh:F1 分数的阈值,默认为0.6。
    # max_steps:计算过程中的最大步数,默认为200
    labeled_imgs = np.array(labeled_imgs)  #
    # labeled_imgs[labeled_imgs <= 0.1] = 0
    # labeled_imgs[labeled_imgs > 0.1] = 1
    labeled_imgs = labeled_imgs.astype(bool)  # 将labeled_imgs转换为NumPy数组,并将其转换为布尔

    max_th = score_imgs.max()  # 计算得分图像的最大值和最小值,并根据最大值和最小值以及max_steps计算步长 delta
    min_th = score_imgs.min()
    delta = (max_th - min_th) / max_steps

    f1_list = []  # 初始化空列表来存储 F1 分数、召回率和精确度
    recall_list = []
    precision_list = []

    binary_score_maps = np.zeros_like(score_imgs, dtype=bool)  # 初始化二值化后的得分图像二维数组 binary_score_maps,作为预测的区域
    for step in range(max_steps):  # 对于每个阈值步骤
        thred = max_th - step * delta
        # segmentation
        binary_score_maps[score_imgs <= thred] = 0  # 根据当前阈值将得分图像二值化为二进制预测区域
        binary_score_maps[score_imgs > thred] = 1

        pro = []  # per region overlap

        predict_region_number = 0
        gt_region_number = 0

        # pro: find each connected gt region, compute the overlapped pixels between the gt region and predicted region
        # iou: for each image, compute the ratio, i.e. intersection/union between the gt and predicted binary map
        for i in range(len(binary_score_maps)):  # for i th image
            # pro (per region level)
            label_map = measure.label(labeled_imgs[i], connectivity=2)  # 计算标记图像的连通区域并遍历每个区域
            props = measure.regionprops(label_map)

            score_map = measure.label(binary_score_maps[i], connectivity=2)
            score_props = measure.regionprops(score_map)

            predict_region_number += len(score_props)
            gt_region_number += len(props)

            # if len(score_props) == 0 or len(props) == 0:
            #     pro.append(0)
            #     continue

            for score_prop in score_props:  # 计算预测区域与标记区域的交集与并集,并计算重叠比例(intersection over union)
                x_min_0, y_min_0, x_max_0, y_max_0 = score_prop.bbox
                cur_pros = [0]
                for prop in props:
                    x_min_1, y_min_1, x_max_1, y_max_1 = prop.bbox

                    x_min = min(x_min_0, x_min_1)
                    y_min = min(y_min_0, y_min_1)
                    x_max = max(x_max_0, x_max_1)
                    y_max = max(y_max_0, y_max_1)  # 将每个区域的最大重叠比例存储在列表 pro 中

                    cropped_pred_label = binary_score_maps[i][x_min:x_max, y_min:y_max]
                    cropped_gt_label = labeled_imgs[i][x_min:x_max, y_min:y_max]

                    # cropped_mask = masks[i][x_min:x_max, y_min:y_max]
                    # cropped_mask = prop.filled_image  # corrected!
                    intersection = np.logical_and(cropped_pred_label, cropped_gt_label).astype(np.float32).sum()
                    union = np.logical_or(cropped_pred_label, cropped_gt_label).astype(np.float32).sum()
                    cur_pros.append(intersection / union)

                pro.append(max(cur_pros))

        pro = np.array(pro)

        if gt_region_number == 0 or predict_region_number == 0:
            print(f'gt_number: {gt_region_number}, pred_number: {predict_region_number}')
            recall = 0
            precision = 0
            f1 = 0
        else:
            recall = np.array(pro >= pro_thresh).astype(np.float32).sum() / gt_region_number
            precision = np.array(pro >= pro_thresh).astype(np.float32).sum() / predict_region_number

            if recall == 0 or precision == 0:
                f1 = 0
            else:
                f1 = 2 * recall * precision / (recall + precision)


        f1_list.append(f1)
        recall_list.append(recall)
        precision_list.append(precision)

    # as array
    f1_list = np.array(f1_list)  # 计算整个数据集的召回率、精确度和 F1 分数
    max_f1 = f1_list.max()
    cor_recall = recall_list[f1_list.argmax()]
    cor_precision = precision_list[f1_list.argmax()]
    print(f'cor recall: {cor_recall}, cor precision: {cor_precision}')
    return max_f1

  • 12
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值