膨胀卷积——《MULTI-
SCALECONTEXTAGGREGATIONBYDILATED。。。
看这篇论⽂主要是想了解膨胀卷积,搜出这篇,看起来貌似⽐deeplab简单⼀些,于是以此⼊⼿。这篇论⽂把膨胀卷积的计算原理讲的很清楚,但是作⽤和产⽣的缘由的话还是deeplab的论⽂更容易懂,deeplab⾥⾯叫"hole algorithm"。
1. dense prediction
在谈膨胀卷积之前想先说⼀下dense prediction,⼀开始对这个概念不太理解,看了看别⼈的解释后说说⾃⼰的理解吧。
在做分类时,我们输⼊⼀整张图⽚(假如是(n,n,3)尺⼨的),输出只需要⼀个class label(尺⼨为1);
做⽬标检测时,给出物体所在边框(⽅框)的4个顶点(尺⼨为(1,4));
做物体边缘检测时,给出物体的边缘像素点,这时候上升到像素级别,预测实际上是基于每个像素了(尺⼨为(n, n,1));
做语义分割时,实际上是对输⼊图像的每个像素点做类别预测,假如有m类,输出实际上是每个像素点被分为各类别的概率(尺⼨为
(n,n,m));实例分割的话类别就更多了,输出也更稠密。
按照输⼊和输出的size对⽐,越往下,输出相对于输⼊的密度越⼤。从笨妞做过的实验来看,迭代需要的时间也越长。
作者认为dense prediction有两个关键点多尺度上下⽂推理和全分辨率输出。“最近的⼯作研究了两种处理多尺度推理和全分辨率密集预测的冲突需求的⽅法.⼀种⽅法是重复向上卷积,⽬的是恢复丢失的分辨率,同时从下采样层进⾏全局透视。另⼀种⽅法包括将图像的多个重新缩放版本作为输⼊到⽹络,并结合为这些多个输⼊获得的预测。” 个⼈理解就是既要通过前⾯的多尺度卷积抽取图像的特征,卷积抽取特征的⼀⼤特点就是特征的尺⼨会变⼩;同时,由于前⾯说了dense prediction需要对每个像素进⾏预测,输出需要保存和原分辨率。像FCN就是前⾯抽特征的时候先缩,特征抽完了再放。
膨胀卷积就是为了保持泛化的抽特征,同时图像的尺⼨不缩减。
2. 膨胀卷积
本篇论⽂中的膨胀卷积平⾯计算层是这样的:
deeplab论⽂⾥⾯的计算细节是这样的:
margin rate
当然,为了保持尺⼨,通常需要先对图像做padding. padding的数量和膨胀率相关。
⼀般卷积和膨胀卷积的计算差别:
“膨胀卷积算⼦在过去被称为“带扩张滤波器的卷积”,它在⼩波分解算法a trous中起着关键的作⽤。我们⽤“扩张卷积”⼀词代替
了“膨胀滤波器卷积”来说明没有构造或表⽰“扩张滤波器”,⽽是对卷积算⼦本⾝进⾏了修改,使其能够以不同的⽅式使⽤滤波器参数。扩张卷积算⼦可以使⽤不同的扩张因⼦在不同的范围内应⽤相同的滤波器,我们的定义反映了扩张卷积算⼦的适当实现,它不涉及扩张滤波器的构造。”
原理:扩展的卷积⽀持指数扩展的接受域,从⽽不丢失分辨率或覆盖范围。
论⽂这个公式笨妞不太能理解,⾃⼰算了⼀下,扩展的接受域⼤概是这样的:
从膨胀卷积的计算过程可以看到,它既带有conv卷积滤波功能,同时具有pool层的泛化作⽤,但与pool层不同,pool只要stride>1,特征图的尺⼨就会⼤幅减⼩,⽽dilate不会。
3. 多尺度的膨胀卷积
论⽂中提到的膨胀卷积⽹络有7层,其中,每⼀层的核size都是(3,3)每层的膨胀率率分别是(1,1,2,4,8,16,1),但是作者提供的程序中⽹络更加复杂,在论⽂讲到的膨胀卷积⽹络前⾯添加了VGG16。同时,vgg16前4个模块之外维持不变,第5个卷积模块改为膨胀率为2,第⼀个全连接层改为核尺⼨为(7,7),膨胀率为4的卷积。可能这就是作者后⾯提到的更⼤的⽹络吧。
论⽂的⽹络结构是这样的:
上⾯⽹络的初始化⽅法:
后⾯提到的更复杂⽹络的初始化⽅法:
4.keras版本的程序解析
作者额源程序是基于caffe。多年不⽤caffe,实在不习惯,从github上了个keras版本的,地址在
程序是加载预训练模型做预测的,预训练模型只有theano的,没有我需要的tensorflow模型。另外,作者采⽤4个数据集,分别是
cityscapes、pascol voc2012、kitti、camvid,每个数据集的图像尺⼨不同,⽹络略有不同,但主要基本⼀样,就以cityscapes对应的⽹络来看吧。
def get_dilation_model_cityscapes(input_shape, apply_softmax, input_tensor, classes):
if input_tensor is None:
model_in = Input(shape=input_shape)
else:
if not K.is_keras_tensor(input_tensor):
model_in = Input(tensor=input_tensor, shape=input_shape)
else:
model_in = input_tensor
"""""""""""""""""""vgg16""""""""""""""""""""
h = Convolution2D(64, 3, 3, activation='relu', name='conv1_1')(model_in)
h = Convolution2D(64, 3, 3, activation='relu', name='conv1_2')(h)
h = MaxPooling2D(pool_size=(2, 2), strides=(2, 2), name='pool1')(h)
h = Convolution2D(128, 3, 3, activation='relu', name='conv2_1')(h)
h = Convolution2D(128, 3, 3, activation='relu', name='conv2_2')(h)
h = MaxPooling2D(pool_size=(2, 2), strides=(2, 2), name='pool2')(h)
h = Convolution2D(256, 3, 3, activation='relu', name='conv3_1')(h)
h = Convolution2D(256, 3, 3, activation='relu', name='conv3_2')(h)
h = Convolution2D(256, 3, 3, activation='relu', name='conv3_3')(h)
h = MaxPooling2D(pool_size=(2, 2), strides=(2, 2), name='pool3')(h)
h = Convolution2D(512, 3, 3, activation='relu', name='conv4_1')(h)
h = Convolution2D(512, 3, 3, activation='relu', name='conv4_2')(h)
h = Convolution2D(512, 3, 3, activation='relu', name='conv4_3')(h)
h = AtrousConvolution2D(512, 3, 3, atrous_rate=(2, 2), activation='relu', name='conv5_1')(h)
h = AtrousConvolution2D(512, 3, 3, atrous_rate=(2, 2), activation='relu', name='conv5_2')(h)
h = AtrousConvolution2D(512, 3, 3, atrous_rate=(2, 2), activation='relu', name='conv5_3')(h)
h = AtrousConvolution2D(4096, 7, 7, atrous_rate=(4, 4), activation='relu', name='fc6')(h)
h = Dropout(0.5, name='drop6')(h)
h = Convolution2D(4096, 1, 1, activation='relu', name='fc7')(h)
h = Dropout(0.5, name='drop7')(h)
h = Convolution2D(classes, 1, 1, name='final')(h)
"""""""""""""""""""vgg16""""""""""""""""""""
#到上⾯为⽌都是vgg16,只是第5个conv模块加⼊了膨胀, fc6从全连接层变成了conv层,并加⼊膨胀,fc7和final也变成conv层。
""""""""""""""""""""论⽂当中的⽹络模块"""""""""""""""""""""""""
h = ZeroPadding2D(padding=(1, 1))(h)  #膨胀卷积之前先padding
h = Convolution2D(classes, 3, 3, activation='relu', name='ctx_conv1_1')(h)
h = ZeroPadding2D(padding=(1, 1))(h)
h = Convolution2D(classes, 3, 3, activation='relu', name='ctx_conv1_2')(h)
h = ZeroPadding2D(padding=(2, 2))(h)
h = AtrousConvolution2D(classes, 3, 3, atrous_rate=(2, 2), activation='relu', name='ctx_conv2_1')(h)
h = ZeroPadding2D(padding=(4, 4))(h)
h = AtrousConvolution2D(classes, 3, 3, atrous_rate=(4, 4), activation='relu', name='ctx_conv3_1')(h)
h = ZeroPadding2D(padding=(8, 8))(h)
h = AtrousConvolution2D(classes, 3, 3, atrous_rate=(8, 8), activation='relu', name='ctx_conv4_1')(h)
h = ZeroPadding2D(padding=(16, 16))(h)
h = AtrousConvolution2D(classes, 3, 3, atrous_rate=(16, 16), activation='relu', name='ctx_conv5_1')(h)
h = ZeroPadding2D(padding=(32, 32))(h)
h = AtrousConvolution2D(classes, 3, 3, atrous_rate=(32, 32), activation='relu', name='ctx_conv6_1')(h)
h = ZeroPadding2D(padding=(64, 64))(h)
h = AtrousConvolution2D(classes, 3, 3, atrous_rate=(64, 64), activation='relu', name='ctx_conv7_1')(h)  #论⽂中有7个卷积层,这⾥多了⼀个。
""""""""""""""""""""论⽂当中的⽹络模块"""""""""""""""""""""""""
h = ZeroPadding2D(padding=(1, 1))(h)
""""""""""""""""""""类似于全连接层部分"""""""""""""""""""""""""
h = Convolution2D(classes, 3, 3, activation='relu', name='ctx_fc1')(h)
h = Convolution2D(classes, 1, 1, name='ctx_final')(h)
""""""""""""""""""""类似于全连接层部分"""""""""""""""""""""""""
"""""""""""""""""""上采样模块,只有cityscape对应的⽹络采⽤"""""""""""""
# the following two layers pretend to be a Deconvolution with grouping layer.
# never managed to implement it in Keras
# since it's just a gaussian upsampling trainable=False is recommended
h = UpSampling2D(size=(8, 8))(h)
logits = Convolution2D(classes, 16, 16, bias=False, trainable=False, name='ctx_upsample')(h)
"""""""""""""""""""上采样模块,只有cityscape对应的⽹络采⽤"""""""""""""
if apply_softmax:
model_out = softmax(logits)  #2维softmax
else:
model_out = logits
model = Model(input=model_in, output=model_out, name='dilation_cityscapes')
return model
预测程序:
#没有细看这个⽅法的算法,⼤致看来是把反射填充的部分重新算回去,并做zoom
#做zoom的原因在于,除了sityscape对应的⽹络有最后两层upsample,部分还原了图像尺⼨,其他数据集对应的⽹络都没有upsample,需要通过zoom来放⼤输出。def interp_map(prob, zoom, width, height):
zoom_prob = np.zeros((prob.shape[0], height, width), dtype=np.float32)
for c in range(prob.shape[0]):
for h in range(height):
for w in range(width):
r0 = h // zoom
r1 = r0 + 1
c0 = w // zoom
c1 = c0 + 1
rt = float(h) / zoom - r0
ct = float(w) / zoom - c0
v0 = rt * prob[c, r1, c0] + (1 - rt) * prob[c, r0, c0]
v1 = rt * prob[c, r1, c1] + (1 - rt) * prob[c, r0, c1]
zoom_prob[c, h, w] = (1 - ct) * v0 + ct * v1
return zoom_prob
def predict(image, model, ds):
image = image.astype(np.float32) - CONFIG[ds]['mean_pixel']
#输⼊图像⼤多是(500,500)以内的图⽚,作者采⽤的是填充⽽不是resize同⼀图像尺⼨,
#先对整张图做同⼀尺⼨的填充,然后再根据每个图像本⾝的尺⼨做图像分割和填充。
conv_margin = CONFIG[ds]['conv_margin']
input_dims = (1,) + CONFIG[ds]['input_shape']
batch_size, num_channels, input_height, input_width = input_dims
model_in = np.zeros(input_dims, dtype=np.float32)
image_size = image.shape
output_height = input_height - 2 * conv_margin
output_width = input_width - 2 * conv_margin
#整体填充
image = pyMakeBorder(image, conv_margin, conv_margin,
conv_margin, conv_margin,
cv2.BORDER_REFLECT_101)  #填充⽅式为“反射填充”
#计算图像分割数量
num_tiles_h = image_size[0] // output_height + (1 if image_size[0] % output_height else 0)
num_tiles_w = image_size[1] // output_width + (1 if image_size[1] % output_width else 0)
row_prediction = []
for h in range(num_tiles_h):
col_prediction = []
for w in range(num_tiles_w):
offset = [output_height * h,
output_width * w]
#有重叠的分割,并填充到input_size
tile = image[offset[0]:offset[0] + input_height,
offset[1]:offset[1] + input_width, :]
margin = [0, input_height - tile.shape[0],
0, input_width - tile.shape[1]]
tile = pyMakeBorder(tile, margin[0], margin[1],
margin[2], margin[3],
cv2.BORDER_REFLECT_101)
model_in[0] = anspose([2, 0, 1])
#每张分割图做⼀次预测
prob = model.predict(model_in)[0]
col_prediction.append(prob)
col_prediction = np.concatenate(col_prediction, axis=2)
row_prediction.append(col_prediction)  #预测图合并成⼤图
prob = np.concatenate(row_prediction, axis=1)
if CONFIG[ds]['zoom'] > 1:
#做zoom,还原图像。
prob = interp_map(prob, CONFIG[ds]['zoom'], image_size[1], image_size[0])
prediction = np.argmax(prob, axis=0)
color_image = CONFIG[ds]['palette'][prediction.ravel()].reshape(image_size)
return color_image
程序只有theano版本的预训练模型,于是笨妞⾃⼰弄了个训练程序,训练pascol voc2012,训练程序如下
import numpy as np
import cv2
from dilation_net import DilationNet
from datasets import CONFIG