You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

关于医学图像分割模型中F1-score、IoU及Dice Score实现逻辑的技术问询

关于医学图像分割模型中F1-score、IoU及Dice Score实现逻辑的技术问询

这篇论文提出了一种用于同时分割医学图像中器官和病变的混合CNN-Transformer模型,模型具备两个输出分支,分别用于输出器官掩码与病变掩码。论文测试阶段的指标描述如下:

为了将我们的方法与当前最优方法进行性能对比,使用了以下评估指标:F1-score(F1-S)、Dice score(D-S)、交并比(IoU)和HD95,其定义如下:

F1、IoU等指标公式

指标参数说明公式

其中TP为真阳性,TN为真阴性,FP为假阳性,FN为假阴性,均与测试图像的分割类别相关。Dice score是一种宏指标,针对N张测试图像的计算方式如下:

宏Dice计算式
其中TPi、FPi和FNi分别为第i张图像的真阳性、假阳性和假阴性。

我对如何实现这些指标(除HD95外)存在困惑,我的理解是:计算F1-score和IoU时,需要对测试集中所有样本的两个输出(病变和器官)的TP、FP、FN进行求和聚合,例如先计算每个样本每个输出的TP,再将所有TP相加得到全局总TP,FP和FN同理,最后代入公式计算。但我不确定这个理解是否正确。

对于Dice score,我认为需要对每个输出单独计算后取平均,但同样不确定。我查看了该论文的GitHub仓库,但由于不熟悉PyTorch,仍无法理解这些指标的实现逻辑,因此希望有人能解释代码中的实现逻辑。

编辑1: 我查看了train_test_DTrAttUnet_BinarySegmentation.py中计算TP、FP、FN的代码:

TP += np.sum(((preds == 1).astype(int) +
             (yy == 1).astype(int)) == 2)
TN += np.sum(((preds == 0).astype(int) +
             (yy == 0).astype(int)) == 2)
FP += np.sum(((preds == 1).astype(int) +
             (yy == 0).astype(int)) == 2)
FN += np.sum(((preds == 0).astype(int) +
             (yy == 1).astype(int)) == 2)

看起来他们通过循环进行前向传播并累加这些数值,之后计算指标:

F1score = TP / (TP + ((1/2)*(FP+FN)) + 1e-8)
IoU = TP / (TP+FP+FN)

这是否意味着他们是对所有图像的两个输出累加TP、FP、FN后再计算指标?

对于Dice Score,代码中的实现让我感到困惑,他们在循环内计算了以下内容:

for idice in range(preds.shape[0]):
    dice_scores += (2 * (preds[idice] * yy[idice]).sum()) / (
        (preds[idice] + yy[idice]).sum() + 1e-8
    )

predss = np.logical_not(preds).astype(int)
yyy = np.logical_not(yy).astype(int)
for idice in range(preds.shape[0]):
    dice_sc1 = (2 * (preds[idice] * yy[idice]).sum()) / (
        (preds[idice] + yy[idice]).sum() + 1e-8
    )
    dice_sc2 = (2 * (predss[idice] * yyy[idice]).sum()) / (
        (predss[idice] + yyy[idice]).sum() + 1e-8
    )
    dice_scores2 += (dice_sc1 + dice_sc2) / 2

循环结束后:

epoch_dise = dice_scores/len(dataloader.dataset)
epoch_dise2 = dice_scores2/len(dataloader.dataset)

我仍然无法理解Dice Score的实现逻辑。


嗨,我来帮你彻底拆解这段代码的指标实现逻辑,你的核心理解其实是对的,咱们一步步理清楚:

一、F1-score与IoU的实现:你的理解完全正确

这段代码确实是通过遍历所有测试样本,累加全局的TP、FP、FN像素数,最后用这些全局总和计算整体的F1和IoU:

  • 代码中np.sum(((preds ==1).astype(int)+(yy==1).astype(int))==2)的本质是统计「预测为1且标签为1」的像素总数(两个1相加等于2,统计满足该条件的像素数),这就是单张图的TP像素数,每处理一张图就把这个数值加到全局总TP中;FP、FN、TN的计算逻辑同理。
  • 最后代入的公式F1score = TP / (TP + ((1/2)*(FP+FN)) + 1e-8)IoU = TP / (TP+FP+FN)完全对应论文中的定义,1e-8是为了避免除数为0的情况。
  • 这里需要注意:代码是针对像素级的统计,因为医学图像分割的指标通常是基于像素层面计算的,而非样本层面。

二、Dice Score的两种实现:对应论文的「宏指标」定义

论文明确说明Dice是宏指标,需要对N张测试图计算后取平均,代码里的两个分支分别实现了两种常见的Dice计算方式:

1. dice_scores:前景类的平均Dice

for idice in range(preds.shape[0]):
    dice_scores += (2 * (preds[idice] * yy[idice]).sum()) / (
        (preds[idice] + yy[idice]).sum() + 1e-8
    )
  • 这段代码对每张测试图(或每个批次内的单张图)单独计算前景类(分割目标,即标签为1的区域)的Dice,公式完全对应论文中的单样本Dice:2TP/(2TP+FP+FN),和代码中的2*(pred*label.sum())/(pred.sum()+label.sum())是等价的(因为pred*label.sum()是TP,pred.sum()是TP+FP,label.sum()是TP+FN,两者相加就是2TP+FP+FN)。
  • 最后epoch_dise = dice_scores/len(dataloader.dataset)就是将所有样本的前景Dice求和后取平均,完全符合论文中「宏指标」的定义。

2. dice_scores2:前景+背景的平均Dice

predss = np.logical_not(preds).astype(int)
yyy = np.logical_not(yy).astype(int)
for idice in range(preds.shape[0]):
    dice_sc1 = (2 * (preds[idice] * yy[idice]).sum()) / (
        (preds[idice] + yy[idice]).sum() + 1e-8
    )
    dice_sc2 = (2 * (predss[idice] * yyy[idice]).sum()) / (
        (predss[idice] + yyy[idice]).sum() + 1e-8
    )
    dice_scores2 += (dice_sc1 + dice_sc2) / 2
  • 这里先将预测和标签取反(把背景类(0)转为1,前景类(1)转为0),然后分别计算前景类Dice(dice_sc1)和背景类Dice(dice_sc2),对每张图取两者的平均值后累加。
  • 最后epoch_dise2 = dice_scores2/len(dataloader.dataset)得到的是所有样本的「前景+背景平均Dice」,这种计算方式可以避免样本不平衡(比如前景像素占比极低时,只算前景Dice会有偏差),是医学图像分割中常用的一种鲁棒性更强的Dice计算方式。

总结

  • 对于F1和IoU:代码采用全局像素累加的方式计算,你的理解完全正确;
  • 对于Dice:代码实现了两种版本,一种是论文明确提到的「前景类宏平均Dice」,另一种是额外的「前景+背景双类平均Dice」,两种都符合宏指标的计算逻辑(单样本计算后取平均)。

备注:内容来源于stack exchange,提问作者Ahmed

火山引擎 最新活动