关于医学图像分割模型中F1-score、IoU及Dice Score实现逻辑的技术问询
这篇论文提出了一种用于同时分割医学图像中器官和病变的混合CNN-Transformer模型,模型具备两个输出分支,分别用于输出器官掩码与病变掩码。论文测试阶段的指标描述如下:
为了将我们的方法与当前最优方法进行性能对比,使用了以下评估指标:F1-score(F1-S)、Dice score(D-S)、交并比(IoU)和HD95,其定义如下:
其中TP为真阳性,TN为真阴性,FP为假阳性,FN为假阴性,均与测试图像的分割类别相关。Dice score是一种宏指标,针对N张测试图像的计算方式如下:
其中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







