文章地址:https://arxiv.org/pdf/2003.13328v1.pdf
源码地址:https://github.com/Andrew-Qibin/SPNet
问题的引出
在某些情况下,目标对象可能具有远距离的带状结构或离散分布(如下图所示)。 使用方形不能很好地解决问题,因为它不可避免地会合并来自无关区域的信息。普通的空间池化,不可避免的会加入一些无关紧要的像素区域。尤其是在利用池化扩大感受野时,这一缺点将更加明显。所以作者提出了 条纹池化。
在本文中,为了更有效地捕获远程依赖关系,进一步研究使用池化来扩大CNN的接收域并收集信息上下文,并提出了条形池化的概念。 作为全局池的替代方法,条形池化具有两个优点。 首先,它沿一个空间维度部署了长条的核,因此可以捕获孤立区域的长期关系。 其次,它沿其他空间维保持窄的内核形状,这有助于捕获本地上下文并防止无关区域干扰标签预测。 集成了如此长而狭窄的内核,使得场景解析网络可以同时聚合全局和局部上下文。 这与传统的从固定的正方形区域收集上下文的池化本质上是不同的。
基于条形池化操作,我们提出了两个场景解析网络模块。 首先,文中设计一个条形池化模块(SPM),以有效地扩大主干的接收范围。其次,文中提出了一种新颖的附加残差构建模块,称为混合池化模块(MPM),以进一步在高语义级别上对远程依赖性进行建模。
方法
为了更有效地捕获长依赖关系,本文在空间池化层扩大卷积神经网络感受野和捕获上下文信息的基础上,提出了条形池化(strip pooling)的概念。条形池有两个优点:
1、它沿一个空间维度部署一个长条的池化形状,能够捕获孤立区域的远程关系。
2、它沿其他空间维度保持一个狭窄的池化形状,有利于捕获上下文,并防止无关区域的污染信息干扰预测。
这种条形池化可以使语义分割网络能够同时聚合全局和局部上下文信息。
令输出张量x∈RCxHxW,C为通道数量,首先将x输入两个平行的路径,每个路径包含一个水平或垂直的条形池化层,然后是一维卷积层,其内核大小为3,用于整合当前位置及其相邻特征。这部分给出了yh∈RCxH和了yv∈RCxW,为了获得含有更多全局先验信息的输出z∈RCxWxH,首先通过下式获得y∈RCxHxW:
然后输出z由下式计算:
其中Scale(,)表示逐元素乘法,σ表示sigmoid函数,f是1*1卷积。应当注意,存在多种方式来组合由两个条形池化层提取的特征,例如计算两个提取的一维特征向量之间的内积。 但是,考虑到效率,我们采用了上述操作。
在以上过程中,输出张量中的每个位置允许建立与输入张量中其他位置的关系。例如,上图,输出张量中黑框的位置与和其有相同水平和垂直位置的点(用红色和紫色框围起来)构建连接。通过重复上述过程多次,可以在整个场景上建立远程依赖关系。 此外,得益于基本的乘法运算,建议的SPM还可以被视为一种注意力机制,并且可以直接应用于任何经过预训练的骨干网,而无需从头开始进行训练。
Mixed Pooling Module(MPM)
Code
class StripPooling(nn.Module):
"""
Reference:
"""
def __init__(self, in_channels, pool_size, norm_layer, up_kwargs):
super(StripPooling, self).__init__()
self.pool1 = nn.AdaptiveAvgPool2d(pool_size[0])
self.pool2 = nn.AdaptiveAvgPool2d(pool_size[1])
self.pool3 = nn.AdaptiveAvgPool2d((1, None))
self.pool4 = nn.AdaptiveAvgPool2d((None, 1))
inter_channels = int(in_channels/4)
self.conv1_1 = nn.Sequential(nn.Conv2d(in_channels, inter_channels, 1, bias=False),
norm_layer(inter_channels),
nn.ReLU(True))
self.conv1_2 = nn.Sequential(nn.Conv2d(in_channels, inter_channels, 1, bias=False),
norm_layer(inter_channels),
nn.ReLU(True))
self.conv2_0 = nn.Sequential(nn.Conv2d(inter_channels, inter_channels, 3, 1, 1, bias=False),
norm_layer(inter_channels))
self.conv2_1 = nn.Sequential(nn.Conv2d(inter_channels, inter_channels, 3, 1, 1, bias=False),
norm_layer(inter_channels))
self.conv2_2 = nn.Sequential(nn.Conv2d(inter_channels, inter_channels, 3, 1, 1, bias=False),
norm_layer(inter_channels))
self.conv2_3 = nn.Sequential(nn.Conv2d(inter_channels, inter_channels, (1, 3), 1, (0, 1), bias=False),
norm_layer(inter_channels))
self.conv2_4 = nn.Sequential(nn.Conv2d(inter_channels, inter_channels, (3, 1), 1, (1, 0), bias=False),
norm_layer(inter_channels))
self.conv2_5 = nn.Sequential(nn.Conv2d(inter_channels, inter_channels, 3, 1, 1, bias=False),
norm_layer(inter_channels),
nn.ReLU(True))
self.conv2_6 = nn.Sequential(nn.Conv2d(inter_channels, inter_channels, 3, 1, 1, bias=False),
norm_layer(inter_channels),
nn.ReLU(True))
self.conv3 = nn.Sequential(nn.Conv2d(inter_channels*2, in_channels, 1, bias=False),
norm_layer(in_channels))
# bilinear interpolate options
self._up_kwargs = up_kwargs
def forward(self, x):
_, _, h, w = x.size()
x1 = self.conv1_1(x)
x2 = self.conv1_2(x)
x2_1 = self.conv2_0(x1)
x2_2 = F.interpolate(self.conv2_1(self.pool1(x1)), (h, w), **self._up_kwargs)
x2_3 = F.interpolate(self.conv2_2(self.pool2(x1)), (h, w), **self._up_kwargs)
x2_4 = F.interpolate(self.conv2_3(self.pool3(x2)), (h, w), **self._up_kwargs)
x2_5 = F.interpolate(self.conv2_4(self.pool4(x2)), (h, w), **self._up_kwargs)
x1 = self.conv2_5(F.relu_(x2_1 + x2_2 + x2_3))
x2 = self.conv2_6(F.relu_(x2_5 + x2_4))
out = self.conv3(torch.cat([x1, x2], dim=1))
return F.relu_(x + out)
数据集评估
在Cityscapes数据集上的评估: