虽然卷积层得名于卷积(convolution)运算,但我们通常在卷积层中使⽤更加直观的互相关(cross-correlation)运算。在⼆维卷积层中,⼀个⼆维输⼊数组和⼀个⼆维核(kernel)数组通过互相关运算输出⼀个⼆维数组。下图展示了⼆维互相关运算的含义。
如图所示,输⼊是⼀个⾼和宽均为3的⼆维数组。我们将该数组的形状记为 3 × 3 3\times3 3×3 或(3,3)。核数组的⾼和宽分别为2。该数组在卷积计算中⼜称卷积核或过滤器(filter)。在二维互相关运算中,窗口中的输入子数组与数组按元素相乘并求和,得到输出数组中相应位置的元素。
二维卷积层将输入和卷积核做互相关运算,并加上一个标量偏移来得到输出。卷积层的模型参数包括了卷积核和标量偏差。在训练模型时,通常我们先对卷积核随机初始化,然后不断迭代更新卷积核和偏差。
卷积核形状为 p × q p\times q p×q 的卷积层称为 p × q p\times q p×q 卷积层。同样 p × q p\times q p×q 卷积或 p × q p\times q p×q 卷积核说明卷积核的高和宽分别为 p p p 和 q q q 。
上面介绍时将互相关运算和卷积运算等价,但实际上这两种运算存在一定区别。事实上,为了得到卷积运算的输出,我们需要将核数组左右翻转并上下翻转,再与输入数组进行互相关运算。可见,卷积运算与互相关运算虽然类似,但如果它们使用相同的核数组,对于同一个输入,输出往往并不相同。
但是,注意在深度学习中核数组都是学习出来的,因此卷积层无论使用互相关运算或卷积运算都不影响模型预测时的输出。
二维卷积层输出的二维数组可以看作是输入在空间维度(宽和高)上某一级的表征,也叫特征图(feature map)。影响元素 x x x 的前向计算的所有可能输入区域(可能大于输入的实际尺寸)叫做 x x x 的感受野(receptive field)。以图5-1为例,输入中阴影部分的四个元素是输出中阴影部分元素的感受野。
在不考虑填充和步长的情况下,假设图像输入形状为 n h × n w n_h\times n_w nh×nw ,卷积核窗口形状是 k h × k w k_h\times k_w kh×kw,那么输出形状将会是:
( n h − k h + 1 ) × ( n w − k w + 1 ) (n_h-k_h+1)\times(n_w-k_w+1) (nh−kh+1)×(nw−kw+1)
所以卷积层的输出形状由输入形状和卷积核形状决定。这部分介绍卷积层的两个超参数:填充和步长,这两个参数在给定卷积核及输入大小的情况下用于改变输出形状。
填充是指在输入高和宽的两侧填充元素(通常是0元素)。在图2中我们在原输入高和宽的两侧分别添加了值为0的元素,使得输入高和宽从3变成了5,并导致输出高和宽由2增加到4。
一般来说,如果在高的两侧一共填充 p h p_h ph行,在宽的两侧一共填充 p w p_w pw列,那么输出形状将会是:
( n h − k h + p h + 1 ) × ( n w − k w + p w + 1 ) (n_h-k_h+p_h+1)\times(n_w-k_w+p_w+1) (nh−kh+ph+1)×(nw−kw+pw+1)
也就是说,输出的高和宽会分别增加 p h p_h ph和 p w p_w pw
在很多情况下,我们会设置 p h = k h − 1 p_h=k_h-1 ph=kh−1和 p w = k w − 1 p_w=k_w-1 pw=kw−1来使输入和输出具有相同的高和宽。这样会方便在构造网络时推测每个层的输出形状。假设这里 k h k_h kh是奇数,我们会在高的两侧分别填充 p h 2 \frac{p_h}{2} 2ph行。如果 k h k_h kh是偶数,一种可能是在输入的顶端一侧填充 ⌈ p h 2 ⌉ \lceil\frac{p_h}{2}\rceil ⌈2ph⌉行,而在底端一侧填充 ⌊ p h 2 ⌋ \lfloor\frac{p_h}{2}\rfloor ⌊2ph⌋行。在宽的两侧填充同理。
卷积神经网络经常使用奇数高宽的卷积核,如1、3、5和7,所以两端上的填充数相同。对于任意的二维数组x,设它的第i行第j列的元素为 x [ i , j ] x[i,j] x[i,j]。当两端上的填充个数相等,并使得输入和输出具有相同的高和宽时,我们就知道输出 y [ i , j ] y[i,j] y[i,j]是由输入以 x [ i , j ] x[i,j] x[i,j]为中心的窗口同卷积核进行互相关计算得到的。
当卷积核的高核宽不同时,我们也可以通过设置高和宽上不同的填充来使输出和输入具有相同的高和宽。
卷积核每次滑动的行数和列数称为步长(stride)。
在先前的例子中,在高和宽两个方向上的步长均为1。我们也可以使用更大的步长,下图展示了在高上步长为3、在宽上步长为2的二维互相关运算。可以看到,输出第一列第二个元素时,卷积窗口向下滑动了3行,而在输出第一行第二个元素时卷积窗口向右滑动了2列。当卷积窗口在输入上再向右滑动2列时,由于输入元素无法填满窗口,无结果输出。
一般来说,当高上步幅为 s k s_k sk,宽上步幅为 s w s_w sw时,输出形状为:
⌊ ( n h − k h + p h + s h ) / s h ⌋ × ⌊ ( n w − k w + p w + s w ) / s w ⌋ \lfloor(n_h-k_h+p_h+s_h)/s_h\rfloor\times\lfloor(n_w-k_w+p_w+s_w)/s_w\rfloor ⌊(nh−kh+ph+sh)/sh⌋×⌊(nw−kw+pw+sw)/sw⌋
大部分输入数据都是RGB数据,这样的图片在长宽两个维度的基础上还有另外一个颜色维度,这时图片可以表示成一个 3 × h × w 3\times h\times w 3×h×w的多维数组。我们将大小为3的这一维称为通道(channel)维。这一节介绍包含多个输入通道或包含多个输出通道的卷积核。
当输⼊数据含多个通道时,我们需要构造⼀个输⼊通道数与输⼊数据的通道数相同的卷积核,从⽽能够与含多通道的输⼊数据做互相关运算。假设输⼊数据的通道数为 c i c_i ci,那么卷积核的输⼊通道数同样为 c i c_i ci。设卷积核窗⼝形状为 k h × k w k_h\times k_w kh×kw。当 c i = 1 c_i=1 ci=1时,我们知道卷积核只包含⼀个形状为 k h × k w k_h\times k_w kh×kw的⼆维数组。当 c i > 1 c_i>1 ci>1时,我们将会为每个输⼊通道各分配⼀个形状为 k h × k w k_h\times k_w kh×kw的核数组。把这 c i c_i ci个数组在输⼊通道维上连结,即得到⼀个形状为 c i × k h × k w c_i\times k_h\times k_w ci×kh×kw的卷积核。由于输⼊和卷积核各有 c i c_i ci个通道,我们可以在各个通道上对输⼊的⼆维数组和卷积核的⼆维核数组做互相关运算,再将这 c i c_i ci个互相关运算的⼆维输出按通道相加,得到⼀个⼆维数组。这就是含多个通道的输⼊数据与多输⼊通道的卷积核做⼆维互相关运算的输出
图5.4展示了含两个输入通道的二维互相关计算的例子:
图5.4 含2个输入通道的互相关计算
当输⼊通道有多个时,因为我们对各个通道的结果做了累加,所以不论输⼊通道数是多少,输出通道数总是为1。设卷积核输⼊通道数和输出通道数分别为 c i c_i ci和 c o c_o co,⾼和宽分别为 k h k_h kh和 k w k_w kw。如果希望得到含多个通道的输出,我们可以为每个输出通道分别创建形状为 c i × k h × k w c_i\times k_h\times k_w ci×kh×kw的核数组。将它们在输出通道维上连结,卷积核的形状即 c o × c i × k h × k w c_o\times c_i\times k_h\times k_w co×ci×kh×kw。在做互相关运算时,每个输出通道上的结果由卷积核在该输出通道上的核数组与整个输⼊数组计算⽽来。
最后我们讨论卷积窗⼝形状为 1 × 1 1\times 1 1×1( k h = k w = 1 k_h=k_w=1 kh=kw=1)的多通道卷积层。我们通常称之为 1 × 1 1\times 1 1×1卷积层,并将其中的卷积运算称为 1 × 1 1\times 1 1×1卷积。因为使⽤了最⼩窗⼝, 1 × 1 1\times 1 1×1卷积失去了卷积层可以识别⾼和宽维度上相邻元素构成的模式的功能。实际上, 1 × 1 1\times 1 1×1卷积的主要计算发⽣在通道维上。图5.5展示了使
⽤输⼊通道数为3、输出通道数为2的 1 × 1 1\times 1 1×1卷积核的互相关计算。值得注意的是,输⼊和输出具有相同的⾼和宽。输出中的每个元素来⾃输⼊中在⾼和宽上相同位置的元素在不同通道之间的按权重累加。假设我们将通道维当作特征维,将⾼和宽维度上的元素当成数据样本,那么 1 × 1 1\times 1 1×1卷积层的作⽤与全连接层等价。
图5.5 1 × 1 1\times 1 1×1卷积核的互相关计算
在之后的模型里我们会看到 1 × 1 1\times 1 1×1卷积层被当作保持高核宽维度不变的全连接层使用。于是,我们可以通过调整网络层之间的通道数来控制模型复杂度。
这部分介绍池化层,它的提出是为了缓解卷积层对位置的过度敏感性。
同卷积层⼀样,池化层每次对输⼊数据的⼀个固定形状窗⼝(⼜称池化窗⼝)中的元素计算输出。不同于卷积层⾥计算输⼊和核的互相关性,池化层直接计算池化窗⼝内元素的最⼤值或者平均值。该运算也分别叫做最⼤池化或平均池化。在⼆维最⼤池化中,池化窗⼝从输⼊数组的最左上⽅开始,按从左往右、从上往下的顺序,依次在输⼊数组上滑动。当池化窗⼝滑动到某⼀位置时,窗⼝中的输⼊⼦数组的最⼤值即输出数组中相应位置的元素。
下图展示了池化窗口形状为 2 × 2 2\times2 2×2的最大池化层的作用过程:
二维平均池化的工作原理与二维最大池化类似,但将最大运算符替换成平均运算符。
池化层通过在输入的高和宽两侧进行填充以及移动步长来改变输出形状(即输出的高和宽)。
下面通过一个例子来说明工作方式,首先构造一个形状为(1,1,4,4)的输入数据,前两个维度分别是批量和通道。
x=torch.arange(16,dtype=torch.float).view((1,1,4,4))
#待补充,这里的创建tensor方式可以记一下
print(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)
print(pool2d(x))
输出:
tensor([[[[10.]]]])
指定步长和填充:
pool2d = nn.MaxPool2d(3,padding=1,stride=2)
print(pool2d(x))
输出:
tensor([[[[ 5., 7.],
[13., 15.]]]])
我们可以指定非正方形的池化窗口,并分别指定高和宽上的填充和步长:
pool2d = nn.MaxPool2d((2,4),padding=(1,2),stride=(2,3))
print(pool2d(x))
输出:
tensor([[[[ 1., 3.],
[ 9., 11.],
[13., 15.]]]])
有一点需要注意,池化层对每个输入通道分别池化,而不是像卷积层那样将各通道的输入按通道相加。这意味着池化层的输出通道数与输入通道数相等。