卷积神经网络在图像处理中的地位已然毋庸置疑。卷积运算具备强大的特征提取能力、相比全连接又消耗更少的参数,应用在图像这样的二维结构数据中有着先天优势。

1. 常规卷积运算

假设输入层为一个大小为64×64像素、三通道彩色图片。经过一个包含4个Filter的卷积层,最终输出4个Feature Map,且尺寸与输入层相同。整个过程可以用下图来概括。

input:$(N, C_{\text{in}}, H, W)$,output:$(N, C_{\text{out}}, H_{\text{out}}, W_{\text{out}})$

  • 图片卷积输出大小计算公式

    输入图像大小$(C_{in}, W_{in},H_{in})$,卷积核Filter大小:$(F,F)$,步长:$S$,填充$padding$:$P$:

  • 参数量

    假设一个卷积核的大小为$(K,K)$,输入的特征图为$(C_{in}, H, W)$,输入为$(C_{out}, H_{out},W_{out})$,不考虑偏置,不补0的卷积。参数量为(即卷积核个数*卷积核参数+偏置数):

  • 计算量

    MAC(Multiply Accumulate),需要考虑输出map的大小,1个MAC算两次操作

  • 示例

    • 输入维度:(6, 64, 64),卷积核:(3, 3),padding=1,stride=1;
    • 输出shape:(3, 64, 64);
    • 参数:$params = 6\times 3\times 3\times 3 = 162$
    • 计算量:$flops=3\times 3\times 64\times 64\times 6\times 3=663552$

    用Pytorch展示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    import torch
    import torch.nn as nn
    import torchsummary
    import thop

    class BaseNet(nn.Module):
    def __init__(self, in_ch, out_ch, group=1):
    super().__init__()
    self.conv1 = nn.Conv2d(in_channels=in_ch, out_channels=out_ch,
    groups=group, kernel_size=3, stride=1,
    padding=1, bias=False)

    def forward(self, x):
    x = self.conv1(x)
    return x


    model = BaseNet(6, 3)

    input_tensor = torch.rand(1,6,64,64)
    input_size = tuple(input_tensor.shape[1:])
    torchsummary.summary(model, input_size=input_size, batch_size=1)

    print("thop:")
    flops, params = thop.profile(model=model, inputs=(input_tensor,))
    print(flops, params)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    ----------------------------------------------------------------
    Layer (type) Output Shape Param #
    ================================================================
    Conv2d-1 [1, 3, 64, 64] 162
    ================================================================
    Total params: 162
    Trainable params: 162
    Non-trainable params: 0
    ----------------------------------------------------------------
    Input size (MB): 0.09
    Forward/backward pass size (MB): 0.09
    Params size (MB): 0.00
    Estimated Total Size (MB): 0.19
    ----------------------------------------------------------------
    thop:
    [INFO] Register count_convNd() for <class 'torch.nn.modules.conv.Conv2d'>.
    [WARN] Cannot find rule for <class '__main__.BaseNet'>. Treat it as zero Macs and zero Params.
    663552.0 162.0

2. 分组卷积

分组卷积(Group Convolution):常规卷积的计算结果中,特征图的每个通道和输入特征图的所有通道都有关。下方左图是普通卷积的示意图,下放右图是分组卷积的示意图,差别就非常明显了。分组卷积的输出特征图的每个通道,只和输入特征图的一部分通道有关,而这部分通道,就是一个分组(Group)。

依旧假设输入特征图的尺寸为$C_{in} \times H \times W$,分为3组进行分组卷积,那么,对于每一组,输出特征图的通道数都是$C_{out}/3$,卷积核大小变为$C_{in} \times K \times K$,最后只需要将各个分组的计算结果按照通道进行连接(Cat)即可。

分组卷积可以很大程度上减少卷积所需的参数量,相同的输入输出特征图,分组卷积所需的参数量为:

即,分组卷积可将参数量减少为原来的$1/G$,$G$为分组数量。

分组卷积最早出现在AlexNet中,如下图所示。在CNN发展初期,GPU资源不足以满足训练任务的要求,因此,Hinton采用了多GPU训练的策略,每个GPU完成一部分卷积,最后把多个GPU的卷积结果进行融合。

  • 代码实例

    • 输入维度:(6, 64, 64),卷积核:(3, 3),padding=1,stride=1, group=3;
    • 输出shape:(3, 64, 64);
    • 参数:$params = (6\times 3\times 3\times 3) /3= 54$
    • 计算量:$flops=(3\times 3\times 64\times 64\times 6\times 3)/3=221184$
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class BaseNet(nn.Module):
    def __init__(self, in_ch, out_ch, group=1):
    super().__init__()
    self.conv1 = nn.Conv2d(in_channels=in_ch, out_channels=out_ch,
    groups=group, kernel_size=3, stride=1,
    padding=1, bias=False)

    def forward(self, x):
    x = self.conv1(x)
    return x


    model = BaseNet(6, 3, 3)
    input_tensor = torch.rand(1,6,64,64)
    input_size = tuple(input_tensor.shape[1:])
    torchsummary.summary(model, input_size=input_size, batch_size=1)

    print("thop:")
    flops, params = thop.profile(model=model, inputs=(input_tensor,))
    print(flops, params)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    ----------------------------------------------------------------
    Layer (type) Output Shape Param #
    ================================================================
    Conv2d-1 [1, 3, 64, 64] 54
    ================================================================
    Total params: 54
    Trainable params: 54
    Non-trainable params: 0
    ----------------------------------------------------------------
    Input size (MB): 0.09
    Forward/backward pass size (MB): 0.09
    Params size (MB): 0.00
    Estimated Total Size (MB): 0.19
    ----------------------------------------------------------------
    thop:
    [INFO] Register count_convNd() for <class 'torch.nn.modules.conv.Conv2d'>.
    [WARN] Cannot find rule for <class '__main__.BaseNet'>. Treat it as zero Macs and zero Params.
    221184.0 54.0

