图像分割领域常见的评价指标和损失函数

第一部分:评价指标

评价指标用于模型训练完成后,在验证集或测试集上量化模型性能

1. 交并比 (IoU - Intersection over Union)

原理
IoU是分割领域最核心的指标。它计算的是模型预测的分割区域真实的分割区域之间的重叠程度。对于每个类别,其计算公式为:

\(IoU = \frac{Target \cap Prediction}{Target \cup Prediction} = \frac{TP}{TP + FP + FN}\)

  • TP (True Positive): 预测为正,实际也为正的像素数。
  • FP (False Positive): 预测为正,实际为负的像素数(误报)。
  • FN (False Negative): 预测为负,实际为正的像素数(漏报)。

特点

  • 范围在 [0, 1] 之间,越接近1表示性能越好。
  • 对类别不平衡问题不敏感,非常常用。

代码实现 (Per Class)

import torch

def iou_score(pred, target, n_classes=2, smooth=1e-5):
    """
    计算每个类别的IoU
    Args:
        pred: [N, H, W] 预测的类别标签(未经softmax)
        target: [N, H, W] 真实的类别标签
        n_classes: 类别数量
        smooth: 平滑因子,防止分母为0
    Returns:
        ious: 一个长度为 n_classes 的列表,表示每个类别的IoU
    """
    # 将预测结果转换为one-hot编码格式 [N, C, H, W]
    pred = torch.argmax(pred, dim=1) # 先取argmax得到 [N, H, W]
    pred = torch.nn.functional.one_hot(pred, n_classes).permute(0, 3, 1, 2).float()
    target = torch.nn.functional.one_hot(target, n_classes).permute(0, 3, 1, 2).float()

    ious = []
    for class_id in range(n_classes):
        pred_inds = pred[:, class_id, ...]
        target_inds = target[:, class_id, ...]

        intersection = (pred_inds * target_inds).sum(dim=(1, 2)) # 计算交集 [N,]
        union = pred_inds.sum(dim=(1, 2)) + target_inds.sum(dim=(1, 2)) - intersection # 计算并集 [N,]

        iou = (intersection + smooth) / (union + smooth) # [N,]
        iou = iou.mean() # 对所有batch求平均
        ious.append(iou.item())

    return ious

# 示例用法
# pred_logits = torch.randn(2, 2, 100, 100) # [Batch, Class, H, W]
# target = torch.randint(0, 2, (2, 100, 100)) # [Batch, H, W]
# ious = iou_score(pred_logits, target, n_classes=2)
# print(f"IoU for background: {ious[0]:.4f}, for foreground: {ious[1]:.4f}")

2. 平均IoU (mIoU - Mean Intersection over Union)

原理
mIoU是所有类别IoU的平均值。它是语义分割论文中最常用、最权威的核心指标,能综合反映模型在所有类别上的性能。

\(mIoU = \frac{1}{k} \sum_{i=1}^{k} IoU_i\)

代码实现
只需在上面 iou_score 函数返回的结果上求平均即可。

miou = sum(ious) / len(ious)

3. Dice系数 (Dice Coefficient / F1 Score)

原理
Dice系数本质上是F1-Score的集合版本,衡量两个样本集合的相似性。计算公式与IoU非常相关:

\(Dice = \frac{2 \times |Target \cap Prediction|}{|Target| + |Prediction|} = \frac{2 \times TP}{2 \times TP + FP + FN}\)

特点

  • 范围在 [0, 1] 之间,越接近1越好。
  • 与IoU高度正相关,但数值上通常比IoU高。
  • 同样对类别不平衡不敏感,在医学图像分割中尤其受欢迎。

代码实现 (Per Class)

def dice_score(pred, target, n_classes=2, smooth=1e-5):
    """
    计算每个类别的Dice系数
    """
    pred = torch.argmax(pred, dim=1)
    pred = torch.nn.functional.one_hot(pred, n_classes).permute(0, 3, 1, 2).float()
    target = torch.nn.functional.one_hot(target, n_classes).permute(0, 3, 1, 2).float()

    dices = []
    for class_id in range(n_classes):
        pred_inds = pred[:, class_id, ...]
        target_inds = target[:, class_id, ...]

        intersection = (pred_inds * target_inds).sum(dim=(1, 2))
        union = pred_inds.sum(dim=(1, 2)) + target_inds.sum(dim=(1, 2))

        dice = (2. * intersection + smooth) / (union + smooth)
        dice = dice.mean()
        dices.append(dice.item())

    return dices

