在PyTorch框架中定义一个识别手写数字的神经网络(Defining a Neural Network in PyTorch)【个人练习】

写在前面

  本文可以看作在学习PyTorch官网的相关教程的同事将英文教程“翻译”成本文,仅供学习笔记和交流沟通之用。

有兴趣的同学或者对本博客感到不解渴的大佬可自行移步至Defining a Neural Network in PyTorch直接学习英文原版,谢谢~

  深度学习可以使用人工神经网络,它是一个由许处于不同层的、层间可以发生相互作用的若干个节点构成的计算系统。通过将数据传入这些节点,神经网络可以学习如何去接近人们想要的,能够实现理想的“输入→输出”的计算过程。在PyTorch中,可以通过torch.nn库来构建神经网络。

引言

  PyTorch可以提供“优雅地”设计而成的模块(modules)和类(classes),其中就有可以帮助你创建和训练神经网络的torch.nn模块。该模块中包含一系列叫层(layers)的方法,能够将输入转化成输出返回。
  在本宝典\(\color{red}{^{\dagger }}\)中,我们将会使用torch.nn来为手写数字识别量身定做一个神经网络,会使用到MNIST dataset。

\(\color{red}{^{\dagger }}\)在英文教程中,本文的所有内容是在一个叫"PyTorch Recipe"目录下的,因此我把recipe翻译成宝典、秘籍。

准备工作

  首先如果你没有torch则需要安装:

pip install torch

  当然你也可以使用Anaconda Prompt的命令行安装适合你电脑配置和环境的PyTorch,官网传送门:PyTorch -- START LOCALLY
PyTorch -- START LOCALLY

  Anaconda基础简介,可参考:Anaconda介绍、安装及使用教程 contributed by 豆先生
  关于Anaconda重装,可参考:春节期间重装anaconda+opencv血泪史分享
  关于CUDA和cuDNN配置(如果你打算用GPU),可参考:重装CUDA和cuDNN(目的是装TensorFlow和pytorch)【个人梳理总结】

对工作进行分块

  完成准备工作后,可以开始我们的代码部分编写,其大致可分为4个有序分块任务:

  1. Import所有需要用到的库
  2. 定义并初始化神经网络
  3. 规定数据在神经网络传输过程中的传递方式和计算规则等
  4. 【可选】传入数据测试

一、Import必需的库

  对于本宝典而言,需要导入torch下的torch.nntorch.nn.functional

import torch
import torch.nn as nn
import torch.nn.functional as F

二、定义并初始化神经网络

  神经网络要去识别图片,因此我们将应用一个叫做“卷积”的操作。卷积操作会用一个核(kernel)——也可以看成一个矩阵——将输入数据的一个元素与其周围元素进行加权求和,这个过程会帮助我们提取特征(例如边缘检测、锐化、平滑等)。
  欲自定义Net类需满足两个要求:

  1. 首先需要写一个__init__()函数继承nn.Module模块的属性,在该函数内定义神经网络的的全连接层
  2. 在使用卷积操作的情况下,我们定义的模型可以接受1个输入通道的图像,然后定义输出匹配数据的10类标签(代表数字0~9)。
class Net(nn.Module):
    """
    自定义神经网络,实现手写数字识别。
    """

    def __init__(self):
      super(Net, self).__init__()  # 如果使用Python3,则可以不需要写参数,即super().__init__()来继承父类的属性

      # 首先是一个2D的卷积层,接受单通道的图像若干个输入,输出32个通道特征,卷积核大小是3×3,步长1(步长为1是nn.Conv2d的默认参数值,对本例而言可以省略设置)
      self.conv1 = nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, stride=1)
      # 第二层依然是一个2D卷积层,接收上一层的32个通道的输出为输入,再输出64个通道特征
      self.conv2 = nn.Conv2d(32, 64, 3, 1)

      # 设置dropout,使某次训练屏蔽部分节点(但权值保留不变)
      self.dropout1 = nn.Dropout2d(0.25)
      self.dropout2 = nn.Dropout2d(0.5)

      # 第一个全连接层
      self.fc1 = nn.Linear(in_features=9216, out_features=128)
      # 第二个全连接层作为输出层,输出节点数为10
      self.fc2 = nn.Linear(128, 10)

my_nn = Net()  # 实例化
print(my_nn)

  关于Python中super().init()参数的文章:python中super().init() contributed by BeanInJ
  关于Dropout的扩展阅读:
Deep learning:四十一(Dropout简单理解) contributed by tornadomeet
Dropout原理解析 contributed by 赏月斋
  另外,如果你运行上述代码也遇到 【WinError 1455】 问题:
WinError 1455
请参考:多种方法彻底解决pycharm中: OSError: [WinError 1455] 页面文件太小,无法完成操作 的问题 contributed by 孤柒「一起学计算机」

  正常运行结果应该是:
image

  我们完成了神经网络的定义,下面我们继续规定数据在神经网络内部传递的规则。

三、规定数据传输方式和计算规则

  当我们使用PyTorch建立模型时,只需要定义前馈函数(forward function)。其作用是将数据传递到计算图(computation graph)中,这里指传递入神经网络中。我们即将展示自己定义的前馈函数算法。
  在前馈函数中,我们可以使用任意的张量操作(Tensor operations)

class Net(nn.Module):
    --snip--

    def forward(self, x):
        """
        定义前馈函数:规定数据在神经网络内部传递的规则
        :param x: 输入数据
        :return: output输出
        """

        # 将输入数据传入conv1层,即__init__()定义的2D卷积层
        x = self.conv1(x)
        # 对传入数据在各节点上使用线性修正单元函数(rectified-linear activation function)
        x = F.relu(x)

        # 第二层重复同样的规则
        x = self.conv2(x)
        x = F.relu(x)

        # 使用最大值池化(max pooling)
        x = F.max_pool2d(x, 2)
        # 将数据传递至dropout1函数中
        x = self.dropout1(x)
        # 将数据的第2维之后的所有维度“扁平化”成维一维,因此参数start_dim=1
        x = torch.flatten(x, 1)
        # 将数据传入全链接层fc1并继续在后续层顺序传递
        x = self.fc1(x)
        x = F.relu(x)
        x = self.dropout2(x)
        x = self.fc2(x)

        # 对每行(dim=1)应用logsoftmax函数
        output = F.log_softmax(x, dim=1)
        return output

  前馈函数的扩展阅读:官网定义
PyTorch之前向传播函数forward
  关于线性修正单元激活函数(ReLu)拓展阅读:
ReLu(Rectified Linear Units)激活函数 contributed by Physcal
Rectified Linear Units, 线性修正单元激活函数 contributed by JohnieLi
  关于torch.flatten()函数:
Docs--TORCH.FLATTEN
torch flatten 的理解 contributed by Linky1990
  对于logsoftmax函数:
Docs--LOGSOFTMAX(F.log_softmax方法也指向它)
为什么LogSoftmax比Softmax更好? contributed by coder1479

四、【可选】传入数据测试

  为了确认神经网络会像我们预想的那样输出结果,我们可以向神经网络输入一些测试数据来验证一下模型。

# 相当于一个28x28单通道图片
random_data = torch.rand((1, 1, 28, 28))

my_nn = Net()
result = my_nn(random_data)
print (result)

  OK,完成~!

posted @ 2022-06-13 20:20  姜子牙会更好  阅读(177)  评论(0)    收藏  举报