二维卷积层
-
卷积神经网络(convolutional neural network)是含有卷积层(convolutional layer)的神经网络。本章中介绍的卷积神经网络均使用最常见的二维卷积层。它有高和宽两个空间维度,常用来处理图像数据。
-
二维互相关运算
-
虽然卷积层得名于卷积(convolution)运算,但我们通常在卷积层中使用更加直观的互相关(cross-correlation)运算。在二维卷积层中,一个二维输入数组和一个二维核(kernel)数组通过互相关运算输出一个二维数组。
-
在二维互相关运算中,卷积窗口从输入数组的最左上方开始,按从左往右、从上往下的顺序,依次在输入数组上滑动。当卷积窗口滑动到某一位置时,窗口中的输入子数组与核数组按元素相乘并求和,得到输出数组中相应位置的元素。
-
0 × 0 + 1 × 1 + 3 × 2 + 4 × 3 = 19 , 1 × 0 + 2 × 1 + 4 × 2 + 5 × 3 = 25 , 3 × 0 + 4 × 1 + 6 × 2 + 7 × 3 = 37 , 4 × 0 + 5 × 1 + 7 × 2 + 8 × 3 = 43. 0\times0+1\times1+3\times2+4\times3=19,\\ 1\times0+2\times1+4\times2+5\times3=25,\\ 3\times0+4\times1+6\times2+7\times3=37,\\ 4\times0+5\times1+7\times2+8\times3=43.\\ 0×0+1×1+3×2+4×3=19,1×0+2×1+4×2+5×3=25,3×0+4×1+6×2+7×3=37,4×0+5×1+7×2+8×3=43.
-
将上述过程实现在
corr2d
函数里。它接受输入数组X
与核数组K
,并输出数组Y
。 -
import torch from torch import nn print(torch.__version__) def corr2d(X, K): h, w = K.shape X, K = X.float(), K.float() 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 = torch.tensor([[0, 1, 2], [3, 4, 5], [6, 7, 8]]) K = torch.tensor([[0, 1], [2, 3]]) corr2d(X, K)
-
1.13.1 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
-
图像中物体边缘检测
- 下面我们来看一个卷积层的简单应用:检测图像中物体的边缘,即找到像素变化的位置。首先我们构造一张
6
×
8
6\times 8
6×8的图像(即高和宽分别为6像素和8像素的图像)。它中间4列为黑(0),其余为白(1)。然后我们构造一个高和宽分别为1和2的卷积核
K
。当它与输入做互相关运算时,如果横向相邻元素相同,输出为0;否则输出为非0。 -
X = torch.ones(6, 8) X[:, 2:6] = 0 print(X) K = torch.tensor([[1, -1]]) # 创建卷积核 print(K) Y = corr2d(X, K) print(Y)
-
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.]]) tensor([[ 1, -1]]) 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.]])
- 下面我们来看一个卷积层的简单应用:检测图像中物体的边缘,即找到像素变化的位置。首先我们构造一张
6
×
8
6\times 8
6×8的图像(即高和宽分别为6像素和8像素的图像)。它中间4列为黑(0),其余为白(1)。然后我们构造一个高和宽分别为1和2的卷积核
-
通过数据学习核数组
-
一个例子,它使用物体边缘检测中的输入数据
X
和输出数据Y
来学习我们构造的核数组K
。我们首先构造一个卷积层,其卷积核将被初始化成随机数组。接下来在每一次迭代中,我们使用平方误差来比较Y
和卷积层的输出,然后计算梯度来更新权重。可以看到,20次迭代后误差已经降到了一个比较小的值。现在来看一下学习到的卷积核的参数。学到的卷积核的权重参数与我们之前定义的核数组K
较接近,而偏置参数接近0。 -
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 # 构造一个核数组形状是(1, 2)的二维卷积层 conv2d = Conv2D(kernel_size=(1, 2)) step = 20 lr = 0.01 for i in range(step): Y_hat = conv2d(X) # X上文的自定义图片 l = ((Y_hat - Y) ** 2).sum() # Y相关运算结果 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())) print("weight: ", conv2d.weight.data) # 拟合K张量 print("bias: ", conv2d.bias.data)
-
Step 5, loss 20.477 Step 10, loss 5.679 Step 15, loss 1.579 Step 20, loss 0.439 weight: tensor([[ 0.8336, -0.8296]]) bias: tensor([-0.0022])
-
-
互相关运算和卷积运算
- 实际上,卷积运算与互相关运算类似。为了得到卷积运算的输出,我们只需将核数组左右翻转并上下翻转,再与输入数组做互相关运算。可见,卷积运算和互相关运算虽然类似,但如果它们使用相同的核数组,对于同一个输入,输出往往并不相同。
-
特征图和感受野
- 二维卷积层输出的二维数组可以看作是输入在空间维度(宽和高)上某一级的表征,也叫特征图(feature map)。影响元素x的前向计算的所有可能输入区域(可能大于输入的实际尺寸)叫做x的感受野(receptive field)。以上图为例,输入中阴影部分的四个元素是输出中阴影部分元素的感受野。我们将图中形状为 2 × 2 2 \times 2 2×2的输出记为Y,并考虑一个更深的卷积神经网络:将Y与另一个形状为 2 × 2 2 \times 2 2×2的核数组做互相关运算,输出单个元素z。那么,z在Y上的感受野包括Y的全部四个元素,在输入上的感受野包括其中全部9个元素。可见,我们可以通过更深的卷积神经网络使特征图中单个元素的感受野变得更加广阔,从而捕捉输入上更大尺寸的特征。
-
二维卷积层的核心计算是二维互相关运算。在最简单的形式下,它对二维输入数据和卷积核做互相关运算然后加上偏差。可以设计卷积核来检测图像中的边缘。可以通过数据来学习卷积核。
padding和步幅
-
使用高和宽为3的输入与高和宽为2的卷积核得到高和宽为2的输出。一般来说,假设输入形状是 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).所以卷积层的输出形状由输入形状和卷积核窗口形状决定。本节我们将介绍卷积层的两个超参数,即填充和步幅。它们可以对给定形状的输入和卷积核改变输出形状。
-
padding
- 填充(padding)是指在输入高和宽的两侧填充元素(通常是0元素)。在原输入高和宽的两侧分别添加了值为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 p_h/2 ph/2行。如果 k h k_h kh是偶数,一种可能是在输入的顶端一侧填充 ⌈ p h / 2 ⌉ \lceil p_h/2\rceil ⌈ph/2⌉行,而在底端一侧填充 ⌊ p h / 2 ⌋ \lfloor p_h/2\rfloor ⌊ph/2⌋行。在宽的两侧填充同理。
- 卷积神经网络经常使用奇数高宽的卷积核,如1、3、5和7,所以两端上的填充个数相等。对任意的二维数组
X
,设它的第i
行第j
列的元素为X[i,j]
。当两端上的填充个数相等,并使输入和输出具有相同的高和宽时,我们就知道输出Y[i,j]
是由输入以X[i,j]
为中心的窗口同卷积核进行互相关计算得到的。 - 下面的例子里我们创建一个高和宽为3的二维卷积层,然后设输入高和宽两侧的填充数分别为1。给定一个高和宽为8的输入,我们发现输出的高和宽也是8。当卷积核的高和宽不同时,我们也可以通过设置高和宽上不同的填充数使输出和输入具有相同的高和宽。
-
import torch from torch import nn print(torch.__version__) # 定义一个函数来计算卷积层。它对输入和输出做相应的升维和降维 def comp_conv2d(conv2d, X): # (1, 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) print(comp_conv2d(conv2d, X).shape) # 使用高为5、宽为3的卷积核。在高和宽两侧的填充数分别为2和1 conv2d = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=(5, 3), padding=(2, 1)) print(comp_conv2d(conv2d, X).shape)
-
1.13.1 torch.Size([8, 8]) torch.Size([8, 8])
-
stride
- 卷积窗口从输入数组的最左上方开始,按从左往右、从上往下的顺序,依次在输入数组上滑动。我们将每次滑动的行数和列数称为步幅(stride)。
- 目前我们看到的例子里,在高和宽两个方向上步幅均为1。我们也可以使用更大步幅。图5.3展示了在高上步幅为3、在宽上步幅为2的二维互相关运算。可以看到,输出第一列第二个元素时,卷积窗口向下滑动了3行,而在输出第一行第二个元素时卷积窗口向右滑动了2列。当卷积窗口在输入上再向右滑动2列时,由于输入元素无法填满窗口,无结果输出。
- 一般来说,当高上步幅为s_h,宽上步幅为s_w时,输出形状为 ⌊ ( 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⌋.如果设置 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,那么输出形状将简化为 ⌊ ( n h + s h − 1 ) / s h ⌋ × ⌊ ( n w + s w − 1 ) / s w ⌋ \lfloor(n_h+s_h-1)/s_h\rfloor \times \lfloor(n_w+s_w-1)/s_w\rfloor ⌊(nh+sh−1)/sh⌋×⌊(nw+sw−1)/sw⌋。更进一步,如果输入的高和宽能分别被高和宽上的步幅整除,那么输出形状将是 ( n h / s h ) × ( n w / s w ) (n_h/s_h) \times (n_w/s_w) (nh/sh)×(nw/sw)。下面我们令高和宽上的步幅均为2,从而使输入的高和宽减半。
-
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2) print(comp_conv2d(conv2d, X).shape) conv2d = nn.Conv2d(1, 1, kernel_size=(3, 5), padding=(0, 1), stride=(3, 4)) print(comp_conv2d(conv2d, X).shape)
-
torch.Size([4, 4]) torch.Size([2, 2])
-
填充可以增加输出的高和宽。这常用来使输出与输入具有相同的高和宽。步幅可以减小输出的高和宽,例如输出的高和宽仅为输入的高和宽的1/n(n为大于1的整数)。
多输入通道和多输出通道
-
前文里我们用到的输入和输出都是二维数组,但真实数据的维度经常更高。例如,彩色图像在高和宽2个维度外还有RGB(红、绿、蓝)3个颜色通道。假设彩色图像的高和宽分别是h和w(像素),那么它可以表示为一个 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个互相关运算的二维输出按通道相加,得到一个二维数组。这就是含多个通道的输入数据与多输入通道的卷积核做二维互相关运算的输出。
- 下图展示了含2个输入通道的二维互相关计算的例子。在每个通道上,二维输入数组与二维核数组做互相关运算,再按通道相加即得到输出。图5.4中阴影部分为第一个输出元素及其计算所使用的输入和核数组元素: ( 1 × 1 + 2 × 2 + 4 × 3 + 5 × 4 ) + ( 0 × 0 + 1 × 1 + 3 × 2 + 4 × 3 ) = 56 (1\times1+2\times2+4\times3+5\times4)+(0\times0+1\times1+3\times2+4\times3)=56 (1×1+2×2+4×3+5×4)+(0×0+1×1+3×2+4×3)=56:
-
接下来我们实现含多个输入通道的互相关运算。我们只需要对每个通道做互相关运算,然后通过
add_n
函数来进行累加。 -
import torch from torch import nn def corr2d(X, K): h, w = K.shape X, K = X.float(), K.float() 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 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 = 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。设卷积核输入通道数和输出通道数分别为 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。在做互相关运算时,每个输出通道上的结果由卷积核在该输出通道上的核数组与整个输入数组计算而来。
-
下面我们实现一个互相关运算函数来计算多个通道的输出。将核数组
K
同K+1
(K
中每个元素加一)和K+2
连结在一起来构造一个输出通道数为3的卷积核。下面我们对输入数组X
与核数组K
做互相关运算。此时的输出含有3个通道。其中第一个通道的结果与之前输入数组X
与多输入通道、单输出通道核的计算结果一致。 -
def corr2d_multi_in_out(X, K): # 对K的第0维遍历,每次同输入X做互相关计算。所有结果使用stack函数合并在一起 return torch.stack([corr2d_multi_in(X, k) for k in K]) K = torch.stack([K, K + 1, K + 2]) print(K.shape) # torch.Size([3, 2, 2, 2]) corr2d_multi_in_out(X, K)
-
torch.Size([3, 2, 2, 2]) tensor([[[ 56., 72.], [104., 120.]], [[ 76., 100.], [148., 172.]], [[ 96., 128.], [192., 224.]]])
-
1 × 1 1\times 1 1×1卷积层
-
卷积窗口形状为 1 × 1 ( k h = k w = 1 ) 1\times 1(k_h=k_w=1) 1×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卷积的主要计算发生在通道维上。值得注意的是,输入和输出具有相同的高和宽。输出中的每个元素来自输入中在高和宽上相同位置的元素在不同通道之间的按权重累加。假设我们将通道维当作特征维,将高和宽维度上的元素当成数据样本,那么 1 × 1 1\times 1 1×1卷积层的作用与全连接层等价。
-
下面我们使用全连接层中的矩阵乘法来实现 1 × 1 1\times 1 1×1卷积。这里需要在矩阵乘法运算前后对数据形状做一些调整。经验证,做 1 × 1 1\times 1 1×1卷积时,以上函数与之前实现的互相关运算函数
corr2d_multi_in_out
等价。 -
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) X = torch.rand(3, 3, 3) K = torch.rand(2, 3, 1, 1) Y1 = corr2d_multi_in_out_1x1(X, K) Y2 = corr2d_multi_in_out(X, K) (Y1 - Y2).norm().item() < 1e-6
-
True
-
1 × 1 1\times 1 1×1卷积层被当作保持高和宽维度形状不变的全连接层使用。于是,我们可以通过调整网络层之间的通道数来控制模型复杂度。使用多通道可以拓展卷积层的模型参数。假设将通道维当作特征维,将高和宽维度上的元素当成数据样本,那么 1 × 1 1\times 1 1×1卷积层的作用与全连接层等价。
池化层
-
介绍的图像物体边缘检测应用中,本文构造卷积核从而精确地找到了像素变化的位置。设任意二维数组
X
的i
行j
列的元素为X[i, j]
。如果我们构造的卷积核输出Y[i, j]=1
,那么说明输入中X[i, j]
和X[i, j+1]
数值不一样。这可能意味着物体边缘通过这两个元素之间。但实际图像里,感兴趣的物体不会总出现在固定位置:即使我们连续拍摄同一个物体也极有可能出现像素位置上的偏移。这会导致同一个边缘对应的输出可能出现在卷积输出Y
中的不同位置,进而对后面的模式识别造成不便。池化(pooling)层,它的提出是为了缓解卷积层对位置的过度敏感性。 -
同卷积层一样,池化层每次对输入数据的一个固定形状窗口(又称池化窗口)中的元素计算输出。不同于卷积层里计算输入和核的互相关性,池化层直接计算池化窗口内元素的最大值或者平均值。该运算也分别叫做最大池化或平均池化。在二维最大池化中,池化窗口从输入数组的最左上方开始,按从左往右、从上往下的顺序,依次在输入数组上滑动。当池化窗口滑动到某一位置时,窗口中的输入子数组的最大值即输出数组中相应位置的元素。
-
下图展示了池化窗口形状为 2 × 2 2\times 2 2×2的最大池化,阴影部分为第一个输出元素及其计算所使用的输入元素。输出数组的高和宽分别为2,其中的4个元素由取最大值运算 max \text{max} max得出:
-
max ( 0 , 1 , 3 , 4 ) = 4 , max ( 1 , 2 , 4 , 5 ) = 5 , max ( 3 , 4 , 6 , 7 ) = 7 , max ( 4 , 5 , 7 , 8 ) = 8. \max(0,1,3,4)=4,\\ \max(1,2,4,5)=5,\\ \max(3,4,6,7)=7,\\ \max(4,5,7,8)=8.\\ max(0,1,3,4)=4,max(1,2,4,5)=5,max(3,4,6,7)=7,max(4,5,7,8)=8.
-
二维平均池化的工作原理与二维最大池化类似,但将最大运算符替换成平均运算符。池化窗口形状为 p × q p \times q p×q的池化层称为 p × q p \times q p×q池化层,其中的池化运算叫作 p × q p \times q p×q池化。
-
开始提到的物体边缘检测的例子。现在我们将卷积层的输出作为 2 × 2 2\times 2 2×2最大池化的输入。设该卷积层输入是
X
、池化层输出为Y
。无论是X[i, j]
和X[i, j+1]
值不同,还是X[i, j+1]
和X[i, j+2]
不同,池化层输出均有Y[i, j]=1
。也就是说,使用 2 × 2 2\times 2 2×2最大池化层时,只要卷积层识别的模式在高和宽上移动不超过一个元素,我们依然可以将它检测出来。 -
下面把池化层的前向计算实现在
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 = torch.tensor([[0, 1, 2], [3, 4, 5], [6, 7, 8]]) print(pool2d(X, (2, 2))) print(pool2d(X, (2, 2), 'avg'))
-
tensor([[4., 5.], [7., 8.]]) tensor([[2., 3.], [5., 6.]])
-
-
填充和步幅
- 同卷积层一样,池化层也可以在输入的高和宽两侧的填充并调整窗口的移动步幅来改变输出形状。池化层填充和步幅与卷积层填充和步幅的工作机制一样。我们将通过
nn
模块里的二维最大池化层MaxPool2d
来演示池化层填充和步幅的工作机制。我们先构造一个形状为(1, 1, 4, 4)的输入数据,前两个维度分别是批量和通道。 -
X = torch.arange(16, dtype=torch.float).view((1, 1, 4, 4)) print(X) pool2d = nn.MaxPool2d(3) print(pool2d(X)) pool2d = nn.MaxPool2d(3, padding=1, stride=2) # 可以手动指定步幅和填充。 print(pool2d(X)) pool2d = nn.MaxPool2d((2, 4), padding=(1, 2), stride=(2, 3)) # 也可以指定非正方形的池化窗口,并分别指定高和宽上的填充和步幅。 print(pool2d(X))
-
tensor([[[[ 0., 1., 2., 3.], [ 4., 5., 6., 7.], [ 8., 9., 10., 11.], [12., 13., 14., 15.]]]]) tensor([[[[10.]]]]) tensor([[[[ 5., 7.], [13., 15.]]]]) tensor([[[[ 1., 3.], [ 9., 11.], [13., 15.]]]])
- 默认情况下,
MaxPool2d
实例里步幅和池化窗口形状相同。下面使用形状为(3, 3)的池化窗口,默认获得形状为(3, 3)的步幅。
- 同卷积层一样,池化层也可以在输入的高和宽两侧的填充并调整窗口的移动步幅来改变输出形状。池化层填充和步幅与卷积层填充和步幅的工作机制一样。我们将通过
多通道
-
在处理多通道输入数据时,池化层对每个输入通道分别池化,而不是像卷积层那样将各通道的输入按通道相加。这意味着池化层的输出通道数与输入通道数相等。下面将数组
X
和X+1
在通道维上连结来构造通道数为2的输入。-
X = torch.cat((X, X + 1), dim=1) print(X) pool2d = nn.MaxPool2d(3, padding=1, stride=2) print(pool2d(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.]]]]) tensor([[[[ 5., 7.], [13., 15.]], [[ 6., 8.], [14., 16.]]]])
-
-
最大池化和平均池化分别取池化窗口中输入元素的最大值和平均值作为输出。池化层的一个主要作用是缓解卷积层对位置的过度敏感性。可以指定池化层的填充和步幅。池化层的输出通道数跟输入通道数相同。
[[ 1., 2., 3., 4.],
[ 5., 6., 7., 8.],
[ 9., 10., 11., 12.],
[13., 14., 15., 16.]]]])
tensor([[[[ 5., 7.],
[13., 15.]],[[ 6., 8.], [14., 16.]]]])
-
最大池化和平均池化分别取池化窗口中输入元素的最大值和平均值作为输出。池化层的一个主要作用是缓解卷积层对位置的过度敏感性。可以指定池化层的填充和步幅。池化层的输出通道数跟输入通道数相同。