論文地址:MULTI-SCALE CONTEXT AGGREGATION BY DILATED CONVOLUTIONS
論文代碼:github
一、簡介
語義分割相比於分類是一種密集信息的分類任務,需要更多的細節和上下文信息,如何獲取更好的上下文信息是目前大多數研究的方向,一般的做法是多尺度輸入,融合網絡不同stage的featue得到多尺度feature或者使用特殊的模塊提取更好的上下文信息,本文提出一個使用空洞卷積的多尺度上下文信息提取模塊,可以被應用於多種網絡。
二、網絡結構
1、空洞卷積
空洞卷積,文中稱(atrous convolution)的公式如下:
其中爲輸出,爲輸入,爲輸入索引,爲卷積核內索引,當時就是標準的卷積核。下面兩張圖時空洞卷積的結構,可以看到空洞卷積只是標準卷積的卷積核採樣之間補零來擴大感受野。需要注意的是,雖然該層的感受野得到了很大的提升但是也會導致網絡在信息提取時掠過一些點而丟失部分信息。如下圖可以看到當rate越大時空洞卷積的感受野越大。作者使用空洞卷積作爲網絡的基本層的原因時空洞卷積可以獲得大範圍的感受野而不會丟失過多的信息,而且能夠收斂。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-6CpNqS82-1572692044241)(imgs/sf.png)]
2、MULTI-SCALE CONTEXT AGGREGATION
該模塊的結構如下圖所示,基本是由空洞卷積組成。整個模塊並不會改變輸入的大小意思就是輸入的大小是則輸出也是。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-6Fe99d4D-1572692044242)(imgs/arch.png)]
從模塊的結構圖中可以看到該結構總共有7層,所有層的kernel size都是3,不同層的rate分別是[1,1,2,4,8,16]。最後一層中的1*1卷積是用來將通道降低到和輸入相同。
def add_context(model: Sequential) -> Sequential:
""" Append the context layers to the frontend. """
model.add(ZeroPadding2D(padding=(33, 33)))
model.add(Convolution2D(42, 3, 3, activation='relu', name='ct_conv1_1'))
model.add(Convolution2D(42, 3, 3, activation='relu', name='ct_conv1_2'))
model.add(AtrousConvolution2D(84, 3, 3, atrous_rate=(2, 2), activation='relu', name='ct_conv2_1'))
model.add(AtrousConvolution2D(168, 3, 3, atrous_rate=(4, 4), activation='relu', name='ct_conv3_1'))
model.add(AtrousConvolution2D(336, 3, 3, atrous_rate=(8, 8), activation='relu', name='ct_conv4_1'))
model.add(AtrousConvolution2D(672, 3, 3, atrous_rate=(16, 16), activation='relu', name='ct_conv5_1'))
model.add(Convolution2D(672, 3, 3, activation='relu', name='ct_fc1'))
model.add(Convolution2D(21, 1, 1, name='ct_final'))
return model
3、初始化
作者做實驗發現使用常用的神經網絡初始化並不能使網絡奏效,因此作者更替換初始化方法。
其中是輸入feature的索引,是輸出feature的索引
其中和是相鄰兩個feature的通道數,,是輸出通道數,也就是類別數。
4、網絡前端
網絡的前段時基於VGG-16修改的,去除了最後兩個pooling和conv層,並使用rate=2的空洞卷積代替,最終網絡能夠得到一個高分辨率的feature,64*64。
def get_frontend(input_width, input_height) -> Sequential:
model = Sequential()
# model.add(ZeroPadding2D((1, 1), input_shape=(input_width, input_height, 3)))
model.add(Convolution2D(64, 3, 3, activation='relu', name='conv1_1', input_shape=(input_width, input_height, 3)))
model.add(Convolution2D(64, 3, 3, activation='relu', name='conv1_2'))
model.add(MaxPooling2D((2, 2), strides=(2, 2)))
model.add(Convolution2D(128, 3, 3, activation='relu', name='conv2_1'))
model.add(Convolution2D(128, 3, 3, activation='relu', name='conv2_2'))
model.add(MaxPooling2D((2, 2), strides=(2, 2)))
model.add(Convolution2D(256, 3, 3, activation='relu', name='conv3_1'))
model.add(Convolution2D(256, 3, 3, activation='relu', name='conv3_2'))
model.add(Convolution2D(256, 3, 3, activation='relu', name='conv3_3'))
model.add(MaxPooling2D((2, 2), strides=(2, 2)))
model.add(Convolution2D(512, 3, 3, activation='relu', name='conv4_1'))
model.add(Convolution2D(512, 3, 3, activation='relu', name='conv4_2'))
model.add(Convolution2D(512, 3, 3, activation='relu', name='conv4_3'))
# Compared to the original VGG16, we skip the next 2 MaxPool layers,
# and go ahead with dilated convolutional layers instead
model.add(AtrousConvolution2D(512, 3, 3, atrous_rate=(2, 2), activation='relu', name='conv5_1'))
model.add(AtrousConvolution2D(512, 3, 3, atrous_rate=(2, 2), activation='relu', name='conv5_2'))
model.add(AtrousConvolution2D(512, 3, 3, atrous_rate=(2, 2), activation='relu', name='conv5_3'))
# Compared to the VGG16, we replace the FC layer with a convolution
model.add(AtrousConvolution2D(4096, 7, 7, atrous_rate=(4, 4), activation='relu', name='fc6'))
model.add(Dropout(0.5))
model.add(Convolution2D(4096, 1, 1, activation='relu', name='fc7'))
model.add(Dropout(0.5))
# Note: this layer has linear activations, not ReLU
model.add(Convolution2D(21, 1, 1, activation='linear', name='fc-final'))
# model.layers[-1].output_shape == (None, 16, 16, 21)
return model
三、結果