返回列表

8th Place solution (Trust your CV)

623. ISIC 2024 - Skin Cancer Detection with 3D-TBP | isic-2024-challenge

开始: 2024-06-27 结束: 2024-09-06 医学影像分析 数据算法赛
第 8 名解决方案(相信你的交叉验证)

第 8 名解决方案(相信你的交叉验证)

作者: Ujjwal Pandey (uryednap)
发布日期: 2024-09-07
竞赛: ISIC-2024

首先,我要向获胜团队表示祝贺,并感谢 Kaggle 社区和竞赛组织者带来了另一场精彩的挑战,ISIC-2024。这是我第一次认真尝试 Kaggle 奖牌竞赛,从中我学到了很多。虽然我是中途加入的,但我始终保持决心并坚持到了最后。

我学到的一个关键教训是:要相信我的交叉验证(CV)分数,即使它低于大多数公共排行榜(LB)分数。我专注于构建稳健的解决方案,而不是对高排行榜排名过于乐观。由于 8 月份工作安排繁忙,我采取了一个直接的策略:如果一个方法或想法第一次尝试不成功,我就完全放弃它并继续前进。

数据准备

我的第一步是彻底了解数据集,并关注论坛中的关键讨论,以了解哪些技术有效,哪些无效。

特征工程与选择

对于特征工程,我基于公开的 Notebook 开始构建,并添加了一个基于 tbp_lv_location 的聚合特征。在后续过程中,我根据 CatBoost 模型(表现最好的模型)确定的 SAGE value of 0 修剪了特征。此外,我还使用贪婪选择方法移除了四到五个特征——如果移除某个特征提高了我的 CV 分数,我就接受这个更改。

CV 选择

关于是否选择分层有两个选项,但我决定坚持使用 StratifiedGroupKFold CV 策略。虽然在我最初的实验中这并没有太大影响,但在整个竞赛过程中使用相同的 CV 可以避免由于管道不同阶段的不同随机种子而导致的任何泄漏。这是我整个比赛的 CV 迭代器:

iterator = StratifiedGroupKFold(n_splits=5, random_state=6855, shuffle=True).split(
    train_meta["isic_id"],
    train_meta["target"],
    groups=train_meta["patient_id"],
)

建模

我的整个建模过程可以分为四个部分:

  • 仅基于表格数据训练 GBDT
  • 训练图像模型
  • 将图像堆叠到表格数据上以训练 GBDT
  • 最终集成所有模型

第一阶段(在表格数据上训练 GBDT)

我不得不做两次这个阶段,因为我最初的方法深受公开 Notebook 的影响,导致 CV 分数不稳定。微小的变化会导致排行榜(LB)性能的显著波动。此外,我依赖早停(early stopping),这对 CV-LB 相关性产生了负面影响。

在第二次尝试中,我设法稳定了 CV 分数。虽然它比我的第一种方法略低,但 CV 和 LB 分数之间的一致模式让我确信我走在正确的道路上。通过进一步调整,我相信我可以提高分数。

我关注的模型是 LGBMXGBoostCatBoost。虽然我计划尝试 IBM SnapML,但由于时间和资源不足,我没能实施它。以下是这个阶段的一些关键点:

  • 我没有使用任何过采样或欠采样技术,虽然我考虑过尝试它们的 bagging 变体,但资源限制阻止了我。
  • 相反,我使用了 XGBoost 和 CatBoost 的内置子采样。对于 LGBM,pos_bagging_fractionneg_bagging_fraction 效果非常好,以至于我没有考虑任何额外的采样技术。
  • 由于避免了过采样和欠采样技术,我发现使用 scale_pos_weight 提高了我的整体分数。

总结表

模型 CV 分数 公共 LB 私有 LB
LGBM 0.17488 0.175 0.160
CatBoost 0.17330 0.173 0.159
XGBoost 0.17508 0.175 0.157

注意:我对 LGBM、CatBoost 和 XGBoost 分别使用了三个独立模型的投票集成。对于每个模型,我选择了前三组超参数。这种集成方法使分数提高了 0.001。

