【学习笔记】实战手写数字识别

搭建简单的卷积神经网络实现手写数字图片识别。
1. torch和torchvision
在Pytorch中有两个核心的包,分别是torch和torchvision。torchvision包主要功能是实现数据的处理、导入和预览等,在处理计算机视觉问题时可以使用torchvision包中的类完成相应工作。导包部分如下:

import torch
from torchvision import datasets, transforms
from torch.autograd import Variable

之后,需要获取手写数字的训练集和测试集。使用torchvision.datasets可以实现对本次需要数据集的训练集和测试集的下载。常用数据及如MNIST,COCO,ImageNet,CIFCAR等都可以通过这个方法快速下载和载入,代码如下:

data_train = datasets.MNIST(root = "./data/",
							transform = transform,
							train = True,
							download = True)
							
data_test = datasets.MNIST(root = "./data/",
							transform = transform,
							train = False)

其中root用于指定数据集在下载之后的存放路径,这里放在根目录下的data文件夹中;transform用于指定导入数据集时需要对数据进行哪种变换操作;train用于指定在数据集下载完成后需要载入哪部分数据,如果为True则说明载入的是训练集部分,如果为False说明载入的是测试集部分。
2.torch.transforms
在transforms中有大量的数据变换类,其中很大一部分可以用于实现数据增强(对图片放大,缩小,水平或垂直翻转等变换)。对手写数据集数据进行载入及相应变化的代码为:

transform = transforms.Compose([transforms.ToTensor(),
								transforms.Normalize(mean=[0.5,0.5,0.5],std=[0.5,0.5,0.5])])

可以将以上代码中compose类看作一种容器,他能够同时对多种数据变换进行组合。传入的参数是一个列表,列表中的元素就是对载入的数据进行的各种变换操作。在以上代码中,compose里只使用了一个类型的转换变换transforms.ToTensor和一个数据标准化变换transforms.Normalize。这个标准化变换也叫标准差变换法,使用原始数据的均值和标准差来进行数据的标准化,再经过标准化变换之后,数据全部符合均值为0、标准差为1的标准正态分布。计算公式如下: x n o r m a l = x − m e a n s t d x^{normal}=\frac{x-mean}{std} xnormal=stdx−mean​

torchvision.transforms中常用的数据变换操作:
(1)torchvision.transforms.Resize:
用于对载入图片数据大小进行缩放。传递给这个类的参数可以是一个整型数据,也可以是类似(h,w)的序列,其中h为高度w为宽度,但如果使用的是一个整型数据,则表示缩放的宽度和高度都是这个整型数据的值(变为正方形)。
(2)torchvision.transforms.Scale:
对载入的图片数据大小进行缩放,用法与Resize类似
(3)torchvision.transforms.CenterCrop:
用于对载入的图片以图片中心为参考点,按照需要的大小进行裁剪。传递给这个类的参数可以是一个整型数据,也可以是类似(h,w)的序列。
(4)torchvision.transforms.RandomCrop:
用于对载入的图片按照需要的大小进行随机裁剪。传递给这个类的参数可以是一个整型数据,也可以是类似(h,w)的序列。
(5)torchvision.transforms.RandomHorizontalFlip:
用于对载入的图片按随机概率进行水平翻转。可以通过传递给这个类的参数自定义随机概率,如果未定义则默认值为0.5
(6)torchvision.transforms.RandomVerticalFlip:
用于对载入的图片按随机概率进行垂直翻转。可以通过传递给这个类的参数自定义随机概率,如果未定义则默认值为0.5
(7)torchvision.transforms.ToTenser:
用于对载入的图片数据进行类型转换,将之前构筑成PIL图片的数据转换成Tensor数据类型的变量,让Pytorch能够对其进行计算和处理。
(8)torchvision.transforms.ToPILImage:
用于将Tensor变量的数据转换成PIL图片数据,方便图片内容的显示。

3.数据预览和数据装载
完成数据下载和载入后,我们还需要对数据进行装载。我们可以将数据的载入理解为对图片的处理,在处理完成后,我们就需要将这些图片打包好送给我们的模型进行训练,而装载就是这个打包的过程。在装载时通过batch_size的值来确认每个包的大小,通过shuffle的值来确认是否在装载的过程中打乱图片的顺序。装载图片的代码如下:

data_loader_train = torch.utils.data.DataLoader(dataset = data_train,
												batch_size = 64,
												shuffle = True)
data_loader_test = torch.utils.data.DataLoader(dataset = data_test,
												batch_size = 64,
												shuffle = True)

对数据的装载使用的是torch.utils.data.DataLoader类,类中的dataset参数用于指定我们载入的数据集名称,batch_size参数设置了每个包中的图片数据个数,代码中的值是64,所以在每个包中会包含64张图片。将shuffle参数设置为True,在装载的过程会将数据随机打乱顺序并进行打包。
在装载完成后,我们可以选取其中一个批次的数据进行预览。进行数据预览的代码如下:

images,labels = next(iter(data_loader_train))
img = torchvision.utils.make_grid(images)
img = img.numpy().transpose(1,2,0)
std = [0.5,0.5,0.5]
mean = [0.5,0.5,0.5]
img = img*std + mean
print([labels[i] for i in range(64)])
plt.imshow(img)

