PyTorch简介:构建神经网络以识别手写数字
作为 Write for DOnations 计划的一部分,作者选择了 Code 2040 来接受捐赠。
介绍
机器学习是计算机科学的一个领域,它在数据中发现模式。 截至 2021 年,机器学习从业者使用这些模式 检测自动驾驶汽车的车道 ; 训练 机器人手解决魔方; 或 生成可疑艺术品味的图像 。 随着机器学习模型变得更加准确和高性能,我们看到主流应用程序和产品的采用率越来越高。
深度学习是机器学习的一个子集,专注于特别复杂的模型,称为神经网络。 在稍后的高级 DigitalOcean 文章中(如构建 Atari 机器人 的教程),我们将正式定义“复杂”的含义。 神经网络是您所听说的高度准确和炒作的现代模型,其应用范围广泛。 在本教程中,您将专注于一项称为对象识别或图像分类的特定任务。 给定手写数字的图像,您的模型将预测显示哪个数字。
您将在 PyTorch 中构建、训练和评估深度神经网络,该框架由 Facebook AI Research 开发用于深度学习。 与 Tensorflow 等其他深度学习框架相比,PyTorch 是一个对初学者友好的框架,具有有助于构建过程的调试功能。 它还可以为高级用户高度定制,研究人员和从业者在 Facebook 和 Tesla 等公司使用它。 在本教程结束时,您将能够:
- 在 PyTorch 中构建、训练和评估深度神经网络
- 了解应用深度学习的风险
虽然您不需要具备实际深度学习或 PyTorch 方面的经验来学习本教程,但我们假设您熟悉机器学习术语和概念,例如训练和测试、特征和标签、优化和评估。 您可以在 机器学习简介 中了解有关这些概念的更多信息。
先决条件
要完成本教程,您将需要一个至少具有 1GB RAM 的 Python 3 本地开发环境。 您可以按照如何为Python 3安装和设置本地编程环境来配置您需要的一切。
第 1 步 - 创建您的项目并安装依赖项
让我们为此项目创建一个工作区并安装您需要的依赖项。 您将调用您的工作区 pytorch
:
mkdir ~/pytorch
导航到 pytorch
目录:
cd ~/pytorch
然后为项目创建一个新的虚拟环境:
python3 -m venv pytorch
激活您的环境:
source pytorch/bin/activate
然后安装 PyTorch。 在 macOS 上,使用以下命令安装 PyTorch:
python -m pip install torch==1.4.0 torchvision==0.5.0
在 Linux 和 Windows 上,对仅 CPU 构建使用以下命令:
pip install torch==1.4.0+cpu torchvision==0.5.0+cpu -f https://download.pytorch.org/whl/torch_stable.html pip install torchvision
安装依赖项后,您现在将构建您的第一个神经网络。
第 2 步——构建“Hello World”神经网络
在这一步中,您将构建您的第一个神经网络并对其进行训练。 您将了解 Pytorch 中的两个子库,用于神经网络操作的 torch.nn
和用于神经网络优化器的 torch.optim
。 要了解“优化器”是什么,您还将了解一种称为 梯度下降 的算法。 在本教程中,您将使用以下五个步骤来构建和训练模型:
- 构建计算图
- 设置优化器
- 设置标准
- 设置数据
- 训练模型
在本教程的第一部分中,您将构建一个带有可管理数据集的小型模型。 首先使用 nano
或您喜欢的文本编辑器创建一个新文件 step_2_helloworld.py
:
nano step_2_helloworld.py
您现在将编写一个简短的 18 行代码段来训练一个小型模型。 首先导入几个 PyTorch 实用程序:
step_2_helloworld.py
import torch import torch.nn as nn import torch.optim as optim
在这里,您将 PyTorch 库别名为几个常用的快捷方式:
torch
包含所有 PyTorch 实用程序。 但是,常规 PyTorch 代码包含一些额外的导入。 我们在这里遵循相同的约定,以便您可以在线了解 PyTorch 教程和随机代码片段。torch.nn
包含用于构建神经网络的实用程序。 这通常表示为nn
。torch.optim
包含训练实用程序。 这通常表示为optim
。
接下来,定义神经网络、训练工具和数据集:
step_2_helloworld.py
. . . net = nn.Linear(1, 1) # 1. Build a computation graph (a line!) optimizer = optim.SGD(net.parameters(), lr=0.1) # 2. Setup optimizers criterion = nn.MSELoss() # 3. Setup criterion x, target = torch.randn((1,)), torch.tensor([0.]) # 4. Setup data . . .
在这里,您定义了任何深度学习训练脚本的几个必要部分:
net = ...
定义了“神经网络”。 在这种情况下,模型是y = m * x
形式的线; 参数nn.Linear(1, 1)
是直线的斜率。 这个 模型参数nn.Linear(1, 1)
将在训练期间更新。 请注意,torch.nn
(别名为nn
)包括许多深度学习操作,例如此处使用的全连接层(nn.Linear
)和卷积层(nn.Conv2d
) .optimizer = ...
定义优化器。 这个优化器决定了神经网络将如何学习。 在编写更多代码行之后,我们将更详细地讨论优化器。 请注意,torch.optim
(别名为optim
)包括许多您可以使用的此类优化器。criterion = ...
定义了损失。 简而言之,损失定义了您的模型试图最小化的 what。 对于直线的基本模型,目标是最小化直线的预测 y 值与训练集中的实际 y 值之间的差异。 请注意,torch.nn
(别名为nn
)包括许多其他您可以使用的损失函数。x, target = ...
定义了你的“数据集”。 现在,数据集只有一个坐标——一个 x 值和一个 y 值。 在这种情况下,torch
包本身提供了 tensor 来创建一个新的张量,并提供randn
来创建一个具有随机值的张量。
最后,通过对数据集进行十次迭代来训练模型。 每次,您调整模型的参数:
step_2_helloworld.py
. . . # 5. Train the model for i in range(10): output = net(x) loss = criterion(output, target) print(round(loss.item(), 2)) net.zero_grad() loss.backward() optimizer.step()
您的总体目标是通过调整线的斜率来最小化损失。 为此,此训练代码实现了一种称为 梯度下降 的算法。 梯度下降的直觉如下:想象你正向下看一个碗。 碗上有很多点,每个点对应一个不同的参数值。 碗本身就是损失面:碗的中心——最低点——表示损失最低的最佳模型。 这是 最优 。 碗的边缘——最高点,以及碗中离你最近的部分——拥有损失最大的最差模型。
要找到具有最低损失的最佳模型:
- 使用
net = nn.Linear(1, 1)
可以初始化一个随机模型。 这相当于在碗上随机选取一个点。 - 在
for i in range(10)
循环中,您开始训练。 这相当于一步一步靠近碗的中心。 - 每一步的方向由梯度给出。 您将在此处跳过正式证明,但总而言之,负梯度指向碗中的最低点。
- 使用
optimizer = ...
中的lr=0.1
,您可以指定 步长 。 这决定了每个步骤可以有多大。
只需十步,您就可以到达碗的中心,这是可能损失最低的最佳模型。 有关梯度下降的可视化,请参阅 Distill 的 “为什么动量真的有效”, 页面顶部的第一个图。
这段代码的最后三行也很重要:
net.zero_grad
清除上一步迭代可能遗留的所有渐变。loss.backward
计算新的梯度。optimizer.step
使用这些渐变来采取措施。 请注意,您自己没有计算梯度。 这是因为 PyTorch 和其他类似的深度学习库 自动区分 。
现在你的“hello world”神经网络结束了。 保存并关闭您的文件。
仔细检查您的脚本是否匹配 step_2_helloworld.py。 然后,运行脚本:
python step_2_helloworld.py
您的脚本将输出以下内容:
Output0.33 0.19 0.11 0.07 0.04 0.02 0.01 0.01 0.0 0.0
请注意,您的损失不断减少,表明您的模型正在学习。 使用 PyTorch 时,还有另外两个实现细节需要注意:
- PyTorch 使用
torch.Tensor
保存所有数据和参数。 在这里,torch.randn
生成一个具有随机值的张量,具有提供的形状。 例如,torch.randn((1, 2))
创建一个 1x2 张量或二维行向量。 - PyTorch 支持多种优化器。 这具有
torch.optim.SGD
,也称为随机梯度下降 (SGD)。 粗略地说,这是本教程中描述的算法,您在其中采取了最佳步骤。 有更多参与的优化器在 SGD 之上添加额外的功能。 损失也很多,torch.nn.MSELoss
只是其中之一。
这样就结束了您在玩具数据集上的第一个模型。 在下一步中,您将用神经网络替换这个小模型,用常用的机器学习基准替换玩具数据集。
第三步——用手写数字训练你的神经网络
在上一节中,您构建了一个小型 PyTorch 模型。 但是,为了更好地理解 PyTorch 的好处,您现在将使用包含更多神经网络操作的 torch.nn.functional
和支持您可以使用的许多数据集的 torchvision.datasets
构建一个深度神经网络,out的盒子。 在本节中,您将使用 预制 数据集构建 相对复杂的自定义 模型。
您将使用 convolutions,它们是模式查找器。 对于图像,卷积在不同“含义”级别上寻找二维模式:直接应用于图像的卷积正在寻找“较低级别”的特征,例如边缘。 但是,应用于许多其他操作的输出的卷积可能正在寻找“更高级别”的特征,例如门。 有关可视化和更彻底的卷积演练,请参阅斯坦福深度学习课程 的 部分。
现在,您将通过定义一个稍微复杂的模型来扩展您构建的第一个 PyTorch 模型。 您的神经网络现在将包含两个卷积层和一个全连接层,用于处理图像输入。
首先使用文本编辑器创建一个新文件 step_3_mnist.py
:
nano step_3_mnist.py
您将遵循与以前相同的五步算法:
- 构建计算图
- 设置优化器
- 设置标准
- 设置数据
- 训练模型
首先,定义你的深度神经网络。 请注意,这是您可能在 MNIST 上找到的其他神经网络的精简版——这是有意为之,以便您可以在笔记本电脑上训练您的神经网络:
step_3_mnist.py
import torch import torch.nn as nn import torch.optim as optim import torch.nn.functional as F from torchvision import datasets, transforms from torch.optim.lr_scheduler import StepLR # 1. Build a computation graph class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(1, 32, 3, 1) self.conv2 = nn.Conv2d(32, 64, 3, 1) self.fc = nn.Linear(1024, 10) def forward(self, x): x = F.relu(self.conv1(x)) x = F.relu(self.conv2(x)) x = F.max_pool2d(x, 1) x = torch.flatten(x, 1) x = self.fc(x) output = F.log_softmax(x, dim=1) return output net = Net() . . .
在这里,您定义了一个神经网络类,继承自 nn.Module
。 神经网络中的所有操作(包括神经网络本身)都必须继承自 nn.Module
。 对于您的神经网络类,典型的范例如下:
- 在构造函数中,定义网络所需的任何操作。 在这种情况下,您有两个卷积和一个全连接层。 (要记住的提示:构造函数始终以
super().__init__()
开头。)PyTorch 期望在将模块(例如,nn.Conv2d
)分配给实例属性([X196X)之前初始化父类])。 - 在
forward
方法中,运行初始化的操作。 此方法确定神经网络架构,明确定义神经网络将如何计算其预测。
这个神经网络使用了一些不同的操作:
nn.Conv2d
:卷积。 卷积在图像中寻找模式。 早期的卷积寻找像边缘这样的“低级”模式。 网络中的后续卷积会寻找“高级”模式,例如狗的腿或耳朵。nn.Linear
:全连接层。 全连接层将所有输入特征与所有输出维度相关联。F.relu
、F.max_pool2d
:这些是非线性类型。 (非线性是任何非线性的函数。)relu
是函数f(x) = max(x, 0)
。max_pool
取每块值中的最大值。 在这种情况下,您在整个图像中取最大值。log_softmax
:对向量中的所有值进行归一化,使这些值的总和为 1。
其次,像以前一样,定义优化器。 这一次,您将使用不同的优化器和不同的 超参数 设置。 超参数配置训练,而训练调整模型参数。 这些超参数设置取自 PyTorch MNIST 示例 :
step_3_mnist.py
. . . optimizer = optim.Adadelta(net.parameters(), lr=1.) # 2. Setup optimizer . . .
第三,与以前不同,您现在将使用不同的损失。 此损失用于 分类 问题,其中模型的输出是类索引。 在此特定示例中,模型将输出输入图像中包含的数字(可能是 0 到 9 之间的任何数字):
step_3_mnist.py
. . . criterion = nn.NLLLoss() # 3. Setup criterion . . .
第四,设置数据。 在这种情况下,您将设置一个名为 MNIST 的数据集,其中包含手写数字。 深度学习 101 教程经常使用这个数据集。 每个图像都是一个包含手写数字的 28x28 像素小图像,目标是将每个手写数字分类为 0、1、2、... 或 9:
step_3_mnist.py
. . . # 4. Setup data transform = transforms.Compose([ transforms.Resize((8, 8)), transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) train_dataset = datasets.MNIST( 'data', train=True, download=True, transform=transform) train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=512) . . .
在这里,您通过调整图像大小、将图像转换为 PyTorch 张量并将张量归一化为均值 0 和方差 1 来预处理 transform = ...
中的图像。
在接下来的两行中,您设置 train=True
,因为这是训练数据集和 download=True
,以便您下载数据集(如果尚未下载)。
batch_size=512
决定一次训练网络的图像数量。 除非批量大得离谱(例如,数万个),否则较大的批量对于大致更快的训练来说是更可取的。
第五,训练模型。 在以下代码块中,您进行了最少的修改。 您现在将在提供的数据集中对所有样本进行一次迭代,而不是在同一个样本上运行十次。 通过一次传递所有样本,以下是一个 epoch 的训练:
step_3_mnist.py
. . . # 5. Train the model for inputs, target in train_loader: output = net(inputs) loss = criterion(output, target) print(round(loss.item(), 2)) net.zero_grad() loss.backward() optimizer.step() . . .
保存并关闭您的文件。
仔细检查您的脚本是否匹配 step_3_mnist.py。 然后,运行脚本。
python step_3_mnist.py
您的脚本将输出以下内容:
Output2.31 2.18 2.03 1.78 1.52 1.35 1.3 1.35 1.07 1.0 ... 0.21 0.2 0.23 0.12 0.12 0.12
请注意,最终损失小于初始损失值的 10% o。 这意味着您的神经网络正在正确训练。
训练到此结束。 但是,0.12 的损失很难推理:我们不知道 0.12 是“好”还是“坏”。 为了评估您的模型的执行情况,您接下来计算此分类模型的准确度。
第四步——评估你的神经网络
之前,您在数据集的 train 拆分上计算了损失值。 但是,最好保留数据集的单独 验证 拆分。 您使用此验证拆分来计算模型的准确性。 但是,您不能将其用于训练。 接下来,您设置验证数据集并在其上评估您的模型。 在此步骤中,您将使用与之前相同的 PyTorch 实用程序,包括用于 MNIST 数据集的 torchvision.datasets
。
首先将您的 step_3_mnist.py
文件复制到 step_4_eval.py
中。 然后,打开文件:
cp step_3_mnist.py step_4_eval.py nano step_4_eval.py
首先,设置验证数据集:
step_4_eval.py
. . . train_loader = ... val_dataset = datasets.MNIST( 'data', train=False, download=True, transform=transform) val_loader = torch.utils.data.DataLoader(val_dataset, batch_size=512) . . .
在文件末尾,在训练循环之后,添加一个验证循环:
step_4_eval.py
. . . optimizer.step() correct = 0. net.eval() for inputs, target in val_loader: output = net(inputs) _, pred = output.max(1) correct += (pred == target).sum() accuracy = correct / len(val_dataset) * 100. print(f'{accuracy:.2f}% correct')
在这里,验证循环执行一些操作来计算准确性:
- 运行
net.eval()
可确保您的神经网络处于评估模式并准备好进行验证。 一些操作在评估模式下的运行方式与在训练模式下不同。 - 遍历
val_loader
中的所有输入和标签。 - 运行模型
net(inputs)
以获得每个类的概率。 - 找到概率最高的类
output.max(1)
。output
是一个张量,尺寸为(n, k)
,用于n
样本和k
类。1
表示您沿着索引1
维度计算最大值。 - 计算正确分类的图像数量:
pred == target
计算一个布尔值向量。.sum()
将这些布尔值转换为整数并有效地计算真值的数量。 correct / len(val_dataset)
最终计算正确分类图像的百分比。
保存并关闭您的文件。
仔细检查您的脚本是否匹配 step_4_eval.py。 然后,运行脚本:
python step_4_eval.py
您的脚本将输出以下内容。 请注意,具体的损失值和最终精度可能会有所不同:
Output2.31 2.21 ... 0.14 0.2 89% correct
您现在已经训练了您的第一个深度神经网络。 您可以通过调整训练的超参数来进行进一步的修改和改进:这包括不同的时期数、学习率和不同的优化器。 我们包含一个带有调整超参数的示例脚本; 这个脚本 训练相同的神经网络,但训练了 10 个 epoch,获得了 97% 的准确率。
深度学习的风险
一个问题是深度学习并不总能获得最先进的结果。 深度学习在特征丰富、数据丰富的场景中表现良好,但在数据稀疏、特征稀疏的情况下表现不佳。 尽管在深度学习的薄弱领域有积极的研究,但许多其他机器学习技术已经非常适合特征稀疏机制,例如决策树、线性回归或支持向量机 (SVM)。
另一个问题是深度学习还没有被很好地理解。 无法保证准确性、最优性甚至收敛性。 另一方面,经典的机器学习技术得到了充分的研究并且具有相对的可解释性。 同样,有积极的研究来解决深度学习中缺乏可解释性的问题。 您可以在 “什么可解释的 AI 无法解释(以及我们如何解决)” 中阅读更多内容。
最重要的是,深度学习缺乏可解释性会导致被忽视的偏见。 例如,加州大学伯克利分校的研究人员能够在字幕中显示模型的性别偏见(“Women also Snowboard”)。 其他研究工作集中在社会问题上,例如机器学习中的“公平”。 鉴于这些问题正在积极研究中,很难为模型中的偏差推荐一个规定的诊断。 因此,作为从业者,您有责任负责任地应用深度学习。
结论
PyTorch 是面向爱好者和研究人员的深度学习框架。 要熟悉 PyTorch,您已经训练了一个深度神经网络,还学习了一些自定义深度学习的技巧和窍门。
您还可以使用预先构建的神经网络架构而不是自己构建。 这是一个可选部分的链接:Use Existing Neural Network Architecture on Google Colab,你可以试试。 出于演示目的,此可选步骤训练具有更大图像的更大模型。
查看我们的其他文章,深入了解机器学习和相关领域:
- 你的模型够复杂吗? 太复杂了? 在 Bias-Variance for Deep Reinforcement Learning: How To Build a Bot for Atari with OpenAI Gym 中了解偏差-方差权衡以找出答案。 在本文中,我们为 Atari Games 构建了 AI 机器人,并探索了一个称为强化学习的研究领域。 或者,在这篇 Understanding the Bias-Variance Trade-off article 中找到对偏差-方差权衡的直观解释。
- 机器学习模型如何处理图像? 在 构建基于情感的狗过滤器 中了解更多信息。 在本文中,我们将更详细地讨论模型如何处理和分类图像,探索一个称为计算机视觉的研究领域。
- 神经网络会被愚弄吗? 在 Tricking a Neural Network 中学习如何操作。 在本文中,我们探讨了对抗性机器学习,这是一个研究领域,它为神经网络设计攻击和防御,以实现更强大的现实世界深度学习部署。
- 我们如何才能更好地理解神经网络的工作原理? 在 如何可视化和解释神经网络 中阅读一类称为“可解释的 AI”的方法。 在本文中,我们探讨了可解释的 AI,特别是可视化神经网络认为对其预测很重要的像素。