基于深度学习的手写数字识别系统
一、实验目的
1、任选实验环境及深度学习框架,实现手写数字识别系统;
2、掌握所采用的深度血迹框架构建方式。
二、实验理论基础
1、MNIST数据集
MNIST 数据集来自美国国家标准与技术研究所, National Institute of Standards and Technology (NIST). 包含四个部分:
(1)Training set images:train-images-idx3-ubyte.gz(9.9MB,解压后47MB,包含60000个样本)
(2)Training set labels:train-labels-idx1-ubyte.gz(29KB,解压后60KB,包含60000个样本)
(3)Test set images:t10k-images-idx3-ubyte.gz(1.6MB,解压后7.8MB,包含10000个样本)
(4)Test set labels:t10k-labels-idx1-ubyte.gz(5KB,解压后10KB,包含10000个样本)
训练集 (training set) 由来自 250 个不同人手写的数字构成, 其中 50% 是高中学生, 50% 来自人口普查局 (the Census Bureau) 的工作人员. 测试集(test set) 也是同样比例的手写数字数据。图片是以字节的形式进行存储, 我们需要把它们读取到 NumPy array 中, 以便训练和测试算法.
可以建立一个Mnist文件夹,将下载的数据集放入此文件夹中,方便处理
2、数据集中训练集、验证集和测试集的作用
使用了所有的原始数据去训练模型,得到的结果很可能是该模型最大程度地拟合了原始数据,亦即该模型是为了拟合所有原始数据而存在。当新的样本出现,再使用该模型进行预测,效果可能还不如只使用一部分数据训练的模型。因此,为了防止模型过拟合,对原始数据进行了三个数据集的划分。
(1)训练集:作用是用来拟合模型,通过设置分类器的参数,训练分类模型。后续结合验证集作用时,会选出同一参数的不同取值,拟合出多个分类器。手写数字识别系统中是用训练集中的images,进行检测,用labels与之对应,使得网络形成记忆。
(2)验证集:作用是当通过训练集训练出多个模型后,为了能找出效果最佳的模型,使用各个模型对验证集数据进行预测,并记录模型准确率。选出效果最佳的模型所对应的参数,即用来调整模型参数。
(3)测试集:通过训练集和验证集得出最优模型后,使用测试集进行模型预测。用来衡量该最优模型的性能和分类能力。即可以把测试集当做从来不存在的数据集,当已经确定模型参数后,使用测试集进行模型性能评价。在手写数字识别系统中是将测试集中的images输入网络,并将结果与labels比较,用于计算测试集准确率。
三、实验内容
1、 网络模型搭建;
网络一共有7层,分别是《卷积层–池化层–卷积层–池化层–全连接层–全连接层–全连接层》;
(1)输入是一个单通道28*28大小的手写数字图片;
(2)定义卷积层1,输入通道为1,卷积核数为6,卷积核大小为5*5,池化层1,池化核大小为2*2;
(3)定义卷积层2,输入通道为6,卷积核数为16,卷积核大小为5*5,池化层2,池化核大小为2*2;
(4)全连接层1、2、3;
(5)定义前向传播顺序。
class LeNet5(nn.Module):
def __init__(self): # 初始化函数
super(LeNet5,self).__init__()# 多基层一般使用super
self.conv1 = nn.Conv2d(1, 6, 5)# 定义第一个卷积层,1是通道数灰度图片,6是卷积核个数,5卷积核大小
self.pool1 = nn.MaxPool2d(2, 2)# 池化核大小2*2,步距也为2,池化层,只改变高和宽,深度不改变
self.conv2 = nn.Conv2d(6, 16, 5)# 输入变为6,因为通过第一个卷积层有6个卷积核,输出深度为6
self.pool2 = nn.MaxPool2d(2, 2)
self.fc1 = nn.Linear(16*4*4, 120) # 展开成一维的,第一层120个节点,输入16*4*4个节点
self.fc2 = nn.Linear(120, 84) # 输入120,设置84个节点
self.fc3 = nn.Linear(84, 10) # 输出根据训练集修改
def forward(self,x):
x = self.pool1(F.relu(self.conv1(x))) # input(1,28,28),output1(6,24,24) output2(6,12,12)
x = self.pool2(F.relu(self.conv2(x))) # input(6,12,12),output1(16,8,8) output2(16,4,4)
x = x.view(-1, 16*4*4) # -1第一个维度
x = F.relu(self.fc1(x)) # 全连接层1及其激活函数
x = F.relu(self.fc2(x)) # 全连接层3得到输出
x = self.fc3(x)
return x
2、 数据集模型搭建 ;
构建数据集模型是为了在之后的训练和测试中方便对数据集的调用。
(1)定义Dataset类型的数据集Mnist,设置三个参数root、train、transform,其中root是需要调用的数据的储存路径,利用train是否为true,判断调用的数据为训练集还是测试集。
class Mnist(Dataset):
def __init__(self, root, train=True, transform=None):
# 根据是否为训练集,得到文件名前缀
self.file_pre = 'train' if train == True else 't10k'
self.transform = transform#定义变换函数
# 生成对应数据集的图片和标签文件路径
self.label_path = os.path.join(root,
'%s-labels-idx1-ubyte.gz' % self.file_pre)
self.image_path = os.path.join(root,
'%s-images-idx3-ubyte.gz' % self.file_pre)
# 读取文件数据,返回图片和标签
self.images, self.labels = self.__read_data__(
self.image_path,
self.label_path)
(2)定义__read_data__函数,将data以流的形式读入转化成ndarray对象,ndarray对象是用于存放同类型元素的多维数组,利用reshape函数将图片以标签文件的元素个数读取,设置大小为28*28。
def __read_data__(self, image_path, label_path):
# 数据集读取
with gzip.open(label_path, 'rb') as lbpath:
labels = np.frombuffer(lbpath.read(), np.uint8,
offset=8)#将data以流的形式读入转化成ndarray对象,ndarray对象是用于存放同类型元素的多维数组
with gzip.open(image_path, 'rb') as imgpath:
images = np.frombuffer(imgpath.read(), np.uint8,
offset=16).reshape(len(labels), 28, 28)#将图片以标签文件的元素个数读取,设置大小为28*28
return images, labels
(3)利用transform函数,将numpy.ndarray类型数据转化为张量。
# 如果需要转成 tensor,(RGB,HWC)张量, 则使用 tansform
if self.transform is not None:
image = self.transform(np.array(image)) # 此处需要用 np.array(image),转化为数组
return image, label
3、 训练模型搭建;
(1)利用定义的Mnist数据集生成训练集,同时利用DataLoader函数,将训练集中的元素随机排序后,以batch_size=32大小返回。
# 生成训练集
train_set = Mnist(
root=r'E:\CUDA\Dataset\Mnist',
train=True,#训练集
transform=transforms.Compose([#Compose方法是将多种变换组合在一起。
transforms.ToTensor(),#函数接受PIL Image或numpy.ndarray,将其先由HWC转置为CHW格式
transforms.Normalize((0.1037,), (0.3081,))#灰度图像,一个通道,均值和方差,标准化
])
)
train_loader = DataLoader(#主要用来将自定义的数据读取接口的输出或者PyTorch已有的数据读取接口的输入按照batch size封装成Tensor
dataset=train_set,#输出的数据
batch_size=32,
shuffle=True)#将元素随机排序
(2)实化一个定义好的网络,并将网络放进GPU中,定义损失函数和优化器,用来训练网络模型。
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")#判断能否调用GPU
print(device)
# 实例化一个网络
net = LeNet5().to(device) #将网络放进GPU
# 定义损失函数和优化器
loss_function = torch.nn.CrossEntropyLoss()
optimizer = optim.SGD(
net.parameters(),#网络参数
lr=0.001,#学习率
momentum=0.9#Momentum 用于加速 SGD(随机梯度下降)在某一方向上的搜索以及抑制震荡的发生。
)
(3)定义训练模型,设置训练10个epoch,在每个epoch中每300个batch打印一次平均损失值,,将训练好的模型保存,打印每次训练后的损失值变化曲线。
# 3 训练模型
loss_list = []#保存损失函数的值
for epoch in range(10):#训练10次
running_loss = 0.0#误差清零?
for batch_idx, data in enumerate(train_loader, start=0):#enumerate索引函数,start下标开始位置
images, labels = data # 读取一个batch的数据
images=images.to(device) #将images放进GPU
labels=labels.to(device) #将labels放进GPU
optimizer.zero_grad() # 梯度清零,初始化,如果不初始化,则梯度会叠加
outputs = net(images) # 前向传播
loss = loss_function(outputs, labels) # 计算误差,label标准?
loss.backward() # 反向传播
optimizer.step() # 权重更新
running_loss += loss.item() # 误差累计
# 每300个batch 打印一次损失值
if batch_idx % 300 == 299:#(0-299)(300-599)
print('epoch:{} batch_idx:{} loss:{}'
.format(epoch+1, batch_idx+1, running_loss/300))
loss_list.append(running_loss/300)#将新的每个平均误差加到损失函数列表后面
running_loss = 0.0 #误差清零
print('Finished Training.')
torch.save(net.state_dict(),"Linear.pth")#保存训练模型
# 打印损失值变化曲线
import matplotlib.pyplot as plt
plt.plot(loss_list)
plt.title('traning loss')
plt.xlabel('epochs')
plt.ylabel('loss')
plt.show()
(4)对训练好的网络进行测试,生成测试集的方法如同生成训练集的方法,记录通过网络预测正确的图片数和总图片数,将测试集中images、labels数据送入GPU中,images输入网络,将输出outputs与labels比较,得到预测正确的数目correct,输出测试集准确率。在测试集images中选取前10个数据,进行预测,并于对应labels比较。
# 测试
test_set = Mnist(#生成测试集
root='E:\CUDA\Dataset\Mnist',
train=False,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1037,), (0.3081,))
])
)
test_loader = DataLoader(
dataset=test_set,
batch_size=32,
shuffle=True
)
correct = 0 # 预测正确数
total = 0 # 总图片数
for data in test_loader:
images, labels = data
images=images.to(device)
labels=labels.to(device)
outputs = net(images)
_, predict = torch.max(outputs.data, 1)#1是指按行,0是按列,-1是指最后一个维度,一般也是按行
total += labels.size(0)
correct += (predict == labels).sum()
print('测试集准确率 {}%'.format(100*correct // total))
#检测网络
test_output=net(images[:10])#在测试集中选择10张图片输入网络,得到结果
pred_y = torch.max(test_output, 1)[1].data#对得到的结果进行预测
print(pred_y, 'prediction numbe')#输出预测结果
print(labels[:10], 'real number')#输出真实结果
4、 验证模型搭建;
(1)调用训练好的网络,生成测试集,从测试集images数据中选取前10个图片输入网络,将预测结果和真实结果输出,并将这10个images数据绘制出来进行比较。
net=LeNet5()
net.load_state_dict(torch.load("Linear.pth"))#调用训练好的网络
test_set = Mnist(#生成测试集
root='E:\CUDA\Dataset\Mnist',
train=False,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1037,), (0.3081,))
])
)
test_loader = DataLoader(
dataset=test_set,
batch_size=32,
shuffle=True)
for data in test_loader:
images, labels = data
#检测网络
test_output=net(images[:10])#在测试集中选择10张图片输入网络,得到结果
pred_y = torch.max(test_output, 1)[1].data#对得到的结果进行预测
print(pred_y, 'prediction numbe')#输出预测结果
print(labels[:10], 'real number')#输出真实结果
def plt_image(image):#定义一个函数,将需要预测的手写数字图画出来
n = 10
plt.figure(figsize=(10,4))
for i in range(n):
ax = plt.subplot(2,5,i+1)
plt.imshow(images[i].reshape(28,28))
plt.gray()
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
plt.show()
plt_image(images)
(2)测试自己手动设计的手写数字
I = Image.open('7.jpg')
L = I.convert('L')#转化为二值图像
plt.imshow(L, cmap='gray')
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1037,), (0.3081,))
])
im = transform(L) # [C, H, W]
im = torch.unsqueeze(im, dim=0) # [N, C, H, W],扩展维度
with torch.no_grad():
outputs = net(im)
_, predict = torch.max(outputs.data, 1)
print(predict)
四、附件
1、实验环境
(1)编程语言:python 3.6.12
(2)编程环境:spyder
(3)深度学习框架:pytorch 1.7.0
(4)系统环境:64位Windows系统
2、实验源码
(1)数据集模块data.py
import os
import numpy as np
from torch.utils.data import Dataset
import gzip
import matplotlib.pyplot as plt#绘图库
class Mnist(Dataset):
def __init__(self, root, train=True, transform=None):
# 根据是否为训练集,得到文件名前缀
self.file_pre = 'train' if train == True else 't10k'
self.transform = transform#定义变换函数
# 生成对应数据集的图片和标签文件路径
self.label_path = os.path.join(root,
'%s-labels-idx1-ubyte.gz' % self.file_pre)
self.image_path = os.path.join(root,
'%s-images-idx3-ubyte.gz' % self.file_pre)
# 读取文件数据,返回图片和标签
self.images, self.labels = self.__read_data__(
self.image_path,
self.label_path)
def __read_data__(self, image_path, label_path):
# 数据集读取
with gzip.open(label_path, 'rb') as lbpath:
labels = np.frombuffer(lbpath.read(), np.uint8,
offset=8)#将data以流的形式读入转化成ndarray对象,ndarray对象是用于存放同类型元素的多维数组
with gzip.open(image_path, 'rb') as imgpath:
images = np.frombuffer(imgpath.read(), np.uint8,
offset=16).reshape(len(labels), 28, 28)#将图片以标签文件的元素个数读取,设置大小为28*28
return images, labels
def __getitem__(self, index):#迭代使用?使用Minist()会调用_getitem_
image, label = self.images[index], int(self.labels[index])
# 如果需要转成 tensor,(RGB,HWC)张量, 则使用 tansform
if self.transform is not None:
image = self.transform(np.array(image)) # 此处需要用 np.array(image),转化为数组
return image, label
def __len__(self):#获取元素个数
return len(self.labels)
if __name__ == '__main__':
# 生成实例
train_set = Mnist(
root=r'E:\CUDA\Dataset\Mnist',#MNIST数据集路径
train=False,
)
# 取一组数据并展示
(data, label) = train_set[0]#第一组数据
plt.imshow(data.reshape(28, 28), cmap='gray')#灰色图像
plt.title('label is :{}'.format(label))
plt.show()
(2)网络模型模块model.py
import torch.nn as nn
import torch.nn.functional as F
class LeNet5(nn.Module):
def __init__(self): # 初始化函数
super(LeNet5,self).__init__()# 多基层一般使用super
self.conv1 = nn.Conv2d(1, 6, 5)# 定义第一个卷积层,1是通道数灰度图片,6是卷积核个数,5卷积核大小
self.pool1 = nn.MaxPool2d(2, 2)# 池化核大小2*2,步距也为2,池化层,只改变高和宽,深度不改变
self.conv2 = nn.Conv2d(6, 16, 5)# 输入变为6,因为通过第一个卷积层有6个卷积核,输出深度为6
self.pool2 = nn.MaxPool2d(2, 2)
self.fc1 = nn.Linear(16*4*4, 120) # 展开成一维的,第一层120个节点,输入16*4*4个节点
self.fc2 = nn.Linear(120, 84) # 输入120,设置84个节点
self.fc3 = nn.Linear(84, 10) # 输出根据训练集修改
def forward(self,x):
x = self.pool1(F.relu(self.conv1(x))) # input(1,28,28),output1(6,24,24) output2(6,12,12)
x = self.pool2(F.relu(self.conv2(x))) # input(6,12,12),output1(16,8,8) output2(16,4,4)
x = x.view(-1, 16*4*4) # -1第一个维度
x = F.relu(self.fc1(x)) # 全连接层1及其激活函数
x = F.relu(self.fc2(x)) # 全连接层3得到输出
x = self.fc3(x)
return x
if __name__ == '__main__':
net = LeNet5()
print(net)
(3)训练模块train.py
import torch
import torchvision.transforms as transforms
import torch.optim as optim#优化器
from torch.utils.data import DataLoader
from data import Mnist
from model import LeNet5
# 生成训练集
train_set = Mnist(
root=r'E:\CUDA\Dataset\Mnist',
train=True,#训练集
transform=transforms.Compose([#Compose方法是将多种变换组合在一起。
transforms.ToTensor(),#函数接受PIL Image或numpy.ndarray,将其先由HWC转置为CHW格式
transforms.Normalize((0.1037,), (0.3081,))#灰度图像,一个通道,均值和方差,标准化
])
)
train_loader = DataLoader(#主要用来将自定义的数据读取接口的输出或者PyTorch已有的数据读取接口的输入按照batch size封装成Tensor
dataset=train_set,#输出的数据
batch_size=32,
shuffle=True#将元素随机排序
)
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")#判断能否调用GPU
print(device)
# 实例化一个网络
net = LeNet5().to(device) #将网络放进GPU
# 定义损失函数和优化器
loss_function = torch.nn.CrossEntropyLoss()
optimizer = optim.SGD(
net.parameters(),#网络参数
lr=0.001,#学习率
momentum=0.9#Momentum 用于加速 SGD(随机梯度下降)在某一方向上的搜索以及抑制震荡的发生。
)
# 3 训练模型
loss_list = []#保存损失函数的值
for epoch in range(10):#训练10次
running_loss = 0.0#误差清零?
for batch_idx, data in enumerate(train_loader, start=0):#enumerate索引函数,start下标开始位置
images, labels = data # 读取一个batch的数据
images=images.to(device) #将images放进GPU
labels=labels.to(device) #将labels放进GPU
optimizer.zero_grad() # 梯度清零,初始化,如果不初始化,则梯度会叠加
outputs = net(images) # 前向传播
loss = loss_function(outputs, labels) # 计算误差,label标准?
loss.backward() # 反向传播
optimizer.step() # 权重更新
running_loss += loss.item() # 误差累计
# 每300个batch 打印一次损失值
if batch_idx % 300 == 299:#(0-299)(300-599)
print('epoch:{} batch_idx:{} loss:{}'
.format(epoch+1, batch_idx+1, running_loss/300))
loss_list.append(running_loss/300)#将新的每个平均误差加到损失函数列表后面
running_loss = 0.0 #误差清零
print('Finished Training.')
torch.save(net.state_dict(),"Linear.pth")#保存训练模型
# 打印损失值变化曲线
import matplotlib.pyplot as plt
plt.plot(loss_list)
plt.title('traning loss')
plt.xlabel('epochs')
plt.ylabel('loss')
plt.show()
# 测试
test_set = Mnist(#生成测试集
root='E:\CUDA\Dataset\Mnist',
train=False,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1037,), (0.3081,))
])
)
test_loader = DataLoader(
dataset=test_set,
batch_size=32,
shuffle=True
)
correct = 0 # 预测正确数
total = 0 # 总图片数
for data in test_loader:
images, labels = data
images=images.to(device)
labels=labels.to(device)
outputs = net(images)
_, predict = torch.max(outputs.data, 1)#1是指按行,0是按列,-1是指最后一个维度,一般也是按行
total += labels.size(0)
correct += (predict == labels).sum()
print('测试集准确率 {}%'.format(100*correct // total))
#检测网络
test_output=net(images[:10])#在测试集中选择10张图片输入网络,得到结果
pred_y = torch.max(test_output, 1)[1].data#对得到的结果进行预测
print(pred_y, 'prediction numbe')#输出预测结果
print(labels[:10], 'real number')#输出真实结果
(4)测试模块test.py
import torch
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
from data import Mnist
from model import LeNet5
import matplotlib.pyplot as plt
net=LeNet5()
net.load_state_dict(torch.load("Linear.pth"))#调用训练好的网络
#检测网络
test_set = Mnist(#生成测试集
root='E:\CUDA\Dataset\Mnist',
train=False,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1037,), (0.3081,))
])
)
test_loader = DataLoader(
dataset=test_set,
batch_size=32,
shuffle=True
)
for data in test_loader:
images, labels = data
test_output=net(images[:10])#在测试集中选择10张图片输入网络,得到结果
pred_y = torch.max(test_output, 1)[1].data#对得到的结果进行预测
print(pred_y, 'prediction numbe')#输出预测结果
print(labels[:10], 'real number')#输出真实结果
def plt_image(image):#定义一个函数,将需要预测的手写数字图画出来
n = 10
plt.figure(figsize=(10,4))
for i in range(n):
ax = plt.subplot(2,5,i+1)
plt.imshow(images[i].reshape(28,28))
plt.gray()
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
plt.show()
plt_image(images)
# 测试自己手动设计的手写数字
from PIL import Image
I = Image.open('2.jpg')
L = I.convert('L')#转化为二值图像
plt.imshow(L, cmap='gray')
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1037,), (0.3081,))
])
im = transform(L) # [C, H, W]
im = torch.unsqueeze(im, dim=0) # [N, C, H, W],扩展维度
with torch.no_grad():
outputs = net(im)
_, predict = torch.max(outputs.data, 1)
print(predict)