【深一点学习】自己实现一下卷积和池化操作,理解超参数意义,理清数学计算方式

news2024/12/23 10:16:12

二维卷积层

  • 卷积神经网络(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__里我们声明weightbias这两个模型参数。前向计算函数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.]])
      
  • 通过数据学习核数组

    • 一个例子,它使用物体边缘检测中的输入数据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) (nhkh+1)×(nwkw+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) (nhkh+ph+1)×(nwkw+pw+1),也就是说,输出的高和宽会分别增加 p h p_h ph p w p_w pw
    • 在很多情况下,我们会设置 p h = k h − 1 p_h=k_h-1 ph=kh1 p w = k w − 1 p_w=k_w-1 pw=kw1来使输入和输出具有相同的高和宽。这样会方便在构造网络时推测每个层的输出形状。假设这里 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 ⌊(nhkh+ph+sh)/sh×⌊(nwkw+pw+sw)/sw.如果设置 p h = k h − 1 p_h=k_h-1 ph=kh1 p w = k w − 1 p_w=k_w-1 pw=kw1,那么输出形状将简化为 ⌊ ( 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+sh1)/sh×⌊(nw+sw1)/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。在做互相关运算时,每个输出通道上的结果由卷积核在该输出通道上的核数组与整个输入数组计算而来。

    • 下面我们实现一个互相关运算函数来计算多个通道的输出。将核数组KK+1K中每个元素加一)和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×1kh=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卷积层的作用与全连接层等价。

池化层

  • 介绍的图像物体边缘检测应用中,本文构造卷积核从而精确地找到了像素变化的位置。设任意二维数组Xij列的元素为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)的步幅。

多通道

  • 在处理多通道输入数据时,池化层对每个输入通道分别池化,而不是像卷积层那样将各通道的输入按通道相加。这意味着池化层的输出通道数与输入通道数相等。下面将数组XX+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.]]]])
    
    
    
  • 最大池化和平均池化分别取池化窗口中输入元素的最大值和平均值作为输出。池化层的一个主要作用是缓解卷积层对位置的过度敏感性。可以指定池化层的填充和步幅。池化层的输出通道数跟输入通道数相同

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/390165.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python、JavaScript、C、C++和Java可视化代码执行工具

Python、JavaScrip、C、C和Java可视化代码执行工具 该工具通过可视化代码执行来帮助您学习Python、JavaScript、C、C和Java编程。可在可视化区动态展示执行过程中的调用栈、相关变量以及对应的变量值。https://pythontutor.com/ 下面以执行下面python这段代码为例 class MyCla…

9万字“联、管、用”三位一体雪亮工程整体建设方案

本资料来源公开网络&#xff0c;仅供个人学习&#xff0c;请勿商用。部分资料内容&#xff1a; 1、 总体设计方案 围绕《公共安全视频监控建设联网应用”十三五”规划方案》中的总体架构和一总两分结构要求的基础上&#xff0c;项目将以“加强社会公共安全管理&#xff0c;提高…

leetcode打卡-贪心算法

455.分发饼干 leetcode题目链接&#xff1a;https://leetcode.cn/problems/assign-cookies leetcode AC记录&#xff1a; 代码如下&#xff1a; public int findContentChildren(int[] g, int[] s) {Arrays.sort(g);Arrays.sort(s);int res 0;int sIndex 0;int gIndex 0…

Kafka生产者的粘性分区算法

分区算法分类 kafka在生产者投递消息时&#xff0c;会根据是否有key采取不用策略来获取分区。 存在key时会根据key计算一个hash值&#xff0c;然后采用hash%分区数的方式获取对应的分区。 而不存在key时采用随机算法选取分区&#xff0c;然后将所有的消息封装到这个batch上直…

2023/3/5 Vue学习笔记 - 生命周期函数探究-2

1 beforeCreated 在组件实例初始化完成之后立即调用。会在实例初始化完成、props 解析之后、data() 和 computed 等选项处理之前立即调用。 组件的组件实例初始化动作&#xff1a;初始化一个空的Vue实例对象&#xff0c;此时&#xff0c;这个对象身上只有一个默认的声明周期函…

Eureka注册中心快速入门

一、提供者与消费者**服务提供者&#xff1a;**一次业务中&#xff0c;被其他微服务调用的服务。&#xff08;提供接口给其他微服务&#xff09;**服务消费者&#xff1a;**一次业务中&#xff0c;调用其他微服务的服务。&#xff08;调用其它微服务提供的接口&#xff09;比如…

如何分辨on-policy和off-policy

on-policy的定义&#xff1a;behavior policy和target-policy相同的是on-policy&#xff0c;不同的是off-policy。 behavior policy&#xff1a;采样数据的策略&#xff0c;影响的是采样出来s,a的分布。 target policy&#xff1a;就是被不断迭代修改的策略。 如果是基于深度…

JavaSE学习笔记总结day18(完结!!!)

