面向对象编程的方式搭建CNN网络 | PyTorch系列(十三)

2020-05-29 14:44:37 浏览数 (1)

文 |AI_study

从我们深度学习项目的高层视角或概括的角度来看,我们准备了数据,现在,我们准备构建我们的模型。

  • 准备数据
  • 构建模型
  • 训练模型
  • 分析模型的结果

说到模型,我们指的是我们的网络。“模型”和“网络”是同一个意思。我们希望我们的网络最终做的是建模或近似一个将图像输入映射到正确输出类的函数。


一、前提

为了在PyTorch中构建神经网络,我们扩展了PyTorch类 torch.nn.Module。这意味着我们需要在Python中利用一点面向对象编程(OOP)。

在这篇文章中,我们将快速回顾一下使用PyTorch神经网络所需的细节,但是如果您发现还需要更多,Python文档中有一个概述教程。

https://docs.python.org/3/tutorial/classes.html

要构建卷积神经网络,我们需要对CNN的工作原理以及用于构建CNN的组件有一个大致的了解。 这个深度学习基础知识系列是该系列的一个很好的先决条件,因此,我强烈建议您(如果尚未)涵盖该系列。 如果您只想使用CNN速成班,可以查看以下特定文章:

  • 卷积神经网络(CNN)的解释
  • 可视化来自CNN的卷积滤波器
  • 卷积神经网络中的零填充解释
  • 卷积神经网络中的最大池解释
  • 卷积神经网络(CNN)中的可学习参数解释

现在让我们快速进行面向对象的编程回顾。

https://deeplizard.com/learn/video/YRhxdVk_sIs https://deeplizard.com/learn/video/cNBBNAxC8l4 https://deeplizard.com/learn/video/qSTv_m-KFk0 https://deeplizard.com/learn/video/ZjM_XQa5s6s https://deeplizard.com/learn/video/gmBfb6LNnZs

二、快速回顾面向对象编程

当我们编写程序或构建软件时,有两个关键组件:代码和数据。有了面向对象编程,我们就可以围绕对象来确定程序设计和结构的方向。

使用类在代码中定义对象。类定义了对象的规范,它指定了类的每个对象应该具有的数据和代码。

当我们创建一个类的对象时,我们称这个对象为类的一个实例,并且一个给定类的所有实例都有两个核心组件:

  • Methods(代码)
  • Attributes(数据)

方法表示代码,而属性表示数据,因此方法和属性是由类定义的。

在一个给定的程序中,有许多对象。给定类的一个实例可以同时存在,所有实例都具有相同的可用属性和相同的可用方法。从这个角度来看,它们是一致的。

相同类的对象之间的区别在于每个属性的对象中包含的值。每个对象都有自己的属性值。这些值决定了对象的内部状态。每个对象的代码和数据都被封装在对象中。

让我们构建一个简单的Lizard 类来演示类如何封装数据和代码:

代码语言:javascript复制
class Lizard: #class declaration
    def __init__(self, name): #class constructor (code)
        self.name = name #attribute (data)
    
    def set_name(self, name): #method declaration (code)
        self.name = name #method implementation (code)

第一行声明类并指定类名,在本例中是Lizard。

第二行定义了一个称为类构造函数的特殊方法。在创建类的新实例时调用类构造函数。作为参数,我们有self和name。

self参数使我们能够创建存储或封装在对象中的属性值。当我们调用这个构造函数或任何其他方法时,我们不会传递self参数。Python自动为我们做这些。

任何其他参数的参数值都是由调用者任意传递的,这些传入方法的传递值可以在计算中使用,也可以在以后使用self保存和访问。

完成构造函数之后,我们可以创建任意数量的专用方法,比如这里的这个方法,它允许调用者更改存储在self中的name值。我们在这里所要做的就是调用该方法并为名称传递一个新值。让我们看看它是如何运作的。

代码语言:javascript复制
> lizard = Lizard('deep')
> print(lizard.name)
deep

> lizard.set_name('lizard')
> print(lizard.name)
lizard

我们通过指定类名并传递构造函数参数来创建类的对象实例。构造函数将接收这些参数,构造函数代码将运行并保存传递的名称。

然后,我们可以访问名称并打印它,还可以调用set_name()方法来更改名称。一个程序中可以存在多个这样的Lizard 实例,每个实例都包含自己的数据。

从面向对象的角度来看,这种设置的重要部分是将属性和方法组合起来并包含在对象中。

现在让我们转换一下,看看面向对象编程如何适合PyTorch。

PyTorch 的torch .nn 包

为了在PyTorch中构建神经网络,我们使用了torch.nn包,这是PyTorch的神经网络(nn)库。我们通常是这样导入包的:

代码语言:javascript复制
import torch.nn as nn

这允许我们使用nn别名访问神经网络包。所以从现在开始,如果我们说nn,就是指torch.nn 。PyTorch的神经网络库包含构建神经网络所需的所有典型组件。

