手写数字识别

概述

本节我们利用之前学到的PyTorch相关知识,建立一个全连接神经网络模型,用于识别手写字符. 经过本章的学习, 大家将明白如何利用PyTorch完成数据集的预处理, 数据加载器的生成, 优化器的定义, 损失的定义, 全连接神经网络的搭建,训练,测试等过程

知识点:

  1. 数据加载器的定义
  2. 优化器的定义
  3. 损失的定义
  4. 全连接网络的建立
  5. 模型的训练与测试

手写字符识别

在开始之前,我们先确定我们的环境是否支持GPU运算

  1. import torch
  2. device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
  3. print(device)

若type为cuda则表示支持GPU,否则就只支持CPU. 无论说CPU环境还是GPU环境,我们的代码都是可以自由切换的. 我们只需要使用.to(device)就可以将数据在CPU和GPU之间进行切换. 若不支持.to(device)就可以使用变量转成可以放入CPU中的类型.

数据的预处理

在前面章节中,我们已经学习了数据加载器. 本节我们直接加载PyTorch自带的数据集合,该数据集合存在于torchvision.datasets中, 可以直接利用torchvision.datasets.MNIST获得

  1. import torch
  2. import torchvision
  3. # 加载训练数据
  4. train_dataset = torchvision.datasets.MNIST(root="./data",
  5. train=True,
  6. transform=torchvision.transforms.ToTensor(),
  7. download=False)
  8. # 加载测试数据
  9. test_dataset = torchvision.datasets.MNIST(root="./data",
  10. train=False,
  11. transform=torchvision.transforms.ToTensor(),
  12. download=False)
  13. len(train_dataset),len(test_dataset)

我们可以看到我们加载了60000条训练的数据,10000条测试的数据

  1. # 将数据放入数据加载器中
  2. batch_size = 100
  3. train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
  4. batch_size=batch_size,
  5. shuffle=True)
  6. test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
  7. batch_size=batch_size,
  8. shuffle=False)
  9. train_loader,test_loader

然后,我们再加载几张图片观察一下

  1. import numpy as np
  2. import matplotlib.pyplot as plt
  3. examples = iter(test_loader)
  4. example_data,example_targets = example.next()
  5. for i in range(6):
  6. plt.subplot(2,3,i+1)
  7. plt.imshow(example_data[i][0],cmap="gray")

模型的建立

因为模型包含输入和输出,所以我们可以先定义这些内容

  1. # 输入节点数就为图片的大小: 28x28x1
  2. input_size = 784
  3. # 由于数字为0-9,因此这是一个10分类的问题,输出节点数为10
  4. num_classes = 10

下面就是我们构建的完成网络模型

  1. # 构建手写数字识别的模型
  2. class NerualNet(torch.nn.Module):
  3. def __init__(self,input_size,hidden_size,num_classes):
  4. super(NerualNet,self).__init__()
  5. self.input_size = input_size
  6. self.l1 = torch.nn.Linear(input_size,hidden_size)
  7. self.relu = torch.nn.ReLU()
  8. self.l2 = torch.nn.Linear(hidden_size,num_classes)
  9. def forward(self,x):
  10. out = self.l1(x)
  11. out = self.relu(out)
  12. out = self.l2(out)
  13. return out
  14. device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
  15. # 建立一个中间层为500的三层神经网络,且将模型转为当前环境支持的类型(cpu或GPU)
  16. model = NerualNet(input_size,500,num_classes).to(device)
  17. print(model)

从上面的数据结构可以知道,该神经网络模型一共有三层.

  • 第一层为输入层,节点数量和图像大小相同
  • 第二层为隐藏层,节点数为500
  • 第三层为输出层, 节点大小为10, 节点数大小和类别相同

损失和优化器的定义

定义完模型后, 接下来, 我们需要定义模型训练时所血药的损失函数和优化器

