PyTorch学习笔记(八):卷积神经网络基础知识

  • 二维卷积层
  • 二维互相关运算
  • 二维卷积层
  • 图像中物体边缘检测
  • 通过数据学习核数组
  • 互相关运算和卷积运算
  • 特征图和感受野
  • 小结
  • 填充和步幅
  • 填充
  • 步幅
  • 小结
  • 多输入通道和多输出通道
  • 多输入通道
  • 多输出通道
  • 1 × 1 1\times 1 1×1卷积层
  • 小结
  • 池化层
  • 二维最大池化层和平均池化层
  • 填充和步幅
  • 多通道
  • 小结
  • 参考

二维卷积层

卷积神经网络(convolutional neural network)是含有卷积层(convolutional layer)的神经网络。卷积神经网络均使用最常见的二维卷积层。它有高和宽两个空间维度,常用来处理图像数据。下面是简单形式的二维卷积层的工作原理。

二维互相关运算

虽然卷积层得名于卷积(convolution)运算,但我们通常在卷积层中使用更加直观的互相关(cross-correlation)运算。在二维卷积层中,一个二维输入数组和一个二维核(kernel)数组通过互相关运算输出一个二维数组。 我们用一个具体例子来解释二维互相关运算的含义。如图所示,输入是一个高和宽均为3的二维数组。我们将该数组的形状记为pytorch 计算r squared pytorch 计算最大互信息系数_深度学习或(3,3)。核数组的高和宽分别为2。该数组在卷积计算中又称卷积核或过滤器(filter)。卷积核窗口(又称卷积窗口)的形状取决于卷积核的高和宽,即pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_02。图中的阴影部分为第一个输出元素及其计算所使用的输入和核数组元素:pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_03

pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_04

pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_05

在二维互相关运算中,卷积窗口从输入数组的最左上方开始,按从左往右、从上往下的顺序,依次在输入数组上滑动。当卷积窗口滑动到某一位置时,窗口中的输入子数组与核数组按元素相乘并求和,得到输出数组中相应位置的元素。图中的输出数组高和宽分别为2,其中的4个元素由二维互相关运算得出:

pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_06

下面我们将上述过程实现在corr2d函数里。它接受输入数组X与核数组K,并输出数组Y。

import torch 
from torch import nn

def corr2d(X, K):
    h, w = K.shape
    Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
    for i in range(Y.shape[0]):
        for j in range(Y.shape[1]):
            Y[i, j] = (X[i: i + h, j: j + w] * K).sum()
    return Y

构造图中的输入数组X、核数组K来验证二维互相关运算的输出。

X = torch.tensor([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
K = torch.tensor([[0, 1], [2, 3]])
corr2d(X, K)
tensor([[19., 25.],
        [37., 43.]])

二维卷积层

二维卷积层将输入和卷积核做互相关运算,并加上一个标量偏差来得到输出。卷积层的模型参数包括了卷积核和标量偏差。在训练模型的时候,通常我们先对卷积核随机初始化,然后不断迭代卷积核和偏差。

下面基于corr2d函数来实现一个自定义的二维卷积层。在构造函数__init__里我们声明weight和bias这两个模型参数。前向计算函数forward则是直接调用corr2d函数再加上偏差。

class Conv2D(nn.Module):
    def __init__(self, kernel_size):
        super(Conv2D, self).__init__()
        self.weight = nn.Parameter(torch.randn(kernel_size))
        self.bias = nn.Parameter(torch.randn(1))

    def forward(self, x):
        return corr2d(x, self.weight) + self.bias

卷积窗口形状为pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_07的卷积层称为pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_07卷积层。同样,pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_07卷积或pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_07卷积核说明卷积核的高和宽分别为pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_11pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_12

图像中物体边缘检测

下面我们来看一个卷积层的简单应用:检测图像中物体的边缘,即找到像素变化的位置。首先我们构造一张pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_13的图像(即高和宽分别为6像素和8像素的图像)。它中间4列为黑(0),其余为白(1)。

X = torch.ones(6, 8)
X[:, 2:6] = 0
X
tensor([[1., 1., 0., 0., 0., 0., 1., 1.],
        [1., 1., 0., 0., 0., 0., 1., 1.],
        [1., 1., 0., 0., 0., 0., 1., 1.],
        [1., 1., 0., 0., 0., 0., 1., 1.],
        [1., 1., 0., 0., 0., 0., 1., 1.],
        [1., 1., 0., 0., 0., 0., 1., 1.]])

构造一个高和宽分别为1和2的卷积核K。当它与输入做互相关运算时,如果横向相邻元素相同,输出为0;否则输出为非0。

K = torch.tensor([[1, -1]])
K
tensor([[ 1, -1]])

将输入X和我们设计的卷积核K做互相关运算。可以看出,我们将从白到黑的边缘和从黑到白的边缘分别检测成了1和-1。其余部分的输出全是0。

Y = corr2d(X, K)
Y
tensor([[ 0.,  1.,  0.,  0.,  0., -1.,  0.],
        [ 0.,  1.,  0.,  0.,  0., -1.,  0.],
        [ 0.,  1.,  0.,  0.,  0., -1.,  0.],
        [ 0.,  1.,  0.,  0.,  0., -1.,  0.],
        [ 0.,  1.,  0.,  0.,  0., -1.,  0.],
        [ 0.,  1.,  0.,  0.,  0., -1.,  0.]])

可以看出,卷积层可通过重复使用卷积核有效地表征局部空间。

通过数据学习核数组

最后我们来看一个例子,它使用物体边缘检测中的输入数据X和输出数据Y来学习我们构造的核数组K。我们首先构造一个卷积层,其卷积核将被初始化成随机数组。接下来在每一次迭代中,我们使用平方误差来比较Y和卷积层的输出,然后计算梯度来更新权重。

# 构造一个核数组形状是(1, 2)的二维卷积层
conv2d = Conv2D(kernel_size=(1, 2))

step = 20
lr = 0.01
for i in range(step):
    Y_hat = conv2d(X) # 卷积输出
    l = ((Y_hat - Y) ** 2).sum() # 损失函数
    l.backward() # 求梯度(求导)
    
    # 梯度下降
    conv2d.weight.data -= lr * conv2d.weight.grad
    conv2d.bias.data -= lr * conv2d.bias.grad
    
    # 梯度清0
    conv2d.weight.grad.fill_(0)
    conv2d.bias.grad.fill_(0)
    if (i + 1) % 5 == 0:
        print('Step %d, loss %.3f' % (i + 1, l.item()))
Step 5, loss 2.266
Step 10, loss 0.472
Step 15, loss 0.114
Step 20, loss 0.030

20次迭代后误差已经降到了一个比较小的值。现在来看一下学习到的卷积核的参数。

print("weight: ", conv2d.weight.data)
print("bias: ", conv2d.bias.data)
weight:  tensor([[ 0.9624, -0.9521]])
bias:  tensor([-0.0058])

可以看到,学到的卷积核的权重参数与我们之前定义的核数组K较接近,而偏置参数接近0。

互相关运算和卷积运算

实际上,卷积运算与互相关运算类似。为了得到卷积运算的输出,我们只需将核数组左右翻转并上下翻转,再与输入数组做互相关运算。可见,卷积运算和互相关运算虽然类似,但如果它们使用相同的核数组,对于同一个输入,输出往往并不相同。

pytorch 计算r squared pytorch 计算最大互信息系数_卷积_14

那么,你也许会好奇卷积层为何能使用互相关运算替代卷积运算。其实,在深度学习中核数组都是学出来的:卷积层无论使用互相关运算或卷积运算都不影响模型预测时的输出。为了解释这一点,假设卷积层使用互相关运算学出图中的核数组。设其他条件不变,使用卷积运算学出的核数组即图中的核数组按上下、左右翻转。也就是说,图中的输入与学出的已翻转的核数组再做卷积运算时,依然得到图中的输出。为了与大多数深度学习文献一致,如无特别说明,卷积运算均指互相关运算。

特征图和感受野

二维卷积层输出的二维数组可以看作是输入在空间维度(宽和高)上某一级的表征,也叫特征图(feature map)。影响元素pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_15的前向计算的所有可能输入区域(可能大于输入的实际尺寸)叫做pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_16的感受野(receptive field)。

pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_04

以上面运算图为例,输入中阴影部分的四个元素是输出中阴影部分元素的感受野。我们将图中形状为pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_02的输出记为pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_19,并考虑一个更深的卷积神经网络:将pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_19与另一个形状为pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_02的核数组做互相关运算,输出单个元素pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_22。那么,pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_22pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_19上的感受野包括pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_19的全部四个元素,在输入上的感受野包括其中全部9个元素。可见,我们可以通过更深的卷积神经网络使特征图中单个元素的感受野变得更加广阔,从而捕捉输入上更大尺寸的特征。

我们常使用“元素”一词来描述数组或矩阵中的成员。在神经网络的术语中,这些元素也可称为“单元”。当含义明确时,不对这两个术语做严格区分。

小结

  • 二维卷积层的核心计算是二维互相关运算。在最简单的形式下,它对二维输入数据和卷积核做互相关运算然后加上偏差。
  • 我们可以设计卷积核来检测图像中的边缘。
  • 我们可以通过数据来学习卷积核。

填充和步幅

使用高和宽为3的输入与高和宽为2的卷积核得到高和宽为2的输出。一般来说,假设输入形状是pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_26,卷积核窗口形状是pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_27,那么输出形状将会是

pytorch 计算r squared pytorch 计算最大互信息系数_卷积_28

pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_04

以上图为例,输入形状是pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_30,卷积核窗口形状是pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_31,那么输出形状是pytorch 计算r squared pytorch 计算最大互信息系数_卷积_32

所以卷积层的输出形状由输入形状和卷积核窗口形状决定。在此介绍卷积层的两个超参数,即填充和步幅。它们可以对给定形状的输入和卷积核改变输出形状。

填充

填充(padding)是指在输入高和宽的两侧填充元素(通常是0元素)。下图里我们在原输入高和宽的两侧分别添加了值为0的元素,使得输入高和宽从3变成了5,并导致输出高和宽由2增加到4。图中的阴影部分为第一个输出元素及其计算所使用的输入和核数组元素:pytorch 计算r squared pytorch 计算最大互信息系数_cnn_33

pytorch 计算r squared pytorch 计算最大互信息系数_卷积_34

一般来说,如果在高的两侧一共填充pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_35行,在宽的两侧一共填充pytorch 计算r squared pytorch 计算最大互信息系数_cnn_36列,那么输出形状将会是

pytorch 计算r squared pytorch 计算最大互信息系数_卷积_37

以上图为例,输入形状是pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_38,卷积核窗口形状是pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_31,那么输出形状是pytorch 计算r squared pytorch 计算最大互信息系数_cnn_40

也就是说,输出的高和宽会分别增加pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_35pytorch 计算r squared pytorch 计算最大互信息系数_cnn_36

在很多情况下,我们会设置pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_43pytorch 计算r squared pytorch 计算最大互信息系数_卷积_44来使输入和输出具有相同的高和宽。这样会方便在构造网络时推测每个层的输出形状。假设这里pytorch 计算r squared pytorch 计算最大互信息系数_cnn_45是奇数,我们会在高的两侧分别填充pytorch 计算r squared pytorch 计算最大互信息系数_卷积_46行。如果pytorch 计算r squared pytorch 计算最大互信息系数_cnn_45是偶数,一种可能是在输入的顶端一侧填充pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_48行,而在底端一侧填充pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_49行。在宽的两侧填充同理。

卷积神经网络经常使用奇数高宽的卷积核,如1、3、5和7,所以两端上的填充个数相等。对任意的二维数组X,设它的第i行第j列的元素为X[i,j]。当两端上的填充个数相等,并使输入和输出具有相同的高和宽时,我们就知道输出Y[i,j]是由输入以X[i,j]为中心的窗口同卷积核进行互相关计算得到的。

下面的例子里我们创建一个高和宽为3的二维卷积层,然后设输入高和宽两侧的填充数分别为1。给定一个高和宽为8的输入,我们发现输出的高和宽也是8。

pytorch 计算r squared pytorch 计算最大互信息系数_卷积_50

import torch
from torch import nn

# 定义一个函数来计算卷积层。它对输入和输出做相应的升维和降维
def comp_conv2d(conv2d, X):
    # (1, 1)代表批量大小和通道数
    X = X.view((1, 1) + X.shape)
    Y = conv2d(X)
    return Y.view(Y.shape[2:])  # 跳过前两维:批量和通道

# 注意这里是两侧分别填充1行或列,所以在两侧一共填充2行或列
conv2d = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, padding=1)

X = torch.rand(8, 8)
comp_conv2d(conv2d, X).shape
torch.Size([8, 8])

当卷积核的高和宽不同时,我们也可以通过设置高和宽上不同的填充数使输出和输入具有相同的高和宽。

pytorch 计算r squared pytorch 计算最大互信息系数_cnn_51

# 使用高为5、宽为3的卷积核。在输入的高和宽两侧的填充数分别为2和1
conv2d = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=(5, 3), padding=(2, 1))
comp_conv2d(conv2d, X).shape
torch.Size([8, 8])

步幅

二维互相关运算。卷积窗口从输入数组的最左上方开始,按从左往右、从上往下的顺序,依次在输入数组上滑动。我们将每次滑动的行数和列数称为步幅(stride)。

目前我们看到的例子里,在高和宽两个方向上步幅均为1。我们也可以使用更大步幅。下图展示了在高上步幅为3、在宽上步幅为2的二维互相关运算。可以看到,输出第一列第二个元素时,卷积窗口向下滑动了3行,而在输出第一行第二个元素时卷积窗口向右滑动了2列。当卷积窗口在输入上再向右滑动2列时,由于输入元素无法填满窗口,无结果输出。图的阴影部分为输出元素及其计算所使用的输入和核数组元素:pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_52pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_53

pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_54

一般来说,当高上步幅为pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_55,宽上步幅为pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_56时,输出形状为

pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_57

例如上图,填充后,输入为pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_58,在高上步幅为3、在宽上步幅为2,输出为pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_59

如果设置pytorch 计算r squared pytorch 计算最大互信息系数_卷积_60pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_61,那么输出形状将简化为pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_62。更进一步,如果输入的高和宽能分别被高和宽上的步幅整除,那么输出形状将是pytorch 计算r squared pytorch 计算最大互信息系数_cnn_63

下面我们令高和宽上的步幅均为2,从而使输入的高和宽减半。

conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2)
comp_conv2d(conv2d, X).shape
torch.Size([4, 4])

一个较复杂的例子。

conv2d = nn.Conv2d(1, 1, kernel_size=(3, 5), padding=(0, 1), stride=(3, 4))
comp_conv2d(conv2d, X).shape
torch.Size([2, 2])

为了表述简洁,当输入的高和宽两侧的填充数分别为pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_35pytorch 计算r squared pytorch 计算最大互信息系数_cnn_36时,我们称填充为pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_66。特别地,当pytorch 计算r squared pytorch 计算最大互信息系数_卷积_67时,填充为pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_11。当在高和宽上的步幅分别为pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_55pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_56时,我们称步幅为pytorch 计算r squared pytorch 计算最大互信息系数_卷积_71。特别地,当pytorch 计算r squared pytorch 计算最大互信息系数_卷积_72时,步幅为pytorch 计算r squared pytorch 计算最大互信息系数_cnn_73。在默认情况下,填充为0,步幅为1。

小结

  • 填充可以增加输出的高和宽。这常用来使输出与输入具有相同的高和宽。
  • 步幅可以减小输出的高和宽,例如输出的高和宽仅为输入的高和宽的pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_74pytorch 计算r squared pytorch 计算最大互信息系数_cnn_75为大于1的整数)。

多输入通道和多输出通道

之前用到的输入和输出都是二维数组,但真实数据的维度经常更高。例如,彩色图像在高和宽2个维度外还有RGB(红、绿、蓝)3个颜色通道。假设彩色图像的高和宽分别是pytorch 计算r squared pytorch 计算最大互信息系数_cnn_76pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_77(像素),那么它可以表示为一个pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_78的多维数组。我们将大小为3的这一维称为通道(channel)维。在此介绍含多个输入通道或多个输出通道的卷积核。

多输入通道

当输入数据含多个通道时,我们需要构造一个输入通道数与输入数据的通道数相同的卷积核,从而能够与含多通道的输入数据做互相关运算。假设输入数据的通道数为pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_79,那么卷积核的输入通道数同样为pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_79。设卷积核窗口形状为pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_27。当pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_82时,我们知道卷积核只包含一个形状为pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_27的二维数组。当pytorch 计算r squared pytorch 计算最大互信息系数_卷积_84时,我们将会为每个输入通道各分配一个形状为pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_27的核数组。把这pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_79个数组在输入通道维上连结,即得到一个形状为pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_87的卷积核。由于输入和卷积核各有pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_79个通道,我们可以在各个通道上对输入的二维数组和卷积核的二维核数组做互相关运算,再将这pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_79个互相关运算的二维输出按通道相加,得到一个二维数组。这就是含多个通道的输入数据与多输入通道的卷积核做二维互相关运算的输出。

下图展示了含2个输入通道的二维互相关计算的例子。在每个通道上,二维输入数组与二维核数组做互相关运算,再按通道相加即得到输出。图中阴影部分为第一个输出元素及其计算所使用的输入和核数组元素:pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_90

pytorch 计算r squared pytorch 计算最大互信息系数_cnn_91

实现含多个输入通道的互相关运算。我们只需要对每个通道做互相关运算,然后通过add_n函数来进行累加。

import torch
from torch import nn
import sys

def corr2d_multi_in(X, K):
    # 沿着X和K的第0维(通道维)分别计算再相加
    res = corr2d(X[0, :, :], K[0, :, :])
    for i in range(1, X.shape[0]):
        res += corr2d(X[i, :, :], K[i, :, :])
    return res

构造图中的输入数组X、核数组K来验证互相关运算的输出。

X = torch.tensor([[[0, 1, 2], [3, 4, 5], [6, 7, 8]],
              [[1, 2, 3], [4, 5, 6], [7, 8, 9]]])
K = torch.tensor([[[0, 1], [2, 3]], [[1, 2], [3, 4]]])

corr2d_multi_in(X, K)
tensor([[ 56.,  72.],
        [104., 120.]])

多输出通道

当输入通道有多个时,因为我们对各个通道的结果做了累加,所以不论输入通道数是多少,输出通道数总是为1。设卷积核输入通道数和输出通道数分别为pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_79pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_93,高和宽分别为pytorch 计算r squared pytorch 计算最大互信息系数_cnn_45pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_95。如果希望得到含多个通道的输出,我们可以为每个输出通道分别创建形状为pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_87的核数组。将它们在输出通道维上连结,卷积核的形状即pytorch 计算r squared pytorch 计算最大互信息系数_卷积_97。在做互相关运算时,每个输出通道上的结果由卷积核在该输出通道上的核数组与整个输入数组计算而来。

实现一个互相关运算函数来计算多个通道的输出。

def corr2d_multi_in_out(X, K):
    # 对K的第0维遍历,每次同输入X做互相关计算。所有结果使用stack函数合并在一起
    return torch.stack([corr2d_multi_in(X, k) for k in K])

将核数组K同K+1(K中每个元素加一)和K+2连结在一起来构造一个输出通道数为3的卷积核。

K = torch.stack([K, K + 1, K + 2])
K.shape # torch.Size([3, 2, 2, 2])
torch.Size([3, 2, 2, 2])

对输入数组X与核数组K做互相关运算。此时的输出含有3个通道。其中第一个通道的结果与之前输入数组X与多输入通道、单输出通道核的计算结果一致。

corr2d_multi_in_out(X, K)
tensor([[[ 56.,  72.],
         [104., 120.]],

        [[ 76., 100.],
         [148., 172.]],

        [[ 96., 128.],
         [192., 224.]]])

pytorch 计算r squared pytorch 计算最大互信息系数_cnn_98卷积层

最后我们讨论卷积窗口形状为pytorch 计算r squared pytorch 计算最大互信息系数_cnn_98pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_100)的多通道卷积层。我们通常称之为pytorch 计算r squared pytorch 计算最大互信息系数_cnn_98卷积层,并将其中的卷积运算称为pytorch 计算r squared pytorch 计算最大互信息系数_cnn_98卷积。因为使用了最小窗口,pytorch 计算r squared pytorch 计算最大互信息系数_cnn_98卷积失去了卷积层可以识别高和宽维度上相邻元素构成的模式的功能。实际上,pytorch 计算r squared pytorch 计算最大互信息系数_cnn_98卷积的主要计算发生在通道维上。图展示了使用输入通道数为3、输出通道数为2的pytorch 计算r squared pytorch 计算最大互信息系数_cnn_98卷积核的互相关计算。值得注意的是,输入和输出具有相同的高和宽。输出中的每个元素来自输入中在高和宽上相同位置的元素在不同通道之间的按权重累加。假设我们将通道维当作特征维,将高和宽维度上的元素当成数据样本,那么pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_106卷积层的作用与全连接层等价。

pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_107

使用全连接层中的矩阵乘法来实现pytorch 计算r squared pytorch 计算最大互信息系数_cnn_98卷积。这里需要在矩阵乘法运算前后对数据形状做一些调整。

