动手学深度学习:卷积神经网络模型的构建以及代码解读

一、卷积神经网络的独特性质以及与多层感知机之间的联系:

        1.卷积神经网络和多层感知机之间的联系:

        之前我们讨论的多层感知机十分适合处理表格数据,行对应样本,列对应特征。对于表格数据,我们寻找的模式可能涉及特征之间的交互,但是我们不能预先假设任何与特征交互相关的先验结构对于高维感知数据,缺少结构的网络可能会变得不实用。

        再之前的猫狗分类实验中,数据集的每张照片具有百万级像素,这意味着网络的每次输入都有一百万个维度,降低隐藏层到1000,全连接层也具备10^{6} * 10^{3} = 10^{9}个参数,训练这种模型需要耗费大量的计算资源。面对大量参数的问题,通过卷积神经网络(CNN)这种数据结构来优化模型。

        2.数据结构的不变性:

         对于儿童游戏“沃尔多在哪里”,尽管沃尔多具备许多特征,但是找到他如同大海捞针。我们可以使用一个”沃尔多检测器“,即卷积神经网络中的核,将图像分割为多个区域,并为每个区域包含沃尔多的可能性打分。卷积神经网络利用”不变性“这个概念,使用较少的参数输出数值用于判断目标是否存在。

         卷积神经网络应该具备“平移不变性”和“局部性”,平移不变性指的是无论检测对象在图像中的哪个位置,神经网络的前面几层应该对相同的图像区域具有类似的反应,即为“平移不变性”。二局部性指的是神经网络的前几层应该只探索输入图像中的局部区域,而不过度在意图像中相隔较远区域的关系,最终聚合这些局部特征,以整个图像级别进行预测。

        3.多层感知机的局限性: 

        首先,我们知道在MLP中输入的是二维图像X,隐藏层输出是H,X和H都是二维张量,具备空间结构。

        我们采用[X]_{i,j}[H]_{i,j}分别表示输入图像和隐藏表示中位置(i, j)处的像素。为了使每个神经元都能接受到每个像素的信息 ,我们将参数从权值矩阵(之前多层感知机中提到的W^{(1)}, W^{(2)}替换为四阶权重张量W。假设U包含偏置参数,我们可以将全连接层形式化地表示为:

[H]_{i,j} = [U]_{i,j} + \sum_{k}\sum_{l}[W]_{i,j,k,l}[X]_{k,l} \\ =[U]_{i,j}+\sum_{a}\sum_{b}[V]_{i,j,a,b}[X]_{i+a, j+b} 

        我们发现在上下两个公式中,两个四阶张量的元素之间存在着一一对应地关系。对于图像中一部分图像[H]_{i,j}的权重由整个图像的权重除以加权值[V]_{i,j,a,b} = [W]_{i,j,i+a,j+b},其中一位二维长度i, j未发生改变,而三四维的长度由局部“k, l” 通过两个通道的加权正偏移和负偏移转变为包含整个图像的“i+a, j+b”。

        3.1平移不变性在公式中的体现:

        平移不变性指的是无论检测对象在图像中的哪个位置,神经网络的前几层相同的图像区域有类似的反应。在公式中类似的反应指的是输入张量X的平移会导致隐藏表示H输出的改变,但是对于权重[V]_{a,b}和偏置[U]_{i,j},不依赖检测对象在图像中的相对位置而改变。且权重的维度由先前的四维转变为了两维,减少了计算量([V]_{i,j,a,b} = [V]_{a,b})。

        3.2局部性在公式中的体现:

        局部性指的是神经网络的前几层只探索输入图像中的局部区域,而不过度在意图像中相隔较远的区域。在上面的文章中讲到求权重的方法,就是对图像特定位置附近的权重求加权均值,一旦超出一定数值,则加权值为0。

        我们为了收集训练参数隐藏层的相关信息[H]_{i,j},不应该将离(i,j)较远的位置纳入加权均值的取样范围。意味着在|a|>\Delta或者|b|>\Delta之外,在特定区域取样的时候权重为0([V]_{a,b} = 0),因此可以将特定位置隐藏层公式写为[H]_{i,j} = u + \sum^{\Delta}_{a=-\Delta}\sum^{\Delta}_{b=-\Delta}[V]_{a,b}[X]_{i+a, j+b}

        3.3同时具备平移不变性和局部性的网络架构:

        将权重由二维变成四维,具备平移不变性(一定区域内权值一样)和局部性(超出一定区域在权值计算时数值为0)的 神经网络即卷积神经网络的一个卷积层。在深度学习研究中,将权值V陈伟卷积核或者滤波器。

        通过使用四维的权重,可以大大减少参数数量。对于一张高像素图片,之前可能需要十亿个参数,转换为四维之后只需要转化为几百个参数,大大减少了计算量。

4.卷积的定义:

        在高数中,两个函数卷积的定义为((f * g)(x) = \int f(z)g(x - z)dz。也就是说,卷积是当把一个函数“翻转”并移位x时,测量f, g之间的重叠。当对象为离散对象时,积分就变成求和。例如,对于由索引为Z,平方可和的,无限维向量集合中抽取的向量,我们得到如下定义:

(f * g)(i) = \sum_{a}f(a)g(i-a)

        对于二维张量,则为f的索引(a,b)和g的索引(i-a, j-b)上对应的加和为:

(f * g)(i,j) = \sum_{a}\sum{b}f(a,b)g(i-a, j-b)

        4.1卷积神经网络的通道: 

        一般的图像具备长,宽,色彩三个通道,对于一个像素为长宽为1024的图像,具有1024*1024*3个像素,色彩可以分为红蓝绿三种向量。X[X]_{i,j}转化成[X]_{i,j,k},卷积层的权重由[V]_{a,b}转化成[V]_{a,b,c}。由于输入图像是三维的,隐藏层的输出也用三维张量表示,对于一个空间位置我们采用一些堆叠的二维空间表示三维张量

        为了支持输入X和隐藏层H中的多个通道,我们可以在V中添加第四个坐标,即[V]_{a,b,c,d}.综上所述表示为[H]_{i,j,d}= \sum_{a=-\Delta}^{\Delta}\sum^{\Delta}_{b=-\Delta}\sum_{c}[V]_{a,b,c,d}[X]_{i+a,j=b,c},其中隐藏层表示H中的索引d表示输出通道,然后核发生转移进入下一个卷积层成为输入的[X]_{i,j,d}

二、图像卷积的实例以及代码实现:

1.卷积神经网络图示输出方法:

        准确来说,卷积神经网络的计算不是卷积计算而是互相关计算(cross-correlation),卷积层通过输入张量和核张量通过互相关计算输出产生张量。暂时忽略通道(第三维)的情况(采用灰度图像演示),对于一个3*3的二维图像。卷积层核的高度和宽度都是2*2,由隐藏层的公式[H]_{i,j} = u + \sum^{\Delta}_{a=-\Delta}\sum^{\Delta}_{b=-\Delta}[V]_{a,b}[X]_{i+a, j+b}可得,隐藏层的输入张量尺寸大小由核参数得张量尺寸大小所决定。下面演示的卷积层不具备偏置,故输出值张量元素为0*0 + 1*1 + 3*2 + 4*3 =19。

        核函数可以从左到右,从上向下对输入张量进行扫描,每划到一个新位置的时候可以计算新的输出函数。对于下面这个隐藏层可以输出2*2的张量。通过推断,我们可以得出如下公式:

(n_{k} - k_{h} + 1) * (n_{w} - k_{w} + 1)

        输入张量的长度为n_{k},核函数的长度为k_{h}。输入张量的宽度为n_{w},核函数的宽度为k_{w},(3 - 2 +1 ) * (3 - 2 +1) = 2 * 2 

 2.卷积神经网络的代码实现: 

        2.1输出层的数值填充:

         通过定义函数corr2d实现上述过程,首先导入卷积神经网络程序包torch.nn和课程专用程序包d2l。在代码中我们设置了两个参数X,K,X是输入的张量,而K是函数的大小。根据我们之前提到的输出张量大小(n_{k} - k_{h} + 1) * (n_{w} - k_{w} + 1),得到核函数的长,宽分别为2,2。使用torch.zeros()形成一个输出元素全为0的张量。

        在通过for ..range循环对输出行的长度进行读取,再利用for...range函数对输出列的长度进行读取。再对X的二维张量进行切片,对每次遍历选择输入向量的数值加和然后对Y中元素进行填充然后返回

import torch
from torch import nn
from d2l import torch as d2l

def corr2d(X, K):  #@save
    """计算二维互相关运算"""
    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 = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])
K = torch.tensor([[0.0, 1.0], [2.0, 3.0]])
corr2d(X, K)

        2.2卷积层类的定义以及卷积核权重&标量偏置的定义: 

        卷积层对输入核卷积核权重进行互相关运算,并在添加标量偏置以后输出,卷积层中两个被训练的参数是卷积核权重标量偏置。在__init__构造函数中,将weight和bias声明为两个参数模型。前向传播函数调用之前定义的corr2d()函数并添加偏置。

        首先使用class定义了一个名为Con2D的子类,继承nn.Moudle的父类。①创建初始化函数__init__()包含参数self和kernel_size,self的意思是把kernel_size这个属性变成实例化属性。②使用"super().__init__()"把nn.Module创造的方法内的属性都放在子类中,使得Conv2D继承nn.Module的属性。③通过self.weight调用父类nn.Module的属性并且使用torch.randn的函数对权重和偏置随机化。④使用forward函数对之前定义的卷积层的卷积层权重和标量偏置进行优化。

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

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

        2.3图像中的目标边缘检测: 

        首先我们定义一个像素为6*8的黑白图像,然后再创造一个高度为1,宽度为2的卷积核K。当进行互相关计算式,如果水平相邻的两元素相同,则输出为零,否则输出为非零。为了实现这个功能我们设置K中张量的数值一个为-1,一个为1,如果相同则输出的张量对应单元格数值为0,反之为1或-1。

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

