目录
四、关于 Hybrid Dilated Convolution
前言
这篇文章是我根据 B 站 霹雳吧啦Wz 的《深度学习:语义分割篇章》中的 膨胀卷积 ( Dilated convolution ) 详解 所作的学习笔记,涵盖内容如目录所示,希望能为正在学习语义分割的小伙伴们提供一些帮助ヾ(^▽^*))) 因为才刚刚开始接触语义分割,所以在表达上可能比较幼稚,希望王子公主们多多包涵啦!如果存在问题的话,请大家直接指出噢~
- 在创作这篇笔记的过程中,我参考了这篇博客:语义分割|学习记录(4)膨胀卷积(空洞卷积 )-CSDN博客
- 在创作这篇笔记的过程中,我参考了这篇博客:语义分割学习笔记 P4-CSDN博客
一、什么是膨胀卷积
膨胀卷积( Dilated convolution )又称空洞卷积( Atrous convolution ),膨胀卷积与普通卷积的对比如下:
普通卷积:k = 3 , r = 1 , p = 0 , s = 1
膨胀卷积:k = 3 , r = 2 , p = 0 , s = 1
通过观察上面的图,我们可以发现这里的膨胀卷积和普通卷积均使用了 3 x 3 的卷积核,但在膨胀卷积中,kernel 元素之间存在间隙,我们称之为 膨胀因子 r 。当 r 设置为 1 时,kernel 元素相邻,即普通卷积。
膨胀卷积的作用主要包括:
- 增大感受野
- 保持原输入特征图的 W 和 H(在上图中,还需要将 padding 设置为 1 才能保证特征图的宽高不变)
二、为什么使用膨胀卷积
在语义分割中,以 FCN 网络为例,我们通常使用分类网络作为 backbone ,输入特征图通过 backbone 会经历一系列的 下采样 ,然后再通过一些列的 上采样 还原回原图的大小。在我们平常使用的分类网络中,会将图片的宽高下采样 32 倍,若下采样倍率过大,对我们还原回原图的操作会造成很大的影响。以 VGG 网络为例,VGG 网络通过 MaxPooling 层进行最大池化操作,虽然能够降低特征图的高度和宽度,但同时会丢失部分细节信息以及较小的目标,这些信息与目标将无法通过上采样进行还原。
那么,我们为什么不移除 MaxPooling 层呢?
这是因为 MaxPooling 层可以增大特征图的感受野,所以如果将 MaxPooling 层去掉,会导致得到的特征图所对应的原图的感受野变小,而后面的一系列卷积层又是在之前对应的感受野上进行操作的,因此移除 MaxPooling 层会带来新问题。
综上所述,我们需要使用 膨胀卷积 来解决这个问题,膨胀卷积既可以增大感受野,又可以保证输出特征图的高宽与输入特征图相同。
三、关于 gridding effect
相关论文:Understanding Convolution for Semantic Segmentation
这篇论文中有对 gridding effect 问题的图片说明,如下图所示,接下来我们将具体描述什么是 gridding effect 。
1、膨胀系数相同
在 Layer1 至 Layer4 这四个卷积层中,我们进行了三次 膨胀卷积 ,且卷积核均为 3 x 3 ,膨胀系数均为 r2 。
首先,通过下图可知 Layer2 中的一个像素 pixel 会使用到 Layer1 中的这 9 个位置上的参数:
其次,通过下图可知 Layer3 中的一个像素 pixel 会使用到 Layer1 中的这 25 个位置上的参数:
最后,通过下图可知 Layer4 中的一个像素 pixel 会使用到 Layer1 中的这 49 个位置上的参数:
综上分析可知,通过膨胀卷积利用到的底层图像像素并不是连续的,每个非零元素间存在间隔,这样势必会导致特征信息的丢失,而这正是我们所说的 gridding effect 问题,我们应该想办法尽可能避免 gridding effect 问题。
2、膨胀系数不同
在 Layer1 至 Layer4 这四个卷积层中,我们进行了三次 膨胀卷积 ,且卷积核均为 3 x 3 ,膨胀系数分别为 r1 、r2 、r3 。
首先,通过下图可知 Layer2 中的一个像素 pixel 会使用到 Layer1 中的这 9 个位置上的参数,即 RF ( receptive field ) = 3 x 3 :
其次,通过下图可知 Layer3 中的一个像素 pixel 会使用到 Layer1 中的这 49 个位置上的参数,即 RF ( receptive field ) = 7 x 7 :
最后,通过下图可知 Layer4 中的一个像素 pixel 会使用到 Layer1 中的这 169 个位置上的参数,即 RF ( receptive field ) = 13 x 13 :
综上分析可知,将膨胀系数设置为 r1 、r2 、r3 后,我们利用到的底层参数位置是连续的,其实无论膨胀系数是否相同,感受野都是相同的,最终都是 13 x 13 的感受野,但是在第一种情况中,有很多像素值未被使用到,所以我们通常更倾向于使用第二种情况哟~
3、膨胀系数均为 1(普通卷积)
在 Layer1 至 Layer4 这四个卷积层中,我们进行了三次 普通卷积 ,且卷积核均为 3 x 3 ,膨胀系数均为 r1 。
首先,通过下图可知 Layer2 中的一个像素 pixel 会使用到 Layer1 中的这 9 个位置上的参数,即 RF ( receptive field ) = 3 x 3 :
其次,通过下图可知 Layer3 中的一个像素 pixel 会使用到 Layer1 中的这 25 个位置上的参数,即 RF ( receptive field ) = 5 x 5 :
最后,通过下图可知 Layer4 中的一个像素 pixel 会使用到 Layer1 中的这 49 个位置上的参数,即 RF ( receptive field ) = 7 x 7 :
综上分析可知,在参数相同的情况下,普通卷积的感受野比膨胀卷积的感受野小,换言之,使用膨胀卷积可以增大感受野。
四、关于 Hybrid Dilated Convolution
相关论文(同上):Understanding Convolution for Semantic Segmentation
1、如何设置膨胀系数
当我们连续使用多个膨胀卷积时,该如何设置膨胀系数?在【三】中我们可以发现,当膨胀系数分别被设置为 r1 、r2 、r3 时进行连续卷积操作的效果优于膨胀系数均被设置为 r2 时的效果。那么具体该如何设计膨胀系数呢?原论文作者提出了 HDC 设计准则。
【说明】当我们连续堆叠 N 个膨胀卷积时,使用 K x K 的卷积核,并将各个膨胀卷积的膨胀系数分别设置为 r1 ,r2 ,r3 , ... ,rn 。则此时 HDC 的目标就是通过一系列膨胀卷积后能够完全覆盖底层特征层的方形区域,并确保在这个方形区域中没有任何孔洞。我们定义第 i 层两个非 0 元素之间的最大距离为:
而第 n 层两个非 0 元素之间的最大距离为最后的膨胀系数 Mn = rn ,我们的设计目标是 M2 ≤ K 。
【实操】我们可以使用实际代码来测试原论文举例的 [ 1 , 2 , 5 ] 和 [ 1 , 2 , 9 ] 这两组膨胀系数:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
def dilated_conv_one_pixel(center: (int, int),
feature_map: np.ndarray,
k: int = 3,
r: int = 1,
v: int = 1):
"""
膨胀卷积核中心在指定坐标center处时,统计哪些像素被利用到,
并在利用到的像素位置处加上增量v
Args:
center: 膨胀卷积核中心的坐标
feature_map: 记录每个像素使用次数的特征图
k: 膨胀卷积核的kernel大小
r: 膨胀卷积的dilation rate
v: 使用次数增量
"""
assert divmod(3, 2)[1] == 1
# left-top: (x, y)
left_top = (center[0] - ((k - 1) // 2) * r, center[1] - ((k - 1) // 2) * r)
for i in range(k):
for j in range(k):
feature_map[left_top[1] + i * r][left_top[0] + j * r] += v
def dilated_conv_all_map(dilated_map: np.ndarray,
k: int = 3,
r: int = 1):
"""
根据输出特征矩阵中哪些像素被使用以及使用次数,
配合膨胀卷积k和r计算输入特征矩阵哪些像素被使用以及使用次数
Args:
dilated_map: 记录输出特征矩阵中每个像素被使用次数的特征图
k: 膨胀卷积核的kernel大小
r: 膨胀卷积的dilation rate
"""
new_map = np.zeros_like(dilated_map)
for i in range(dilated_map.shape[0]):
for j in range(dilated_map.shape[1]):
if dilated_map[i][j] > 0:
dilated_conv_one_pixel((j, i), new_map, k=k, r=r, v=dilated_map[i][j])
return new_map
def plot_map(matrix: np.ndarray):
plt.figure()
c_list = ['white', 'blue', 'red']
new_cmp = LinearSegmentedColormap.from_list('chaos', c_list)
plt.imshow(matrix, cmap=new_cmp)
ax = plt.gca()
ax.set_xticks(np.arange(-0.5, matrix.shape[1], 1), minor=True)
ax.set_yticks(np.arange(-0.5, matrix.shape[0], 1), minor=True)
# 显示color bar
plt.colorbar()
# 在图中标注数量
thresh = 5
for x in range(matrix.shape[1]):
for y in range(matrix.shape[0]):
# 注意这里的matrix[y, x]不是matrix[x, y]
info = int(matrix[y, x])
ax.text(x, y, info,
verticalalignment='center',
horizontalalignment='center',
color="white" if info > thresh else "black")
ax.grid(which='minor', color='black', linestyle='-', linewidth=1.5)
plt.show()
plt.close()
def main():
# bottom to top
dilated_rates = [1, 2, 5]
# init feature map
size = 31
m = np.zeros(shape=(size, size), dtype=np.int32)
center = size // 2
m[center][center] = 1
# print(m)
# plot_map(m)
for index, dilated_r in enumerate(dilated_rates[::-1]):
new_map = dilated_conv_all_map(m, r=dilated_r)
m = new_map
print(m)
plot_map(m)
if __name__ == '__main__':
main()
代码参考自:语义分割|学习记录(4)膨胀卷积(空洞卷积 )-CSDN博客
当膨胀系数设置为 [ 1 , 2 , 5 ] 时,最高层的 pixel 将最低层区域中的所有像素信息都利用到了,不存在 gridding effect 问题。
当膨胀系数设置为 [ 1 , 2 , 9 ] 时,区域内非 0 零元素间的最大距离为 3 ,即元素与元素之间存在 2 行或 2 列 0 元素。
根据原论文提供的公式可知,膨胀系数设置为 [ 1 , 2 , 9 ] 时,M3 = r 3 = 9 ,M2 = max [ M3 - 2 * r2 , - M3 + 2 * r2 , r2 ] = 5 ,根据上面代码的运行结果可知,M1 应该等于 3 ,用公式验证 M1 = max [ M2 - 2 * r1 , - M2 + 2 * r1 , r1 ] = 3 ,M1 确实为 3 。
【补充】我们可以发现原论文作者使用的膨胀系数都是从 1 开始的,这是因为如果希望在高层特征图中的 pixel 能够利用到底层的所有像素,就需要令 M1 = 1 ,由于根据公式可知 M1 ≥ r1 ,若 r1>1 则 M1>1 ,非 0 元素之间将存在间隙,因此必须设置 r1 = 1 。
2、设置膨胀系数的建议
根据论文,我们可以得到关于设置膨胀系数的建议如下:
- 将 dilation rate 设置成锯齿结构,例如:[ 1 , 2 , 3 , 1 , 2 , 3 ] √
- 令 dilation rate 的公约数不大于 1 ,例如:[ 2 , 4 , 8 ] ×
【示例】当公约数大于 1 时,假如将膨胀系数设置为 [ 2 , 4 , 8 ] ,则特征图非 0 元素之间存在间隙,并不连续,具体如下图所示:
3、是否应用 HDC 的分割效果对比
在下面这张对比图中,第一行图片是我们人工标注的 Ground Truth ,第二行图片是 ResNet-DUC 模型的预测结果,可以看出它存在很明显的 gridding effect 问题,第三行图片是使用 ResNet-DUC-HDC 模型的预测结果,可以看出应用 HDC 设计准则后,科学的设置膨胀系数会对图片的分割效果带来正面影响。