返回列表

23rd place solution

639. CZII - CryoET Object Identification | czii-cryo-et-object-identification

开始: 2024-11-06 结束: 2025-02-05 医学影像分析 数据算法赛
第 23 名解决方案

第 23 名解决方案

作者: chemdatafarmer | 日期: 2025-02-07 | 排名: 23

大家好!

这是一场我非常享受的超级有趣的比赛,这也是我在 Kaggle 上获得的第一枚银牌!感谢组织者举办了一场关于有趣主题且拥有酷数据集的比赛。特别感谢 @hengck23 的讨论帖和连通分量工作流,这真的帮助我开始了这项工作。

数据预处理

我按百分位数(0.99 和 0.01)裁剪了数据,并通过减去完整体素网格的均值并除以标准差,对每个实验进行了标准化。我这样做是因为我注意到存在一些相当严重的异常值。请看下方标准化后裁剪与未裁剪的强度直方图。一旦数据被裁剪,实验之间的数据看起来非常一致。

下方为未裁剪的标准化数据
未裁剪的标准化数据直方图

下方为裁剪后的标准化数据
裁剪后的标准化数据直方图

Ground Truth 生成

我对所有粒子使用了半径为 60 的高斯热力图(在缩放到体素坐标之前)。我使用了 @davidlist 识别出的 + 0.5 偏移来设定粒子中心。所有高斯图都被缩放,使得中心的最大值为 1.0,并从那里衰减,无论视图中有多少粒子。我尝试使用由感兴趣粒子的实际半径定义的高斯图,但在使用恒定半径时我的模型更加一致。粒子大小 60 是基于 trial and error 选择的,半径小于等于 apo-ferritin 的粒子大小,这似乎是一个最佳点。

模型架构

我使用了带有残差连接的 3D U-Net,处理 (64,64,64) 的数据裁剪,具有 5 通道 Sigmoid 输出。这部分是因为我想看看只用一个模型能做到多好。我一直想深入研究使用 3D 卷积网络,我某种程度上把这次比赛当作这样做的借口。这样做非常有趣,我也学到了很多。网络的参数是通过 Optuna 确定的,浅层网络往往比深层网络表现更好,考虑到我的机器上只有 12 GB 显存来容纳模型,这很好。更大的模型可能会迫使我使用 2D 解决方案。

第一个训练集生成

由于本次比赛侧重于召回率,为了节省计算时间,第一轮训练我只在真阳性样本上进行训练。我裁剪了 (80,80,80) 的区块,将实验中的每个粒子居中于视图中,为每个粒子生成至少一个视图。在训练期间,我使用了随机翻转、亮度和对比度调整进行增强。我还随机裁剪到模型输入大小 (64,64,64) 以提供平移增强。我也尝试了 (48,48,48) 和 (96,96,96) 的输入大小,但 64 似乎效果最好。

第二个训练集生成

正如你所料,使用上述准备的数据集训练的模型偏向于真阳性。这是故意的,我使用生成的模型在整个数据集上进行预测。然后我使用硬假阳性作为反例,所以第二个数据集准备如上 + 所有原始模型最难处理的假阳性。这有助于降低假阳性率,同时保持假阴性率相对较低。花了很多精力才调整好,我想找到一种更快、更有效的方法来确定最佳阈值,以挑选出对于这种硬负例挖掘训练方法有用的硬负例。我认为这是我在本次比赛中学到的最有价值的事情之一,我希望能在另一个问题上继续完善这里的方法。

损失函数

我使用了自定义的像素级交叉熵损失,使用了比赛权重,并对 Ground Truth 低于阈值的错误预测进行降权,以帮助模型专注于召回率。这是我解决方案中非常重要的一部分,但承认有点经验主义。概念上,背景是多数类,我想降低其权重,但找到合适的降权主要是 trial and error。这是另一个我认为可以改进的地方,通过建立一个框架来系统地评估这一点。我想在未来,如果我实现自定义损失,我需要找到更好的方法来标准化如何评估和优化它们。

训练

我使用早停法训练早期模型,并在 plateau 时降低学习率。起初收敛有点棘手,但将 batch size 作为变量之一的贝叶斯优化帮助我意识到较大的 batch size 通常是不利的。我对 4、8、16 和 32 的 batch size 做了一点系统研究,意识到模型在 batch size > 8 时收敛不好。当我使用 batch size 为 4 时,我能够克服这个限制(这对保持训练在我的 3060 显存内也有帮助)。我使用了每个实验的交叉验证,开始时 5 个实验用于训练,2 个实验用于验证。然后我切换到 6 个实验用于训练,1 个实验用于验证。这显著提高了我的分数,有点令人惊讶,但这告诉我我的增强不足,更多数据有助于提高泛化能力。这促使我尝试找到一种可以使用所有 7 个实验的训练协议。为了找到这些实验的学习率计划,我为 7 个模型中的每一个的学习率拟合了一条指数曲线,然后使用该曲线作为学习率在所有 7 个实验上进行训练。下方的图表显示了 5 个模型的样本,每个模型代表 7 折交叉验证的一折,以及拟合给定 epoch 平均学习率的曲线。我使用这个学习率计划训练了一个 5 种子集成,并从第 40 个 epoch 开始每 5 个 epoch checkpoint 模型,然后使用公共排行榜(因为它与我的内部验证跟踪得很好)来看模型在哪里开始过拟合。我的最终提交是围绕 epoch 40 的 checkpoint 的 5 种子集成。

用于训练全数据模型的学习率拟合曲线
学习率拟合曲线

阈值设定

提交的最后一部分是弄清楚如何设置阈值。我遵循了一个与 @hengck23 非常相似的协议,其中我使用 cc3d 查找大于 7 个体素的连通分量,然后使用质心作为给定感兴趣粒子的提交。当我尝试做非极大值抑制或评估连通分量形状的事情时,我的模型没有太大改进,所以我把它们排除在最终解决方案之外。真正的技巧是为粒子获得正确的阈值。我独立地对每个粒子使用网格搜索来找到最佳阈值。有些分布是峰值的,有些分布在超过某个阈值后相对平坦。在峰值分布上,我使用了最大值,在相对平坦的分布上,我取了最左边的最大值,因为问题偏向于召回率。我通过在公共排行榜上扫描较平坦分布的阈值来检查这个假设,当我选择最左边(最低)的阈值时,我获得了最好的泛化能力。

如果你读到这里,谢谢!我非常享受参加这次比赛,我期待着 Kaggle 上的下一场计算机视觉比赛 :).

同比赛其他方案