639. CZII - CryoET Object Identification | czii-cryo-et-object-identification
首先,我要衷心感谢竞赛主办方的和 Kaggle 工作人员组织了如此迷人的竞赛。我非常享受这次竞赛,并在这个过程中学到了很多!
此外,我要感谢 @hengck23 和 @davidlist。@hengck23 的讨论和 notebook 是我解决方案的起点,而 @davidlist 的许多讨论和评论也非常有帮助。
我的解决方案简单直接。我使用了一个类似 3D ConvNeXt 的模型进行分割,并进一步采用了尽可能多的模型集成。随后,我使用 cc3d 计算了粒子的质心,并用 DBSCAN 对预测的质心进行了整理。
我使用了针对每个粒子调整半径后的 ground truth 掩膜。结果,公共 leaderboard 分数提高了 0.02~0.04。具体而言,我如下表所示调整了掩膜大小;
| 铁蛋白 (r=60) | β-半乳糖苷酶 (r=90) | 核糖体 (r=150) | 甲状腺球蛋白 (r=130) | 病毒样颗粒 (r=135) |
|---|---|---|---|---|
| 60/2 | 90/2 | 150/3 | 130/3 | 135/3 |
由于竞赛指标要求预测的质心落在距离 ground truth 质心 r×0.5 的半径内,我认为将每个粒子的半径乘以 0.5 或更小的因子是合理的。
在本节中,我将解释我的模型架构,包括编码器和解码器。
为了实现编码器,我从 ConvNeXt 开始,因为 ConvNeXt 非常强大且快速。然后,我 customized 了模型以适应预测小粒子的任务。以下是相对于原始 ConvNeXt 的更改;
解码器的流程基于 U-Net。conv block 的实现代码如下;
# conv block for decoder
class ConvBlock3D(nn.Module):
def __init__(self, in_channels, out_channels):
super().__init__()
self.conv1 = nn.Conv3d(in_channels, in_channels, kernel_size=3, padding=1, bias=False, groups=in_channels)
self.norm1 = nn.GroupNorm(num_groups=1, num_channels=in_channels)
self.act1 = nn.GELU()
self.conv2 = nn.Conv3d(in_channels, in_channels*3, kernel_size=1, bias=False)
self.conv3 = nn.Conv3d(in_channels*3, out_channels, kernel_size=1, bias=False)
def forward(self, x):
# x: (B, C, D, H, W)
x = self.conv1(x)
x = self.norm1(x)
x = self.conv2(x)
x = self.act1(x)
x = self.conv3(x)
return x
解码器的实现代码如下;
class Decoder3D(nn.Module):
def __init__(self,
encoder_dims=[64, 128, 256, 512],
decoder_dims=[32, 64, 128, 256],
out_channels=5):
super().__init__()
self.up3 = nn.Upsample(scale_factor=(2,2,2), mode='trilinear', align_corners=True)
#self.up3 = nn.ConvTranspose3d(encoder_dims[3], encoder_dims[3], kernel_size=2, stride=2)
self.dec3 = ConvBlock3D(in_channels=encoder_dims[3] + encoder_dims[2], out_channels=decoder_dims[2])
...
def forward(self, features):
x, f0, f1, f2, f3 = features
# --- 1) f3 -> f2 ---
d3 = self.up3(f3)
d3 = torch.cat([d3, f2], dim=1)
d3 = self.dec3(d3)
...
由于分割的 ground truth simplement 是一个球体,我使用了基本的 nn.Upsample 进行上采样,而不是 nn.ConvTranspose3d,以减少参数。
def normalize_numpy(self, x):
lower, upper = np.percentile(x, (1, 99))
x = np.clip(x, lower, upper)
x = x - np.min(x)
x = x / np.max(x)
return x