突然喘气,无缘无故地无助地呼吸,这会是肺衰竭吗?气胸可由胸部钝伤、肺部疾病的损害引起,有时候甚至无法探寻诱因。在某些情况下,肺萎陷可能会危及生命。气胸通常由胸部X射线放射科医生诊断,但有时很难确诊。因此由医学影像信息学学会(SIIM)提供了气胸图片数据,kaggle举办了一场比赛,开发模型,为非放射科医生提供更可靠的诊断,并在疾病早期识别气胸,挽救生命。
图片数据是胸透图片,并标注好了气胸区域。
模型输出的是一个像素是mask的概率,作者将这种mask称为sigmoid mask,并使用三种不同阈值:top_score_threshold, min_contour_area, bottom_score_threshold。
基于top_score_threshold, min_contour_area来生成决策规则,而不是单单进行有无气胸的分类。
没有通过top_score_threshold和min_contour_area这两个阈值的图像则为非气胸图像。
对于剩下的气胸图像,使用bottom_score_threshold阈值(另外一个阈值,小于基于top_score_threshold),大多数参赛者假设bottom_score_threshold阈值等于top_score_threshold。
classification_mask = predicted > top_score_threshold
mask = predicted.copy()
mask[classification_mask.sum(axis=(1,2,3)) < min_contour_area, :,:,:] = np.zeros_like(predicted[0])
mask = mask > bot_score_threshold
return mask
在最后提交阶段作者选了介于这两者之间的三元阈值。
使用Combo Loss(https://github.com/SpaceNetChallenge/SpaceNet_Off_Nadir_Solutions/blob/master/selim_sef/training/losses.py),结合BCE,dice,focal损失,损失权重:
将气胸图片比例称为采样率,并且在采样的时候控制比例。
每个epoch,选取所有的气胸图片,然后根据这一比例选取非气胸图片,比例从训练开始的0.8减少至0.4。
使用大比例,可以在训练前期加快训练,后期使用小的比例有助于网络收敛。
使用albumentations中提供的数据增强方法:
albu.Compose([
albu.HorizontalFlip(),
albu.OneOf([
albu.RandomContrast(),
albu.RandomGamma(),
albu.RandomBrightness(),
], p=0.3),
albu.OneOf([
albu.ElasticTransform(alpha=120, sigma=120 * 0.05, alpha_affine=120 * 0.03),
albu.GridDistortion(),
albu.OpticalDistortion(distort_limit=2, shift_limit=0.5),
], p=0.3),
albu.ShiftScaleRotate(),
albu.Resize(img_size,img_size,always_apply=True),
])
判断一张图片是否有气胸,模型为multi-task模型,基于Unet,加上了分类分支。
使用unet和deeplabv3作为分割模型。
由于图片太大,作者在1024x1024的图片上训练Unet来分割肺区域。
作者充分使用了CheXpert和NIH数据集,并且在阅读了相关论文后发现数据标注不是很准确,因此不能直接使用,而是使用伪标签。因此作者使用比赛的数据训练了主干网络为resnet34的Unet模型,在CheXpert上预测标记为正样本的数据,然后从中选出模型预测也为正样本的数据,而由于作者参加的另外一场比赛(Dogs-GAN)推迟结束,参加这个比赛的时间有限,所以直接使用负样本数据,而不进行同样的筛选。而对于NIH数据集,则是直接使用自己的预测结果,不使用数据标记。
在训练伪标签模型时,保持正负样本比例一样,伪标签样本数为正确标签样本数的一半。
在Unet上主要尝试了resnet34和SE-resnext50两种主干网络,因为resnet34是轻量级的,适合用于实验,而SE-resnext50足够深,适合用于比赛,作者没有更多的时间和资源来训练更大更深的网络。
作者最后的三个模型:
2.7 * BCE(pred_mask, gt_mask) + 0.9 * DICE(pred_mask, gt_mask) + 0.1 * BCE(pred_empty, gt_empty)
if pred_empty > 0.4 or area(pred_mask) < 800: pred_mask = empty
作者的解决方法基于半监督学习,并且在网络中加入了两个分类器。