返回列表

13th place solution

558. Vesuvius Challenge - Ink Detection | vesuvius-challenge-ink-detection

开始: 2023-03-15 结束: 2023-06-14 医学影像分析 数据算法赛
第13名解决方案 - 维苏威火山挑战墨水检测

第13名解决方案

作者:Mikhail Kotyushev(专家级)
排名:第13名(私有排行榜得分:0.641814,公共排行榜得分:0.723345)
发布时间:2023年6月15日

大家好!这里是我在本次比赛中最佳私有排行榜解决方案的总结(前13名,得分0.641814,公共排行榜得分0.723345)。

我非常感谢组织者和参赛者们,这场比赛非常有趣且富有挑战性。我学到了很多关于图像分割的知识,也获得了很多乐趣!我解决方案中的一些想法来自于Kaggle的代码和讨论区,希望我已经全部注明了出处。

如果您有任何问题,请随时在评论区留言。

概述

数据

  • 基于平均层强度曲线拟合的Z轴位移和缩放预处理(受此笔记本启发)
  • 5折训练(1、2a、2b、2c、3号碎片,2号碎片按卷轴遮罩区域分割:顶部 + 底部两个部分)
  • 根据卷轴遮罩区域加权采样碎片
  • 补丁大小为384,测试时使用192重叠并通过样条加权平均(受此方法启发)
  • 24个切片,索引范围[20, 44),随机裁剪其中18个,每个3切片窗口输入模型,6个输出logits通过线性层聚合

训练

  • 模型为SMP Unet,使用2D预训练编码器 + 线性层聚合
  • 在卷轴遮罩内随机裁剪 + 标准视觉增强
  • BCE损失函数,正样本权重为0.5,根据验证折的F0.5分数选择模型
  • 64个epoch训练,每个epoch长度约等于训练集补丁数量(由于随机裁剪而不固定)
  • 学习率1e-4,不冻结层/不使用学习率衰减,使用带warmup的余弦退火调度(系数1e-1, 1, 1e-2,10% warmup),优化器为AdamW

推理

  • TTA:4种旋转(0、90、180、270度)和3种翻转(无翻转、水平、垂直)的笛卡尔积 -> 每个体积12次预测(从此处获得此想法)
  • TTA概率取平均后写入图像文件,然后对5个模型的图像结果进行平均

环境与工具

  • docker、pytorch lightning CLI、wandb
  • 1块3090,1块3080Ti移动版

详细细节

数据处理

此分析启发,我绘制了每个碎片每层的平均强度曲线,包括完整图像、仅墨水和仅背景。

各碎片每层平均强度

从图中可以观察到,碎片1和3在强度值以及最小和最大位置方面较为相似,而碎片2则不同。

我背后的物理直觉是:纸莎草在3D体积内似乎没有按深度对齐,而且纸莎草的厚度在碎片1、3和碎片2之间似乎有所不同。

因此,我决定通过位移和缩放操作在Z轴方向对齐并拉伸/压缩纸莎草:

  1. 在高度和宽度维度上将体积分割为重叠的补丁
  2. 对每个补丁:
    • 计算每层的平均强度
    • 拟合z_shift参数以最小化以下损失(y_target为3号碎片的完整强度曲线)
z_shifted = z + z_shift
f = interpolate.interp1d(z_shifted, y, bounds_error=False, fill_value='extrapolate')
return f(z_target) - y_target
  1. 对整个体积以类似方式拟合单个z_scale(假设同一碎片的纸莎草厚度相同)
  2. 将得到的z_shiftz_scale图线性上采样至完整体积大小并保存到磁盘
  3. 通过scipy.ndimage.geometric_transform应用z_shiftz_scale到完整体积,变换函数用C模块编写以提高速度
  4. 训练时保存到磁盘,推理时实时应用

得到的z_shift图和z_scale值如下:

碎片1 碎片2 碎片3
Z位移图和直方图 Z位移图和直方图 Z位移图和直方图

Z缩放值:碎片1和3约为1.0,碎片2约为0.6,这表明碎片2的厚度约为其他碎片的两倍。

这种操作对2D方法应该是有益的。它似乎能将CV分数提高约0.03-0.05 F05,但由于时间限制,我没有进行完整的对比实验。

此外,我尝试分别拟合/计算每个碎片的归一化以完全匹配强度曲线,但效果不佳。

模型