def corr2d_multi_in_out_1x1(X, K):
    c_i, h, w = X.shape
    c_o = K.shape[0]
    X = X.view(c_i, h * w)
    K = K.view(c_o, c_i)
    Y = torch.mm(K, X)  # 全连接层的矩阵乘法
    return Y.view(c_o, h, w)

pytorch 计算r squared pytorch 计算最大互信息系数_cnn_98卷积时,以上函数与之前实现的互相关运算函数corr2d_multi_in_out等价。

X = torch.rand(3, 3, 3) # 表示1个3通道的3*3的输入
K = torch.rand(2, 3, 1, 1) # 表示2个3通道的1*1的卷积核

Y1 = corr2d_multi_in_out_1x1(X, K)
Y2 = corr2d_multi_in_out(X, K)
print(Y1,"\n",Y2)

(Y1 - Y2).norm().item() < 1e-6
tensor([[[0.5889, 0.6927, 0.8316],
         [0.9074, 0.8981, 1.0469],
         [0.2360, 0.7004, 0.7526]],

        [[0.6526, 0.5803, 0.4793],
         [0.8773, 0.5761, 0.7366],
         [0.2898, 0.3570, 0.6876]]]) 
 tensor([[[0.5889, 0.6927, 0.8316],
         [0.9074, 0.8981, 1.0469],
         [0.2360, 0.7004, 0.7526]],

        [[0.6526, 0.5803, 0.4793],
         [0.8773, 0.5761, 0.7366],
         [0.2898, 0.3570, 0.6876]]])





True

在之后的模型里我们将会看到pytorch 计算r squared pytorch 计算最大互信息系数_cnn_98卷积层被当作保持高和宽维度形状不变的全连接层使用。于是,我们可以通过调整网络层之间的通道数来控制模型复杂度。

小结

  • 使用多通道可以拓展卷积层的模型参数。
  • 假设将通道维当作特征维,将高和宽维度上的元素当成数据样本,那么pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_106卷积层的作用与全连接层等价。
  • pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_106卷积层通常用来调整网络层之间的通道数,并控制模型复杂度。

池化层

在二维卷积层里介绍的图像物体边缘检测应用中,我们构造卷积核从而精确地找到了像素变化的位置。设任意二维数组X的i行j列的元素为X[i, j]。如果我们构造的卷积核输出Y[i, j]=1,那么说明输入中X[i, j]和X[i, j+1]数值不一样。这可能意味着物体边缘通过这两个元素之间。但实际图像里,我们感兴趣的物体不会总出现在固定位置:即使我们连续拍摄同一个物体也极有可能出现像素位置上的偏移。这会导致同一个边缘对应的输出可能出现在卷积输出Y中的不同位置,进而对后面的模式识别造成不便。

所以提出了池化(pooling)层,它的提出是为了缓解卷积层对位置的过度敏感性。

二维最大池化层和平均池化层

同卷积层一样,池化层每次对输入数据的一个固定形状窗口(又称池化窗口)中的元素计算输出。不同于卷积层里计算输入和核的互相关性,池化层直接计算池化窗口内元素的最大值或者平均值。该运算也分别叫做最大池化或平均池化。在二维最大池化中,池化窗口从输入数组的最左上方开始,按从左往右、从上往下的顺序,依次在输入数组上滑动。当池化窗口滑动到某一位置时,窗口中的输入子数组的最大值即输出数组中相应位置的元素。

pytorch 计算r squared pytorch 计算最大互信息系数_pytorch_113

图展示了池化窗口形状为pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_31的最大池化,阴影部分为第一个输出元素及其计算所使用的输入元素。输出数组的高和宽分别为2,其中的4个元素由取最大值运算pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_115得出:

pytorch 计算r squared pytorch 计算最大互信息系数_cnn_116

二维平均池化的工作原理与二维最大池化类似,但将最大运算符替换成平均运算符。池化窗口形状为pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_07的池化层称为pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_07池化层,其中的池化运算叫作pytorch 计算r squared pytorch 计算最大互信息系数_pytorch 计算r squared_07池化。

回顾之前提到的物体边缘检测的例子。现在我们将卷积层的输出作为pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_31最大池化的输入。设该卷积层输入是X、池化层输出为Y。无论是X[i, j]和X[i, j+1]值不同,还是X[i, j+1]和X[i, j+2]不同,池化层输出均有Y[i, j]=1。也就是说,使用pytorch 计算r squared pytorch 计算最大互信息系数_深度学习_31最大池化层时,只要卷积层识别的模式在高和宽上移动不超过一个元素,我们依然可以将它检测出来。

