pytorch编程练习 GPU手写体字符识别

pytorch_GPU

    • 1. 超参数的确定
    • 2. 模型的训练和评估
    • 3. 代码
      • 3.1 导入相关的包
      • 3.2 获取手写数字的训练集和测试集
      • 3.3 模型搭建和参数优化
      • 3.4 整合后的代码

本文将在PyTorch中构建一个简单的卷积神经网络,并使用MNIST数据集训练它识别手写数字。在MNIST数据集上训练分类器可以看作是图像识别的“hello world”。

  • MNIST包含70,000张手写数字图像:
    60,000张用于培训,10,000张用于测试。图像是灰度的,28x28像素的,并且居中的,以减少预处理和加快运行。
    pytorch编程练习 GPU手写体字符识别_第1张图片
    pytorch编程练习 GPU手写体字符识别_第2张图片

1. 超参数的确定

首先需要确定网络的层数和每层的节点数。关于第一个问题,实际上并没有什么理论化的方法,大家都是根据经验来拍,如果没有经验的话就随便设一个。然后可以多试几个值,训练不同层数的神经网络,看看哪个效果最好就用哪个。嗯,现在可能明白为什么说深度学习是个手艺活了,有些手艺很让人无语,而有些手艺还是很有技术含量的。

不过,有些基本道理,都知道网络层数越多越好,也知道层数越多训练难度越大。对于全连接网络,隐藏层最好不要超过三层。那么,可以先试试仅有一个隐藏层的神经网络效果怎么样。毕竟模型小的话,训练起来也快些(刚开始玩模型的时候,都希望快点看到结果)。

输入层节点数是确定的。因为MNIST数据集每个训练数据是28*28的图片,共784个像素,因此,输入层节点数应该是784,每个像素对应一个输入节点。

输出层节点数也是确定的。因为是10分类, 可以用10个节点,每个节点对应一个分类。输出层10个节点中,输出最大值的那个节点对应的分类,就是模型的预测结果。

隐藏层节点数量是不好确定的,从1到100万都可以。下面有几个经验公式:
  pytorch编程练习 GPU手写体字符识别_第3张图片
因此,可以先根据上面的公式设置一个隐藏层节点数。如果有时间,也可以设置不同的节点数,分别训练,看看哪个效果最好就用哪个。 设隐藏层节点数为300 。

对于3层78430010的全连接网络,总共有300*(784+1)+10*(300+1)=238510个参数!神经网络之所以强大,是它提供了一种非常简单的方法去实现大量的参数。目前百亿参数、千亿样本的超大规模神经网络也是有的。因为MNIST只有6万个训练样本,参数太多了很容易过拟合,效果反而不好。

2. 模型的训练和评估

MNIST数据集包含10000个测试样本。 先用60000个训练样本训练我们的网络,然后再用测试样本对网络进行测试,计算识别错误率:
  pytorch编程练习 GPU手写体字符识别_第4张图片
每训练10轮,评估一次准确率。当准确率开始下降时(出现了过拟合)终止训练。

3. 代码

3.1 导入相关的包

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

import torch
import torch.nn as nn
import torchvision.datasets as normal_datasets
import torchvision.transforms as transforms
from torch.autograd import Variable

3.2 获取手写数字的训练集和测试集

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

# 首先获取手写数字的训练集和测试集
# root 用于指定数据集在下载之后的存放路径
# transform 用于指定导入数据集需要对数据进行那种变化操作
# train是指定在数据集下载完成后需要载入那部分数据,
# 如果设置为True 则说明载入的是该数据集的训练集部分
# 如果设置为FALSE 则说明载入的是该数据集的测试集部分
# 从torchvision.datasets中加载数据集
train_dataset = normal_datasets.MNIST(
    root='./mnist/',  # 数据集保存路径
    train=True,  # 是否作为训练集
    transform=transforms.ToTensor(),  # 数据处理
    download=True)  # 下载数据

其中,root用于指定数据集在下载之后的存放路径,这里存放在根目录下的data文件夹中;transform用于指定导入数据集时需要对数据进行哪种变换操作,在后面会介绍详细的变换操作类型,注意,要提前定义这些变换操作;train用于指定在数据集下载完成后需要载入哪部分数据,如果设置为True,则说明载入的是该数据集的训练集部分;如果设置为False,则说明载入的是该数据集的测试集部分。

