记事
随着 Kaggle: SIIM-ACR Pneumothorax Segmentation 接近尾声,我感觉有必要写一篇 blog 来记录一下这两个月的比赛经历,顺便总结一下经验。
刚开始的时候想着这不过是一场普通CV类的比赛而已,肝一肝就能上金牌。但现实狠狠地打了我的脸。最初三天看了看比赛规则,了解了下RLE等语义分割的基本概念,看了看各路大佬的EDA,算是入了个小门。随后就一直沉沦在MMDetection和COCO格式的配置中,由于网上资料太少太旧,导致我花了整整15天才把程序跑通orz,还是在各路大神的帮助下。详情可参考另一篇博客:使用MMDetection进行语义分割。最后出的结果也非常地不尽人意,分数才 0.6+,而把所有预测结果全填上 -1 都有 0.78 分,着实难搞😑。
中间一个月基本在走亲访友旅游摸鱼,直到八月中旬回学校,才重回赛场,放弃了MMDetection,找了个比较高分的PyTorch baseline,开始调参,也算是为这场比赛正式拉开了序幕(虽然只剩半个月了)。
Baseline细节:
- 网络模型:UNet + ResNet34,使用imagenet进行预训练
- 输入图片尺寸:512 * 512 (为了更加贴近模型预训练时使用的图片尺寸)
- 训练集:验证集 = 4:1,使用 sklearn 中的 StratifiedKFold 进行五折划分
- 在验证集和训练集中,正负样本数量1:1
- 学习率策略:ReduceLROnPlateau
- Loss:Focal Loss & Dice Loss
- 优化器:Adam
- 最优模型选择:根据Loss的值进行选择,loss越小模型越优
- 生成结果时,单个分割区域的最少像素数:min_size == 3500
- 输出:1024*1024 的概率矩阵(因为原数据图像大小是 1024*1024),每个元素对应像素点属于 mask 的概率。最后用一个 sigmoid 函数生成 mask
虽然是个分数挺高的 baseline,但还是有一些瑕疵:
- 某行代码的 ‘!=’ 写成了 ‘==’
- Trainer 类里的部分属性与下面传入函数的参数不是同一个变量,导致改了属性后传入的参数依然没改
- 验证时没有加 with torch.no_grad() 导致显存溢出
- 对数据去重的时候把单图多分割区域给删成了单图单分割区域
改完上述问题后单 resnet34 分数能上 0.84+。
改完 bug 后第一步,把模型换成 SENet154 😏,单折 0.855 左右,好像海星的亚子。
随后又测了 SE_ReNeXt101、EfficientUNet_B5、DPN131、DenseNet201、DenseNet121等模型,但只有 EfficientUNet_B5 能跟 SENet154 不相上下,而其他模型基本跟 ResNet34 差不多。
对 SENet154 进行五折交叉验证,分数提高到 0.863。
对 EfficientUNet_B5、ResNet34、SENet154 三模型进行等比例融合、min_size == 3000,分数提高到 0.869。同时 EfficientUNet_B5、SENet154 双模型 、min_size == 2800,分数提高到 0.868。
使用 EfficientNet 单独进行二分类,将二分类中的负样本对应的预测样本替换成负样本,结果不理想,大概多模型融合后的分类能力已经很强了。
对 EfficientUNet_B5、ResNet34、SENet154、SE_ReNeXt101 四模型按 3:2:3:2 的比例进行融合,min_size == 3000,分数提高到 0.8694。
与此同时的另一边使用了 chexnet 进行二分类,将二分类的正样本对应的三模型预测样本的min_size降低到2500,负样本保持3000,将三模型的分数也提升到了 0.8694 。在 public leaderboard 排行60名,位于银牌区。
可是离金牌还有 0.01 分的差距,光是这样调参怕是很难上金牌😪 —— 2019.8.28
比赛进入第二阶段,更换了测试集。以新测试集的 1% 数据的成绩作为公榜成绩,剩下 99% 作为最终成绩。—— 2019.8.31
盲区
FocalLoss和diceLoss的实现细节和应用场景
Unet、Unet++、FastRCNN、MaskRCNN、FPN、DCN、Cascade、SENet、efficientNet的技术细节
学习率的相关优化算法及应用场景,如ReduceLROnPlateau、warm up
threshold的调整策略
PyTorch实战经验不足,baseline的自主编写
新兴模型复现
疑问
Q:为什么将训练集中的正负样本划为1:1能提高分数?
A:能避免模型分类时倾向某一方,减少在分类时出现的错误。
Q:代码中最佳模型的评判标准为什么不是iOU而是loss?
A:因为比赛分数的评判标准是Dice Loss
Q:每次五折验证的选择是否相同?
A:是。StratifiedKFold在随机种子不变的情况下,每次五折交叉验证选择的样本都是相同的。
改进
下次比赛一定要记录每个模型提交的参数、文件和分数啊啊啊啊,不然最后多模型融合的时候不知道如何分配权重
通过对比两份分数的高低和csv的差别,是否能确定哪些预测是对的(好像有点场外)
下次比赛要从头跟到尾,这样能尝试到更多的tips和参数
高分 Solution
1st place solution
2rd place solution
3rd place solution
4th place solution
5th place solution
- 基于半监督学习,在网络添加了二分类器。
- 网络模型:带有 ASPP 结构的 UNet(ASPP 为 DeepLabV3+中的一种结构)
- Backbone:se50 & se101
- 图片尺寸:1024*1024
- 优化器:Adam
- 损失函数:1024 * BCE(results, masks) + BCE(cls, cls_target)
- 半监督学习:mean-teacher[1-2] with NIH Dataset
mean-teacher 参考资料:
[1] https://github.com/CuriousAI/mean-teacher
[2] https://arxiv.org/pdf/1703.01780.pdf
6th place solution
- 网络模型
- EncodingNet (ResNets, 512 and 1024 size)
- UNet (EfficientNet4, se-resnext50, SENet154 with 512, 640 and 1024 sizes)
- 数据增强:Crops 和 Rotations 类型的增强
- 损失函数:BCE + Dice (表示FocalLoss不太好用)
- 比起原始尺寸的图像,小尺寸图像会少很多分
- Tricks:
- 在 EncodingNet 使用了 11 种 TTA
- 删除了预测结果种面积小的mask
8th place solution
- 数据分割:10%分出来用于融合(ensemble),在剩下90%的数据里进行十折交叉验证
- 模型架构:DeepLabV3
- Backbone:使用了组归一化的ResNet50/101 和 ResNeXt50/101
- 损失函数:BCE(在所有图像上训练)或者 Dice(只在正样本上训练)
- 优化器:Vanilla SGD, momentum 0.9
- 训练:
- batch size 4, 1024 x 1024
- batch size 1, 1280 x 1280
- 学习率策略:余弦退火,LR 0.01-0.0001
- 模型融合:
- 4 个模型使用 Dice 损失函数,在正样本上训练
- 8 个模型使用 BCE 损失函数,在所有样本上训练。其中四个作为分类器使用
- Max pixel value was taken as classification score, averaged across 4 models
- Multiplied pixel-level scores from 4 models trained on positives only by this classification score, then averaged
- Final ensemble: multiplied score as above averaged with pixel-level scores based on other 4/8 models trained on all images
- TTA:Hflip
- 后处理:删除了大小小于2048像素的mask(stage2),stage1中为4096像素
没起到效果的工作:
- 使用了Unet, LinkNet, PSPNet, EncNet, HRNet等架构,但效果没有DeepLab好
- SGD 的效果比 Adam, Adabound 优化器的效果更好