第二阶段:训练图像模型

纯图像模型

这个阶段的时间很有限,因为我大部分时间都花在了第一阶段,只剩下大约一周的时间。基于公开讨论,我决定使用 EfficientNet-B1,这被认为是最好的模型之一。我尝试了 EfficientNet-B0,但发现像 EfficientNet-B1 和 B2 这样的大模型允许我训练更长时间,而不会有过拟合或过于乐观的交叉验证(CV)分数的风险。然而,使用大模型的缺点是 Kaggle 上的 GPU 内存限制和更长的提交时间,所以我坚持使用 EfficientNet-B1,并对 MaxVit-T 做了一个小实验。

对于训练,我遵循了以下方法:

  • 我没有使用过采样或欠采样,而是使用了 PyTorch 的 WeightedRandomSampler,给正样本分配权重 1,给负样本分配权重 0.01。
  • 我加入了 2018、2019 和 2020 年 ISIC 竞赛的额外数据集。对于这些数据集,我采样了每年的所有正例和 10 倍的负例。在我的 DataLoader 中,我将额外数据的正负样本权重都分配为 0.01。
  • 批量大小(Batch size)有显著影响,所以我对于 EfficientNet-B1 使用了 64 的批量大小,对于 MaxViT-T 使用了 32。
  • 我使用了 OneCycleLR 调度器,并观察到迭代次数对 CV 和排行榜分数都有重大影响。我训练 EfficientNet-B1 进行了 200,000 次迭代(即 WeightedRandomSampler 中的 200,000 个样本),由于 MaxViT-T 是一个更大的模型,我将其训练延长至 300,000 次迭代。
  • 以下图像变换为我提供了最好的结果:
train_transform = A.Compose(
[
    A.RandomRotate90(),
    A.Flip(),
    A.ShiftScaleRotate(shift_limit=0.0, scale_limit=0.15, rotate_limit=90, p=0.5),
    A.RandomBrightnessContrast(brightness_limit=0.18, contrast_limit=0.12, p=0.5),
    A.HueSaturationValue(
        hue_shift_limit=3, sat_shift_limit=10, val_shift_limit=1, p=0.25
    ),
    A.GaussNoise(var_limit=(10.0, 50.0), p=0.5),
    A.OneOf(
        [
            A.OpticalDistortion(distort_limit=0.5, shift_limit=0.0),
            A.ElasticTransform(alpha=0.2, sigma=6.0),
            A.GridDistortion(num_steps=2, distort_limit=0.2),
        ],
        p=0.7,
    ),
    A.Resize(224, 224),
    A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
    ToTensorV2(),
]
)

val_transform = A.Compose([
    A.Resize(224, 224),
    A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
    ToTensorV2(),
])

下面是我的训练设置示例,类似于 @richolson 在此讨论中指出的内容 (链接)

pos_bagging_fraction = 1.0  # WeightedRandomSampler 中正样本的权重
neg_bagging_fraction = 0.01 # WeightedRandomSampler 中负样本的权重
learning_rate = 1e-5
weight_decay = 0.0008
_scheduler = OneCycleLR(
    _optimizer,
    max_lr=0.0008,  # 峰值学习率
    steps_per_epoch=len(_train_loader),
    epochs=1,  # 1 个 epoch
    pct_start=0.4,  # 花费 40% 的时间预热
    anneal_strategy='cos'  # 余弦退火
)

图像 + 嵌入模型 (Image + Embedding Model)

通过在管道中结合表格数据和图像数据,我看到了显著的改进。这种方法允许我继续使用额外数据集以及原始数据集。我在输入中增加了一个维度,指示元数据是否缺失。对于数值特征,缺失值填充为零。2020 ISIC 数据集有一些与 2024 ISIC 数据相似的元数据特征,这可能很有用,但由于资源限制,我没能探索这种方法。

下表总结了我的图像模型实验:

模型 CV 分数 公共 LB 私有 LB
Effnet-b1 0.1546 0.150 0.148
MaxVit-T 0.157 0.151 0.144
Effnet-b1 + MetaEmbedding 0.170 0.162 0.154