K = tensor([[1.0, -1.0]])
Y = Corr2d(X, K)
Y

#得到输出张量:
tensor([[ 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.,  0., -1.,  0.,  0.]])

        将输入图像使用.t()函数进行转置, 看输出的张量,可以看到在检测垂直边缘的时候第二列的数值都为1,而水平边缘的时候所有数值都是0,无法检测水平边缘

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

K = tensor([[1.0, -1.0]])
Y = Corr2d(X.t(), K)
Y

#输出张量:
tensor([[0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0.],
        [0., 0., 0., 0., 0.]])

         2.4卷积核的学习方法:

参考文献:Layer weight vs. weight.data - PyTorch Forums

pytorch中.weight.data与.weight的区别-CSDN博客

        对于在黑白图像中(仅有1,0两个数值)中使用检测器torch.tensor([[1, -1]])即可1,但是在面对更复杂的卷积层时,不能手动设计kernel,我们可以通过“输入-输出”来学习由X到Y的卷积核。我们先构造一个卷积层,初始化卷积核为随机张量。解析来,在每次迭代中,我们比较Y与卷积层输出的方差,然后计算梯度来更新卷积核。为了简单起见,我们使用二维的卷积层并忽略偏置。

        首先让我们看一下代码中的第一行,直接调用torch.nn中的nn.Conv2d构造一个卷积层,由于使用的是上面用于检测的黑白图像,故输入输出的channel都是1,设定的卷积核的张良大小是(1, 2),设置的偏置为0。

        再对X,Y的张量由原先的(6, 8)和(6, 7)(只包含张量的大小)改变为(1,1,6,8),(1,1,6,7)(包含了灰度输出的channel和单层)。学习率设置为3e -2。

        下来使用一个遍历函数,通过缩小预测值和输出值之间的方差大小进行反向传播,使用“l.sum().backward()”对反向传播过程中的损失函数进行加和(目前搭建的神经网络只有一层)。更具方差的大小不断调整conv2d.weight.data大小直至loss稳定。

        最后使用了一个if语句决定输出的内容,使用“(i + 1) % 2”输出偶数epoch的损失函数,由于if判断的逻辑而非数值运算,采用“==”,如果不是偶数隐式返回False。通过对权重的不断修正,可以得到一个损失函数为0.022的权重张量,这时候使用conv2d.weight.data()调用训练好的权重,可以得到

#nn.Conv2d中第一,二个参数是四维张量的输入通道数和输出通道数
conv2d = nn.Conv2d(1,1, kernel_size=(1, 2), bias=False)

#对X和Y添加两个分别为一维的维度
X = X.reshape(1,1,6,8)
Y = Y.reshape(1,1,6,7)
lr = 3e-2

for i in range(10):
    Y_hat = conv2d(X)
    l = (Y_hat - Y) ** 2
    conv2d.zero_grad()
    l.sum().backward()
    #迭代卷积核
    conv2d.weight.data[:] -= lr * conv2d.weight.data
    if (i + 1) % 2 == 0:
        print(f'epoch {i+1}, loss {l.sum():.3f}')


#得到输出结果
epoch 2, loss 6.422
epoch 4, loss 1.225
epoch 6, loss 0.266
epoch 8, loss 0.070
epoch 10, loss 0.022


conv2d.weight.data.reshape((1, 2))


