返回列表

🥈24th Place Solution(A potential solution for achieving private 0.70)

596. SenNet + HOA - Hacking the Human Vasculature in 3D | blood-vessel-segmentation

开始: 2023-11-07 结束: 2024-02-06 医学影像分析 数据算法赛
第24名解决方案:实现私有0.70分数的潜在方案

🥈第24名解决方案:实现私有0.70分数的潜在方案

作者:siwooyong(Kaggle Master)
发布时间:2024年2月7日

TL;DR

提升模型鲁棒性能的关键因素包括Tversky损失函数增大推理尺寸分辨率增强。这些元素在最终排名中起到了至关重要的作用。最终模型是基于RegNetY-016架构的2D U-Net模型集成。

有趣发现

这次比赛最难的是获得可靠的验证集(很遗憾我未能实现)。回顾结果时,我发现公开榜和私有榜存在显著差异。令人惊讶的是,过去的一些提交本可以进入金牌区。这很令人意外,因为我当时并不重视这些提交,最终也没有提交它们。

在我的实验中,发现给Tversky损失函数中的正类分配更高权重(使用较大的beta值)在私有榜上表现更好。然而最终提交时,我使用了较小的beta值训练,模型在公开榜上表现良好,却在私有榜上意外表现不佳。这种差异可能源于私有数据集更高的分辨率,包含更详细的输入信息。因此建议在这种情况下使用更低的模型logits阈值。此外,推理时缩放图像(1.2, 1.5...)似乎会稀释输入信息,降低分辨率的影响。

数据处理

使用三个连续切片作为模型输入,并以原始图像尺寸进行训练。尽管尝试增加切片数量(5, 7, 9...),结果显示性能反而下降。

比赛初期,我通过调整数据到特定尺寸来训练模型。但如我在讨论中提到的,Albumentations库的Resize函数对掩码使用最近邻插值,导致精细标签产生显著噪声,性能明显下降。因此我决定使用原始图像尺寸。

数据增强

意识到公开和私有数据存在分辨率差异后,我致力于创建对分辨率变化鲁棒的模型。同时考虑到分箱过程中存在尺寸调整,我采用了模糊增强:

def blur_augmentation(x):
    h, w, _ = x.shape
    scale = np.random.uniform(0.5, 1.5)

    x = A.Resize(int(h*scale), int(w*scale))(image=x)['image']
    x = A.Resize(h, w)(image=x)['image']
    return x

此外,由于通道是通过堆叠深度构建的,我应用了以下增强:

def channel_augmentation(x, prob=0.5, n_channel=3):
    assert x.shape[2]==n_channel
    if np.random.rand()

最后,考虑到数据标注固有的噪声,我采用了强cutout增强来防止过拟合:

A.Cutout(num_holes=8, max_h_size=128, max_w_size=128, p=0.8),

这些方法有效提升了交叉验证和排行榜的性能。

模型架构

模型采用了2D U-Net架构,CNN骨干网络使用轻量级模型RegNetY-016。其余设置保持Segmentation Models PyTorch库的默认值。

尽管投入了大量时间开发基于3D的模型,但它在交叉验证和排行榜上均未显示出显著的得分提升。由于资源限制,我转向专注于2D模型。

class CustomModel(nn.Module):
    def __init__(self):
        super(CustomModel, self).__init__()

        self.n_classes = 1
        self.in_chans = 3

        self.encoder = timm.create_model(
            'regnety_016',
            pretrained=True,
            features_only=True,
            in_chans=self.in_chans,
        )
        encoder_channels = tuple(
            [self.in_chans]
            + [
                self.encoder.feature_info[i]["num_chs"]
                for i in range(len(self.encoder.feature_info))
            ]
        )
        self.decoder = UnetDecoder(
            encoder_channels=encoder_channels,
            decoder_channels=(256, 128, 64, 32, 16),
            n_blocks=5,
            use_batchnorm=True,
            center=False,
            attention_type=None,
        )

        self.segmentation_head = SegmentationHead(
            in_channels=16,
            out_channels=self.n_classes,
            activation=None,
            kernel_size=3,
        )

        self.train_loss = smp.losses.TverskyLoss(mode='binary', alpha=0.1, beta=0.9)
        self.test_loss = smp.losses.DiceLoss(mode='binary')

    def forward(self, batch, training=False):

        x_in = batch["input"]

        enc_out = self.encoder(x_in)

        decoder_out = self.decoder(*[x_in] + enc_out)
        x_seg = self.segmentation_head(decoder_out)

        output = {}
        one_hot_mask = batch["mask"][:, None]
        if training:
            loss = self.train_loss(x_seg, one_hot_mask.float())
        else:
            loss = self.test_loss(x_seg, one_hot_mask.float())

        output["loss"] = loss
        output['logit'] = nn.Sigmoid()(x_seg)[:, 0]

        return output

训练配置

  • 学习率调度器:lr_warmup_cosine_decay
  • 预热比例:0.1
  • 优化器:AdamW
  • 权重衰减:0.01
  • 训练轮次:20
  • 学习率:2e-4
  • 损失函数:TverskyLoss(mode='binary', alpha=0.1, beta=0.9)
  • 批大小:4
  • 梯度累积:4

推理策略

推理时将图像尺寸缩放1.5倍,在交叉验证、公开榜和私有榜上均持续提升了分数。这相当于一种扩张操作,显著减少了假阴性,提升了模型性能。简单地将推理图像尺寸增加1.2倍,就在私有榜上带来了0.1的改进。

未成功的方法

  • 尝试通过伪标签增强kidney2和kidney3的效用,但未带来显著提升
  • 使用更重的增强创建更鲁棒的模型,效果不显著
  • 尝试更大的CNN模型导致过拟合问题