什么是空洞卷积
上图展示了空洞卷积,其卷积核大小为3×3,步长s=1,r=2。你或许会问了,这个r是什么呢,其实啊,这个r正是普通卷积和空洞卷积的差别所在,我们称之为膨胀因子。当r=1时,表示卷积核各元素之前没有空隙,即相邻两个元素间位置相差1,此时其实就是我们正常的卷积,所以广义上说,普通的卷积是一种特殊的空洞卷积;当r=2时,表示卷积核各元素之前有一个空隙,即相邻两个元素间位置相差2,此时就是我们上图中的卷积核。
定义
空洞卷积是一种卷积操作,其中卷积核中间带有一些洞,跳过一些元素进行卷积。这种操作使得卷积核可以在不增加参数量的前提下扩大其感受野。
原理
在传统的卷积操作中,滤波器的每个元素都与输入的相应位置进行相乘,然后求和。而在空洞卷积中,滤波器的元素之间有一定的间隔,也就是所谓的空洞。这个间隔可以是一个或多个像素。通过增加空洞的大小,空洞卷积可以有效地扩大滤波器的感受野,即滤波器能够看到输入图像更广阔的范围。
优势
- 扩大感受野:空洞卷积可以在不牺牲空间分辨率的情况下扩大感受野,这对于捕捉更大尺度的特征和提供更好的上下文信息非常有帮助。
- 获取多尺度上下文信息:当多个带有不同扩张率的空洞卷积核叠加时,不同的感受野会带来多尺度信息,这对于分割任务是非常重要的。
- 降低计算量:空洞卷积不需要引入额外的参数,因此可以有效地减少计算量。
缺点
- 网格效应:由于空洞卷积是一种稀疏的采样方式,当多个空洞卷积叠加时,有些像素根本没有被利用到,会损失信息的连续性与相关性,进而影响分割、检测等要求较高的任务。
- 多尺度问题:大的扩张率对于大物体分割与检测有利,但是对于小物体则有弊无利,如何处理好多尺度问题的检测,是空洞卷积设计的重点。
感受野就是特征图上的一个像素对应原图夺嫂尺寸的像素
感受野计算公式
srtide表示步长,k kk表示卷积核大小。
至于空洞卷积为什么可以增大感受野,如下图所示:
上图表示使用3×3的卷积核进行空洞卷积,r=2,很明显,此时灰色特征层的感受野为5×5。其实呢,计算空洞卷积感受野也有公式,我们这样来思考,上图是3×3的卷积核,r=2,这样构成的一个空洞卷积核(乱起的名字哈,不用在意)的尺寸就相当于是5×5大小了,这时候我们感受野的计算就相当于5×5卷积核的感受野了。
空洞卷积的缺点,有些像素没有利用到。网格效应
解决缺点:
使用不同膨胀因子的空洞卷积
这样效果就有所改善了
接下来我们用numpy拆解一下空洞卷积
import numpy as np
def dilated_convld(input_data,kernel,dilation,stride):
"""
一维空洞卷积实现。
参数:
- input_data: 输入数据,形状为 (length, channels)
- kernel: 卷积核,形状为 (kernel_size, channels, out_channels)
- dilation: 空洞大小
- stride: 步长
返回:
- 输出数据,形状为 (output_length, out_channels)
"""
# 输入数据的长度和通道数
length, channels = input_data.shape
# 卷积核的大小和输出通道数
kernel_size, _, out_channels = kernel.shape
# 计算输出数据的长度
output_length = (length - dilation * (kernel_size - 1) - 1) // stride + 1
# 初始化输出数据
output_data = np.zeros((output_length, out_channels))
# 空洞卷积
for i in range(0, output_length):
for j in range(out_channels):
# 计算当前卷积核的起始位置
start = i * stride
end = start + dilation * kernel_size
# 提取对应的输入数据片段
input_slice = input_data[start:end:dilation, :]
# 执行卷积操作
output_data[i, j] = np.sum(input_slice * kernel[:, :, j], axis=(0, 1))
return output_data
# 示例使用
if __name__ == "__main__":
# 输入数据:长度为10,1个通道
input_data = np.random.rand(10, 1)
# 卷积核:大小为3,1个输入通道,1个输出通道
kernel = np.random.rand(3, 1, 1)
# 空洞大小为2,步长为1
dilation = 2
stride = 1
# 执行空洞卷积
output_data = dilated_conv1d(input_data, kernel, dilation, stride)
print("Input Data Shape:", input_data.shape)
print("Output Data Shape:", output_data.shape)