#输出的结果:
tensor([[ 1.0010, -0.9739]])

三、卷积神经网络的填充和步幅:

        1.卷积神经网络的填充:

       1.1理论讲解:

        对于一个卷积神经网络,输出张量的形状大小取决于输入张量的形状大小和卷积核的形状大小。由于卷积核的形状大小大于等于1*1,所以在卷积神经网络向前传播的时候,会导致输出的张量形状大小逐渐减小

        对于一个240*240像素的输入图像,经过10层5*5像素的卷积核之后,输出的张量大小为200*200像素大小。通过前向传播,原始图像的边界丢失了许多有用的信息,但是我们可以通过“填充”来解决大幅降低图像h和w的问题。但如果图像边界存在许多冗余的信息,可以调整步长更快的去除边界冗余的信息。

        填充通常在输入张量的周围填充(h+1, w+1)的元素0,在下面的图像中,我们将原先输入的3*3张量填充为5*5张量,在2*2的卷积核下输出4*4的张量。在卷积神经网络中,我们通常设置卷积核的宽度和高度为奇数,保持空间维度的同时,我们可以在顶部和底部填充相同数量的行。

        同时使用奇数的卷积核大小和填充大小也提供了书写上的便利,对于任意的二维数组,当满足①卷积核的大小是奇数。②所有边的填充行数和列数相同。③输入和输出具有相同的高度和宽度。则可以得出结论:输出Y[i, j]是通过以输入X[i, j]为中心,与卷积核进行互计算得到的。

       1.2代码实操: 

        通过示例,大家可以更加明白上述三条规则。我们创建一个高度和宽度为3的二维卷积层,并在所有侧边填充1个像素。给定高度和宽度为8的输入,则输出的高度和宽度也是8。根据上述输出张量大小的计算公式,我们知道卷积核的高度和宽度应该都是3。

        之前我们定义过一个conv2d的函数,通过conv2d的函数可以对核函数进行优化,这里我们定义一个名为“comp_con2d”的函数,具有函数conv2d和X两种参数。使用reshape()函数对X的批量大小和通道数赋值为1和1,调用conv2d函数对核函数优化,最终返回Y前两个维度。注意,这里调用的是torch.nn中的nn.Conv2d函数,而不是2.4中定义的函数。

         下面我们调用nn.Conv2d函数对输入张量的行列进行填充,由于对每个边界;都填充了一个向量,填充后的张量h和w都多了两行和两列,所以卷积核的h和w应该都是3。创建了一个X的填充随机数的张量,最终使用定义的comp_conv2d返回张量,用.shape()输出张量的大小。

import torch
from torch import nn


def comp_conv2d(conv2d, X):
    X = X.reshape((1,1) + X.shape)
    Y = conv2d(X)
    
    return Y.reshape(Y.shape[2:])


conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1)
X = torch.randn(size=(8, 8))
comp_conv2d(conv2d, X).shape
#输出
torch.Size([8, 8])

        2.卷积神经网络的步幅: 

        2.1理论讲解:

        在计算互相关的时候,卷积窗口从输入张量的左上角开始,向下、向右滑动。再前面的例子中,我们默认每一次滑动一个元素。但是,有时候为了高效计算或者缩减采样次数卷积窗口可以跳过中间位置,每次滑动多个元素

         下面是一个垂直步幅为3,水平步幅为2的二维互相关运算。我们可以看到为了输出第一行第二个元素和第一列第三个元素,卷积核分别向右移动了两列和三行。当垂直步幅为S_{h}水平步幅为S_{w}的时候,输出形状为[(n_{k} - k_{h} + p_{h} + S_{h}) / S_{h}] * [(n_{w} - k_{w} + p_{w} +S_{w}) / S_{w}]公式中的除法是为了能得到整数w,h的输出张量。 

         2.2代码实现:

        首先我们使用Conv2d创造一个二维卷积核,输入和输出的通道数都是1,卷积核的张量宽高为3,周围各填充1个张量,垂直步长和水平步长都是2。再使用comp_cnv2d返回一个只包含输出张量后两维的张量。可以得到一个宽和高都为4的张量。

        我们用上面推导出的公式验证[(n_{k} - k_{h} + p_{h} + S_{h}) / S_{h}] * [(n_{w} - k_{w} + p_{w} +S_{w}) / S_{w}],[(8 - 3 + 2 + 2) / 2 ] * [(8 - 3 + 2 + 2) / 2 ] = 4.5 * 4.5,取小不取大,故 4*4。