4. 像素准确率 (Pixel Accuracy)

原理
最简单直观的指标,计算所有像素中预测正确的比例。

\(Accuracy = \frac{TP + TN}{TP + TN + FP + FN}\)

特点

  • 在类别极度不平衡时(例如背景占90%),该指标会失效(即使全部预测为背景,准确率也有90%),因此不推荐作为主要评价指标。

代码实现

def pixel_accuracy(pred, target):
    """
    计算像素准确率
    """
    pred = torch.argmax(pred, dim=1)
    correct = (pred == target).float()
    accuracy = correct.sum() / correct.numel()
    return accuracy.item()

第二部分:损失函数

损失函数用于在训练过程中指导模型参数的优化

1. 交叉熵损失 (Cross Entropy Loss)

原理
这是分类任务中最标准的损失函数。它衡量的是模型预测的概率分布与真实的概率分布(one-hot)之间的差异。

\(L_{CE} = -\frac{1}{N} \sum_{i=1}^{N} \sum_{c=1}^{C} y_{i,c} \log(p_{i,c})\)

  • \(N\): 像素总数
  • \(C\): 类别总数
  • \(y_{i,c}\): 像素\(i\)的真实标签(如果属于类别\(c\)则为1,否则为0)
  • \(p_{i,c}\): 模型预测像素\(i\)属于类别\(c\)的概率

特点

  • 是分割任务的基线损失函数,几乎所有模型都会使用或以其为基础。
  • 对于类别不平衡问题,效果可能不佳。

代码实现
PyTorch提供了高度优化的实现,支持权重和忽略特定标签。

import torch.nn as nn

# 假设我们有一个二分类问题,前景像素较少,我们给前景类别更高的权重
class_weights = torch.tensor([0.1, 0.9]) # 背景权重0.1,前景权重0.9

# 定义损失函数
ce_loss_fn = nn.CrossEntropyLoss(weight=class_weights)

# 在训练循环中
# pred_logits: [B, C, H, W]
# target: [B, H, W] (值为0, 1, ..., C-1)
loss = ce_loss_fn(pred_logits, target)

2. Dice Loss

原理
直接将评价指标Dice系数转化为损失函数使用。因为我们希望Dice系数越大越好,所以损失函数取其负数。

\(L_{Dice} = 1 - Dice\)

特点

  • 优点: 能够直接优化我们关心的目标(Dice/IoU),并且对前景区域的大小相对不敏感,能有效缓解类别不平衡问题。
  • 缺点: 使用Dice Loss时,正负样本的梯度会随着分母的增大而减小,可能导致训练不稳定,尤其是对小目标。

代码实现

class DiceLoss(nn.Module):
    def __init__(self, smooth=1e-5):
        super(DiceLoss, self).__init__()
        self.smooth = smooth

    def forward(self, pred, target):
        # pred: [N, C, H, W] (未归一化的logits)
        # target: [N, H, W] (标签)
        
        # 将预测值转换为概率 [0, 1]
        pred = torch.softmax(pred, dim=1)
        
        # 将target转换为one-hot [N, C, H, W]
        target_one_hot = torch.nn.functional.one_hot(target, num_classes=pred.size(1)).permute(0, 3, 1, 2).float()
        
        # 计算每个类别的交集和并集
        intersection = (pred * target_one_hot).sum(dim=(2, 3))
        union = pred.sum(dim=(2, 3)) + target_one_hot.sum(dim=(2, 3))
        
        # 计算每个样本每个类别的dice
        dice = (2. * intersection + self.smooth) / (union + self.smooth)
        
        # 对所有类别和所有batch求平均损失
        loss = 1 - dice.mean()
        return loss

# 使用
dice_loss_fn = DiceLoss()
loss = dice_loss_fn(pred_logits, target)

3. BCEWithLogitsLoss (二分类交叉熵损失)

原理
这是Sigmoid + BCELoss的合并版本,数值计算更稳定。仅适用于二分类问题(例如前景/背景)。对于每个像素,它独立地计算一个二分类交叉熵。

代码实现

# 假设是二分类任务,网络的输出通道数为1
# pred_logits: [B, 1, H, W]
# target: [B, 1, H, W] (值为0或1的float tensor)