预训练的maxvit编码器 + SMP 2D unet与聚合在CV分数上表现最佳,因此最佳解决方案使用的是maxvit_rmlp_base_rw_384.sw_in12k_ft_in1k

2D unet与聚合的工作方式如下:unet模型应用于输入的每个切片(沿Z维度大小=步长=3,共6个切片),然后6个输出的logits通过线性层聚合。

使用预训练模型比随机初始化产生的效果好得多。不幸的是,我没有想出更好的架构来处理3D输入和2D输出,同时允许使用预训练模型。

该模型的最佳CV分数及相应预测如下:

碎片 F05分数 预测结果
1 0.6544 碎片1概率图
2a 0.6643 碎片2a概率图
2b 0.7489 碎片2b概率图
2c 0.6583 碎片2c概率图
3 0.7027 碎片3概率图

尝试过但效果不佳的方法

多种骨干网络模型:

  • convnext v2
  • swin transformer v2
  • caformer
  • eve02
  • maxvit

以及多种完整模型方法:最后一层聚合的全3D模型、2.5D、带聚合的2D、从2D转换为3D的自定义3D模型并使用预训练权重。

数据增强

训练时使用以下增强:

train_transform = A.Compose(
    [
        RandomCropVolumeInside2dMask(
            base_size=self.hparams.img_size, 
            base_depth=self.hparams.img_size_z,
            scale=(0.5, 2.0),
            ratio=(0.9, 1.1),
            scale_z=(1 / self.hparams.z_scale_limit, self.hparams.z_scale_limit),
            always_apply=True,
            crop_mask_index=0,
        ),
        A.Rotate(
            p=0.5, 
            limit=rotate_limit_degrees_xy, 
            crop_border=False,
        ),
        ResizeVolume(
            height=self.hparams.img_size, 
            width=self.hparams.img_size,
            depth=self.hparams.img_size_z,
            always_apply=True,
        ),
        A.HorizontalFlip(p=0.5),
        A.VerticalFlip(p=0.5),
        A.RandomRotate90(p=0.5),
        A.RandomBrightnessContrast(p=0.5, brightness_limit=0.1, contrast_limit=0.1),
        A.OneOf(
            [
                A.GaussNoise(var_limit=[10, 50]),
                A.GaussianBlur(),
                A.MotionBlur(),
            ], 
            p=0.4
        ),
        A.GridDistortion(num_steps=5, distort_limit=0.3, p=0.5),
        A.CoarseDropout(
            max_holes=1, 
            max_width=int(self.hparams.img_size * 0.3), 
            max_height=int(self.hparams.img_size * 0.3), 
            mask_fill_value=0, p=0.5
        ),
        A.Normalize(
            max_pixel_value=MAX_PIXEL_VALUE,
            mean=self.train_volume_mean,
            std=self.train_volume_std,
            always_apply=True,
        ),
        ToTensorV2(),
        ToCHWD(always_apply=True),
    ],
)

其中,RandomCropVolumeInside2dMask从体积中裁剪随机3D补丁,使其中心点(x, y)位于遮罩内;ResizeVolume是三次线性3D缩放;ToCHWD仅置换维度。参数设置为:img_size = 384,img_size_z = 18,z_scale_limit = 1.33,rotate_limit_degrees_xy = 45,train_volume_meantrain_volume_std为ImageNet统计量的平均值。

推理时使用以下变换:

test_transform = A.Compose(
    [
        CenterCropVolume(
            height=None, 
            width=None,
            depth=math.ceil(self.hparams.img_size_z * self.hparams.z_scale_limit),
            strict=True,
            always_apply=True,
            p=1.0,
        ),
        ResizeVolume(
            height=self.hparams.img_size, 
            width=self.hparams.img_size,
            depth=self.hparams.img_size_z,
            always_apply=True,
        ),
        A.Normalize(
            max_pixel_value=MAX_PIXEL_VALUE,
            mean=self.train_volume_mean,
            std=self.train_volume_std,
            always_apply=True,
        ),
        ToTensorV2(),
        ToCHWD(always_apply=True),
    ],
)

其中,CenterCropVolume是3D体积的中心裁剪。

还尝试了混合方法(cutmix、mixup、复制粘贴正类体素等自定义方法),但结果相互矛盾,因此未包含在最终方案中。

TTA使用无翻转/H翻转/V翻转无旋转/90度/180度/270度旋转的笛卡尔积,共产生12次预测。

更新:训练项目的源代码可在此处找到。

同比赛其他方案