写在开头 我其实根本就不想写这方面内容的文章,不过既然人工智能课的结课作业要写项目文档,我就顺便更一下博客而已。(其实说到底就是逮到机会想水博客而已。)没有学过Python,代码来源自网络,整理一下也能方便日后的自己拿出来学习。 下面开始就是作业了。
基于Pytorch的MNIST手写数字识别 摘要 数字识别(Digit Recognition)是计算机从纸质文档,照片,或其他来源接收和理解并识别可读的数字的能力。 根据数字来源的产生方式的不同,目前数字识别问题可以区分为手写体数字识别,印刷体数字识别,光学数字识别,自然场景下的数字识别等,具有很大的实际应用价值。目前比较受到关注的问题主要是手写体数字识别,由于其具有MNIST这种大型标准易用的成熟数据集,简单的0-9数字识别已经被作为计算机视觉领域的入门问题。本文通过介绍手写数字识别的应用,并指出传统研究方法及其不足之处;然后引入深度学习的概念,以卷积神经网络为例,详细介绍卷积神经网络的关键技术特点。最后通过一个实例说明卷积神经网络在手写数字识别方面的应用。(此段来源自网络)
项目背景概述/介绍 MNIST手写数字识别,是一个比较简单的入门项目,就相当于我们在学习编程语言时最开始的Hello World,可以让我们快速地了解构建神经网络的大致过程。 这次我们选择使用PyTorch的神经网络框架。PyTorch是torch的python版本,它是由Facebook开源的神经网络框架。 Torch 是一个经典的对多维矩阵数据进行操作的张量(tensor)库,在机器学习和其他数学密集型应用有广泛应用。与Tensorflow的静态计算图不同,pytorch的计算图是动态的,可以根据计算需要实时改变计算图。但由于Torch语言采用 Lua,导致在国内一直很小众,并逐渐被支持 Python 的 Tensorflow 抢走用户。作为经典机器学习库 Torch 的端口,PyTorch 为 Python 语言使用者提供了舒适的写代码选择。PyTorch的设计追求最少的封装,尽量避免重复造轮子。不像 TensorFlow 中充斥着session、graph、operation、name_scope、variable、tensor、layer等全新的概念,PyTorch 的设计遵循tensor→variable(autograd)→nn.Module 三个由低到高的抽象层次,分别代表高维数组(张量)、自动求导(变量)和神经网络(层/模块),而且这三个抽象之间联系紧密,可以同时进行修改和操作。 简洁的设计带来的另外一个好处就是代码易于理解。PyTorch的源码只有TensorFlow的十分之一左右,更少的抽象、更直观的设计使得PyTorch的源码十分易于阅读。 总的来说,选择Pytorch,是因为它能够在短时间内建立结果,适用于小规模的项目。(其实最主要的是因为老师让我们用Pytorch)
项目实现原理 流程 安装PyTorch -> 安装mnist -> 通过代码下载MNIST训练集 -> 通过代码定义卷积神经网络 -> 通过代码使用MNIST训练集中的训练库和测试库训练出一个模型MNIST.pth -> 通过模型与定义的卷积神经网络进行识别
PyTorch安装 根据自己计算机的实际情况在官网 https://pytorch.org 安装相应的PyTorch 详细安装教程:https://blog.csdn.net/learningpawn/article/details/106531514
安装Python的mnist库 打开cmd,输入pip install mnist
可以通过 pip list
检查自己是否安装相应的库
通过代码进行的操作 通过代码下载MNIST的训练集和测试集,同时定义卷积神经网络,然后训练出相应的模型,并将其保存,以下为执行代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 import torchimport torch.nn as nnimport torch.nn.functional as Fimport torch.optim as optimfrom torchvision import datasets, transformstorch.__version__ BATCH_SIZE=512 EPOCHS=20 DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu" ) train_loader = torch.utils.data.DataLoader( datasets.MNIST('data' , train=True , download=True , transform=transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307 ,), (0.3081 ,)) ])), batch_size=BATCH_SIZE, shuffle=True ) test_loader = torch.utils.data.DataLoader( datasets.MNIST('data' , train=False , transform=transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307 ,), (0.3081 ,)) ])), batch_size=BATCH_SIZE, shuffle=True ) class ConvNet (nn.Module ): def __init__ (self ): super ().__init__() self.conv1 = nn.Conv2d(1 , 10 , 5 ) self.conv2 = nn.Conv2d(10 , 20 , 3 ) self.fc1 = nn.Linear(20 *10 *10 , 500 ) self.fc2 = nn.Linear(500 , 10 ) def forward (self,x ): in_size = x.size(0 ) out = self.conv1(x) out = F.relu(out) out = F.max_pool2d(out, 2 , 2 ) out = self.conv2(out) out = F.relu(out) out = out.view(in_size, -1 ) out = self.fc1(out) out = F.relu(out) out = self.fc2(out) out = F.log_softmax(out, dim=1 ) return out def train (model, device, train_loader, optimizer, epoch ): model.train() for batch_idx, (data, target) in enumerate (train_loader): data, target = data.to(device), target.to(device) optimizer.zero_grad() output = model(data) loss = F.nll_loss(output, target) loss.backward() optimizer.step() if (batch_idx+1 )%30 == 0 : print ('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}' .format ( epoch, batch_idx * len (data), len (train_loader.dataset), 100. * batch_idx / len (train_loader), loss.item())) def test (model, device, test_loader ): model.eval () test_loss = 0 correct = 0 with torch.no_grad(): for data, target in test_loader: data, target = data.to(device), target.to(device) output = model(data) test_loss += F.nll_loss(output, target, reduction='sum' ).item() pred = output.max (1 , keepdim=True )[1 ] correct += pred.eq(target.view_as(pred)).sum ().item() test_loss /= len (test_loader.dataset) print ('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n' .format ( test_loss, correct, len (test_loader.dataset), 100. * correct / len (test_loader.dataset))) if __name__ == '__main__' : model = ConvNet().to(DEVICE) optimizer = optim.Adam(model.parameters()) for epoch in range (1 , EPOCHS + 1 ): train(model, DEVICE, train_loader, optimizer, epoch) test(model, DEVICE, test_loader) torch.save(model, './MNIST.pth' )
以下为测试代码,它将指定进行测试的图片通过opencv转化为灰度图,然后使用前面训练好的模型MNIST.pth和定义好的卷积神经网络进行识别 (opencv安装代码 : pip install python-opencv
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import torchfrom mnist import *import globimport cv2import numpy as np import torch.nn.functional as Ffrom torch.autograd import Variablefrom torchvision import datasets, transformsimport numpy as npimport torchvisionfrom skimage import io,transformfrom train import *if __name__ =='__main__' : device = torch.device('cuda' if torch.cuda.is_available() else 'cpu' ) model = torch.load(r'D:/学习/Python/手写数字识别/MNIST.pth' ) model = model.to(device) model.eval () img = cv2.imread('./numbers/number1.jpg' , 0 ) img = cv2.resize(img, (28 , 28 )) height,width=img.shape dst=np.zeros((height,width),np.uint8) for i in range (height): for j in range (width): dst[i,j]=255 -img[i,j] img = dst img=np.array(img).astype(np.float32) img=np.expand_dims(img,0 ) img=np.expand_dims(img,0 ) img=torch.from_numpy(img) img = img.to(device) output=model(Variable(img)) prob = F.softmax(output, dim=1 ) prob = Variable(prob) prob = prob.cpu().numpy() print (prob) pred = np.argmax(prob) print ("识别结果为:" ) print (pred.item())
项目实现结果/分析 以下为最终实现的成果,发现手写数字的粗细程度会影响识别的精度,在一定粗细程度的笔触下写下的数字能够实现高精度的识别。
下图为一定粗细程度笔触写下的数字的实现结果,识别波动几乎为无,而且能够完美识别。
下图则为过细程度笔触写下的数字的实现结果,可发现识别波动范围比较大,而且出现了识别错误的情况。
(两图数字皆为博主所写)
要想了解问题为何发生,就得从源头开始查起。 MNIST数据集来自美国国家标准与技术研究所(National Institute of Standards and Technology),简称 (NIST)。 训练集由来自250个不同人手写的数字构成, 其中50%是高中学生, 50%来自人口普查局的工作人员。测试集也是同样比例的手写数字数据。 于是我通过以下代码得到了MNIST训练集和测试集中的手写数字图片。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import osfrom skimage import ioimport torchvision.datasets.mnist as mnistroot="D:/学习/Python/手写数字识别/程序/data/MNIST/raw" train_set = ( mnist.read_image_file(os.path.join(root, 'train-images-idx3-ubyte' )), mnist.read_label_file(os.path.join(root, 'train-labels-idx1-ubyte' )) ) test_set = ( mnist.read_image_file(os.path.join(root, 't10k-images-idx3-ubyte' )), mnist.read_label_file(os.path.join(root, 't10k-labels-idx1-ubyte' )) ) print ("training set :" ,train_set[0 ].size())print ("test set :" ,test_set[0 ].size())def convert_to_img (train=True ): if (train): f=open (root+'train.txt' ,'w' ) data_path=root+'/train/' if (not os.path.exists(data_path)): os.makedirs(data_path) for i, (img,label) in enumerate (zip (train_set[0 ],train_set[1 ])): img_path=data_path+str (i)+'.jpg' io.imsave(img_path,img.numpy()) f.write(img_path+' ' +str (label)+'\n' ) f.close() else : f = open (root + 'test.txt' , 'w' ) data_path = root + '/test/' if (not os.path.exists(data_path)): os.makedirs(data_path) for i, (img,label) in enumerate (zip (test_set[0 ],test_set[1 ])): img_path = data_path+ str (i) + '.jpg' io.imsave(img_path, img.numpy()) f.write(img_path + ' ' + str (label) + '\n' ) f.close() convert_to_img(True ) convert_to_img(False )
通过得到的MNIST训练集与测试集中的图片可以看出,虽然他们收集的手写数字皆为不同人所写,但字体的粗细程度几乎相同,或许是使用了同一种笔来进行数据的收集。 这才使得字体的粗细程度会影响识别精度的情况发生。 根据我的初步理解,要想解决这种情况,得丰富数据库,重新进行深度学习。
结束语 通过这次的学习,进一步了解深度学习,也对卷积神经网络有了一个初步的认识。 这个暑假会开展对Python语言的学习,这里的代码算是一个预习了。