作者选择Code 2040接受捐赠,作为Write for DOnations计划的一部分。
介绍
机器学习是计算机科学的一个领域,它在数据中发现模式。到 2021 年,机器学习从业者使用这些模式来检测自动驾驶汽车的车道;训练机械手解决魔方;或产生可疑的艺术品味的图像。随着机器学习模型变得更加准确和高效,我们看到主流应用程序和产品越来越多地被采用。
深度学习是机器学习的一个子集,专注于特别复杂的模型,称为神经网络。在稍后的高级 DigitalOcean 文章中(例如构建Atari 机器人的教程),我们将正式定义“复杂”的含义。神经网络是您听说过的高度准确且引人入胜的现代模型,其应用范围涵盖广泛的任务。在本教程中,您将专注于一项称为对象识别或图像分类的特定任务。给定手写数字的图像,您的模型将预测显示的是哪个数字。
您将在PyTorch 中构建、训练和评估深度神经网络,PyTorch是Facebook AI Research为深度学习开发的框架。与其他深度学习框架(如 Tensorflow)相比,PyTorch 是一个初学者友好的框架,具有有助于构建过程的调试功能。它还可以为高级用户高度定制,研究人员和从业人员可以在 Facebook 和特斯拉等公司使用它。在本教程结束时,您将能够:
- 在 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
神经网络优化器。要了解什么是“优化器”,您还将了解一种称为梯度下降的算法。在本教程中,您将使用以下五个步骤来构建和训练模型:
- 构建计算图
- 设置优化器
- 设置标准
- 设置数据
- 训练模型
在本教程的第一部分中,您将使用可管理的数据集构建一个小型模型。首先创建一个新文件step_2_helloworld.py
,使用nano
或您最喜欢的文本编辑器:
- nano step_2_helloworld.py
您现在将编写一个简短的 18 行代码片段来训练一个小模型。首先导入几个 PyTorch 实用程序:
import torch
import torch.nn as nn
import torch.optim as optim
在这里,您将 PyTorch 库别名为几个常用的快捷方式:
torch
包含所有 PyTorch 实用程序。但是,常规 PyTorch 代码包括一些额外的导入。我们在这里遵循相同的约定,以便您可以在线了解 PyTorch 教程和随机代码片段。torch.nn
包含用于构建神经网络的实用程序。这通常表示nn
。torch.optim
包含培训实用程序。这通常表示optim
。
接下来,定义神经网络、训练实用程序和数据集:
. . .
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 = ...
定义损失。简而言之,损失定义了您的模型试图最小化的内容。对于线的基本模型,目标是最小化线的预测 y 值与训练集中实际 y 值之间的差异。请注意,torch.nn
(以 为别名nn
)包括您可以使用的许多其他损失函数。x, target = ...
定义你的“数据集”。现在,数据集只有一个坐标——一个 x 值和一个 y 值。在这种情况下,torch
包本身提供tensor
, 创建一个新的张量,并randn
创建一个具有随机值的张量。
最后,通过对数据集迭代十次来训练模型。每次,您调整模型的参数:
. . .
# 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)
循环中,您开始训练。这相当于更接近碗的中心。 - 每一步的方向由梯度给出。您将在这里跳过正式的证明,但总而言之,负梯度指向碗中的最低点。
- 使用
lr=0.1
inoptimizer = ...
指定步长。这决定了每个步骤的大小。
只需十步,您就可以到达碗的中心,这是损失最低的最佳模型。有关梯度下降的可视化,请参阅 Distill 的“为什么 Momentum 真正起作用”,页面顶部的第一个图。
这段代码的最后三行也很重要:
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
使用提供的形状生成一个具有随机值的张量。例如, atorch.randn((1, 2))
创建一个 1×2 张量,或一个二维行向量。 - PyTorch 支持多种优化器。此功能
torch.optim.SGD
,也称为随机梯度下降 (SGD)。粗略地说,这就是本教程中描述的算法,您在其中朝着最佳方向迈进了一步。有更多涉及的优化器可以在 SGD 之上添加额外的功能。还有很多损失,torch.nn.MSELoss
只是其中之一。
这结束了您在玩具数据集上的第一个模型。在下一步中,您将用神经网络替换这个小模型,用常用的机器学习基准替换玩具数据集。
第 3 步 — 在手写数字上训练您的神经网络
在上一节中,您构建了一个小型 PyTorch 模型。但是,为了更好地理解 PyTorch 的好处,您现在将使用 构建一个深度神经网络torch.nn.functional
,其中包含更多神经网络操作,并且torchvision.datasets
支持您可以使用的许多数据集,开箱即用。在本节中,您将使用预制数据集构建一个相对复杂的自定义模型。
您将使用卷积,它是模式查找器。对于图像,卷积在“意义”的各个层次上寻找 2D 模式:直接应用于图像的卷积寻找“较低层次”的特征,例如边缘。然而,应用于许多其他操作的输出的卷积可能正在寻找“更高级别”的特征,例如门。有关可视化和更彻底的卷积演练,请参阅斯坦福深度学习课程的一部分。
您现在将通过定义稍微复杂的模型来扩展您构建的第一个 PyTorch 模型。您的神经网络现在将包含两个卷积和一个全连接层,以处理图像输入。
首先step_3_mnist.py
使用文本编辑器创建一个新文件:
- nano step_3_mnist.py
您将遵循与以前相同的五步算法:
- 构建计算图
- 设置优化器
- 设置标准
- 设置数据
- 训练模型
首先,定义你的深度神经网络。请注意,这是您可能在 MNIST 上找到的其他神经网络的精简版本——这是有意为之,以便您可以在笔记本电脑上训练您的神经网络:
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
)分配给实例属性 (self.conv1
)之前初始化父类。 - 在
forward
方法中,运行初始化操作。该方法确定了神经网络架构,明确定义了神经网络将如何计算其预测。
这个神经网络使用了几种不同的操作:
nn.Conv2d
: 卷积。卷积在图像中寻找模式。早期的卷积寻找像边缘这样的“低级”模式。网络中的后续卷积寻找“高级”模式,例如狗的腿或耳朵。nn.Linear
:全连接层。全连接层将所有输入特征与所有输出维度相关联。F.relu
,F.max_pool2d
: 这些是非线性的类型。(非线性是任何不是线性relu
的函数f(x) = max(x, 0)
。)是函数。max_pool
在每个值块中取最大值。在这种情况下,您在整个图像中取最大值。log_softmax
:对向量中的所有值进行归一化,使值总和为 1。
其次,像以前一样,定义优化器。这一次,您将使用不同的优化器和不同的超参数设置。超参数配置训练,而训练调整模型参数。这些超参数设置取自PyTorch MNIST 示例:
. . .
optimizer = optim.Adadelta(net.parameters(), lr=1.) # 2. Setup optimizer
. . .
第三,与以前不同,您现在将使用不同的损失。此损失用于分类问题,其中模型的输出是类索引。在这个特定的例子中,模型将输出输入图像中包含的数字(可能是 0 到 9 之间的任何数字):
. . .
criterion = nn.NLLLoss() # 3. Setup criterion
. . .
第四,设置数据。在这种情况下,您将设置一个名为MNIST的数据集,其中包含手写数字。Deep Learning 101 教程经常使用这个数据集。每个图像都是一个包含手写数字的 28×28 像素小图像,目标是将每个手写数字分类为 0、1、2、… 或 9:
. . .
# 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)
. . .
在这里,您transform = ...
通过调整图像大小、将图像转换为 PyTorch 张量并将张量归一化为均值 0 和方差 1 来预处理图像。
在接下来的两行中,您设置train=True
,因为这是训练数据集,download=True
因此您可以下载数据集(如果尚未下载)。
batch_size=512
确定一次训练网络的图像数量。除非大批量(例如,数以万计),否则较大的批次对于大致更快的训练更可取。
第五,训练模型。在以下代码块中,您进行了最少的修改。您现在将对提供的数据集中的所有样本迭代一次,而不是在同一样本上运行十次。通过一次传递所有样本,以下是一个epoch 的训练:
. . .
# 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%。这意味着您的神经网络正在正确训练。
培训到此结束。然而,0.12 的损失很难推理:我们不知道 0.12 是“好”还是“坏”。要评估您的模型的执行情况,您接下来要计算此分类模型的准确度。
第 4 步 – 评估您的神经网络
早些时候,您在数据集的训练分割上计算了损失值。但是,保持数据集的单独验证拆分是一种很好的做法。您可以使用此验证拆分来计算模型的准确性。但是,您不能将其用于训练。接下来,您设置验证数据集并在其上评估您的模型。在此步骤中,您将使用与之前相同的 PyTorch 实用程序,包括torchvision.datasets
用于 MNIST 数据集。
首先将您的step_3_mnist.py
文件复制到step_4_eval.py
. 然后,打开文件:
- cp step_3_mnist.py step_4_eval.py
- nano 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)
. . .
在文件的末尾,在训练循环之后,添加一个验证循环:
. . .
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,您既训练了一个深度神经网络,还学习了一些自定义深度学习的技巧和窍门。
您还可以使用预先构建的神经网络架构,而不是构建自己的架构。这是一个可选部分的链接:您可以尝试使用 Google Colab 上的现有神经网络架构。出于演示目的,此可选步骤使用更大的图像训练更大的模型。
查看我们的其他文章,深入了解机器学习和相关领域:
- 你的模型足够复杂吗?太复杂?在深度强化学习的偏差-方差中了解偏差-方差权衡:如何使用 OpenAI Gym 为 Atari 构建机器人以找出答案。在本文中,我们为 Atari Games 构建 AI 机器人,并探索称为强化学习的研究领域。或者,在这篇理解偏差-方差权衡文章中找到偏差-方差权衡的直观解释。
- 机器学习模型如何处理图像?在构建基于情感的狗过滤器中了解更多信息。在本文中,我们将更详细地讨论模型如何处理和分类图像,探索称为计算机视觉的研究领域。
- 神经网络会被愚弄吗?了解如何欺骗神经网络。在本文中,我们探索对抗性机器学习,这是一个研究领域,为神经网络设计攻击和防御,以实现更强大的现实世界深度学习部署。
- 我们如何才能更好地理解神经网络的工作原理?在How To Visualize and Interpret Neural Networks 中阅读一类称为“可解释 AI”的方法。在本文中,我们探索可解释的 AI,特别是可视化神经网络认为对其预测很重要的像素。