2016年提出的
空洞卷积广泛应用于语义分割与目标检测等任务中
空洞卷积(膨胀卷积/扩张卷积) Dilated/Atrous Convolution
空洞卷积是一种不增加参数量(运算量)同时增加输出单元感受野的一种方法。Atrous 卷积,就是带洞的卷积,卷积核是稀疏的
此外,空洞卷积还可以捕获多尺度上下文信息。通过设置不同的dilation rate,感受野就会不一样,也即获取了多尺度信息。多尺度信息在视觉任务中相当重要。
空洞卷积的概念是从语义分割任务中发展出来的,是为了解决基于FCN思想的语义分割中,输出图像的size要求和输入图像的size一致而需要upsample,但由于FCN中使用pooling操作来增大感受野同时降低分辨率,导致upsample无法还原由于pooling导致的一些细节信息的损失的问题而提出的。为了减小这种损失,自然需要移除pooling层,因此空洞卷积应运而生。
原先的 CNN architecture 是用一个“实心”的 kernal 去扫描 input data,然后使用 pooling 方法直接暴力的删掉其余 75% 的信息量,只留下 25% 的原汁原味,这样的做法在还没有精确到 pixel-wise 的情况时还是可以行的通的,一旦要归类到小至 pixel 等级的尺度时,pooling 对原 data 的破坏力足以让事情搞砸,试想一个原始数据被经过 Convolution 剥离出了其区域特征后,接着下一个环节被大刀阔斧般的砍去内部信息,并且永久无法复原,小物体信息无法重建 (假设有四个pooling layer 则任何小于 2^4 = 16 pixel 的物体信息在理论上将无法重建和分割)。这是在微小的 pixel 世界中无法接受的做法。
如果说 pooling 这种简单删减 data 让单位 output 中 Receptive Field 增大的方法不可行,就需要一个新的在不删减原始数据的情况下,直接让 Receptive Field 提升的办法:Dilated Convolution。简单的说,就是把原本“实心”的 kernal 元素之间按照一定规律插入 0 元素作为空格
空洞卷积向卷积层引入了一个称为 “扩张率(dilation rate)”的新参数,该参数定义了卷积核处理数据时各值的间距。
看下图可以对膨胀率有一个比较直观的理解
假设原始特征为feat0
首先使用扩张率为1的空洞卷积生成feat1,feat1上一点相对feat0感受野为3*3(如图a);
然后使用扩张率为2的空洞卷积处理feat1生成feat2(如图b),使第一次空洞卷积的卷积核大小等于第二次空洞卷积的一个像素点的感受野,图b即feat1上一个点综合了图a即feat0上3*3区域的信息,则生成的feat2感受野为7*7,即整个图b深色区域;
第三次处理同上,第二次空洞卷积的整个卷积核大小等于第三次空洞卷积的一个像素点的感受野,图c即feat2上每个点综合了feat0上7*7的信息(感受野),则采用扩张率为3的空洞卷积,生成的feat3每一个点感受野为15*15。
普通卷积的结果的size是
经过空洞卷积后的结果的size是
r是dilation
例子:7*7的feature map,kernel size = 3, padding = 0,stride = 1, dilation =2
标准卷积后大小F为(7-3+0)/1+1 = 5,Dilated卷积后大小F为[7-(3+2*1)+0]/1+1=3空洞卷积存在的问题
潜在问题 1:The Gridding Effect
假设我们仅仅多次叠加 dilation rate 2 的 3 x 3 kernel 的话,则会出现这个问题:
我们发现我们的 kernel 并不连续,也就是并不是所有的 pixel 都用来计算了,因此这里将信息看做 checker-board 的方式会损失信息的连续性。这对 pixel-level dense prediction 的任务来说是致命的。
由于空洞卷积的计算方式类似于棋盘格式,某一层得到的卷积结果,来自上一层的独立的集合,没有相互依赖,因此该层的卷积结果之间没有相关性,即局部信息丢失
潜在问题 2:Long-ranged information might be not relevant.
我们从 dilated convolution 的设计背景来看就能推测出这样的设计是用来获取 long-ranged information。然而光采用大 dilation rate 的信息或许只对一些大物体分割有效果,而对小物体来说可能则有弊无利了。如何同时处理不同大小的物体的关系,则是设计好 dilated convolution 网络的关键。
由于空洞卷积稀疏的采样输入信号,使得远距离卷积得到的信息之间没有相关性,影响分类结果。
解决方法: Hybrid Dilated Convolution (HDC)
代码实现 nn.Conv2d(dialation)
dilation convolution的操作内置在nn.Conv2d里面了
正常的紧密的kernel,dialation=1(默认)
dialation=2的情况,就不是紧凑的了
手动实现
import torch from torch import nn import torch.nn.functional as F import math def my_conv(input, kernel, stride=1, padding=0, bias=0, dilation=1): if padding > 0: input = F.pad(input, (padding,padding,padding,padding)) batch_size, in_channel, input_h, input_w= input.shape out_channel, _, kernel_h, kernel_w = kernel.shape #本来紧凑的输入,现在相邻点之间要插入dialation-1个空洞,共插入kernel_h-1次 kernel_h = (kernel_h-1)*(dilation-1)+kernel_h kernel_w = (kernel_w-1)*(dilation-1)+kernel_w output_h = math.floor((input_h - kernel_h) / stride + 1) output_w = math.floor((input_w - kernel_w) / stride + 1) output = torch.zeros(batch_size, out_channel, output_h, output_w) if bias is None: bias = torch.zeros(out_channel) for b in range(batch_size): for c in range(out_channel): for i in range(0, input_h - kernel_h + 1, stride): for j in range(0, input_w - kernel_w + 1, stride): region = input[b,:,i:i+kernel_h:dilation, j:j+kernel_w:dilation] output[b,c,int(i/stride), int(j/stride)] = torch.sum(region*kernel[c]) + bias[c] return output batch_size = 4 in_channel = 4 out_channel = 16 dilation = 2 input = torch.rand(batch_size, in_channel ,5,5) kernel = torch.rand(out_channel, in_channel, 3,3) bias = torch.rand(out_channel) my_output = my_conv(input, kernel, padding=1, stride=2, bias=bias, dilation=dilation) output = F.conv2d(input, kernel, padding=1, stride=2, bias=bias, dilation=dilation) assert torch.allclose(my_output, output)