paper:Pyramid Scene Parsing Network
code: GitHub - hszhao/semseg: Semantic Segmentation in Pytorch
mmsegmentation/psp_head.py at v0.17.0 · open-mmlab/mmsegmentation · GitHub
作者首先观察分析了将FCN方法应用于场景解析(Scene Parsing)时的几种代表性失败的情况,具体有以下三类:
语境关系普遍存在并且非常重要,尤其是对于复杂的场景理解问题。现实中往往存在的一些共存的视觉模式,比如,飞机通常是在跑道或空中飞行,而不是在马路上。如下图中的第一行,FCN根据外观将黄色框里的船预测为车,但众所周知汽车很少会在河上,缺少收集上下文信息的能力会增加错误分类的概率。
在分类中有很多类别标签容易混淆,比如filed和earth;mountain和hill;wall, house, building和skyscraper。他们的外观比较相似。如上图中第二行,FCN将黄色框中的物体一部分预测为摩天大楼,一部分预测为建筑物,这种结果显然是有问题的,目标物体应该要么被预测为skyscraper要么被预测为building,而不是两者都有。这个问题可以利用类别之间的关系来缓解。
场景图片中会包含任意大小的对象,一些小尺寸的对象比如路灯和告示牌,很难找到但可能非常重要。相反,一些大的物体可能会超过FCN的感受野从而导致不连续的预测。如上图中的第三行所示,枕头的外观和床单相似,忽略全局场景类别可能会导致无法识别枕头。为了提高非常小和非常大物体的识别能力,应该注意那些包含不明显类别的不同的子区域。
综上所述,许多错误都或多或少的与不同感受野的上下文关系以及全局信息有关,因此,一个具有合适的全局上下文先验的网络可以大大提高场景解析的精度。
基于上述分析,作者提出了金字塔池化模块(pyramid pooling module),经验证明它可以作为一个有效的全局上下文先验。
全局平均池化是一种很好的全局上下文先验,常用于分类任务中。但对于复杂的场景图像,这种策略并不足以覆盖必要的信息,这些图像中的像素被标注为许多种类的对象,直接将它们融合成一个单一的向量可能会失去空间关系并造成歧义。而将全局上下文信息与各个子区域的上下文信息进行结合对区分各个类别非常有帮助,将不同子区域的信息进行融合可以得到一个更强大的特征表示。
在空间金字塔池化(spatial pyramid pooling, SPP)中,介绍见SPP: Spatial Pyramid Pooling_00000cj的博客-CSDN博客 ,金字塔池化生成的不同层级的特征图最终被展平、拼接然后送入全连接层进行分类,这种全局先验旨在消除CNN图像分类对固定输入大小的约束。为了进一步减少不同子区域之间的上下文信息的损失,本文提出了一种层级的全局先验,它包含了不同子区域中不同尺度的信息,即金字塔池化模块(Pyramid Pooling Module, PPM),它作用于网络的最后一个feature map上,如下图所示
PPM在多个不同尺度下融合特征,其中红色显示的是最粗糙全局池化,得到一个向量。下面的金字塔层将特征图划分为不同的子区域,然后对每个子区域进行池化从而得到各个子区域的集合特征表示。文中金字塔池化模块采用采用四个level,分别为1x1,2x2,3x3,6x6。作者通过消融实验发现average pooling比max pooling的效果好,因此最终采用平均池化。在每一层级的池化后接一个1x1卷积用于降维,假设层级为N,则降维比例为1/N,文中采用4个等级,输入通道为2048,因此每个层级1x1卷积的输出通道数为2048/4=512。每个层级的输出通过双线性插值上采样到输入大小,并与输入进行concatenate,最终的输出通道为2048+512*4=4096。
文中作者还提出了一个auxiliary loss,对网络的中间结果进行监督,以ResNet101为例,该loss加在第4个stage后,即res4b22 residue block,如下图所示
引入的辅助损失有助于优化学习过程同时又不影响主分支的学习,文中通过实验发现添加辅助损失可以提高最终的精度,当辅助损失的权重\(\alpha=0.4\)时,精度最高。
在2016年的ImageNet的场景解析比赛中,PSPNet获得了第一,如下所示
在PASCAL VOC 2012数据集的语义分割任务中,当只用VOC 2012的数据进行训练时,PSPNet在所有20个类别上都达到了最高的精度。当用MS-COCO数据集进行预训练时,在19个类别上达到了最高的精度,如下表所示
import torch.nn as nn
from mmcv.cnn import ConvModule
from mmseg.ops import resize
class PPM(nn.ModuleList):
"""Pooling Pyramid Module used in PSPNet.
Args:
pool_scales (tuple[int]): Pooling scales used in Pooling Pyramid
Module.
in_channels (int): Input channels.
channels (int): Channels after modules, before conv_seg.
conv_cfg (dict|None): Config of conv layers.
norm_cfg (dict|None): Config of norm layers.
act_cfg (dict): Config of activation layers.
align_corners (bool): align_corners argument of F.interpolate.
"""
def __init__(self, pool_scales, in_channels, channels, conv_cfg, norm_cfg,
act_cfg, align_corners, **kwargs):
super(PPM, self).__init__()
self.pool_scales = pool_scales # (1, 2, 3, 6)
self.align_corners = align_corners
self.in_channels = in_channels # 2048
self.channels = channels # 512
self.conv_cfg = conv_cfg
self.norm_cfg = norm_cfg
self.act_cfg = act_cfg
for pool_scale in pool_scales:
self.append(
nn.Sequential(
nn.AdaptiveAvgPool2d(pool_scale),
ConvModule(
self.in_channels,
self.channels,
1,
conv_cfg=self.conv_cfg,
norm_cfg=self.norm_cfg,
act_cfg=self.act_cfg,
**kwargs)))
def forward(self, x):
"""Forward function."""
ppm_outs = []
for ppm in self:
ppm_out = ppm(x)
upsampled_ppm_out = resize(
ppm_out,
size=x.size()[2:],
mode='bilinear',
align_corners=self.align_corners)
ppm_outs.append(upsampled_ppm_out)
return ppm_outs