返回列表

12st Place Solution for the SenNet + HOA - Hacking the Human Vasculature in 3D

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

开始: 2023-11-07 结束: 2024-02-06 医学影像分析 数据算法赛
SenNet + HOA 血管分割竞赛第12名解决方案

SenNet + HOA - 人体血管3D分割竞赛第12名解决方案

作者: Igor PI
排名: 第12名
发布时间: 2024年2月12日
框架: TensorFlow

背景

该解决方案是作为由Common Fund细胞衰老网络(SenNet)计划与人类器官图谱(HOA)合作组织的血管分割竞赛的一部分而实现的。

竞赛概览页面:SenNet + HOA - Hacking the Human Vasculature in 3D

竞赛数据集:数据集链接

非常感谢组织者提供的这次机会!

概述

框架 — TensorFlow
数据管道 — 2D, ROI, 调整尺寸 (1024x704), tfrecord
模型 — 几乎是经典的U-net(详情见下文)

解决方案在两个notebook中呈现:

免责声明

该解决方案开发于2023年12月,在圣诞老人新年礼物之前,最终帮助700多名参赛者跃升至0.8以上分数。我带着0.567分排在第238位开始了假期。一周后当我再次打开排行榜时,我已经下降了超过150个名次!而这仅仅是个开始!;)我又在这个方法上花了一周时间。我必须承认,由于提高分数让我在排名中越来越靠后,我并没有太大热情继续。

最终,通过增加图像尺寸和进行少量架构改进,我达到了0.636的结果,我开始寻找其他方法(见下文失败的尝试章节)。

数据准备

所有数据(除了kidney_3_dense标签)都用作训练数据。图像包含大量无用的信息区域。为了减少这些区域,使用统计方法对图像进行预处理以提取ROI。

def apply_roi(image, label=None):
    """
    排除无用的图像区域
    """
    # 移除标准差低的行和列
    row_mask = image.std(axis=1) > 0.22
    clmn_mask = image.std(axis=0) > 0.22
    
    # 清理这种方法产生的噪声并获取可靠的区域
    row_mask = cleaning_mask(row_mask)
    clmn_mask = cleaning_mask(clmn_mask)
    
    image = image[row_mask, :][:, clmn_mask]
    label = label[row_mask, :][:, clmn_mask] if isinstance(label, np.ndarray) else None
    
    # 记录填充区域的大小以便后续正确恢复
    row_pad = (row_mask.argmax(), row_mask[::-1].argmax())
    clmn_pad = (clmn_mask.argmax(), clmn_mask[::-1].argmax())
    
    return image, label, (row_pad, clmn_pad)

def cleaning_mask(mask):
    """
    从噪声掩码中选择可靠区域
    """
    # 如果边框从第一个元素开始或在最后一个元素结束
    mask[0] = False
    mask[-1] = False
    
    # 获取边框边缘
    frames = np.nonzero(mask[:-1] != mask[1:])[0]
    # 获取边框长度
    delta = frames[1:] - frames[:-1]
    # 获取最大长度边框的索引
    max_solid_block_begin = np.argmax(delta)
    # 其他都是垃圾数据
    garbage = np.delete(frames, [max_solid_block_begin, max_solid_block_begin + 1])
    # 清理掩码
    for a, b in zip(garbage[::2], garbage[1::2]):
        mask[a + 1:b + 1] = False
    
    return mask

接下来,所有图像都被调整为1024x704的统一尺寸。实验从384x256尺寸开始,随着尺寸增加,结果如预期般改善。1024x704是未导致OOM错误的最大尺寸。处理后的图像示例如下。

处理后的图像示例

每25张图像(4%)用于验证,因为标签密度沿z轴变化很大。

最终的图像和标签被打包到tfrecord文件中,以组织多线程管道(总文件数 - 92个,每个162 MB)。对于1024x704形状的图像,最大可能的批量大小为32。未使用数据增强——我只是没来得及做!

模型

使用或多或少的经典U-net架构作为模型。

U-net架构

使用二元交叉熵评估损失。使用Adam优化器进行优化。学习率按照带有warmup的余弦衰减计划进行调整。

由于存在严重的类别不平衡,因此使用了权重。想法是在实例级别设置权重,因为当我们从肾脏中心向边缘移动时(沿z轴),类别比例变化很大。但首先我硬编码了权重,效果还可以接受。后来我没有回到这个问题,所以还有改进空间。

模型从头开始训练,共60个epoch。预测时选择验证损失最小的epoch。

预测结果大致如下:

预测结果

这里有很多FP(假阳性)… 然而,对样本权重进行调整似乎很有意义 🤔

失败的尝试

显然,考虑到大量小细节,任何缩放都会损害结果。我尝试通过将图像分割成片段(256x256大小的相交瓦片)来解决这个问题。我使用了相同的模型架构。但标签只出现在瓦片重叠的地方,当从瓦片组装掩码时,我得到了空白!我还没来得及弄清楚这个问题。

第二。我尝试通过改变架构来解决标签缩放问题——我在解码器末尾添加了另一个"类U-net"结构——2个卷积层和两个转置卷积层。这里做得也不太好,但在私有排行榜上本可以排到第67名 😉

一些观察

是的,是的… 发生了一场大地震… 不知为何,大多数解决方案都以可疑的相似方式失败了 ;) 在约100-600名的区间内,这一点很明显。

排行榜震动图

我的主要解决方案,与获胜方案在网络架构和图像尺寸上相似,在公共和私有数据集上都给出了稳定的结果。在私有数据集上——甚至更好一点!

获胜模型分数

公共和私有数据集之间的差异为0.012分。在第一千名中,如此稳定的结果屈指可数。总的来说,方差已经正常,剩下的就是处理偏差了 😁

感谢所有致力于该问题的人!与你们一起工作很有趣!祝好运! ✋

同比赛其他方案