今日内容 零、 复习昨日 一、作业 二、进程与线程 三、创建线程 四、线程的API 五、线程状态 六、线程同步 零、 复习昨日 晨考 一、作业 见答案 二、进程与线程[了解] 一个进程就是一个应用程序,进程包含线程 一个进程至少包含一个线程,大部分都是有多条线程在执行任务(多线…

Win系统蓝牙设备频繁卡顿/断连 - 解决方案

Win系统蓝牙设备频繁卡顿/断连 - 解决方案前言常见网卡Intel无线网卡&#xff08;推荐&#xff09;Realtek无线网卡总结查看本机网卡解决方案更新驱动更换网卡&#xff08;推荐&#xff09;前言 无线网卡有2个模块&#xff0c;一个是WiFi&#xff0c;一个是蓝牙&#xff0c;因…

Kubernetes之存储管理(下)

动态卷供应 上篇文章讲述的持久性存储&#xff0c;是先创建pv&#xff0c;然后才能创建pvc。如果不同的命名空间里同时要创建不同的pvc&#xff0c;那么就需要提前创建好pv&#xff0c;这样才能为pvc提供存储。但是这种方式太过繁琐&#xff0c;可以使用storageClass&#xff…

yolov5算法,训练模型,模型检测

嘟嘟嘟嘟&#xff01;工作需要&#xff0c;所以学习了下yolov5算法。是干什么的呢&#xff1f; 通俗来说&#xff0c;可以将它看做是一个小孩儿&#xff0c;通过成年人&#xff08;开发人员&#xff09;提供的大量图片的学习&#xff0c;让自己知道我看到的哪些场景需要提醒给成…

MySQL底层存储B-Tree和B+Tree原理分析

1.B-Tree的原理分析 &#xff08;1&#xff09;什么是B-Tree B-树&#xff0c;全称是 Balanced Tree&#xff0c;是一种多路平衡查找树。 一个节点包括多个key (数量看业务)&#xff0c;具有M阶的B树&#xff0c;每个节点最多有M-1个Key。 节点的key元素个数就是指这个节点能…

Andorid:关于Binder几个面试问题

1.简单介绍下binderbinder是一种进程间通讯的机制进程间通讯需要了解用户空间和内核空间每个进程拥有自己的独立虚拟机&#xff0c;系统为他们分配的地址空间都是互相隔离的。如两个进程需要进行通讯&#xff0c;则需要使用到内核空间做载体&#xff0c;内核空间是所有进程共享…

FL2440(S3C2440A 芯片) 开发板开发笔记

FL2440(S3C2440A 芯片) 开发板开发笔记 开发板的拨码开关指南&#xff1a; FL2440 改 vnfg 飞凌嵌入式 www. witech. com. cn 09. 8. 22 1 开发板使用手册 version4. 0 FL2440 保定飞凌嵌入式技术有限公司 网站&#xff1a;http: //www. witech. com. cn http: //www. he…

动态规划之买卖股票问题

&#x1f308;&#x1f308;&#x1f604;&#x1f604; 欢迎来到茶色岛独家岛屿&#xff0c;本期将为大家揭晓动态规划之买卖股票问题 &#xff0c;做好准备了么&#xff0c;那么开始吧。 &#x1f332;&#x1f332;&#x1f434;&#x1f434; 动态规划算法本质上就是穷举…

synchronized底层

Monitor概念一、Java对象头二、Monitor2.1、Monitor—工作原理2.2、Monitor工作原理—字节码角度2.2、synchronized进阶原理&#xff08;优化&#xff09;2.3、synchronized优化原理——轻量级锁2.4、synchronized优化原理——锁膨胀2.5、synchronized优化原理——自旋优化2.6、…

VUE3-Cesium(加载GeoJSON数据)

目录 一、准备工作 1、新建vue项目 解决报错&#xff1a;使用nvm后找不到vue -V找不到版本 2、安装Cesium插件 3、安装 Element Plus、unplugin-vue-components 和 unplugin-auto-import 4、按需自动导入element-plus 测试按需自动导入element-plus是否配置成功 二、项…

2023年软考中级电子商务设计师考什么?

首先&#xff0c;电子商务设计师属于软考中级&#xff0c;因此难度也不是特别大。但并不是说就完全没有难度&#xff0c;难度还是有的&#xff0c;像上午题一般把基本知识点掌握了&#xff0c;是没什么问题的&#xff0c;重点就在于下午题会比较难。 接下来我们来剖析一下考试…

408考研计算机之计算机组成与设计——知识点及其做题经验篇目1:RAM与ROM

目录 一、RAM 1、特点 2、分类 ①SRAM ②DRAM 二、ROM 1、特点 2、分类 可能是小编的学习风格和大家的不一样&#xff0c;小编喜欢由难到易的学习风格&#xff0c;所以在408计算机考研的四门课中&#xff0c;先选择了最难的计算机组成与设计进行学习。近…

【C++】初识类和对象

&#x1f3d6;️作者&#xff1a;malloc不出对象 ⛺专栏&#xff1a;C的学习之路 &#x1f466;个人简介&#xff1a;一名双非本科院校大二在读的科班编程菜鸟&#xff0c;努力编程只为赶上各位大佬的步伐&#x1f648;&#x1f648; 目录前言一、面向过程和面向对象初步认识二…