Unet医学图像语义分割训练中Dice Loss异常负值问题求助
医学图像语义分割中Dice Loss异常问题排查
我在使用Unet架构进行医学图像语义分割的监督训练(图像/掩码配对训练)时,遇到了Dice Loss数值异常的问题,训练日志如下:
Vanila_UNet Epoch [0] Mean loss on train: -140.31943819224836 Mean DICE on train: 1.7142934219089918 Mean DICE on validation: 1.8950854703170916 Epoch [1] Mean loss on train: -154.01165542602538 Mean DICE on train: 1.8439450739097656 Mean DICE on validation: 1.923283325048502 Epoch [2] Mean loss on train: -155.57704811096193 Mean DICE on train: 1.8617926383475962 Mean DICE on validation: 1.9318473889899364 Epoch [3] Mean loss on train: -156.61962712605794 Mean DICE on train: 1.8733720566917649 Mean DICE on validation: 1.933697909810023 Epoch [4] Mean loss on train: -157.22541224161785 Mean DICE on train: 1.8788127825940564 Mean DICE on validation: 1.9533974303968433
当前数据处理逻辑:图像通过数据增强完成归一化,掩码保持0-255范围时,模型具备一定预测能力;但如果在数据加载时将掩码除以255.,训练集和验证集的Mean DICE均变为0,Loss仍为负值(约-0.025),最终预测结果为空白图像。怀疑问题出在数据加载环节,相关代码如下:
class DukePeopleDataset(Dataset): def __init__(self, df, img_w, img_h): self.IMG_SIZE_W = img_w self.IMG_SIZE_H = img_h self.df = df self.in_channels = 3 self.out_channels = 1 self.transforms = self.define_transorms() def __len__(self): return len(self.df) def __getitem__(self, idx): image = cv2.resize(cv2.imread(self.df.iloc[idx, 0]), (self.IMG_SIZE_W, self.IMG_SIZE_H)) mask = cv2.resize(cv2.imread(self.df.iloc[idx, 1],0), (self.IMG_SIZE_W, self.IMG_SIZE_H)) # mask = mask/255. augmented = self.transforms(image = image, mask = mask) image = augmented['image'] mask = mask.unsqueeze(0) return image, mask def get_dataframe(self): return self.df def define_transorms(self): transforms = A.Compose([ A.HorizontalFlip(p=0.5), A.Normalize(p=1.0), ToTensorV2(), ]) return transforms
问题原因分析及解决方案
1. 掩码未使用增强后的数据
代码中augmented = self.transforms(image=image, mask=mask)已经对掩码应用了增强变换,但后续直接使用了原始的mask变量,而非augmented['mask']。这会导致:
- 掩码为0-255时,虽然未用到增强后的掩码,但模型仍能学到部分特征;
- 掩码除以255后,原始掩码与增强后的掩码未同步,模型完全无法对齐训练信号,出现Dice为0的情况。
修复代码:
def __getitem__(self, idx): image = cv2.resize(cv2.imread(self.df.iloc[idx, 0]), (self.IMG_SIZE_W, self.IMG_SIZE_H)) mask = cv2.resize(cv2.imread(self.df.iloc[idx, 1],0), (self.IMG_SIZE_W, self.IMG_SIZE_H)).astype(np.float32) mask = mask / 255. # 启用这行,将掩码归一化到0-1范围 augmented = self.transforms(image=image, mask=mask) image = augmented['image'] mask = augmented['mask'].unsqueeze(0) # 使用增强后的掩码 return image, mask
2. Dice Loss计算逻辑错误
正常Dice系数范围是[0,1],Loss通常为1 - Dice系数,因此Loss应在[0,1]之间。当前出现负Loss和大于1的Dice值,说明存在以下可能:
- 模型输出未经过
sigmoid(二分类场景)转换为0-1的概率值; - Dice计算时未做数值裁剪,或分子分母顺序错误;
- 未添加平滑项避免除零错误。
标准Dice Loss实现参考:
def dice_loss(pred, target): smooth = 1e-6 pred = torch.sigmoid(pred) # 二分类场景需先转概率 intersection = (pred * target).sum() union = pred.sum() + target.sum() dice = (2. * intersection + smooth) / (union + smooth) return 1 - dice
3. 掩码数据类型问题
掩码除以255时,若未转为float类型,会触发整数除法导致掩码全为0。需确保掩码读取后转为float32类型(如上述修复代码所示)。
内容的提问来源于stack exchange,提问作者jokkk2312
相关产品推荐
相关产品推荐