bce_loss_fn = nn.BCEWithLogitsLoss()
loss = bce_loss_fn(pred_logits, target)

4. 组合损失 (Combined Loss)

原理
为了结合不同损失函数的优点,最常见的做法是将CE LossDice Loss线性组合起来。

\(L_{Total} = L_{CE} + \lambda L_{Dice}\)

  • CE Loss保证训练的稳定性和梯度多样性。
  • Dice Loss直接优化分割目标并处理类别不平衡。
  • \(\lambda\) 是超参数,通常设为1。

代码实现

class CombinedLoss(nn.Module):
    def __init__(self, weight_ce=1.0, weight_dice=1.0):
        super(CombinedLoss, self).__init__()
        self.weight_ce = weight_ce
        self.weight_dice = weight_dice
        self.ce = nn.CrossEntropyLoss()
        self.dice = DiceLoss()

    def forward(self, pred, target):
        loss_ce = self.ce(pred, target)
        loss_dice = self.dice(pred, target)
        loss = self.weight_ce * loss_ce + self.weight_dice * loss_dice
        return loss

# 使用
combined_loss_fn = CombinedLoss(weight_ce=1.0, weight_dice=1.0)
loss = combined_loss_fn(pred_logits, target)

5. Focal Loss

原理
Focal Loss是CE Loss的改进版,用于解决难易样本不平衡问题(即简单样本太多,主导了梯度)。它通过一个调制因子\((1 - p_t)^\gamma\),降低简单样本的权重,让模型更专注于难分样本。

\(FL(p_t) = -\alpha_t (1 - p_t)^\gamma \log(p_t)\)

  • \(p_t\): 模型对真实类别的预测概率。
  • \(\alpha_t\): 用于处理类别不平衡的权重因子。
  • \(\gamma\): 调节因子,\(\gamma > 0\) 使得难样本的损失相对增加。

特点

  • 在目标检测(如RetinaNet)和分割中表现优异,特别适用于小目标或难样本多的场景。

代码实现
PyTorch没有官方实现,但可以轻松自定义。

class FocalLoss(nn.Module):
    def __init__(self, alpha=0.25, gamma=2.0, reduction='mean'):
        super(FocalLoss, self).__init__()
        self.alpha = alpha
        self.gamma = gamma
        self.reduction = reduction

    def forward(self, pred, target):
        # 先计算标准的交叉熵
        ce_loss = nn.functional.cross_entropy(pred, target, reduction='none')
        
        # 获取模型对真实类别的预测概率
        p = torch.exp(-ce_loss) # pt = exp(-CE)
        
        # 计算Focal Loss
        focal_loss = self.alpha * (1 - p) ** self.gamma * ce_loss

        if self.reduction == 'mean':
            return focal_loss.mean()
        elif self.reduction == 'sum':
            return focal_loss.sum()
        else:
            return focal_loss

# 可以将其与Dice Loss组合使用

总结与选择建议

名称 类型 优点 缺点 适用场景
mIoU 指标 最权威,综合性能好 - 所有分割任务的首选评价指标
Dice系数 指标 对不平衡数据鲁棒,医学图像常用 数值上可能高估性能 医学图像、二分类任务
交叉熵损失 损失函数 梯度稳定,是基线方法 对类别不平衡敏感 几乎所有模型的基础
Dice Loss 损失函数 直接优化IoU,缓解类别不平衡 训练可能不稳定 类别不平衡严重的数据
组合损失 损失函数 兼具CE的稳定和Dice的优点 需要调权重超参 目前最常用、最有效的策略
Focal Loss 损失函数 专注于难样本,提升小目标性能 引入额外超参 难样本多、小目标多的场景

通用建议

  1. 评价指标: 始终以 mIoU 作为核心指标,辅以各类别的IoU和Dice系数进行分析。
  2. 损失函数: 从 CE Loss + Dice Loss 的组合开始,这是一个强大且通用的基线。如果遇到大量难样本或小目标,可以尝试引入 Focal Loss
  3. 实践: 对于二分类任务,也可以尝试 BCEWithLogitsLoss + Dice Loss 的组合。

希望这份详细的介绍和代码能对您有所帮助!

posted @ 2025-08-28 23:34  剪水行舟154  阅读(94)  评论(0)    收藏  举报