623. ISIC 2024 - Skin Cancer Detection with 3D-TBP | isic-2024-challenge
首先,我要向获胜团队表示祝贺,并感谢 Kaggle 社区和竞赛组织者带来了另一场精彩的挑战,ISIC-2024。这是我第一次认真尝试 Kaggle 奖牌竞赛,从中我学到了很多。虽然我是中途加入的,但我始终保持决心并坚持到了最后。
我学到的一个关键教训是:要相信我的交叉验证(CV)分数,即使它低于大多数公共排行榜(LB)分数。我专注于构建稳健的解决方案,而不是对高排行榜排名过于乐观。由于 8 月份工作安排繁忙,我采取了一个直接的策略:如果一个方法或想法第一次尝试不成功,我就完全放弃它并继续前进。
我的第一步是彻底了解数据集,并关注论坛中的关键讨论,以了解哪些技术有效,哪些无效。
对于特征工程,我基于公开的 Notebook 开始构建,并添加了一个基于 tbp_lv_location 的聚合特征。在后续过程中,我根据 CatBoost 模型(表现最好的模型)确定的 SAGE value of 0 修剪了特征。此外,我还使用贪婪选择方法移除了四到五个特征——如果移除某个特征提高了我的 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"],
)
我的整个建模过程可以分为四个部分:
我不得不做两次这个阶段,因为我最初的方法深受公开 Notebook 的影响,导致 CV 分数不稳定。微小的变化会导致排行榜(LB)性能的显著波动。此外,我依赖早停(early stopping),这对 CV-LB 相关性产生了负面影响。
在第二次尝试中,我设法稳定了 CV 分数。虽然它比我的第一种方法略低,但 CV 和 LB 分数之间的一致模式让我确信我走在正确的道路上。通过进一步调整,我相信我可以提高分数。
我关注的模型是 LGBM、XGBoost 和 CatBoost。虽然我计划尝试 IBM SnapML,但由于时间和资源不足,我没能实施它。以下是这个阶段的一些关键点:
pos_bagging_fraction 和 neg_bagging_fraction 效果非常好,以至于我没有考虑任何额外的采样技术。总结表
| 模型 | 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 做了一个小实验。
对于训练,我遵循了以下方法:
WeightedRandomSampler,给正样本分配权重 1,给负样本分配权重 0.01。DataLoader 中,我将额外数据的正负样本权重都分配为 0.01。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' # 余弦退火
)
通过在管道中结合表格数据和图像数据,我看到了显著的改进。这种方法允许我继续使用额外数据集以及原始数据集。我在输入中增加了一个维度,指示元数据是否缺失。对于数值特征,缺失值填充为零。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 |
我只能使用 Effnet-b1 的 OOF 预测进行下游处理,没有足够的时间放入 Effnetb1 + MetaEmbedding,尽管我后来的看法表明,使用它的 GBDT 模型的 CV 将接近 ~0.179x。
下表总结了我的模型。在这个阶段,我将模型分为两部分:
| 模型 | 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 来寻找平均权重。
最终分数如下:
CV: ~0.1790(由于 Cmaes Sampler 和 Catboost GPU 的变化,这也会有波动)
公共 LB: 0.180
私有 LB: 0.171
以下是我的 Notebook 链接:
我相信还有额外的策略可能会提高分数。不幸的是,由于时间和资源限制,我无法充分探索它们。我不确定这些方法是否有效,但我很想听听有谁尝试过:
patient_id 和 tbp_lv_location。OneCycleLR 技术而不是标准的 epoch 训练(我最初做的是 2 个 epochs)。