卷积核(Convolution Kernel)

卷积核(Convolution Kernel)

卷积核(Convolution Kernel)是卷积神经网络(CNN)里最核心的组件之一,大量用在图像处理和深度学习模型中。它本质上是一个小矩阵,拿它对输入图像做卷积运算,就能从图里提出特征。具体做法是让这个小矩阵在图像上慢慢滑动,每到一个位置就做一次逐点相乘再求和,最终把原图转成一张特征图。

1. 卷积计算

  1. input 表示输入的图像。
  2. filter 表示卷积核,也叫滤波器。
  3. input 经过 filter 处理后得到最右侧那张图,这张输出就叫特征图。

那么它到底是怎么算的呢?卷积运算的本质,就是在滤波器和输入数据的局部区域之间做点积。

先看左上角那个点是怎么算出来的:

按上面的方法把每个位置都算一遍,就得到最终的特征图:

2. Padding

看完上面的计算我们会发现,卷积出来的特征图比原图小了一圈。如果想让卷积之后的图像尺寸保持不变,办法是在原图四周补一圈 padding。

3. Stride

卷积核每走一步的距离叫步长(Stride)。先看步长为 1 时的特征图:

如果把 Stride 增大到 2,卷积核每次跳两格,同样能提取出特征图,只是尺寸更小,如下图:

4. 多通道卷积

真实的图像都是多个通道叠起来的,比如 RGB 三个通道。这种多通道的卷积该怎么算呢?

计算规则是这样的:

  1. 当输入有多个通道(Channel)时,比如 RGB 三个通道,卷积核的通道数也必须跟它保持一致。
  2. 卷积核的每一个通道,分别和输入图像对应的那个通道做卷积。
  3. 最后把各个通道的卷积结果按位相加,就得到最终的一张特征图。

如下图所示:

5. 多核卷积

上面的例子里我们只用了一个卷积核。真正做图像特征提取时,往往要用一堆卷积核。这一堆卷积核可以理解成从不同视角、不同角度去打量同一张图,各自捕捉不同的特征。

那么用上多个卷积核之后,特征该怎么提取呢?

6. 特征图大小

输出特征图的大小,由下面这几个参数决定:

  1. size:卷积核(过滤器)的大小,一般选奇数,比如 1*1、3*3、5*5。
  2. Padding:四周零填充的圈数。
  3. Stride:卷积核移动的步长。

具体怎么算呢,先把变量约定一下:

  1. 输入图像大小:W x W。
  2. 卷积核大小:F x F。
  3. Stride:S。
  4. Padding:P。
  5. 输出图像大小:N x N。

我们拿一组数字代入算一遍:

  1. 图像大小:5 x 5。
  2. 卷积核大小:3 x 3。
  3. Stride:1。
  4. Padding:1。
  5. 代入公式 (5 – 3 + 2) / 1 + 1 = 5,得到的特征图大小就是 5 x 5。

7. PyTorch 卷积层 API

做图像主要用 Conv2D 卷积层,我们来看它怎么用。示例代码如下:

import torch.nn as nn
import torch
import torch.nn.functional as F


# 1. nn.Conv2d 参数解释
def test01():

    # Conv2d 表示卷积层, 由 out_channels 个卷积核组成,每个卷积核有 in_channels 个通道
    conv2d = nn.Conv2d(in_channels=3, out_channels=5, kernel_size=2)

    # 卷积核权重参数:
    # 5: 表示需要 5 个卷积核, 输出几个通道 out_channels 对应就有几个卷积核
    # 3: 输入的图像有 in_channels=3 个,那么对应每个卷积核就有 3 个通道
    # [2, 2]: 表示卷积核的 kernel size
    # torch.Size([5, 3, 2, 2])
    print(conv2d.weight.shape)

    # 卷积核偏置参数:
    # 5: 表示共有 5 个卷积核对应 5 个偏置
    # torch.Size([5])
    print(conv2d.bias.shape)

    # 卷积时候向右、向下的步长分别为 1
    # (1, 1)
    print(conv2d.stride)

    # 卷积时候向右、向下的填充分别为 0
    # (0, 0)
    print(conv2d.padding)

    # 卷积时候向右、向下的扩张为 1,可以增加范围
    # (1, 1)
    print(conv2d.dilation)


# 2. nn.Conv2d 计算过程
def test02():

    torch.manual_seed(0)
    inputs = torch.randint(0, 10, size=[2, 3, 3, 3]).float()
    print(inputs)
    print('-' * 60)

    # 设置卷积核权重参数
    weight = torch.ones(size=[5, 3, 2, 2]).float()
    # 设置卷积核偏置参数
    bias = torch.zeros(size=[5,]).float()
    output = F.conv2d(inputs, weight=weight, bias=bias)
    print(output)


if __name__ == '__main__':
    test02()