在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

Anaconda基础简介,可参考:Anaconda介绍、安装及使用教程 contributed by 豆先生
关于Anaconda重装,可参考:春节期间重装anaconda+opencv血泪史分享
关于CUDA和cuDNN配置(如果你打算用GPU),可参考:重装CUDA和cuDNN(目的是装TensorFlow和pytorch)【个人梳理总结】
对工作进行分块
完成准备工作后,可以开始我们的代码部分编写,其大致可分为4个有序分块任务:
- Import所有需要用到的库
- 定义并初始化神经网络
- 规定数据在神经网络传输过程中的传递方式和计算规则等
- 【可选】传入数据测试
一、Import必需的库
对于本宝典而言,需要导入torch下的torch.nn和torch.nn.functional
import torch
import torch.nn as nn
import torch.nn.functional as F
二、定义并初始化神经网络
神经网络要去识别图片,因此我们将应用一个叫做“卷积”的操作。卷积操作会用一个核(kernel)——也可以看成一个矩阵——将输入数据的一个元素与其周围元素进行加权求和,这个过程会帮助我们提取特征(例如边缘检测、锐化、平滑等)。
欲自定义Net类需满足两个要求:
- 首先需要写一个__init__()函数继承nn.Module模块的属性,在该函数内定义神经网络的的全连接层
- 在使用卷积操作的情况下,我们定义的模型可以接受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】 问题:
请参考:多种方法彻底解决pycharm中: OSError: [WinError 1455] 页面文件太小,无法完成操作 的问题 contributed by 孤柒「一起学计算机」
正常运行结果应该是:

我们完成了神经网络的定义,下面我们继续规定数据在神经网络内部传递的规则。
三、规定数据传输方式和计算规则
当我们使用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,完成~!

![[WinError 1455] WinError 1455](https://img2022.cnblogs.com/blog/2745075/202206/2745075-20220612205007315-67468452.png)

浙公网安备 33010602011771号