PyTorch中CNN的Forward方法 | PyTorch系列(十七)

点击上方AI算法与图像处理”,选择加"星标"或“置顶”

重磅干货,第一时间送达

文 |AI_study

原标题:CNN Forward Method - PyTorch Deep Learning Implementation

神经网络程序设计系列(综述)

到目前为止,在这个系列中,我们已经准备好了我们的数据,现在构建我们的模型。

我们通过扩展nn.Module PyTorch基类来创建网络,然后在类构造函数中将网络层定义为类属性。现在,我们需要实现网络的 forward() 方法,最后,我们将准备训练我们的模型。

  • 准备数据

  • 构建模型

    • 创建一个扩展nn.Module基类的神经网络类。

    • 在类构造函数中,将网络层定义为类属性。

    • 使用网络的层属性以及nn.functional API操作来定义网络的前向传递

  • 训练模型

  • 分析模型的结果

回顾一下网络

目前,我们知道forward()方法接受张量作为输入,然后返回张量作为输出。现在,返回的张量与传递的张量相同。

但是,在构建实现之后,返回的张量将是网络的输出。

这意味着forward 方法实现将使用我们在构造函数内部定义的所有层。这样,前向方法显式定义了网络的转换。

forward()方法是实际的网络转换。forward 方法是将输入张量映射到预测输出张量的映射。让我们看看这是如何完成的。

回想一下,在网络的构造函数中,我们可以看到定义了五层。

class Network(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5)
        self.conv2 = nn.Conv2d(in_channels=6, out_channels=12, kernel_size=5)
        
        self.fc1 = nn.Linear(in_features=12 * 4 * 4, out_features=120)
        self.fc2 = nn.Linear(in_features=120, out_features=60)
        self.out = nn.Linear(in_features=60, out_features=10)
    
    def forward(self, t):
        # implement the forward pass      
        return t

我们有两个卷积层和三个Linear 层。如果算上输入层,这将为我们提供一个总共六层的网络。

实现forward 方法

让我们对此进行代码编写。我们将从输入层开始。

输入层#1

任何神经网络的输入层都由输入数据确定。例如,如果我们的输入张量包含三个元素,那么我们的网络将在其输入层中包含三个节点。

因此,我们可以将输入层视为 identity transformation 。从数学上讲,这是下面的函数

f(x)=x.

我们给任何x 作为输入,我们得到相同的结果 x  作为输出。无论我们使用的是具有三个元素的张量,还是表示具有三个通道的图像的张量,此逻辑都是相同的。输入是数据输出!

这非常琐碎,这就是使用神经网络API时通常看不到输入层的原因。输入层隐式存在。

绝对不是必需的,但是为了完整起见,我们将在forward方法中显示标识操作。

# (1) input layer
t = t

隐藏的卷积层:第2层和第3层

就执行转换而言,两个隐藏的卷积层都将非常相似。在深度学习基础知识系列中,我们在有关层的文章中解释说,不是输入或输出层的所有层都称为隐藏层,这就是为什么我们将这些卷积层称为隐藏层。

深度学习基础:https://deeplizard.com/learn/video/gZmobeGL0Yg

层的解释:https://deeplizard.com/learn/video/FK77zZxaBoI

为了执行卷积运算,我们将张量传递给第一卷积层self.conv1的forward 方法。我们已经了解了所有PyTorch神经网络模块如何具有forward() 方法,并且当我们调用nn.Module的forward() 方法时,有一种特殊的调用方法。

当要调用nn.Module实例的forward() 方法时,我们将调用实际实例,而不是直接调用forward() 方法。

代替执行此self.conv1.forward(tensor),我们执行此self.conv1(tensor)。确保您看到了本系列的上一篇文章,以了解有关此主题的所有详细信息。

让我们继续并添加实现两个卷积层所需的所有调用。

# (2) hidden conv layer
t = self.conv1(t)
t = F.relu(t)
t = F.max_pool2d(t, kernel_size=2, stride=2)


# (3) hidden conv layer
t = self.conv2(t)
t = F.relu(t)
t = F.max_pool2d(t, kernel_size=2, stride=2)

正如我们在这里看到的那样,当我们在卷积层中移动时,输入张量将发生变换。第一卷积层具有卷积运算,然后是 relu 激活运算,其输出随后传递到kernel_size = 2和stride = 2的最大池化中。

然后将第一个卷积层的输出张量 t 传递到下一个卷积层,除了我们调用self.conv2()而不是self.conv1()以外,其他卷积层均相同。

这些层中的每一个都由权重(数据)和收集操作(代码)组成。权重封装在nn.Conv2d() 类实例中。relu() 和max_pool2d() 调用只是纯运算。这些都不具有权重,这就是为什么我们直接从nn.functional API调用它们的原因。