import torch
from torch import nn


def comp_conv2d(conv2d, X):
    X = X.reshape((1,1) + X.shape)
    Y = conv2d(X)
    
    return Y.reshape(Y.shape[2:])



X = torch.randn(size=(8, 8))
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1, stride=2)
comp_conv2d(conv2d, X).shape

四、多输入&多输出通道:

         1.多输入通道:

           1.1多输入通道的理论基础:

            当输入包含多个通道的时候,需要构造一个与输入数据具有相同输入通道数的卷积核,便于与输入数据进行互相关运算。对于一个多通道的卷积核,需要对每一个通道设定一个特有的卷积核,将这些卷积核集合在一起就形成了多通道的卷积核(k_{h } * k_{w}c_{i} * k_{h} * k_{w}。 

        我们首先计算一下输出张量的大小,(3 - 2 + 1)/1 *(3 - 2 + 1)/1得到2*2的张量。注意,对于输出张量,每个单元格的数值是两个同一步的核函数与输入张量的乘积。(1*1 + 2*2 +  4*3 + 5*4) + (0 *0 + 1*1 + 3*2 +4*3) = 56。

        1.2多输入通道的代码实现: 

        首先导入包torch,从d2l包中导出工具torch并以d2l作为该工具的简称。再定义函数corr2d_multi_in用于对输入张量和核函数的张量进行遍历,利用corr2d()以通道数为2,核函数为2,核函数大小为2*2,填充和步长默认为1得到输出张量。 

import torch
from d2l import torch as d2l

def corr2d_multi_in(X, K):
    return sum(d2l.corr2d(x, k) for x, k in zip(X, K))

X = torch.tensor([[[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]],
               [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]])
K = torch.tensor([[[0.0, 1.0], [2.0, 3.0]], [[1.0, 2.0], [3.0, 4.0]]])

corr2d_multi_in(X, K)

#输出张量
tensor([[ 56.,  72.],
        [104., 120.]])

        2.多输出通道: 

        2.1多输出通道的理论基础:

        在最流行的神经网络架构中,随着神经网络层数的加深,通常会增加输出通道的维数,通过减少空间分辨率以实现更大的通道深度。直观的说,每个输出通道可以视为对不同特征的响应。而现实情况更加复杂一点,每个通道不是独立学习的,而是为了共同使用而优化的。因此,多通道并不是多个单通道的检测器。

        c_{i}c_{o}分别表示输入和输出通道的数目,并以k_{h}k_{w}为卷积核的高度和宽度。为了获得多个通道的输出每个通道创造c_{i} * k_{h} * k_{w}的卷积核张量,对于全部的卷积核进行汇总,可以得到c_{o} * c_{i} * k_{h} * k_{w},下面我们用代码实现一个多输出通道的互相关函数。

        首先使用torch.stack在一个新的维度上进行连接,需要所有需要在同一个维度上连接的张量高度和宽度应该相同。torch.stack(tensorsdim=0*out=None) → Tensor,其中参数tensors是需要连接的张量,而维度的范围是0到连接张量的维度最大值

        这里定义的函数corr2d_multi_in_out有两个参数X和K,其中torch.stack中套用上述的corr2d_multi_in函数用于遍历每个通道的X,K求解出输出张量Y,再使用torch.stack在第0维(行)上对多个通道的输出张量进行叠加,得到一个多输出通道叠加的张量

        定义好函数之后,我们将核张量K与K+1(K中的每个元素加1)和K+2连接起来,构造一个具有3个输出通道的卷积核。

import torch
from d2l import torch as d2l

def corr2d_multi_in(X, K):
    return sum(d2l.corr2d(x, k) for x, k in zip(X, K))

X = torch.tensor([[[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]],
               [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]])
K = torch.tensor([[[0.0, 1.0], [2.0, 3.0]], [[1.0, 2.0], [3.0, 4.0]]])

corr2d_multi_in(X, K)


def corr2d_multi_in_out(X, K):
    return torch.stack([corr2d_multi_in(X, k) for k in K], 0)
K = torch.stack((K, K+1, K+2), 0)
K.shape


corr2d_multi_in_out(X, K)
#输出结果
tensor([[[ 56.,  72.],
         [104., 120.]],

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

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

五、汇聚层的作用以及代码实现方法:

        1.汇聚层的应用场景:

        通常在处理图像时,我们希望逐渐降低隐藏表示的空间分辨率、聚集信息,这样随着我们在神经网络中层叠上升,每个神经元对其敏感的感受输入就更大。 而我们的机器学习任务通常和全局图像有关,所以我们最后一层的神经元应该对整个输入的全局敏感。通过逐渐聚合信息,生成越来越粗糙的映射,最终实现学习全局表示的目标

        当检测较底层的特征时(边缘特征),我们通常希望这些特征保持某种程度上的平移不变性。我们拍摄黑白之间轮廓清晰的图像X,并将整个图像向右移动一个像素,即Z[i, j] = X[i, j+1],则新图像Z的输出图像由于局部性过于小(|a| < \Delta , |b| < \Delta)而导致输出结果可能大不相同,无法满足相对不变性。对于边缘特征的这个问题,我们采用池化层(也称汇聚层)降低卷积层对位置的敏感性(局部性过小)且降低对空间降采样的敏感性(平移不变性忽略输入通道数和输入层数)

        2.最大汇聚层和平均汇聚层:

         2.1 汇聚层的分类方法:

        于卷积层类似,汇聚层运算符由一个固定形状的窗口组成,该窗口根据步幅大小在输入的所有区域滑动,为汇聚窗口遍历的每个位置计算一个输出。然而,不同于卷积层中输入和卷积核之间的互相关计算,汇聚层不包含参数。汇聚层根据输出汇聚窗口的所有元素的最大值或者平均值,被称为最大汇聚层平均汇聚层

        下述采用了一个最大汇聚层,将输入张量的作为输出值填充在输出张量中。

        2.2 汇聚层的代码实现:

        在下面的代码实现中,我们将使用卷积层的输出作为2*2的最大汇聚层输入。设置卷积层的输出为X,汇聚层输出为Y。无论X[i, j]X[i, j+1]的数值(包含X[i+1, j]X[i+1, j+1])相同与否,汇聚层输出值Y[i, j]的输出值相同。在下面的代码中pool2d函数,我们实现汇聚层的前向传播。

        在函数pool2d的函数中,我们设置了三个参数X,pool_size和一个命名"max"的参数变量mode(用于)。将pool_size这个形参赋值给p_h和p_w,再使用torch.zeros()初始化一个输出张量。再嵌套了两个for ...in...的遍历嵌套,首先对输出的Y张量的行进行遍历,再对Y张量的列进行遍历。如果我们在后面调用pool2d函数时参数设置为max则对输入层的采样元素取的最大值,如果参数设置输入为avg,则求输入层采样元素的平均值。

        我们构建一个输入张量X,验证最大汇聚层的输出。注意pool2d函数中的if和elif两个条件,在默认条件下,采用的是池化层取最小值。如果我们使用函数pool2d()中mode设置为“avg”,则池化层取输入张量的均值。

import torch
from torch import nn
from d2l import torch as d2l

def pool2d(X, pool_size, mode='max'):
    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.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])
pool2d(X, (2, 2))

#输出结果
tensor([[4., 5.],
        [7., 8.]])

pool2d(X, (2, 2), 'avg')

#输出结果
tensor([[2., 3.],
        [5., 6.]])

        3.填充和步幅: 

        与卷积层一样,汇聚层(池化层pooling)也可以改变输出形状。和以前一样,我们可以通过填充和步幅以获得所需的输出形状。 下面,我们用深度学习框架中内置的二维最大汇聚层,来掩饰汇聚层中填充和步幅的使用。首先我们使用torch.arange()创建一个数列,再使用参数dtype设置数列的数据形式为float.32,最后采用.reshape()将这个数列转变为一个四行四列的张量。

        在默认情况下,深度学习框架中的步幅与池化层窗口的大小相同。因此,如果我们使用形状为(3, 3)的汇聚窗口,那么在默认条件下,我们得到的步幅形状为(3, 3)。这里pool2d直接调用torch.nn中的MaxPool2d函数设置对输入张量的填充,步长以及池化层的张量大小,这里我们设置池化层张量大小为3,填充为1,步长为2。

X = torch.arange(16, dtype = torch.float32).reshape((1,1,4,4))
X

#输出结果
tensor([[[[ 0.,  1.,  2.,  3.],
          [ 4.,  5.,  6.,  7.],
          [ 8.,  9., 10., 11.],
          [12., 13., 14., 15.]]]])

pool2d = nn.MaxPool2d(3, padding=1, stride=2)
pool2d(X)

#输出张量
tensor([[[[ 5.,  7.],
          [13., 15.]]]])

 

  • 9
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值