使用pytorch实现手写数字识别的主要步骤如下:
(1) 导入需要的各类包
(2) 定义代码中用到的各个超参数
(3) 对数据进行预处理
(4) 下载和分批加载数据集
(5) 利用nn工具箱构建神经网络模型,实例化模型,并定义损失函数及优化器
(6) 对模型进行训练
(7) 运用训练好的模型在测试集上检验效果
(8) 通过可视化的方法输出模型性能结果
神经网络结构设计如下:
四层神经网络:输入层 + 隐藏层1+ 隐藏层2 + 输出层
(实际上我们激活函数均使用的ReLU)
torchvision提供的mnist数据集几乎是每个深度学习新手的入门数据集,所以我们先来了解一下它:
MNIST 包括6万张图像和标签的训练集,1万张图像和标签的测试集,每张为28x28大小的灰度图片(784个像素点,每个点用一个浮点数表示其亮度),其中包含一个0-9的数字。我们的任务就是训练一个模型尽可能的准确识别出图像中的数字。
本次案例仅简单设计了全连接层,并不包含卷积层、池化层等,因此最终的识别准确率相对来说没有那么高,这样做对于新手来说,一来可以及时巩固神经网络的学习成果,二来可以简化模型复杂度而更加注重了解一个实际项目的完整工作流程,三来也可以后续在此模型基础上添加卷积池化等,进一步感受模型性能的提升。
注: 案例中包含大量注释以便理解其含义
import torch
import numpy as np
#导入 pytorch 内置的mnist数据集
from torchvision.datasets import mnist
#导入对图像的预处理模块
import torchvision.transforms as transforms
#导入dataset的分批读取包
from torch.utils.data import DataLoader
#导入神经网络包nn(可用来定义和运行神经网络)
from torch import nn
#functional这个包中包含了神经网络中使用的一些常用函数,这些函数的特点是:不具有可学习的参数(如ReLU,pool,DropOut等)
import torch.nn.functional as F
#optim中实现了大多数的优化方法来更新网络权重和参数,如SGD、Adam
import torch.optim as optim
#导入可视化绘图库
import matplotlib.pyplot as plt
#2定义代码中用到的各个超参数
train_batch_size = 64 #指定DataLoader在训练集中每批加载的样本数量
test_batch_size = 128 #指定DataLoader在测试集中每批加载的样本数量
num_epoches = 20 # 模型训练轮数
lr = 0.01 #设置SGD中的初始学习率
momentum = 0.5 #设置SGD中的冲量
由于pytorch读取数据集minst中的图像时默认使用python中的PIL,所以我们首先需要把PIL图像转化为更加适合pytorch计算使用的图像张量,其次需要把原始0 ~ 255之间的像素值通过归一化处理成0 ~ 1之间的值,这两步预处理的目的都是欲使数据在神经网络中运算更高效。
transforms.ToTensor()
作用就是将PIL中28x28的灰度图像转化为tensor张量其维度为1x28x28(CxWxH)其中1的含义为单通道(彩色图像时调整为三通道)
transforms.Normalize([0.1307], [0.3081])
使用minst数据集的均值和标准差将数据标准化处理
#3对数据进行预处理
# Compose方法即是将两个操作合并一起
transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize([0.1307], [0.3081])])
之所以要分批加载数据集,是因为虽然我们的mnist数据集只有几十兆完全可以一次性加载进内存以供训练模型使用,但是当我们的训练的模型需要的数据集大小远超我们内存大小时,分批加载数据就可以解决这一问题。
#4下载和分批加载数据集
#将训练和测试数据集下载到同目录下的data文件夹下
train_dataset = mnist.MNIST('.\data', train=True, transform=transform, download=True)
test_dataset = mnist.MNIST('.\data', train=False, transform=transform,download=True)
#dataloader是一个可迭代对象,可以使用迭代器一样使用。
#其中shuffle参数为是否打乱原有数据顺序
train_loader = DataLoader(train_dataset, batch_size=train_batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=test_batch_size, shuffle=False)
#5定义一个神经网络模型
class Net(nn.Module):
def __init__(self,in_dim,n_hidden_1,n_hidden_2,out_dim):
super(Net,self).__init__()
self.layer1=nn.Sequential(nn.Linear(in_dim,n_hidden_1),nn.ReLU(True))
self.layer2=nn.Sequential(nn.Linear(n_hidden_1,n_hidden_2),nn.ReLU(True))
self.layer3=nn.Linear(n_hidden_2,out_dim) #最后一层接Softmax所以不需要ReLU激活
def forward(self,x):
x=self.layer1(x)
x=self.layer2(x)
x=self.layer3(x)
return x
# Sequential() 即相当于把多个模块按顺序封装成一个模块
#实例化网络模型
#检测是否有可用的GPU,否则使用cpu
device=torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
#网络模型参数分别为:输入层大小、隐藏层1大小、隐藏层2大小、输出层大小(10分类)
model=Net(28*28,300,100,10)
#将模型移动到GPU加速计算
model.to(device)
#定义模型训练中用到的损失函数和优化器
criterion=nn.CrossEntropyLoss() #交叉熵损失函数
optimizer=optim.SGD(model.parameters(),lr=lr,momentum=momentum)
# parameters()将model中可优化的参数传入到SGD中
其中:CrossEntropyLoss() == LogSoftmax() + NLLLoss()
Softmax: 在K分类问题中,此函数运算公式如下所示,可将K个输入值经过运算后得到的K个输出值拥有两个特性:(1)每个输出都大于0(2)K个输出之和等于1,由此K个输出即可代表每个分类的概率大小。
NLLLoss: 负对数似然损失函数,公式如下所示,其中Y帽即为Softmax中输出的概率,Y为图像真实标签值
#6对模型进行训练
# 开始训练
losses = [] #记录训练集损失
acces = [] #记录训练集准确率
eval_losses = [] #记录测试集损失
eval_acces = [] #记录测试集准确率
for epoch in range(num_epoches):
train_loss = 0
train_acc = 0
model.train() #指明接下来model进行的是训练过程
#动态修改参数学习率
if epoch%5==0:
optimizer.param_groups[0]['lr'] *= 0.9
for img, label in train_loader:
img = img.to(device) #将img移动到GPU计算
label = label.to(device)
img = img.view(img.size(0), -1)#把输入图像的维度由四维转化为2维,因为在torch中只能处理二维数据
#img.size(0)为取size的第0个参数即此批样本的个数,-1为自适应参数
# 前向传播
out = model(img)
loss = criterion(out, label)
# 反向传播
optimizer.zero_grad() #先清空上一轮的梯度
loss.backward() #根据前向传播得到损失,再由损失反向传播求得各个梯度
optimizer.step() #根据反向传播得到的梯度优化模型中的参数
train_loss += loss.item() # 所有批次损失的和
# 计算分类的准确率
_, pred = out.max(1) #返回输出二维矩阵中每一行的最大值及其下标,1含义为以第1个维度(列)为参考
#pred=torch.argmax(out,1)
num_correct = (pred == label).sum().item()
#num_correct = pred.eq(label).sum().item()
acc = num_correct / img.shape[0] #每一批样本的准确率
train_acc += acc
losses.append(train_loss / len(train_loader)) #所有样本平均损失
acces.append(train_acc / len(train_loader)) #所有样本的准确率
# 7运用训练好的模型在测试集上检验效果
eval_loss = 0
eval_acc = 0
# 将模型改为预测模式
model.eval() #指明接下来要进行模型测试(不需要反向传播)
#with torch.no_grad():
for img, label in test_loader:
img=img.to(device)
label = label.to(device)
img = img.view(img.size(0), -1)
out = model(img)
loss = criterion(out, label)
# 记录误差
eval_loss += loss.item()
# 记录准确率
_, pred = out.max(1)
num_correct = (pred == label).sum().item()
acc = num_correct / img.shape[0]
eval_acc += acc
eval_losses.append(eval_loss / len(test_loader))
eval_acces.append(eval_acc / len(test_loader))
print('epoch: {}, Train Loss: {:.4f}, Train Acc: {:.4f}, Test Loss: {:.4f}, Test Acc: {:.4f}'
.format(epoch, train_loss / len(train_loader), train_acc / len(train_loader),
eval_loss / len(test_loader), eval_acc / len(test_loader)))
#8通过可视化的方法输出模型性能结果
plt.title('train loss')
plt.plot(np.arange(len(losses)), losses)
plt.legend(['Train Loss'], loc='best')