下面把池化层的前向计算实现在pool2d函数里。它跟二维卷积层里corr2d函数非常类似,唯一的区别在计算输出Y上。

import torch
from torch import nn

def pool2d(X, pool_size, mode='max'):
    X = X.float()
    p_h, p_w = pool_size
    Y = torch.zeros(X.shape[0] - p_h + 1, X.shape[1] - p_w + 1)
    for i in range(Y.shape[0]):
        for j in range(Y.shape[1]):
            if mode == 'max':
                Y[i, j] = X[i: i + p_h, j: j + p_w].max()
            elif mode == 'avg':
                Y[i, j] = X[i: i + p_h, j: j + p_w].mean()       
    return Y

构造图中的输入数组X来验证二维最大池化层的输出。

X = torch.tensor([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
pool2d(X, (2, 2))
tensor([[4., 5.],
        [7., 8.]])

平均池化层

pool2d(X, (2, 2), 'avg')
tensor([[2., 3.],
        [5., 6.]])

填充和步幅

同卷积层一样,池化层也可以在输入的高和宽两侧的填充并调整窗口的移动步幅来改变输出形状。池化层填充和步幅与卷积层填充和步幅的工作机制一样。我们将通过nn模块里的二维最大池化层MaxPool2d来演示池化层填充和步幅的工作机制。我们先构造一个形状为(1, 1, 4, 4)的输入数据,前两个维度分别是批量和通道。

X = torch.arange(16, dtype=torch.float).view((1, 1, 4, 4)) # 前两个维度分别是批量和通道。
X
tensor([[[[ 0.,  1.,  2.,  3.],
          [ 4.,  5.,  6.,  7.],
          [ 8.,  9., 10., 11.],
          [12., 13., 14., 15.]]]])

默认情况下,MaxPool2d实例里步幅和池化窗口形状相同。下面使用形状为(3, 3)的池化窗口,默认获得形状为(3, 3)的步幅。

pool2d = nn.MaxPool2d(3)
pool2d(X)
tensor([[[[10.]]]])

指定步幅和填充

pool2d = nn.MaxPool2d(3, padding=1, stride=2)
pool2d(X)
tensor([[[[ 5.,  7.],
          [13., 15.]]]])

也可以指定非正方形的池化窗口,并分别指定高和宽上的填充和步幅。

pool2d = nn.MaxPool2d((2, 4), padding=(1, 2), stride=(2, 3))
pool2d(X)
tensor([[[[ 1.,  3.],
          [ 9., 11.],
          [13., 15.]]]])

多通道

在处理多通道输入数据时,池化层对每个输入通道分别池化,而不是像卷积层那样将各通道的输入按通道相加。这意味着池化层的输出通道数与输入通道数相等。下面将数组X和X+1在通道维上连结来构造通道数为2的输入。

X = torch.cat((X, X + 1), dim=1)
X
tensor([[[[ 0.,  1.,  2.,  3.],
          [ 4.,  5.,  6.,  7.],
          [ 8.,  9., 10., 11.],
          [12., 13., 14., 15.]],

         [[ 1.,  2.,  3.,  4.],
          [ 5.,  6.,  7.,  8.],
          [ 9., 10., 11., 12.],
          [13., 14., 15., 16.]]]])

池化后,输出通道数仍然是2。

pool2d = nn.MaxPool2d(3, padding=1, stride=2)
pool2d(X)
tensor([[[[ 5.,  7.],
          [13., 15.]],

         [[ 6.,  8.],
          [14., 16.]]]])

小结

  • 最大池化和平均池化分别取池化窗口中输入元素的最大值和平均值作为输出。
  • 池化层的一个主要作用是缓解卷积层对位置的过度敏感性。
  • 可以指定池化层的填充和步幅。
  • 池化层的输出通道数跟输入通道数相同。

参考

[1] 阿斯顿·张(Aston Zhang),李沐(Mu Li),[美] 扎卡里·C.立顿(Zachary C.Lipton) 等. 动手学深度学习. 北京: 人民邮电出版社,2019

[2] PyTorch官方文档

[3] https://github.com/ShusenTang/Dive-into-DL-PyTorch