我们每训练10轮,评估一次准确率。当准确率开始下降时(出现了过拟合)终止训练。
1.1 导入相关包

import torch
# torchvision包的主要功能是实现数据的处理,导入和预览等
import torchvision
from torchvision import datasets
from torchvision import transforms
from torch.autograd import Variable

   首先,导入必要的包。对这个手写数字识别问题的解决只用到了torchvision中的部分功能,所以这里通过 from torchvision import方法导入其中的两个子包 datasets和transforms,我们将会用到这两个包。

1.2 获取手写数字的训练集和测试集
  我们就要想办法获取手写数字的训练集和测试集。使用torchvision.datasets可以轻易实现对这些数据集的训练集和测试集的下
载,只需要使用 torchvision.datasets再加上需要下载的数据集的名称就可以了,比如在这个问题中我们要用到手写数字数据集,它的名称是MNIST,那么实现下载的代码就是torchvision.datasets.MNIST。其他常用的数据集如COCO、ImageNet、CIFCAR等都可以通过这个方法快速下载和载入。实现数据集下载的代码如下:

# 首先获取手写数字的训练集和测试集
# root 用于指定数据集在下载之后的存放路径
# transform 用于指定导入数据集需要对数据进行那种变化操作
# train是指定在数据集下载完成后需要载入那部分数据,
# 如果设置为True 则说明载入的是该数据集的训练集部分
# 如果设置为FALSE 则说明载入的是该数据集的测试集部分
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,则说明载入的是该数据集的测试集部分。

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

#数据预览和数据装载
# 下面对数据进行装载,我们可以将数据的载入理解为对图片的处理,
# 在处理完成后,我们就需要将这些图片打包好送给我们的模型进行训练 了  而装载就是这个打包的过程
# dataset 参数用于指定我们载入的数据集名称
# batch_size参数设置了每个包中的图片数据个数
#  在装载的过程会将数据随机打乱顺序并进打包
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)
# print([labels[i] for i in range(64)])
# 由于matplotlab中的展示图片无法显示,所以现在使用OpenCV中显示图片
# plt.imshow(img)
cv2.imshow('win',img)
key_pressed=cv2.waitKey(0)

  在以上代码中使用了iter和next来获取取一个批次的图片数据和其对应的图片标签,然后使用torchvision.utils中的make_grid类方法将一个批次的图片构造成网格模式。需要传递给torchvision.utils.make_grid的参数就是一个批次的装载数据,每个批次的装载数据都是4维的,维度的构成从前往后分别为batch_size、channel、height和weight,分别对应一个批次中的数据个数、每张图片的色彩通道数、每张图片的高度和宽度。在通过torchvision.utils.make_grid之后,图片的维度变成了(channel,height,weight),这个批次的图片全部被整合到了一起,所以在这个维度中对应的值也和之前不一样了,但是色彩通道数保持不变。

  若我们想使用Matplotlib将数据显示成正常的图片形式,则使用的数据首先必须是数组,其次这个数组的维度必须是
(height,weight,channel),即色彩通道数在最后面。所以我们要通过numpy和transpose完成原始数据类型的转换和数据维度的交换,这样才能够使用Matplotlib绘制出正确的图像。

  在完成数据预览的代码中,我们先打印输出了这个批次中的数据的全部标签,然后才对这个批次中的所有图片数据进行显示,代码如下

tensor([7, 1, 8, 3, 2, 3, 6, 7, 3, 9, 7, 3, 0, 0, 7, 5, 0, 0, 2, 5, 4, 4, 2, 7,
        6, 9, 8, 1, 8, 3, 7, 1, 8, 9, 6, 5, 3, 2, 0, 0, 4, 6, 1, 9, 2, 2, 2, 6,
        9, 8, 7, 2, 2, 6, 8, 2, 5, 8, 6, 1, 9, 9, 9, 8])

  效果如下,可以看到输出的首先是64张图片对应的标签,然后是64张图片的预览结果:

图片说明

*1.4 模型搭建和参数优化 *
  在顺利完成数据装载后,我们就可以开始编写卷积神经网络模型的搭建和参数优化的代码了,因为我们想要搭建一个包含了卷积层,激活函数,池化层,全连接层的卷积神经网络来解决这个问题,所以模型在结构上会和之前简单的神经网络有所区别,当然,各个部分的功能实现依然是通过torch.nn中的类来完成的,比如卷积层使用torch.nn.Conv2d类方法来搭建,激活层使用torch.nn.ReLU类来搭建;池化层使用torch.nn.MaxPool2d类方法来搭建;全连接层使用torch.nn.Linear类方法来搭建。

实现卷积神经网络模型搭建的代码如下:

#模型搭建和参数优化
# 在顺利完成数据装载后,我们可以开始编写卷积神经网络模型的搭建和参数优化的代码
#卷积层使用torch.nn.Conv2d类来搭建
# 激活层使用torch.nn.ReLU 类方法来搭建
# 池化层使用torch.nn.MaxPool2d类方法来搭建
# 全连接层使用 torch.nn.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*128)
        x = self.dense(x)
        return x

特征图尺寸=图片说明
注意:卷积后的尺寸不为整数时,向下取整,而池化操作向上取整

因为这个问题并不复杂,所以我们选择搭建一个在结构层次上有所简化的卷积神经网络模型,在结构上使用了两个卷积层:一个最大池化层和两个全连接层。

  最后说一下代码中前向传播forward函数中的内容,首先经过self.conv1进行卷积处理,然后进行x.view(-1,1414128),对参数实现扁平化,因为之后紧接着的就是全连接层,所以如果不进行扁平化,则全连接层的实际输出的参数维度和其定义输入的维度将不匹配,程序将会报错,最后通过self.dense定义的全连接层进行最后的分类。

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

# 在编写完搭建卷积神经网络模型的代码后,我们可以对模型进行训练和参数进行优化了
# 首先 定义在训练之前使用哪种损失函数和优化函数
# 下面定义了计算损失值的损失函数使用的是交叉熵
# 优化函数使用的额是Adam自适应优化算法
model = Model()
cost = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters())

在以上的代码中定义了计算损失值的损失函数使用的是交叉熵,也确定了优化函数使用的是Adam自适应优化算法,需要优化的参数在Model中生成的全部参数,因为没有定义学习效率的值,所以使用默认值,然后通过打印输出的方式查看搭建好的模型的完整结构们只需要print(model)就OK。输出的结果如下:

Model(
  (conv1): Sequential(
    (0): Conv2d(1, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU()
    (2): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU()
    (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (dense): Sequential(
    (0): Linear(in_features=25088, out_features=1024, bias=True)
    (1): ReLU()
    (2): Dropout(p=0.5)
    (3): Linear(in_features=1024, out_features=10, bias=True)
  )
)

  最后,卷积神经网络模型进行模型训练和参数优化的代码如下:

# 卷积神经网络模型进行模型训练和参数优化的代码
n_epochs = 5

for epoch in range(n_epochs):
    running_loss = 0.0
    running_corrrect = 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)
        outputs = 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[0]
        running_corrrect += torch.sum(pred == y_train.data)
    testing_correct = 0
    for data in data_loader_train:
        X_test,y_test = data
        X_test,y_test = Variable(X_test),Variable(y_test)
        outputs = model(X_test)
        _,pred = torch.max(X_test)
        testing_correct += torch.sum(pred == y_test.data)

    print("Loss is {:.4f}, Train Accuracy is:{:.4f}%,Test Accuracy is:{:.4f}"
          .format(running_loss/len(data_train),100*running_corrrect/len(data_train),
                  100*testing_correct/len(data_test)))

深度学习之PyTorch实战(3)——实战手写数字识别