膨胀卷积(空洞卷积)

膨胀卷积

介绍

膨胀卷积又叫空洞卷积,最初的提出是为了解决图像分割的问题,常见的图像分割算法通常使用池化层和卷积层来增加感受野(Receptive Filed),同时也缩小了特征图尺寸,然后再利用上采样来还原图像尺寸,特征图缩小再放大的过程造成了精度上的损失,因此需要一种操作可以再增加感受野的同时保持特征图的尺寸不变,从而代替下采样和上采样操作。

英文: Dilated convolution (Atrous convolution)

作用

  • 增大感受野

  • 保持原输入特征图W、H

计算过程

kernel_size = 3, dilation=2

在卷积核每行每列之间插入dilation-1的间隔,这些间隔在实际做卷积时不纳入运算。

dilation=1时为普通卷积

dilation

image-20241111141932631

Pytorch代码使用

# Dilated Conv
conv5 = nn.Conv2d(in_channels=3, out_channels=10, kernel_size=2, dilation=2)
input = torch.randn(1, 3, 30, 30)
output = conv5(input)
print("dilated conv kernel_size 2 dilation 2:", output.shape)
​
​
# Dilated Conv
conv6 = nn.Conv2d(in_channels=3, out_channels=10, kernel_size=3, dilation=2)
input = torch.randn(1, 3, 30, 30)
output = conv6(input)
# (1, 10, 26, 26)
print("dilated conv kernel_size 3 dilation 2:", output.shape)
​
# Dilated Conv
conv7 = nn.Conv2d(in_channels=3, out_channels=10, kernel_size=3, dilation=3)
input = torch.randn(1, 3, 30, 30)
output = conv7(input)
# (1, 10, 4, 24)
print("dilated conv kernel_size 3 dilation 3", output.shape)
​
# Dilated Conv
conv8 = nn.Conv2d(in_channels=3, out_channels=10, kernel_size=4, dilation=2)
input = torch.randn(1, 3, 30, 30)
output = conv8(input)
# (1, 10, 24, 24)
print("dilated conv kernel_size 4 dilation 2:", output.shape)
​
# Dilated Conv
conv9 = nn.Conv2d(in_channels=3, out_channels=10, kernel_size=4, dilation=3)
input = torch.randn(1, 3, 30, 30)
output = conv9(input)
# (1, 10, 21, 21)
print("dilated conv kernel_size 4 dilation 3:", output.shape)

结果显示

image-20241111142130315

使用过程

使用过程会造成 gridding effect 的问题

假设连续使用三个膨胀卷积,使用kernel_size=3, 膨胀系数为2的卷积层

image-20241111082713082

Layer2的一个pixel对应的Layer1信息

Layer3的一个pixel对应的Layer1的信息

image-20241111082937544

Layer4 一个pixel对应Layer1的信息

image-20241111083003411

从这里发现Layer4的一个pixel信息对应Layer1的信息不是连续的,这样就造成这个gridding effect的问题。

假设现在将膨胀系数依次改成1、2、3

Layer2的一个pixel信息

image-20241111083223157

Layer3 一个pixel的信息

image-20241111083259548

Layer4一个pixel的信息

image-20241111083325206

对比可以看出,膨胀系数不同,看到信息的连续性也会不同,实际使用时,更倾向于第二种的膨胀卷积

再对比一下,使用相同系数的普通卷积的效果

image-20241111083607744

普通卷积的感受野也会小一点,可以看出在使用相同参数数量的卷积核时,膨胀卷积能看到的信息会多一点。

如何设计膨胀系数

如何设计

Hybrid Dilated Convolution(HDC)准则

如何设计系数呢,使得最后一层出来的信息都是连续的且之间没有间隙

这里看出一个公式

image-20241111084448673

M(i): 代表第i层的像素之间的距离(如果两个像素在附近则为1,如果两个像素之间间隔一个像素则为2,以此类推)

r(i): 代表第i层的膨胀系数,r=1为普通卷积

K: Kernel_Size的大小

已知: M(n) = r(n)

要使得, M(2) <= K

公式:

image-20241111084751284

例子1:

K=3, r=[1,2,5]

根据公式:

M(3) = 5

M(2) = max[M3-2r2, M3-2(M3-r2),r2]

= max(5-4, 5-2*(5-2),2)

= max(1, -1, 2)

= 2 <= 3

符合条件

实际效果展示:

代码

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()

结果

例子2:

K=3, r=[1,2,9]

根据公式:

M(3) = 9

M(2) = max[M3-2r2, M3-2(M3-r2),r2]

= max(9-4, 9-2*(9-2),2)

= max(5, -5, 2)

= 5 > 3 M(1) = 3, 实际显示两个非零元素之间间隔为2

不符合条件

实际效果展示:

代码

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, 9]
    # 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()

结果

通过以上发现,第一层系数都为1,通过公式可以发现,M1=max(..., r1), 如何希望M1=1的话,r1必须为1,所以第一层要从1开始

设计建议
  1. image-20241111090911804

  2. 公约数不能大于1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值