3. 深度可分离卷积

深度可分离卷积(DepthwiseSeparable Convolution),在Google的Xception以及MobileNet论文中均有描述。它的核心思想是将一个完整的卷积运算分解为两步进行,分别为Depthwise ConvolutionPointwise Convolution

  • Depthwise Convolution

    当分组卷积(Group Convolution)的group等于输入map维度时,分组卷积就变成了depthwise卷积:

  • Pointwise Convolution

    Pointwise Convolution的运算与常规卷积运算非常相似,它的卷积核的尺寸为1×1×M,M为上一层的通道数。所以这里的卷积运算会将上一步的map在深度方向上进行加权组合,生成新的Feature map。有几个卷积核就有几个输出Feature map。如下图所示:

  • 代码实例

    • 输入维度:(6, 64, 64),卷积核:(3, 3),padding=1,stride=1, group=3;
    • 输出shape:(3, 64, 64);
    • 参数:$params = (6\times 3\times 3)+(6\times3)= 72$
    • 计算量:$flops=294912$

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      class BaseNet_DWCONV_sp(nn.Module):
      def __init__(self, in_ch, out_ch, group):
      super().__init__()
      # depthwise
      self.dwconv = nn.Conv2d(in_channels=in_ch, out_channels=in_ch,
      groups=in_ch, kernel_size=3,
      stride=1, padding=1, bias=False)
      # pointwise
      self.poconv = nn.Conv2d(in_channels=in_ch, out_channels=out_ch,
      kernel_size=1, stride=1, bias=False)

      def forward(self, x):
      x = self.dwconv(x)
      x = self.poconv(x)

      return x

      model = BaseNet_DWCONV_sp(6, 3, 3)
      input_tensor = torch.rand(1,6,64,64)
      input_size = tuple(input_tensor.shape[1:])
      torchsummary.summary(model, input_size=input_size, batch_size=1)

      print("thop:")
      flops, params = thop.profile(model=model, inputs=(input_tensor,))
      print(flops, params)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      ----------------------------------------------------------------
      Layer (type) Output Shape Param #
      ================================================================
      Conv2d-1 [1, 6, 64, 64] 54
      Conv2d-2 [1, 3, 64, 64] 18
      ================================================================
      Total params: 72
      Trainable params: 72
      Non-trainable params: 0
      ----------------------------------------------------------------
      Input size (MB): 0.09
      Forward/backward pass size (MB): 0.28
      Params size (MB): 0.00
      Estimated Total Size (MB): 0.38
      ----------------------------------------------------------------
      thop:
      [INFO] Register count_convNd() for <class 'torch.nn.modules.conv.Conv2d'>.
      [WARN] Cannot find rule for <class '__main__.BaseNet_DWCONV_sp'>. Treat it as zero Macs and zero Params.
      294912.0 72.0

4. 空洞卷积

这部分待完善…

空洞卷积(atrous convolutions)又名扩张卷积(dilated convolutions),向卷积层引入了一个称为 “扩张率(dilation rate)”的新参数,该参数定义了卷积核处理数据时各值的间距。

空洞卷积(Dilated/Atrous Convolution),广泛应用于语义分割与目标检测等任务中,语义分割中经典的deeplab系列与DUC对空洞卷积进行了深入的思考。目标检测中SSD与RFBNet,同样使用了空洞卷积。

在相同的计算条件下,空洞卷积提供了更大的感受野。空洞卷积经常用在实时图像分割中。当网络层需要较大的感受野,但计算资源有限而无法提高卷积核数量或大小时,可以考虑空洞卷积。

  • 空洞卷积的作用
    • 扩大感受野:在deep net中为了增加感受野且降低计算量,总要进行降采样(pooling或s2/conv),这样虽然可以增加感受野,但空间分辨率降低了。为了能不丢失分辨率,且仍然扩大感受野,可以使用空洞卷积。这在检测,分割任务中十分有用。一方面感受野大了可以检测分割大目标,另一方面分辨率高了可以精确定位目标。
    • 捕获多尺度上下文信息:空洞卷积有一个参数可以设置dilation rate,具体含义就是在卷积核中填充dilation rate-1个0,因此,当设置不同dilation rate时,感受野就会不一样,也即获取了多尺度信息。多尺度信息在视觉任务中相当重要啊。

参考

Depthwise卷积与Pointwise卷积

Group Convolution分组卷积,以及Depthwise Convolution和Global Depthwise Convolution

卷积网络基础知识—-Depthwise Convolution && Pointwise Convolution && Separable Convolution