引言
已经有很多U-Net-Like的神经网络被提出。
U-Net适用于医学图像分割、自然图像生成。
在医学图像分割表现好:
-
因为利用了底层的特征(同分辨率级联)改善上采样的信息不足。
-
医学图像数据一般较少,底层的特征其实很重要。
不只是医学图像,对于二分类的语义分割问题,类 UNet 结构均取得不错的效果。linknet、large kernel 和 Tiramisu 等模型的效果也不错,但不如类 UNet 结构
本文的内容主要是根据我在 Kaggle TGS Salt Identification Challenge 比赛中所做的尝试,以及别人分享的实验结果。
一、损失函数
-
最常见的损失函数就是 binary cross entropy loss 结合 dice coeff loss
前者是像素级别的损失函数
后者是图像级别或者是 batch 级别的损失函数,适合基于以 IOU 作为评价指标的问题。 -
online bootstrapped cross entropy loss
比如FRNN,难样本挖掘的一种 -
lovasz loss
来自论文 The Lovasz-Softmax loss: A tractable surrogate for the optimization of the intersection-over-union measure in neural networks
也是适合以 IOU 作为评价指标的问题。
二、网络的 Backbone
比较流行的 Backbone 如 SE-ResNeXt101,SE-ResNeXt50,SE-ResNet101,我觉得在数据集不是特别充足的情况下,差别不大。
由于显存的限制,我用的是 ResNet34
之前做的一些实例检测,实例分割问题,用 ResNet50 的效果也和 ResNet101 差不多。
三、基于 Attention 的 UNet
Concurrent Spatial and Channel Squeeze & Excitation in Fully Convolutional Networks
SE-Net 中的 SE 结构就是对 feature maps 中的不同 channel 进行加权处理。
这篇论文中把这种 attention 通用化,SE-Net 中采用的是 cSELayer,还有对不同的 position 进行加权的 sSELayer,以及两种加权方式结合起来的 scSELayer
论文中的实验表明这些 Attention-Gated 结构,放在 不同阶段的 encoder 和 decoder 之后,比起不加 attention,效果更好
class cSELayer(nn.Module):
def __init__(self, channel, reduction=2):
super(cSELayer, self).__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.fc = nn.Sequential(
nn.Linear(channel, channel // reduction),
nn.ELU(inplace=True),
nn.Linear(channel // reduction, channel),
nn.Sigmoid()
)
def forward(self, x):
b, c, _, _ = x.size()
y = self.avg_pool(x).view(b, c)
y = self.fc(y).view(b, c, 1, 1)
return x * y
class sSELayer(nn.Module):
def __init__(self, channel):
super(sSELayer, self).__init__()
self.fc = nn.Conv2d(channel, 1, kernel_size=1)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
y = self.fc(x)
y = self.sigmoid(y)
return x * y
class scSELayer(nn.Module):
def __init__(self, channels, reduction=2):
super(scSELayer, self).__init__()
self.sSE = sSELayer(channels)
self.cSE = cSELayer(channels, reduction=reduction)
def forward(self, x):
sx = self.sSE(x)
cx = self.cSE(x)
x = sx + cx
return x
四、关于 Context
class Dblock(nn.Module):
def __init__(self, channel):
super(Dblock, self).__init__()
self.dilate1 = nn.Conv2d(channel, channel, kernel_size=3, dilation=1, padding=1)
self.dilate2 = nn.Conv2d(channel, channel, kernel_size=3, dilation=2, padding=2)
self.dilate3 = nn.Conv2d(channel, channel, kernel_size=3, dilation=4, padding=4)
for m in self.modules():
if isinstance(m, nn.Conv2d):
if m.bias is not None:
m.bias.data.zero_()
def forward(self, x):
dilate1_out = F.relu(self.dilate1(x), inplace=True)
dilate2_out = F.relu(self.dilate2(dilate1_out), inplace=True)
dilate3_out = F.relu(self.dilate3(dilate2_out), inplace=True)
out = x + dilate1_out + dilate2_out + dilate3_out
return out
OCNet: Object Context Network for Scene Parsing
对于语义分割,模型既需要高纬度的上下文信息(全局信息),又需要分辨率能力(即图片的局部信息)。UNet 通过 concatenate 来提高图片的局部信息。那么如何获得更好的全局信息呢。OCNet论文中对 UNet 结构中间的 center block 进行了讨论。
五、Hyper columns
Hypercolumns for Object Segmentation and Fine-grained Localization
d5 = self.decoder5(center)
d4 = self.decoder4(d5, e4)
d3 = self.decoder3(d4, e3)
d2 = self.decoder2(d3, e2)
d1 = self.decoder1(d2, e1)
f = torch.cat((
d1,
F.interpolate(d2, scale_factor=2, mode='bilinear', align_corners=False),
F.interpolate(d3, scale_factor=4, mode='bilinear', align_corners=False),
F.interpolate(d4, scale_factor=8, mode='bilinear', align_corners=False),
F.interpolate(d5, scale_factor=16, mode='bilinear', align_corners=False),
), 1)