以上代码中使用了iter和next来获取一个批次的图片数据和其对应的图片标签,然后使用torchvision.utils中的make_grid类方法将一个批次的图片构造呈网格模式。需要传递给make_grid的参数就是一个批次的装载数据,每个批次的装载数据都是4维的,维度的构成从前往后分别为batch_size,channel,height,weight,即一个批次中的数据个数,每张图片的色彩通道数,每张图片的高度和宽度。通过make_grid之后图片的维度变成了(channel,height,weight),这个批次的图片全部被整合到了一起。
若想使用Matplotlib将数据显示成正常的图片形式,则使用的数据首先必须是数组,其次这个数组的维度必须是(height,weight,channel)。所以通过numpy和transpose完成原始数据类型的转换和数据维度的交换,这样才能够使用Matplotlib绘制出正确的图像。

4.模型搭建和参数优化
搭建一个包含卷积层,激活函数,池化层,全连接层的卷积神经网络,通过torch.nn中的类实现。卷积层使用torch.nn.Conv2d;激活层ReLU;池化层使用MaxPool2d;全连接层使用Linear。搭建代码如下:

class Model(torch.nn.Module):

	def __init__(self):
		super(Model,self).__init__()

		self.conv1 = torch.nn.Sequential(
					torch.nn.Conv2d(1,64,kernel_size=3,stride=1,padding=1),
					torch.nn.ReLU(),
					torch.nn.Conv2d(64,128,kernel_size=3,stride=1,padding=1),
					torch.nn.ReLU(),
					torch.nn.MaxPool2d(stride=2,kernel_size=2))
		
		self.dense=torch.nn.Sequential(
					torch.nn.Linear(14*14*128,1024),
					torch.nn.ReLU(),
					torch.nn.Dropout(p=0.5),
					torch.nn.Linear(1024,10))
	def forward(self,x):
		x = self.conv1(x)
		x = x.view(-1,14*14*1024)
		x = self.dense(x)
		return x

网络结构上使用了两个卷积层:一个最大池化层和两个全连接层。
(1)torch.nn.Conv2d:
用于搭建卷积神经网络的卷积层,主要参数有输入通道数,输出通道数,卷积核大小,卷积核移动步长和Padding的值。其中每个参数的数据类型都是整形。卷积核移动步长用于确定卷积核每次滑动的步长,Padding为0时表示不进行边界像素的填充,如果值大于0,那么增加数字所对应的边界像素层数。
(2)torch.nn.MaxPool2d:
用于实现卷积神经网络中的最大池化层,主要输入参数是池化窗口的大小,池化窗口移动步长和Padding的值。
(3)torch.nn.Dropout:
用于防止卷积神经网络在训练过程中发生过拟合,其工作原理就是在模型训练过程中,以一定的随机概率将卷积神经网络模型的部分参数归零,以达到减少相邻两层神经连接的目的。这样做是为了最后训练出来的模型对各部分的权重参数不产生过度依赖,从而防止过拟合。可以设置随机概率值,不设置默认0.5.

在前向传播forward函数中,首先经过self.conv1进行卷积处理;然后进行x.view(-1,1414128),对参数实现扁平化,因为紧接着是全连接层没如果不进行扁平化,则全连接层的实际输出的参数维度和其定义输入的维度将不匹配。最后经过self.dense定义的全连接进行最后的分类。

完成搭建卷积神经网络的代码后,可以对模型进行训练和优化了。首先,定义在训练之前使用哪种损失函数和优化函数:

model = Model()
cost = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters())
print(model)

以上代码中定义了计算损失值的损失函数使用交叉熵,优化函数使用Adam。需要优化的参数是model中的全部参数,学习速率是默认值。
最后,卷积神经网络进行模型训练和参数优化的代码如下:

n_epochs = 5
for epoch in range(n_epochs):
	running_loss = 0.0
	running_correct = 0
	print("Epoch:{}/{}".format(epoch,n_epochs))
	print("-"*10)
	for data in data_loader_train:
		X_train, y_train = data
		X_train, y_train = Variable(X_train),Variable(y_train)
		outpus = model(X_train)
		_,pred = torch.max(outputs.data,1)
		optimizer.zero_grad()
		loss = cost(outputs,y_train)
		
		loss.backward()
		optimizer.step()
		running_loss += loss.data
		running_correct += torch.sum(pred == y_train.data)
	testing_correct = 0
	for data in data_loader_test:
		X_test,y_test = data
		X_test,y_test = Variable(X_test),Variable(y_test)
		outputs = model(X_test)
		_,pred = torch.max(outputs.data,1)
		testing_correct += torch.sum(pred==y_test.data)
	print("Loss:{:.4f},Train Accuracy is:{:.4f}%,Test Accuracy is:{:.4f}".format(running_loss/len(data_train),100*running_correct/len(data_train),100*testing_correct/len(data_test)))

每轮训练完成时,会使用测试集验证模型的泛化能力并计算准确率。之后可以利用训练好的模型进行预测并对结果进行可视化,代码如下:

data_loader_test = torch.utils.data.DataLoader(dataset=data_test,batch_size = 4,shuffle = True)
X_test,y_test = next(iter(data_loader_test))
inputs = Variable(X_test)
pred = model(inputs)
_,pred = torch.max(pred,1)

print("Predict Lable is:",[i for i in pred.data])
print("Real Lable is:",[i for i in y_test])

img = torchvision.utils.make_grid(X_test)
img = img.numpy().transpose(1,2,0)

std =[0.5,0.5,0.5]
mean = [0.5,0.5,0.5]
img = img*std+mean
plt.imshow(img)
上一篇:PyTorch实现Mnist数据集


下一篇:ConvertJSONDateToJSDateObject 方法实现json格式时间串转换为 对应的时间格式串