构建神经网络所需的主要组件是一个层,因此,正如我们所期望的那样,PyTorch的神经网络库包含一些类,可以帮助我们构建层。

PyTorch的nn.Module类

众所周知,深层神经网络是由多层结构构成的。这就是网络 深 的原因。神经网络中的每一层都有两个主要的组成部分:

  • 转换(代码)
  • 一组权重(数据)

与生活中的许多事情一样,这一事实使得层成为使用OOP表示对象的最佳候选对象。OOP是面向对象编程的简称。

实际上,PyTorch就是这种情况。在神经网络包中,有一个类叫做Module,它是所有神经网络模块的基类,包括层。

这意味着PyTorch中的所有层都扩展了nn.Module类,并继承了PyTorch在nn.Module 中的所有内置功能。在面向对象编程中,这个概念被称为继承。

甚至神经网络也会扩展nn.Module的类。这是有道理的,因为神经网络本身可以被认为是一个大的层(如果需要,让它随着时间的推移而下沉)。

PyTorch中的神经网络和层扩展了nn.Module类。这意味着在PyTorch中构建新层或神经网络时,我们必须扩展nn.Module类。

PyTorch的nn.Modules 有一个forward()方法

当我们把一个张量作为输入传递给网络时,张量通过每一层变换向前流动,直到张量到达输出层。这个张量通过网络向前流动的过程被称为前向传递。

The tensor input is passed forward though the network.

PyTorch的nn.functional包

当我们实现nn.Module子类的forward() 方法时,通常将使用nn.functional包中的函数。 该软件包为我们提供了许多可用于构建层的神经网络操作。 实际上,许多nn.Module层类都使用nn.functional函数来执行其操作。

nn.functional软件包中包含nn.Module子类用于实现其forward() 函数的方法。 稍后,通过查看nn.Conv2d卷积层类的PyTorch源代码,来观察一个示例。

在PyTorch中建立神经网络

现在,我们有足够的信息来提供在PyTorch中构建神经网络的概述。 步骤如下:

精简版:

  1. 扩展nn.Module基类。
  2. 将层定义为类属性。
  3. 实现forward() 方法。

更详细的版本:

  1. 创建一个扩展nn.Module基类的神经网络类。
  2. 在类构造函数中,使用torch.nn中的预构建层将网络的图层定义为类属性。
  3. 使用网络的层属性以及nn.functional API中的操作来定义网络的前向传播。

(1)扩展PyTorch的nn.Module类

就像我们在lizard 类示例中所做的一样,让我们创建一个简单的类来表示神经网络。

代码语言:javascript复制
class Network:
    def __init__(self):
        self.layer = None

    def forward(self, t):
        t = self.layer(t)
        return t

这为我们提供了一个简单的网络类,该类在构造函数内部具有单个虚拟层,并且对forward函数具有虚拟实现。

forward() 函数的实现采用张量t 并使用虚拟层对其进行转换。 张量转换后,将返回新的张量。

这是一个好的开始,但是该类尚未扩展nn.Module类。 为了使我们的Network类扩展nn.Module,我们必须做另外两件事:

  1. 在第1行的括号中指定nn.Module类。
  2. 在构造函数内部的第3行上插入对super 类构造函数的调用。

这给了我们:

代码语言:javascript复制
class Network(nn.Module): # line 1
    def __init__(self):
        super().__init__() # line 3
        self.layer = None

    def forward(self, t):
        t = self.layer(t)
        return t

这些更改将我们的简单神经网络转换为PyTorch神经网络,因为我们现在正在扩展PyTorch的nn.Module基类。

这样,我们就完成了! 现在我们有了一个Network类,它具有PyTorch nn.Module类的所有功能。

(2)将网络的层定义为类属性

目前,我们的Network类具有单个虚拟层作为属性。 现在,让我们用PyTorch的nn库中为我们预先构建的一些真实层替换它。 我们正在构建CNN,因此我们将使用的两种类型的层是线性层和卷积层。

代码语言:javascript复制
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

好的。 至此,我们有了一个名为Network的Python类,该类扩展了PyTorch的nn.Module类。 在Network类内部,我们有五个定义为属性的层。 我们有两个卷积层,self.conv1和self.conv2,以及三个线性层,self.fc1,self.fc2,self.out。

我们在fc1和fc2中使用了缩写fc,因为线性层也称为完全连接层。 它们也有一个我们可能会听到的叫做 “dense” 的名字。 因此,linear, dense, 和 fully connected 都是指同一类型的层的所有方法。 PyTorch使用线性这个词,因此使用nn.Linear类名。

我们将名称out用作最后一个线性层,因为网络中的最后一层是输出层。

总结

现在,我们应该对如何使用torch.nn库开始在PyTorch中构建神经网络有一个好主意。 在下一篇文章中,我们将研究层的不同类型的参数,并了解如何选择它们。 下一个见。

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

英文原文链接是:

https://deeplizard.com/learn/video/k4jY9L8H89U

0 人点赞