语义分割实战:5分钟掌握五大核心指标计算与Python实现
刚接触语义分割时,最让人头疼的莫过于那一堆缩写指标——PA、CPA、MPA、IoU、MIoU。第一次看到这些名词时,我盯着论文里的公式看了半天,还是不明白它们到底在说什么。直到自己动手写代码实现后,才发现这些指标的计算原来如此直观。本文将用最简单的语言和可运行的Python代码,带你快速理解这些指标的计算逻辑。
1. 从混淆矩阵开始:语义分割的评估基础
任何分类问题的评估都离不开混淆矩阵,语义分割作为像素级分类任务也不例外。假设我们有一个3x3的预测结果和真实标签:
python复制import numpy as np
# 预测结果 (模型输出)
pred = np.array([
[0, 0, 1],
[0, 2, 1],
[2, 2, 2]
])
# 真实标签 (人工标注)
label = np.array([
[0, 0, 1],
[0, 1, 1],
[2, 2, 1]
])
构建混淆矩阵时,我们统计每个类别被预测为其他类别的次数。对于3分类问题,混淆矩阵是3x3的:
code复制 预测
类0 类1 类2
真 类0 2 0 0
实 类1 0 2 1
标 类2 0 1 2
这个矩阵告诉我们:
- 类0的2个像素全部预测正确
- 类1的3个像素中2个预测正确,1个被预测为类2
- 类2的3个像素中2个预测正确,1个被预测为类1
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 五大核心指标详解与Python实现
2.1 像素准确率(PA):最简单的评估指标
PA(Pixel Accuracy)计算所有像素中被正确分类的比例:
python复制def pixel_accuracy(confusion_matrix):
return np.diag(confusion_matrix).sum() / confusion_matrix.sum()
对于上面的例子:
- 正确像素数 = 2(类0) + 2(类1) + 2(类2) = 6
- 总像素数 = 9
- PA = 6/9 ≈ 0.6667
PA的局限性:当类别极度不均衡时,PA会失真。比如背景占90%的图像,模型只要全部预测为背景就能获得90%的PA。
2.2 类别像素准确率(CPA):各类别的精确率
CPA(Class Pixel Accuracy)计算每个类别被正确预测的比例:
python复制def class_pixel_accuracy(confusion_matrix):
return np.diag(confusion_matrix) / confusion_matrix.sum(axis=1)
计算结果为:
- CPA类0 = 2/(2+0+0) = 1.0
- CPA类1 = 2/(0+2+1) ≈ 0.6667
- CPA类2 = 2/(0+1+2) ≈ 0.6667
2.3 平均像素准确率(MPA):CPA的平均值
MPA(Mean Pixel Accuracy)就是各类别CPA的平均:
python复制def mean_pixel_accuracy(confusion_matrix):
class_acc = class_pixel_accuracy(confusion_matrix)
return np.nanmean(class_acc)
上面例子的MPA = (1.0 + 0.6667 + 0.6667)/3 ≈ 0.7778
2.4 交并比(IoU):最常用的分割指标
IoU(Intersection over Union)计算预测和真实区域的交集与并集之比:
python复制def iou(confusion_matrix):
intersection = np.diag(confusion_matrix)
union = confusion_matrix.sum(axis=1) + confusion_matrix.sum(axis=0) - intersection
return intersection / union
计算过程:
- 类0 IoU = 2/(2+0+0+0+0) = 1.0
- 类1 IoU = 2/(2+1+0+1) = 0.5
- 类2 IoU = 2/(2+1+0+1) = 0.5
2.5 平均交并比(MIoU):IoU的均值
MIoU(Mean IoU)就是各类别IoU的平均值:
python复制def mean_iou(confusion_matrix):
return np.nanmean(iou(confusion_matrix))
上例中MIoU = (1.0 + 0.5 + 0.5)/3 ≈ 0.6667
3. 完整实现:面向实际应用的指标计算类
下面是一个可以直接用于项目的Python类实现:
python复制class SegmentationMetrics:
def __init__(self, num_classes):
self.num_classes = num_classes
self.confusion_matrix = np.zeros((num_classes, num_classes))
def update(self, pred, label):
mask = (label >= 0) & (label < self.num_classes)
hist = np.bincount(
self.num_classes * label[mask].astype(int) + pred[mask],
minlength=self.num_classes**2
).reshape(self.num_classes, self.num_classes)
self.confusion_matrix += hist
def compute_pa(self):
return np.diag(self.confusion_matrix).sum() / self.confusion_matrix.sum()
def compute_cpa(self):
return np.diag(self.confusion_matrix) / (self.confusion_matrix.sum(axis=1) + 1e-12)
def compute_mpa(self):
return np.nanmean(self.compute_cpa())
def compute_iou(self):
intersection = np.diag(self.confusion_matrix)
union = (self.confusion_matrix.sum(axis=1) +
self.confusion_matrix.sum(axis=0) -
intersection)
return intersection / (union + 1e-12)
def compute_miou(self):
return np.nanmean(self.compute_iou())
def reset(self):
self.confusion_matrix = np.zeros((self.num_classes, self.num_classes))
使用示例:
python复制metrics = SegmentationMetrics(num_classes=3)
metrics.update(pred, label)
print(f"PA: {metrics.compute_pa():.4f}")
print(f"CPA: {metrics.compute_cpa()}")
print(f"MPA: {metrics.compute_mpa():.4f}")
print(f"IoU: {metrics.compute_iou()}")
print(f"MIoU: {metrics.compute_miou():.4f}")
4. 实际应用中的注意事项与优化技巧
4.1 处理类别不平衡问题
当某些类别像素数量极少时,可以:
- 使用加权MIoU,根据类别频率赋予不同权重
- 忽略像素数少于阈值的类别
python复制def weighted_miou(confusion_matrix, class_weights):
iou_scores = iou(confusion_matrix)
return np.nansum(iou_scores * class_weights) / np.sum(class_weights)
4.2 多批次计算的正确方式
对于大型数据集,应该:
- 累计所有批次的混淆矩阵
- 最后统一计算指标
python复制metrics = SegmentationMetrics(num_classes=3)
for batch in dataloader:
pred = model(batch["image"])
metrics.update(pred.argmax(1), batch["label"])
final_miou = metrics.compute_miou()
4.3 常见错误排查
- 指标值异常高:检查是否混淆了预测和标签的顺序
- NaN值出现:某些类别可能没有出现在验证集中
- 指标波动大:确保shuffle=True,使每批样本分布均匀
python复制# 检查混淆矩阵是否合理
print(metrics.confusion_matrix)
# 处理NaN值
iou_scores = metrics.compute_iou()
iou_scores = np.nan_to_num(iou_scores, nan=0) # 将NaN替换为0
掌握这些指标的计算原理后,我发现自己阅读论文时能更快理解实验结果,调参时也更有针对性。特别是在处理医疗影像这类类别不平衡数据时,单纯看PA往往会误判模型性能,而MIoU能给出更全面的评估。
