mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
689 字
2 分钟
评价指标封装
2025-11-09

为什么需要封装评价指标?#

做深度学习实验时,经常需要同时算好几个指标——Accuracy、IOU、F1、Precision、Recall 等等。如果每个指标单独写一个函数,代码就会变得很零散,而且容易重复计算(比如 intersection 和 union 在多个指标里都要用)。把它们封装在一个函数里,一次计算、统一返回,既整洁又高效。

什么时候用哪个指标?#

Accuracy(准确率)#

Accuracy=TP+TNTP+TN+FP+FN\text{Accuracy} = \frac{TP + TN}{TP + TN + FP + FN}
  • 适用场景:类别均衡的数据集
  • 问题:如果正样本只占 1%,模型全预测为负样本也能有 99% 的准确率,但毫无意义。所以类别不平衡时别只看 Accuracy。

IOU(交并比)#

IOU=TPTP+FP+FN\text{IOU} = \frac{TP}{TP + FP + FN}
  • 适用场景:目标检测、语义分割
  • 特点:比 Accuracy 更严格,因为只看预测为正的区域里有多少是真正正确的。IOU > 0.5 通常认为检测成功。
  • 注意:IOU 对小目标不友好——小目标稍微偏移一点,IOU 就掉得很厉害。

Precision(精确率)和 Recall(召回率)#

  • Precision = TP / (TP + FP) —— 预测为正的样本中有多少是真的正
  • Recall = TP / (TP + FN) —— 真实为正的样本中有多少被找出来了

两者通常此消彼长,所以需要 F1 来综合平衡。

F1 Score#

F1=2×Precision×RecallPrecision+RecallF1 = 2 \times \frac{Precision \times Recall}{Precision + Recall}
  • 适用场景:需要同时关注 Precision 和 Recall 的情况
  • 特点:是 Precision 和 Recall 的调和平均数,对极端值更敏感(比算术平均更严格)

常见坑点#

  1. 分母为零:如果预测结果全是负样本,precision 的分母 TP + FP = 0,直接除会报错。代码里最好加个判断,返回 0 或 NaN。
  2. 数据类型:mask 是布尔类型还是浮点类型?sum() 的结果是不是预期的?用 torch.float().sum() 比 .sum() 更安全,避免 bool 转 int 的隐式转换。
  3. IOU 和 Accuracy 的混淆:在分割任务中,Accuracy 算的是所有像素的正确率,IOU 算的是正类区域的交并比。如果物体很小,Accuracy 可能很高但 IOU 很低——两者都算才全面。
  4. batch 处理:上面的代码是单张图的,实际训练时要处理 batch。要么在 batch 维度上累加再算,要么每张图算完再平均。

封装后的代码#

import torch
def calculate_evaluation_metrics(mask_true, mask_pred):
intersection = torch.logical_and(mask_true, mask_pred).float().sum()
union = torch.logical_or(mask_true, mask_pred).float().sum()
iou = intersection / union
true_positive = torch.logical_and(mask_true, mask_pred).float().sum()
false_positive = torch.logical_and(~mask_true, mask_pred).float().sum()
false_negative = torch.logical_and(mask_true, ~mask_pred).float().sum()
precision = true_positive / (true_positive + false_positive)
recall = true_positive / (true_positive + false_negative)
f1_score = 2 * (precision * recall) / (precision + recall)
accuracy = (true_positive + torch.logical_and(~mask_true, ~mask_pred).float().sum()) / mask_true.numel()
metrics = {
'accuracy': accuracy.item(),
'iou': iou.item(),
'precision': precision.item(),
'recall': recall.item(),
'f1_score': f1_score.item()
}
return metrics

改进建议#

上面的代码可以加一个 epsilon 避免除零:

def calculate_evaluation_metrics(mask_true, mask_pred, eps=1e-7):
# ... 计算 TP, FP, FN ...
precision = true_positive / (true_positive + false_positive + eps)
recall = true_positive / (true_positive + false_negative + eps)
iou = intersection / (union + eps)
f1 = 2 * precision * recall / (precision + recall + eps)
# ...

这样在极端情况下也不会崩,在训练脚本里省心很多。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

部分信息可能已经过时

目录