第三阶段(图像模型 + GBDT)

我只能使用 Effnet-b1 的 OOF 预测进行下游处理,没有足够的时间放入 Effnetb1 + MetaEmbedding,尽管我后来的看法表明,使用它的 GBDT 模型的 CV 将接近 ~0.179x

下表总结了我的模型。在这个阶段,我将模型分为两部分:

  • V1:基于公共特征训练
  • V2:基于修剪后 + 额外特征训练
模型 CV 分数 公共 LB 私有 LB
LGBM_V1 0.17606 0.178 0.168
LGBM_V2 0.1766 0.176 0.167
CatBoost_V1 0.1789** 0.180 0.170
CatBoost_V2 0.1780** 0.180 0.171
XGB_V1 0.1765 0.176 0.163
XGB_V2 0.1768 0.177 0.166

注意:由于 GPU 训练的可变性,CatBoost 模型分数在 [0.1792 - 0.1781] 之间波动。

第四阶段(集成)

我是在最后一天使用最后 5 次提交完成的,想不出比 Optuna 投票更好的策略,最终结果与我的 CatBoost 模型性能相同 :(。

这是我的管道。对于 Optuna 集成,我使用了 CmaEsSampler 来寻找平均权重。

Ensemble Pipeline

最终分数如下:

CV: ~0.1790(由于 Cmaes Sampler 和 Catboost GPU 的变化,这也会有波动)
公共 LB: 0.180
私有 LB: 0.171

以下是我的 Notebook 链接:

潜在的改进(计划但未测试)

我相信还有额外的策略可能会提高分数。不幸的是,由于时间和资源限制,我无法充分探索它们。我不确定这些方法是否有效,但我很想听听有谁尝试过:

  1. 在下游使用 MetaEmbedding 图像模型 OOF 预测:将 MetaEmbedding 图像模型的折叠外(OOF)预测纳入下游模型可能会改善结果。
  2. 使用 CNN 最后一层特征进行 GBDT:从 CNN 模型的最后一层提取特征,并使用低维投影(PCA、SVD 或其他技术)作为 GBDT 模型的输入,可能会提供更好的特征表示。
  3. 将 Transformer 或分解机与图像模型结合:我最初尝试将 TabTransformer 与图像模型集成并未成功。然而,如果有足够的计算能力和大批量大小,结合 Transformer 或分解机与图像模型可能会起作用。
  4. 在随机森林叶子上拟合微型图像模型:我有一个有点非传统的想法,即在随机森林的叶子上拟合微型版本的图像模型,并以某种方式组合预测。我没有尝试这个,因为我不确定这是否是一个可行的方法或值得付出努力。
  5. 第三阶段堆叠:我在本地运行了使用 CatBoost 作为元估计器来堆叠第三阶段预测,但由于用完了最后 5 次提交,无法进一步探索,尽管它产生了比加权平均更好的验证分数。

什么奏效了

  1. 使用聚合特征,特别是 patient_idtbp_lv_location
  2. 使用 OneCycleLR 技术而不是标准的 epoch 训练(我最初做的是 2 个 epochs)。
  3. 对于 PyTorch 图像训练使用 WeightedRandomSampler。(我最初做的是手动全局下采样)
  4. 不使用早停(防止我对结果过于乐观)。
  5. 结合过去 ISIC 竞赛的额外数据集。
  6. 使用元数据提高图像模型的分数。

什么没奏效(仅尝试过一次)

  1. LGBM 线性变体:CV-LB 模式不稳定,测试集中缺失值的存在导致多次提交失败,因此没有在第 3 阶段尝试。
  2. Transformer (TabTransformer) 与图像模型:当与图像模型结合时,这种方法没有产生预期的结果。
  3. 图像模型的两阶段调整:第一阶段在组合数据集(原始 + 额外)上进行微调,然后使用元嵌入和第一阶段图像模型再次微调的策略并没有产生有意义的改进。
同比赛其他方案