有时,我们可能会看到称为池化层的池化操作。有时我们甚至可能听到称为激活层的激活操作。

但是,使层与操作区分开的原因在于层具有权重。由于池操作和激活功能没有权重,因此我们将它们称为操作,并将其视为已添加到层操作集合中。

例如,我们说网络中的第二层是一个卷积层,其中包含权重的集合,并执行三个操作,即卷积操作,relu激活操作和最大池化操作。

请注意,此处的规则和术语并不严格。这只是描述网络的一种方式。还有其他表达这些想法的方法。我们需要知道的主要事情是哪些操作是使用权重定义的,哪些操作不使用任何权重。

从历史上看,使用权重定义的操作就是我们所说的层。后来,其他操作被添加到mix中,例如激活功能和池化操作,这引起了术语上的一些混乱。

从数学上来说,整个网络只是函数的组合,函数的组合就是函数本身。因此,网络只是一种函数。诸如层,激活函数和权重之类的所有术语仅用于帮助描述不同的部分。

不要让这些术语混淆整个网络只是函数的组合这一事实,而我们现在正在做的就是在forward()方法中定义这种组合。

隐藏的Linear层:第4层和第5层

在将输入传递到第一个隐藏的Linear 层之前,我们必须reshape() 或展平我们的张量。每当我们将卷积层的输出作为Linear层的输入传递时,都是这种情况。

由于第四层是第一个Linear层,因此我们将reshape操作作为第四层的一部分。

# (4) hidden linear layer
t = t.reshape(-1, 12 * 4 * 4)
t = self.fc1(t)
t = F.relu(t)


# (5) hidden linear layer
t = self.fc2(t)
t = F.relu(t)

我们在CNN权重的文章中看到,reshape 操作中的数字 12 由来自前一个卷积层的输出通道数确定。

然而,4 * 4仍然是一个悬而未决的问题。让我们现在揭示答案。4 * 4实际上是12个输出通道中每个通道的高度和宽度。

我们从1 x 28 x 28输入张量开始。这样就给出了一个单一的彩色通道,即28 x 28的图像,并且在我们的张量到达第一 Linear 层时,尺寸已经改变。

通过卷积和池化操作,将高度和宽度尺寸从28 x 28减小到4 x 4。

卷积和池化操作是对高度和宽度尺寸的化简操作。我们将在下一篇文章中看到这是如何工作的,并看到用于计算这些减少量的公式。现在,让我们完成实现此forward() 方法。

张量重构后,我们将展平的张量传递给 Linear 层,并将此结果传递给relu() 激活函数。

输出层#6

我们网络的第六层也是最后一层是 Linear 层,我们称为输出层。当我们将张量传递到输出层时,结果将是预测张量。由于我们的数据具有十个预测类别,因此我们知道我们的输出张量将具有十个元素。

# (6) output layer
t = self.out(t)
#t = F.softmax(t, dim=1)

十个组件中的每个组件内的值将对应于我们每个预测类的预测值。

在网络内部,我们通常使用relu() 作为我们的非线性激活函数,但是对于输出层,每当我们尝试预测一个类别时,我们就使用softmax()。softmax函数针对每个预测类返回正概率,并且概率之和为1。

但是,在本例中,我们不会使用softmax(),因为我们将使用的损失函数F.cross_entropy()在其输入上隐式执行softmax()操作,因此我们只返回 最后的线性变换。

这意味着我们的网络将使用softmax操作进行训练,但是当训练过程完成后将网络用于推理时,无需计算额外的操作。

结论

很好!我们做到了。这就是我们在PyTorch中实现神经网络forward方法的方式。

PyTorch在__ call __()方法中运行的额外代码就是我们从不直接调用forward()方法的原因。如果我们这样做,额外的PyTorch代码将不会被执行。因此,每当我们想要调用forward()方法时,我们都会调用对象实例。这既适用于层,也适用于网络,因为它们都是PyTorch神经网络模块。

现在可以实现网络的forward()方法了。

文章中内容都是经过仔细研究的,本人水平有限,翻译无法做到完美,但是真的是费了很大功夫,希望小伙伴能动动你性感的小手,分享朋友圈或点个“在看”,支持一下我 ^_^

英文原文链接是:

https://deeplizard.com/learn/video/MasG7tZj-hw

加群交流

欢迎小伙伴加群交流,目前已有交流群的方向包括:AI学习交流群,目标检测,秋招互助,资料下载等等;加群可扫描并回复感兴趣方向即可(注明:地区+学校/企业+研究方向+暱称)

 谢谢你看到这里! ????

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章