返回列表

30th Place Solution for the Google Research - Identify Contrails to Reduce Global Warming Competition

567. Google Research - Identify Contrails to Reduce Global Warming | google-research-identify-contrails-reduce-global-warming

开始: 2023-05-10 结束: 2023-08-09 双碳与可持续发展 数据算法赛
Google Research - 识别凝结尾迹以减少全球变暖竞赛 - 第30名解决方案

Google Research - 识别凝结尾迹以减少全球变暖竞赛 - 第30名解决方案

排名: 第30名

发布日期: 2023年8月16日

团队成员: delai50, Maximiliano Diaz Battan, Enric Domingo, Juan Sensio

首先,我们要感谢Kaggle和主办方举办了这场有趣(而且*对我们来说没什么效果*)的比赛。这并不是一个 fancy 的解决方案,但我们为采取正确步骤在私有测试集上实现良好泛化感到自豪(确实有42个名次的波动 😃)。

1. 背景

2. 方法概述

我们的最终解决方案是一个包含8个模型的集成,结合了不同的编码器和图像尺寸。每个模型配置使用不同的随机种子训练3次(共24个模型),并且始终使用所有可用数据(训练+验证)。

数据预处理

ASH伪彩色图像,仅使用带标签帧的2D模型和硬标签。

验证方案

使用官方提供的训练/验证分割。

3. 方法详解

3.1. 单个模型和最终集成

所有模型都基于SMP库中的UNet架构。我们通过对单个模型输出的概率进行加权平均来组合它们,其中权重通过Optuna优化验证集上的全局dice分数来确定。最终的CV分数为0.6825(由于我们直接在验证集上优化权重,这个分数可能有些偏差)。

权重 编码器 图像尺寸 学习率 轮数 批大小 CV分数
0.02 timm-resnest101e 384 5e-4 20 48 0.64397
0.07 maxvit_base_tf_384.in21k_ft_in1k 384 1e-4 20 16 0.65699
0.18 maxvit_base_tf_512 512 1e-4 40 8* 0.66414
0.02 tf_efficientnetv2_b3.in1k 768 5e-4 35 24* 0.65178
0.23 tf_efficientnetv2_m.in21k_ft_in1k 512 5e-4 35 24* 0.6525
0.14 tf_efficientnetv2_l.in21k_ft_in1k 384 5e-4 35 24* 0.64538
0.11 maxvit_large_tf_224.in1k 448 1e-4 40 8* 0.66407
0.23 maxvit_large_tf_384.in1k 384 1e-4 40 8* 0.66782

*梯度累积步数为2。

3.2. 阈值选择

集成模型的最优阈值为0.43,这是通过遍历0到1之间以0.1为步长的阈值并计算对应的全局dice分数得到的。我们观察到在0.43处有一个相对宽泛的最大值,这给了我们足够的信心选择该阈值。

3.3. 训练过程

我们使用了CosineAnnealingLR学习率调度器和AdamW优化器。需要注意的是,我们总是尝试选择轮数,使得最后一个epoch就是最佳模型。这比在训练过程中选择最佳检查点具有更好的泛化能力,特别是在我们的情况中,训练曲线表现出不规则的行为。最后,一旦模型验证通过,我们会使用相同的配置并在所有可用数据上重新训练。

3.4. 数据增强

我们没有注意到掩码偏移的问题,因此翻转和旋转对我们没有效果。其他有效的增强方法包括:

A.Compose([  
     A.RandomResizedCrop(height=256, width=256, p=0.5),  
     A.RandomBrightnessContrast(p=0.5)  
])  
CutMix(p=0.5)  
MixUp(p=0.5)

以下是我们的CutMix和MixUp实现:

def rand_bbox(size, lam):
    W = size[2]
    H = size[3]
    cut_rat = np.sqrt(1. - lam)
    cut_w = int(W * cut_rat)
    cut_h = int(H * cut_rat)

    # 均匀分布
    cx = np.random.randint(W)
    cy = np.random.randint(H)

    bbx1 = np.clip(cx - cut_w // 2, 0, W)
    bby1 = np.clip(cy - cut_h // 2, 0, H)
    bbx2 = np.clip(cx + cut_w // 2, 0, W)
    bby2 = np.clip(cy + cut_h // 2, 0, H)
    return bbx1, bby1, bbx2, bby2

def cutmix(data, target, alpha):
    indices = torch.randperm(data.size(0))
    # shuffled_data = data[indices]
    shuffled_target = target[indices]

    lam = np.clip(np.random.beta(alpha, alpha), 0.3, 0.4)
    bbx1, bby1, bbx2, bby2 = rand_bbox(data.size(), lam)
    new_data = data.clone()
    new_data[:, :, bby1:bby2, bbx1:bbx2] = data[indices, :, bby1:bby2, bbx1:bbx2]
    # 调整lambda以精确匹配像素比例
    lam = 1 - ((bbx2 - bbx1) * (bby2 - bby1) / (data.size()[-1] * data.size()[-2]))
    # targets = (target, shuffled_target, lam)
    return new_data, target, shuffled_target, lam

def mixup(x: torch.Tensor, y: torch.Tensor, alpha: float = 1.0):
    assert alpha > 0, "alpha应大于0"
    assert x.size(0) > 1, "Mixup不能应用于单个实例。"

    lam = np.random.beta(alpha, alpha)
    rand_index = torch.randperm(x.size()[0])
    mixed_x = lam * x + (1 - lam) * x[rand_index, :]
    target_a, target_b = y, y[rand_index]
    return mixed_x, target_a, target_b, lam

损失计算方式如下:

if self.cfg.mixup and torch.rand(1)[0] < self.cfg.mixup_p:
    mix_images, target_a, target_b, lam = mixup(images, masks, alpha=self.cfg.mixup_alpha)
    logits = self(mix_images)
    loss = self.loss_fn(logits, target_a) * lam + (1 - lam) * self.loss_fn(logits, target_b)
                
elif self.cfg.cutmix and torch.rand(1)[0] < self.cfg.cutmix_p:
    mix_images, target_a, target_b, lam = cutmix(images, masks, alpha=self.cfg.cutmix_alpha)
    logits = self(mix_images)
    loss = self.loss_fn(logits, target_a) * lam + (1 - lam) * self.loss_fn(logits, target_b)

3.5. 无效的方法

很多方法都没有效果:

  • 使用其他波段/其他数据方案
  • 测试时增强(TTA)
  • 我们尝试在每个UNet阶段将前后帧与带标签帧组合(仅尝试了拼接),但没有成功
  • 在比赛末期我们发现伪标签对某些模型(不是最强的那些)有很大提升,但没有足够时间进行充分实验
  • ……

致谢

感谢我们出色的队友 @maxdiazbattan@edomingo@juansensio(如果我遗漏了什么,请在评论中补充)。

同比赛其他方案