3.3 模型搭建和参数优化

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

卷积神经网络CNN的结构一般包含这几层:
 
    输入层:用于数据的输入
 
    卷积层:使用卷积核进行特征提取和特征映射
 
    激励层:由于卷积也是一种线性运算,因此需要增加非线性映射
 
    池化层:进行下采样,对特征图稀疏处理,减少特征信息的损失
 
    输出层:用于输出结果

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

# 两层卷积
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        # 使用序列工具快速构建
        self.conv1 = nn.Sequential(
            nn.Conv2d(1, 16, kernel_size=5, padding=2),
            nn.BatchNorm2d(16),
            nn.ReLU(),
            nn.MaxPool2d(2))
        self.conv2 = nn.Sequential(
            nn.Conv2d(16, 32, kernel_size=5, padding=2),
            nn.BatchNorm2d(32),
            nn.ReLU(),
            nn.MaxPool2d(2))
        self.fc = nn.Linear(7 * 7 * 32, 10)

    def forward(self, x):
        out = self.conv1(x)
        out = self.conv2(out)
        out = out.view(out.size(0), -1)  # reshape,扁平化
        out = self.fc(out)
        return out

对模型进行训练和对参数进行优化了。首先,定义在训练之前使用哪种损失函数和优化函数:

# 定义了计算损失值的损失函数使用的是交叉熵
# 优化函数使用的额是Adam自适应优化算法
cnn = CNN()
if torch.cuda.is_available():
    cnn = cnn.cuda()

# 选择损失函数和优化方法
loss_func = nn.CrossEntropyLoss()#损失函数
optimizer = torch.optim.Adam(cnn.parameters(), lr=learning_rate)#优化

3.4 整合后的代码

import torch
import torch.nn as nn
import torchvision.datasets as normal_datasets
import torchvision.transforms as transforms
from torch.autograd import Variable

num_epochs = 5
batch_size = 100
learning_rate = 0.001


# 将数据处理成Variable, 如果有GPU,转成cuda形式
def get_variable(x):
    x = Variable(x)
    return x.cuda() if torch.cuda.is_available() else x


# 从torchvision.datasets中加载数据集
train_dataset = normal_datasets.MNIST(
    root='./mnist/',  # 数据集保存路径
    train=True,  # 是否作为训练集
    transform=transforms.ToTensor(),  # 数据处理
    download=True)  # 下载数据

# 数据加载器和batch
test_dataset = normal_datasets.MNIST(root='./mnist/',
                                     train=False,
                                     transform=transforms.ToTensor())

train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
                                           batch_size=batch_size,
                                           shuffle=True)

test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
                                          batch_size=batch_size,
                                          shuffle=False)


# 两层卷积
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        # 使用序列工具快速构建
        self.conv1 = nn.Sequential(
            nn.Conv2d(1, 16, kernel_size=5, padding=2),
            nn.BatchNorm2d(16),
            nn.ReLU(),
            nn.MaxPool2d(2))
        self.conv2 = nn.Sequential(
            nn.Conv2d(16, 32, kernel_size=5, padding=2),
            nn.BatchNorm2d(32),
            nn.ReLU(),
            nn.MaxPool2d(2))
        self.fc = nn.Linear(7 * 7 * 32, 10)

    def forward(self, x):
        out = self.conv1(x)
        out = self.conv2(out)
        out = out.view(out.size(0), -1)  # reshape
        out = self.fc(out)
        return out


cnn = CNN()
if torch.cuda.is_available():
    cnn = cnn.cuda()

# 选择损失函数和优化方法
loss_func = nn.CrossEntropyLoss()#损失函数
optimizer = torch.optim.Adam(cnn.parameters(), lr=learning_rate)#优化

for epoch in range(num_epochs):
    for i, (images, labels) in enumerate(train_loader):
        images = get_variable(images)
        labels = get_variable(labels)

        outputs = cnn(images)
        loss = loss_func(outputs, labels)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        if (i + 1) % 100 == 0:
            print('Epoch [%d/%d], Iter [%d/%d] Loss: %.4f'
                  % (epoch + 1, num_epochs, i + 1, len(train_dataset) // batch_size, loss.item()))

# Save the Trained Model
torch.save(cnn.state_dict(), 'cnn.pkl')

结果:
pytorch编程练习 GPU手写体字符识别_第5张图片

你可能感兴趣的:(python)