更少的代码通常会产生易于理解和维护的可读代码。Python编程语言在机器学习社区中非常流行,与其他编程语言相比,它可以让你以更少的代码获得更好的结果。
PyTorch是一个流行的Python深度学习框架,它有一个干净的API,允许你编写真正像Python的代码。因此,在Python中使用PyTorch创建模型并执行机器学习实验非常有趣。
在本文中,我将向你展示训练识别手写数字的简单分类器所需的基本步骤。你将看到如何
- 使用PyTorch的数据加载器加载MNIST数据集(用于机器学习的“Hello World”数据集)
- 声明模型的架构
- 选择优化器
- 实施训练循环
- 确定训练模型的准确性
我想让一切尽可能简单。因此,我不涉及过拟合、数据预处理或不同的度量来评估分类器的性能。我们将只实现训练分类器所需的基本构建块,这些构建块可以在其他机器学习实验中轻松重用。
所以,让我们开始编写一些代码。
我们需要做的第一件事是导入必要的包。当我们使用PyTorch时,我们需要导入包torch和torchvision。
import torch
import torchvision as tv
加载数据
加载数据
t = tv.transforms.ToTensor()
mnist_training = tv.datasets.MNIST(
root='/tmp/mnist',
train=True,
download=True,
transform=t
)
mnist_val = tv.datasets.MNIST(
root='/tmp/mnist',
train=False,
download=True,
transform=t
)
首先,我们创建一个ToTensor实例,用于将从数据集包获得的图像转换为张量。我们需要这个步骤,因为所有PyTorch函数都在张量上运行。如果你不知道张量,这些基本上只是多维数组的一个奇特名称。
然后,我们加载训练和验证数据集。使用root,我们可以指定用于在磁盘上存储数据集的目录。如果我们将train设置为true,则加载训练集。否则将加载验证集。如果我们将download设置为true,PyTorch将下载数据集并将其存储到通过root指定的目录中。
最后,我们可以指定应该应用于训练和验证数据集的每个示例的转换。在我们的例子中,它是ToTensor。
指定我们模型的架构
接下来,我们指定模型的架构。
model = torch.nn.Sequential(
torch.nn.Linear(28*28, 128),
torch.nn.ReLU(),
torch.nn.Linear(128, 10)
)
选择优化器和损失函数
接下来,我们指定优化器和损失函数。
我们正在使用Adam优化器。使用第一个参数,我们指定优化器需要优化的模型参数。使用第二个参数lr,我们指定学习速率。
在第二行中,我们选择CrossEntropyLoss作为损失函数(常用的损失函数的另一个词是criteria)。此函数获取输出层的非标准化(N x 10)维输出(N是批次的样本数),并计算网络输出和目标标签之间的损失。
目标标签被表示为包含输入样本的类索引的N维向量(或更具体地,秩1的张量)。如你所见,CrossEntropyLoss是一个非常方便的函数。首先,我们不需要网络末端的标准化层,比如softmax。其次,我们不必在标签的不同表示形式之间进行转换。我们的网络输出一个10维的分数向量,目标标签作为类索引向量(0到9之间的整数)提供。
接下来,我们为训练数据集创建一个数据加载器。
loader = torch.utils.data.DataLoader(
mnist_training,
batch_size=500,
shuffle=True
)
数据加载器用于从数据集中检索样本。我们可以使用数据加载器轻松地迭代批量样本。这里,我们创建了一个加载器,它在每次迭代中从训练数据集中返回500个样本。如果我们将shuffle设置为true,则样本将在批处理中进行混洗。
训练机器学习模型
现在,我们拥有训练模型所需的一切。
for epoch in range(10):
for imgs, labels in loader:
n = len(imgs)
imgs = imgs.view(n, -1)
predictions = model(imgs)
loss = loss_fn(predictions, labels)
opt.zero_grad()
loss.backward()
opt.step()
print(f"Epoch: {epoch}, Loss: {float(loss)}")
我们使用10个epoch来训练我们的网络(第1行)。在每个epoch中,我们迭代加载器以在每次迭代中获得500个图像及其标签(第2行)。变量imgs的形状为(500,1,28,28)。变量标签是具有500个类索引的秩1的张量。
在第3行中,我们将当前批次的图像数量保存在变量n中。在第4行中,将imgs张量从形状(n,1,28,28)重塑为(n,784)。在第5行中,我们使用我们的模型来预测当前批次的所有图像的标签。然后,在第6行中,我们计算这些预测和事实之间的损失。张量预测是形状(n,10)的张量,标签是包含类索引的秩1的张量。在第7行到第9行中,我们为网络的所有参数重置梯度,计算梯度并更新模型的参数。
我们还打印每个epoch后的损失,以便我们可以验证网络在每个epoch之后变得更好(即损失减少)。
确定准确度
现在,我们已经训练了网络,我们可以确定模型识别手写数字的准确性。
首先,我们需要从验证数据集中获取数据。
n = 10000
loader = torch.utils.data.DataLoader(mnist_val, batch_size=n)
images, labels = iter(loader).next()
我们的验证数据集mnist_val包含10000个图像。为了获取所有这些图像,我们使用DataLoader并将batch_size设置为10000。然后,我们可以通过从数据加载器创建一个迭代器并在该迭代器上调用next来获取第一个元素。
结果是一个元组。这个元组的第一个元素是形状张量(10000,1,28,28)。第二个元素是秩1的张量,它包含图像的类索引。
现在,我们可以使用我们的模型来预测所有图像的标签。
predictions = model(images.view(n, -1))
在向模型提供数据之前,我们需要对其进行重新塑造(类似于我们在训练循环中所做的)。我们模型的输入张量需要具有形状(n,784)。当数据具有正确的形状时,我们可以将其用作模型的输入。
结果是形状的张量(10000,10)。对于验证集的每个图像,这个张量存储了十个可能标签中每一个的得分。标签的得分越高,样品属于相应标签的可能性就越大。
通常,样本被分配给得分最高的标签。我们可以通过argmax方法从张量中轻松确定该标签,因为该方法返回最大值的位置。
predicted_labels = predictions.argmax(dim=1)
我们对维度1的最大值感兴趣,因为每个样本的分数都是沿着这个维度存储的。结果是一个秩为1的张量,它现在存储预测的类索引而不是分数。
现在,我们可以将预测的类索引与真实标签进行比较,以计算验证数据集的准确性。准确度定义为已正确预测的样本分数,即正确预测样本的数量除以样本总数。
获得这个度量的诀窍是对预测的标签和真实标签进行元素比较。
torch.sum(predicted_labels == labels) / n
我们将predicted_labels与等式标签进行比较,得到布尔向量。如果同一位置的两个元素相等,则该向量中的一个元素为真。否则,元素为false。然后,我们使用sum来计算为真的元素的数量,并将该数量除以n。
如果我们执行所有这些步骤,我们应该达到大约97%的准确率。
完整的代码:
import torch
import torchvision as tv
t = tv.transforms.ToTensor()
mnist_training = tv.datasets.MNIST(
root='/tmp/mnist',
train=True,
download=True,
transform=t
)
mnist_val = tv.datasets.MNIST(
root='/tmp/mnist',
train=False,
download=True,
transform=t
)
model = torch.nn.Sequential(
torch.nn.Linear(28*28, 128),
torch.nn.ReLU(),
torch.nn.Linear(128, 10)
)
opt = torch.optim.Adam(params=model.parameters(), lr=0.01)
loss_fn = torch.nn.CrossEntropyLoss()
loader = torch.utils.data.DataLoader(
mnist_training,
batch_size=500,
shuffle=True
)
for epoch in range(10):
for imgs, labels in loader:
n = len(imgs)
imgs = imgs.view(n, -1)
predictions = model(imgs)
loss = loss_fn(predictions, labels)
opt.zero_grad()
loss.backward()
opt.step()
print(f"Epoch: {epoch}, Loss: {float(loss)}")
n = 10000
loader = torch.utils.data.DataLoader(mnist_val, batch_size=n)
images, labels = iter(loader).next()
predictions = model(images.view(n, -1))
predicted_labels = predictions.argmax(dim=1)
torch.sum(predicted_labels == labels) / n
Epoch: 0, Loss: 0.1344318389892578
Epoch: 1, Loss: 0.10877793282270432
Epoch: 2, Loss: 0.1007857620716095
Epoch: 3, Loss: 0.06776227056980133
Epoch: 4, Loss: 0.03420461341738701
Epoch: 5, Loss: 0.11107035726308823
Epoch: 6, Loss: 0.028283704072237015
Epoch: 7, Loss: 0.01456095464527607
Epoch: 8, Loss: 0.0457158200442791
Epoch: 9, Loss: 0.030669715255498886
tensor(0.9742)
结论
作为一名软件工程师,我喜欢干净的代码。有时我会坐在一小段代码前半个小时甚至更长时间,只是为了让它更优雅、更漂亮。
PyTorch是一个很棒的深度学习框架,它允许你编写易于理解且感觉像Python的干净代码。我们已经看到,只需几行代码就可以生成表达性代码来创建和训练最先进的机器学习模型。
我们在这个简单演示中使用的构建块可以在许多机器学习项目中重用,我希望它们在你的机器学习过程中也会对你有所帮助。