这里我们就使用之前讲过的交叉熵损失函数

  1. criterion = nn.CrossEntropyLoss()

由于传统的梯度下降算法存在一定的缺陷, 比如学习速率一直不变. 因此,我们利用pyTorch中定义的梯度下降算法的优化算法,Adam算法来进行模型的训练.

  1. learning_rate = 0.01
  2. # 定义优化算法
  3. optimizer = torch.optim.Adam(model.parameters(),lr=learning_rate)

模型的训练

模型训练步骤和之前案例中的步骤一致,步骤是固定的:

  • 通过模型的正向传播,输出预测结果
  • 通过预测结果和真实标签计算损失
  • 通过后向传播,获得梯度
  • 通过梯度更新模型的权重
  • 进行梯度的清空
  • 循环上面的操作, 直到损失较小为止

接下来,我们用代码完成上面的步骤:

  1. num_epochs = 2
  2. # 数据总长度
  3. total_steps = len(train_loader)
  4. for epoch in range(num_epochs):
  5. for i,(images,labels) in enumerate(train_loader):
  6. # 100*784
  7. images = images.reshape(-1,28*28).to(device)
  8. labels = labels.to(device)
  9. # 正向传播
  10. outputs = model(images)
  11. l = loss(outputs,labels)
  12. # 反向传播
  13. optimizer.zero_grad()
  14. l.backward()
  15. optimizer.step()
  16. if i%100 == 0:
  17. print(f"Loss:{l.item():.4f}")
  18. print("模型训练完成")

从结果我们可以看出损失已经收敛了.下面我们从测试集中选几张图片来验证一下

  1. examples = iter(test_loader)
  2. example_data,example_targets = examples.next()
  3. # 图片的展示
  4. for i in range(3):
  5. plt.subplot(1,3,i+1)
  6. plt.imshow(example_data[i][0],cmap="gray")
  7. plt.show()

调用我们之前训练的模型,对数据进行预测

  1. # 结果预测
  2. images = example_data
  3. images = images.reshape(-1,28*28).to(device)
  4. labels = labels.to(device)
  5. # 正向传播
  6. outputs = model(images)
  7. # 输出真实结果
  8. print("预测结果:",example_targets[0:3].detach().numpy())
  9. print("预测结果:",np.argmax(outputs[0:3].cpu().detach().numpy(),axis=1))

注意,这里model得到的预测结果为每一个数对应的概率值,我们要利用np.argmax求取该数组中最大数的下标, 下标就是对应的第几类

模型的测试

在训练完模型后,我们将导入测试数据集, 对模型进行测试, 对比模型的预测结果和实际结果, 进而得到模型的识别准确率.

模型的测试代码很简单, 其实就是将数据传入模型之中, 并进行一次正向传播即可.

简单的说,就是复制上面的模型训练代码,然后更换数据集, 删除后面的梯度下降相关代码即可.

  1. # 用测试集的数据,校验模型的准确率
  2. with torch.no_grad():
  3. n_correct = 0
  4. n_samples = 0
  5. for images,labels in test_loader:
  6. # 和训练代码一致
  7. images = images.reshape(-1,28*28).to(device)
  8. labels = labels.to(device)
  9. outputs = model(images)
  10. # 返1 最大值 返2 索引 0每列最大值 1每行最大值
  11. _, predicted = torch.max(outputs.data,1)
  12. n_samples += labels.size(0)
  13. n_correct += (predicted == labels).sum().item()
  14. acc = 100.0 * n_correct/n_samples
  15. print(f"准确率:{acc}%")

从结果可以看出, 我们利用Pytorch建立的三层全连接网络对手写字符图片也有较高的识别准确率,这侧面说明了神经网络的强大

小结

通过本节内容, 大家应该已经掌握了如何利用PyTorch完成数据集的预处理,数据加载器的生成,优化器的定义,损失的定义,全连接神经网络的搭建与训练以及模型的测试等过程.