596. SenNet + HOA - Hacking the Human Vasculature in 3D | blood-vessel-segmentation
提升模型鲁棒性能的关键因素包括Tversky损失函数、增大推理尺寸和分辨率增强。这些元素在最终排名中起到了至关重要的作用。最终模型是基于RegNetY-016架构的2D U-Net模型集成。
这次比赛最难的是获得可靠的验证集(很遗憾我未能实现)。回顾结果时,我发现公开榜和私有榜存在显著差异。令人惊讶的是,过去的一些提交本可以进入金牌区。这很令人意外,因为我当时并不重视这些提交,最终也没有提交它们。
在我的实验中,发现给Tversky损失函数中的正类分配更高权重(使用较大的beta值)在私有榜上表现更好。然而最终提交时,我使用了较小的beta值训练,模型在公开榜上表现良好,却在私有榜上意外表现不佳。这种差异可能源于私有数据集更高的分辨率,包含更详细的输入信息。因此建议在这种情况下使用更低的模型logits阈值。此外,推理时缩放图像(1.2, 1.5...)似乎会稀释输入信息,降低分辨率的影响。
使用三个连续切片作为模型输入,并以原始图像尺寸进行训练。尽管尝试增加切片数量(5, 7, 9...),结果显示性能反而下降。
比赛初期,我通过调整数据到特定尺寸来训练模型。但如我在讨论中提到的,Albumentations库的Resize函数对掩码使用最近邻插值,导致精细标签产生显著噪声,性能明显下降。因此我决定使用原始图像尺寸。
意识到公开和私有数据存在分辨率差异后,我致力于创建对分辨率变化鲁棒的模型。同时考虑到分箱过程中存在尺寸调整,我采用了模糊增强:
def blur_augmentation(x):
h, w, _ = x.shape
scale = np.random.uniform(0.5, 1.5)
x = A.Resize(int(h*scale), int(w*scale))(image=x)['image']
x = A.Resize(h, w)(image=x)['image']
return x
此外,由于通道是通过堆叠深度构建的,我应用了以下增强:
def channel_augmentation(x, prob=0.5, n_channel=3):
assert x.shape[2]==n_channel
if np.random.rand()
最后,考虑到数据标注固有的噪声,我采用了强cutout增强来防止过拟合:
A.Cutout(num_holes=8, max_h_size=128, max_w_size=128, p=0.8),
这些方法有效提升了交叉验证和排行榜的性能。
模型采用了2D U-Net架构,CNN骨干网络使用轻量级模型RegNetY-016。其余设置保持Segmentation Models PyTorch库的默认值。
尽管投入了大量时间开发基于3D的模型,但它在交叉验证和排行榜上均未显示出显著的得分提升。由于资源限制,我转向专注于2D模型。
class CustomModel(nn.Module):
def __init__(self):
super(CustomModel, self).__init__()
self.n_classes = 1
self.in_chans = 3
self.encoder = timm.create_model(
'regnety_016',
pretrained=True,
features_only=True,
in_chans=self.in_chans,
)
encoder_channels = tuple(
[self.in_chans]
+ [
self.encoder.feature_info[i]["num_chs"]
for i in range(len(self.encoder.feature_info))
]
)
self.decoder = UnetDecoder(
encoder_channels=encoder_channels,
decoder_channels=(256, 128, 64, 32, 16),
n_blocks=5,
use_batchnorm=True,
center=False,
attention_type=None,
)
self.segmentation_head = SegmentationHead(
in_channels=16,
out_channels=self.n_classes,
activation=None,
kernel_size=3,
)
self.train_loss = smp.losses.TverskyLoss(mode='binary', alpha=0.1, beta=0.9)
self.test_loss = smp.losses.DiceLoss(mode='binary')
def forward(self, batch, training=False):
x_in = batch["input"]
enc_out = self.encoder(x_in)
decoder_out = self.decoder(*[x_in] + enc_out)
x_seg = self.segmentation_head(decoder_out)
output = {}
one_hot_mask = batch["mask"][:, None]
if training:
loss = self.train_loss(x_seg, one_hot_mask.float())
else:
loss = self.test_loss(x_seg, one_hot_mask.float())
output["loss"] = loss
output['logit'] = nn.Sigmoid()(x_seg)[:, 0]
return output
推理时将图像尺寸缩放1.5倍,在交叉验证、公开榜和私有榜上均持续提升了分数。这相当于一种扩张操作,显著减少了假阴性,提升了模型性能。简单地将推理图像尺寸增加1.2倍,就在私有榜上带来了0.1的改进。