Pyramid Scene Parsing Network
Keras实现代码链接:https://github.com/BBuf/Keras-Semantic-Segmentation
Contribution
- 提出了金字塔场景解析网络,以将困难的场景上下文特征嵌入基于FCN的像素预测框架中。
- 基于深度监督的损失为深度ResNet开发了有效的优化策略。
- 构建了一个用于最新场景解析和语义分割的实用系统,其中包括了所有关键的实现细节。
Related Work
大多数语义分割模型的工作基于两个方面:
- 一方面:具有多尺度的特征融合。高层特征包含更多的语义信息和较少的位置信息。 组合多尺度特征可以提高性能。
- 另一个方面:是基于结构预测。例如使用CRF(条件随机场)做处理细化分割结果。
Pyramid Pooling Module
为了进一步减少不同子区域之间的上下文信息丢失,本文提出了一个分层的全局先验,其中包含具有不同规模和不同子区域之间变化的信息,称其为金字塔池模块,用于在深度神经网络的最终层特征图上进行全局场景提前构造,如图3(c)所示。
该模块融合了4种不同金字塔尺度的特征,第一行红色是最粗糙的特征–全局池化生成单个bin输出,后面三行是不同尺度的池化特征。为了保证全局特征的权重,如果金字塔共有N个级别,则在每个级别后使用1×1的卷积将对于级别通道降为原本的1/N。再通过双线性插值进行up-sample获得未池化前的大小,最终将它们concat到一起。
金字塔等级的池化核大小是可以设定的,这与送到金字塔的输入有关。论文中使用的4个等级,核大小分别为1×1,2×2,3×3,6×6。
Network Architecture
- 基础层经过预训练的模型(ResNet101)和空洞卷积策略(dilated network strategy)提取feature map,提取后的feature map是输入大小的1/8
- feature map经过Pyramid Pooling Module得到融合的带有整体信息的feature,在上采样之后与池化前的feature map相concat
- 最后过一个卷积层得到最终输出
PSPNet本身提供了一个全局上下文的先验(即指代Pyramid Pooling Module这个结构),后面的实验会验证这一结构的有效性。
Deep Supervision for ResNet-Based FCN
图4展示了本文受深度监督的ResNet101模型的示例。除了使用softmax损失来训练最终分类器的主分支外,在第四阶段之后使用了另一个分类器,即res4b22残基块。 与将反向辅助损耗阻止到几个浅层的中继反向传播不同,本文让两个损耗函数通过所有先前的层。 辅助损失有助于优化学习过程,而主分支损失承担最大责任,增加权重以平衡辅助损失。
Implementation Details
- 1、学习率:采用“poly”策略,即 l r = l r b a s e ∗ ( 1 − i t e r m a x i t e r ) p o w e r lr = lr_base * (1 - iter \over max_iter)^power maxiter)powerlr=lrbase∗(1−iter,设置 l r b a s e = 0.01 , p o w e r = 0.9 , m o m e n t u m = 0.9 , w e i g h t d e c a y = 0.0001 lr_base = 0.01,power = 0.9,momentum = 0.9,weight decay = 0.0001 lrbase=0.01,power=0.9,momentum=0.9,weightdecay=0.0001
- 2、迭代次数:ImageNet数据集:150k,PASCAL VOC:30k,Cityscapes:90k
- 3、数据增强:随机翻转、尺度在0.5-2之间随机缩放、角度在-10-10度之间随机旋转、随机高斯滤波
- 4、batchsize = 16
- 5、辅助loss的权重为0.4
- 6、语言:Caffe
Keras实现的模型代码:
#coding=utf-8
from keras.models import *
from keras.layers import *
import keras.backend as K
import tensorflow as tf
#池化块(POOL)
def pool_block(inp, pool_factor):
h = K.int_shape(inp)[1]
w = K.int_shape(inp)[2]
pool_size = strides = [int(np.round( float(h) / pool_factor)), int(np.round( float(w)/ pool_factor))]
x = AveragePooling2D(pool_size, strides=strides, padding='same')(inp)
x = Conv2D(256, (1, 1), padding='same', activation='relu')(x)
x = BatchNormalization()(x)
x = Lambda(lambda x: tf.image.resize_bilinear(x, size=(int(x.shape[1])*strides[0], int(x.shape[2])*strides[1])))(x) # 上采样up-sample
x = Conv2D(256, (1, 1), padding='same', activation='relu')(x)
return x
def PSPNet(nClasses, input_width=384, input_height=384):
assert input_height % 192 == 0
assert input_width % 192 == 0
inputs = Input(shape=(input_height, input_width, 3))
x = (Conv2D(64, (3, 3), activation='relu', padding='same'))(inputs)
x = (BatchNormalization())(x)
x = (MaxPooling2D((2, 2)))(x)
f1 = x
# 192 x 192
x = (Conv2D(128, (3, 3), activation='relu', padding='same'))(x)
x = (BatchNormalization())(x)
x = (MaxPooling2D((2, 2)))(x)
f2 = x
# 96 x 96
x = (Conv2D(256, (3, 3), activation='relu', padding='same'))(x)
x = (BatchNormalization())(x)
x = (MaxPooling2D((2, 2)))(x)
f3 = x
# 48 x 48
x = (Conv2D(256, (3, 3), activation='relu', padding='same'))(x)
x = (BatchNormalization())(x)
x = (MaxPooling2D((2, 2)))(x)
f4 = x
# 24 x 24
o = f4
pool_factors = [1, 2, 3, 6] # 不同尺度的池化因子(4个)
pool_outs = [o]
for p in pool_factors:
pooled = pool_block(o, p)
pool_outs.append(pooled)
o = Concatenate(axis=3)(pool_outs) # 将上采样后的特征图拼接在一起
o = Conv2D(256, (3, 3), activation='relu', padding='same')(o)
o = BatchNormalization()(o)
o = Lambda(lambda x: tf.image.resize_bilinear(x, size=(int(x.shape[1])*8, int(x.shape[2])*8)))(x)
o = Conv2D(nClasses, (1, 1), padding='same')(o)
o_shape = Model(inputs, o).output_shape
outputHeight = o_shape[1]
outputWidth = o_shape[2]
print(outputHeight)
print(outputWidth)
o = (Reshape((outputHeight*outputWidth, nClasses)))(o)
o = (Activation('softmax'))(o)
model = Model(inputs, o)
model.outputWidth = outputWidth
model.outputHeight = outputHeight
return model