596. SenNet + HOA - Hacking the Human Vasculature in 3D | blood-vessel-segmentation
嗨,Kagglers!
在经历了大洗牌后重新登上排行榜并恢复心理健康后,我们可以深入探讨第六名的解决方案了。但在此之前,有几句非常重要的话要说:
我要感谢乌克兰武装部队、乌克兰国家安全局、乌克兰国防情报局和乌克兰国家紧急服务局,为参与这场伟大竞赛、完成这项工作以及帮助科学、技术和商业继续前进提供了安全保障。
我使用2个器官进行验证(不同折):kidney_3_dense 和 kidney_2。在发布了3D表面Dice的快速版本后,我能够在训练时计算验证分数,并得到以下见解:
总之,验证没有起作用(至少我的验证是这样)。这并不奇怪,因为CV、Public和Private都只有一个数据点。
我使用了除kidney_1_voi样本外的所有训练数据。为了扩大训练数据,我使用了来自人类器官图谱的50um_LADAF_2020_31_kidney_pag数据。
对于数据归一化,我使用了@hengck23提出的方法 - 百分位归一化。
我主要采用5个切片的2.5D方法。我最初从一个视图模型开始,沿着最后一个轴迭代,但后来切换到多视图,并在训练中使用所有三个轴的切片。
我使用了512方形裁剪,非空概率为0.5,相当标准的增强方法,以及在一个器官和一个视图内使用CutMix,概率为0.5,alpha为1.0:
"cutmix_transform":lambda : [
A.PadIfNeeded(
min_height=crop_size,
min_width=crop_size,
always_apply=True,
),
# 以0.5概率采样非空掩码
# 否则将采样空或非空掩码
A.OneOrOther(
first=A.CropNonEmptyMaskIfExists(crop_size, crop_size),
second=A.RandomCrop(crop_size, crop_size),
p=0.5
),
],
"transform": A.Compose(
[
A.ShiftScaleRotate(
scale_limit=0.2,
),
# 二面体增强
A.RandomRotate90(p=0.5),
A.HorizontalFlip(p=0.5),
A.VerticalFlip(p=0.5),
A.Transpose(p=0.5),
A.OneOf(
[
A.RandomBrightnessContrast(),
A.RandomBrightness(),
A.RandomGamma(),
],
p=1.0,
),
ToTensorV2(transpose_mask=True),
]
),
"do_cutmix": True,
"cutmix_params": {"prob": 0.5, "alpha": 1.0},
我使用Adam优化器,并使用CosineAnnealingLR从1e-3降低学习率到1e-6。
关于损失函数的选择,我最初从经典的BCE+Dice损失开始,然后尝试实现直接优化指标的损失函数,但效果不佳。幸运的是,我发现了BoundaryLoss,它最初与BCE+Dice损失相当,后来效果更好。有趣的是,最好的(未被选中的)模型是在BoundaryLoss + 0.5 Focal Symmetric Loss上训练的,在Private上得分0.756,Public上0.867,kidney_3_dense上0.916,这是一个相当平衡的分数(当然,与其他所有模型的分数分布相比)。
我训练了30个epoch,将原始训练集重复3次,伪训练集重复2次。我使用14的批次大小,在2个GPU上用DDP训练,所以最终批次大小为28。
在我看到关于3D模型有希望的结果的帖子后,我开始探索3D方法,它们效果很好。为了使其训练时不出现NaN,我改变了优化策略,切换到SGD,momentum=0.99,weight_decay=3e-5,nesterov=True,并将起始学习率改为1e-6 - 取自monai示例。
由于图像的整体分辨率显著增加,我不得不将批次减少到每个GPU上3个,所以聚合批次大小为6。我总共训练了约12万次迭代。
至于增强方法 - 它们与2.5D设置基本相同,只是去掉了Zoom。
"transform_init": lambda : mt.Compose(
[
mt.OneOf([
mt.RandRotate90d(keys=('image', 'mask'), prob=0.5, spatial_axes=(-3,-1)),
mt.RandRotate90d(keys=('image', 'mask'), prob=0.5, spatial_axes=(-2,-1)),
mt.RandRotate90d(keys=('image', 'mask'), prob=0.5, spatial_axes=(-3,-2))
]),
mt.RandFlipd(keys=('image', 'mask'), prob=0.5, spatial_axis=-1),
mt.RandFlipd(keys=('image', 'mask'), prob=0.5, spatial_axis=-2),
mt.RandFlipd(keys=('image', 'mask'), prob=0.5, spatial_axis=-3),
mt.RandScaleIntensityd(keys=('image'), prob=0.5, factors=0.2)
]
),
Zoom在CV上效果更好,但在Public和Private上更差(为什么?- 谁知道呢……)
我主要使用EfficientNet系列作为编码器(来自noisy student权重),从B3开始,然后切换到B5,不幸的是B7在CV和Public上效果都不好。
有趣的是,se_resnext50_32x4d在Public LB(0.852)和CV(0.909)上表现不佳,但在Private上表现很好(0.702)。
对于解码器,我主要使用Unet++。我尝试过Unet3+,但结果明显更差。
对于3D网络,我使用DynUNet,并根据下一个脚本调整了模型架构。我尝试过使用MONAI Model Zoo中的预训练Unet,但它在所有数据集上表现都很差。
我使用512滑动窗口,0.5重叠,flip TTA,以及2折的最后一个检查点。切换到多视图模型后,我还添加了多视图TTA。
下一步是创建肾脏掩码。我尝试了几种方法:
第一种方法有很高的FP率但几乎为零的FN率,而第二种方法有很高的FN率。两者在kidney_3上表现几乎完美,所以没有真正影响fold 0的分数,但算法方法切掉了kidney_2的肾脏区域,显著降低了fold 1的分数。但同时,第二种方法提高了Public分数(0.874->0.882)。我明白这有90%过拟合到Public LB,但我决定冒险。
对于最终提交,我选择了以下模型:
对于两个模型,我使用了0.05的阈值。
在这里,我想指出几个对我来说最有趣的方法:
对我来说,选择第一个或第二个是合乎逻辑的,但与其他更好的提交一样,这听起来像是纯粹的随机。
在单个数据样本上计算指标会导致严重的洗牌。
希望你们在阅读时没有睡着。最后,我要感谢整个Kaggle社区,祝贺所有参与者和获胜者。特别感谢印第安纳大学布卢明顿分校、伦敦大学学院、Yashvardhan Jain (@yashvrdnjain)、Claire Walsh (@clairewalsh)、Kaggle团队和其他组织者。