参考的是唐进民的《深度学习之PyTorch实战计算机视觉》6.4部分,代码部分按照原书的来会有报错,本文给出的是修改后的可以完整编译的代码。
参考书本学习过程中出现的一些报错以及修改思路可以参考PyTorch实战手写数字识别。
现在来进行一个基于 PyTorch 框架使用神经网络实战手写数字识别的实例:
torch和torchvision是 PyTorch 中的两个核心的包。我们之前已经接触了 torch包的一部分内容,比如使用了 torch.nn 中的线性层加激活函数配合 torch.optim 完成了神经网络模型的搭建和模型参数的优化,并使用了 torch.autograd 实现自动梯度的功能(见专栏人工智能实例),接下来会介绍如何使用 torch.nn 中的类来搭建卷积神经网络。
torchvision 包的主要功能是实现数据的处理、导入和预览等,所以如果需要对计算机视觉的相关问题进行处理,就可以借用在 torchvision 包中提供的大量的类来完成相应的工作。以下是torchvision的构成:
'''导入必要的包'''
import torch
from torchvision import datasets, transforms #此实例只用到了 torchvision 中 datasets, transforms 这两个子包
from torch.autograd import Variable
在 torch.transforms 中有大量的数据变换类,其中有很大一部分可以用于实现数据增强(Data Argumentation)。若在我们需要解决的问题上能够参与到模型训练中的图片数据非常有限,则要通过对有限的图片数据进行各种变换,来生成新的训练集,这些变换可以是缩放、水平或者垂直翻转等,都是数据增强的方法。
不过在手写数字识别的问题上可以不使用数据增强的方法,因为可用于模型训练的数据已经足够了。
在 torch.transforms 中提供了丰富的类对载入的数据进行变换,比如数据类型转换、归一化和大小缩放等。在此实例中,我们需要将图片类型转换为 Tensor 类型,且
'''定义transform'''
transform = transforms.Compose([transforms.ToTensor(),
transforms.Lambda(lambda x: x.repeat(3,1,1)),
transforms.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))
])
上面的代码中:
使用 torchvision.datasets + 需要下载的数据集的名称 可以轻易实现对这些数据集的训练集和测试集的下载,比如手写数字数据集的名称是 MNIST,那么代码就是 torchvision.datasets.MNIST。其他常用的数据集(如 COCO、ImageNet、CIFCAR 等)同理。
'''实现数据集下载'''
data_train = datasets.MNIST(root = "./data/",
train = True,
transform = transform,
download = True)
data_test = datasets.MNIST(root="./data/",
train = False,
transform = transform,)
在数据下载完成并且载入(对图片的处理)后,我们还需要对数据进行装载(处理完成后,将这些图片打包好送给我们的模型进行训练,装载就是这个打包的过程)。
'''使用 torch.utils.data.DataLoader 类对数据装载'''
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)
'''选取其中一个批次的数据进行预览'''
images, labels = next(iter(data_loader_train)) #使用 iter 和 next 获取其中一个批次的图片数据及标签
img = torchvision.utils.make_grid(images) # make_grid 类方法将一个批次的图片构造成网格模式
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)
若我们想使用 Matplotlib 将数据显示成正常的图片形式,则使用的数据首先必须是数组,其次这个数组的维度必须是(height,weight,channel),即色彩通道数在最后面。所以我们要通过 numpy 和 transpose 完成原始数据类型的转换和数据维度的交换,这样才能够使用 Matplotlib 绘制出正确的图像。
上面代码编译之后的结果是先打印输出了这个批次中的 64 张图片对应的全部标签,然后才对这个批次中的所有图片数据进行显示:
[tensor(2), tensor(9), tensor(8), tensor(8), tensor(5), tensor(0), tensor(1), tensor(3), tensor(0), tensor(7), tensor(0), tensor(1), tensor(5), tensor(1), tensor(7), tensor(3), tensor(8), tensor(7), tensor(1), tensor(8), tensor(9), tensor(0), tensor(0), tensor(5), tensor(7), tensor(4), tensor(1), tensor(6), tensor(0), tensor(8), tensor(7), tensor(9), tensor(9), tensor(5), tensor(6), tensor(4), tensor(3), tensor(7), tensor(4), tensor(4), tensor(7), tensor(7), tensor(2), tensor(3), tensor(8), tensor(0), tensor(8), tensor(6), tensor(3), tensor(6), tensor(6), tensor(1), tensor(7), tensor(3), tensor(3), tensor(9), tensor(8), tensor(1), tensor(2), tensor(1), tensor(9), tensor(9), tensor(7), tensor(4)]
已经顺利完成了数据装载,可以开始编写卷积神经网络模型的搭建和参数优化的代码了。这个模型包含了卷积层、激活函数、池化层、全连接层(介绍在之前的深度学习计算机视觉理论基础(PyTorch)已经讲过了),所以在结构上会和之前搭建复杂神经网络同时优化参数有所区别。
不过各个部分的功能实现依然是通过 torch.nn 中的类来完成的,比如:
'''搭建一个在结构层次上有所简化的卷积神经网络模型'''
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
用于搭建卷积神经网络的卷积层,主要的输入参数(都是整型)有:
用于实现卷积神经网络中的最大池化层,主要的输入参数是池化窗口大小、池化窗口移动步长和 Paddingde 值(都是整型)。
torch.nn.Dropout 类用于防止卷积神经网络在训练的过程中发生过拟合,其工作原理简单来说就是在模型训练的过程中,以一定的随机概率将卷积神经网络模型的部分参数归零,以达到减少相邻两层神经连接的目的,如图所示:
打叉的神经节点就是被随机抽中并丢弃的神经连接,正是因为选取方式的随机性,所以在模型的每轮训练中选择丢弃的神经连接也是不同的,这样做是为了让我们最后训练出来的模型对各部分的权重参数不产生过度依赖,从而防止过拟合。
对于torch.nn.Dropout 类,我们可以对随机概率值的大小进行设置,如果不做任何设置,就使用默认的概率值 0.5。
过程如下:
首先,定义在训练之前使用哪种损失函数和优化函数(因为没有定
义学习速率的值,所以使用默认值):
'''定义在训练之前使用哪种损失函数和优化函数'''
model = Model()
cost = torch.nn.CrossEntropyLoss() #计算损失值的损失函数使用的是交叉熵
optimizer = torch.optim.Adam(model.parameters()) #优化函数使用的是 Adam 自适应优化算法,需要优化的参数是在 Model 中生成的全部参数
print(model) #查看搭建好的模型的完整结构
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, inplace=False)
(3): Linear(in_features=1024, out_features=10, bias=True)
)
)
卷积神经网络模型进行模型训练和参数优化的代码如下:
'''模型训练和参数优化'''
n_epochs = 5
for epoch in range(n_epochs):
running_loss = 0.0
running_correct = 0
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
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("Epoch {}: loss = {:.4f}, train accuracy = {:.4f}%, test accuracy = {:.4f}".format(
epoch, running_loss/len(data_train),
100*running_correct/len(data_train),
100*testing_correct/len(data_test)))
Epoch 0: loss = 0.0007, train accuracy = 98.5133%, test accuracy = 98.4100
Epoch 1: loss = 0.0005, train accuracy = 98.9283%, test accuracy = 98.5900
Epoch 2: loss = 0.0004, train accuracy = 99.3133%, test accuracy = 98.8300
Epoch 3: loss = 0.0003, train accuracy = 99.3667%, test accuracy = 98.7800
Epoch 4: loss = 0.0002, train accuracy = 99.5633%, test accuracy = 98.7800
为了验证我们训练的模型是不是真的已如结果显示的一样准确,则最好的方法就是随机选取一部分测试集中的图片,用训练好的模型进行预测,看看和真实值有多大的偏差,并对结果进行可视化。
data_loader_test = torch.utils.data.DataLoader(dataset=data_test,
batch_size=64,
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 Label is:')
for i in range(len(pred.data)):
print(pred.data[i], end=' ')
if (i+1) % 8 == 0:
print('\n')
print('Real Label is:')
for i in range(len(y_test)):
print(y_test.data[i], end=' ')
if (i+1) % 8 == 0:
print('\n')
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)
test_correct = 0
for i in range(len(pred)):
if pred.data[i]==y_test.data[i]:
test_correct += 1
print('test_correct:{:.4f}%'.format(100*test_correct/len(pred)))
Predict Label is:
tensor(5) tensor(9) tensor(1) tensor(1) tensor(1) tensor(1) tensor(2) tensor(7)
tensor(9) tensor(8) tensor(1) tensor(3) tensor(1) tensor(1) tensor(8) tensor(2)
tensor(8) tensor(5) tensor(5) tensor(9) tensor(7) tensor(4) tensor(0) tensor(0)
tensor(5) tensor(4) tensor(2) tensor(2) tensor(1) tensor(7) tensor(8) tensor(8)
tensor(1) tensor(2) tensor(2) tensor(3) tensor(2) tensor(7) tensor(2) tensor(8)
tensor(9) tensor(8) tensor(5) tensor(8) tensor(1) tensor(5) tensor(8) tensor(8)
tensor(5) tensor(0) tensor(8) tensor(4) tensor(0) tensor(4) tensor(1) tensor(1)
tensor(6) tensor(8) tensor(4) tensor(6) tensor(3) tensor(8) tensor(5) tensor(9)
Real Label is:
tensor(5) tensor(5) tensor(1) tensor(1) tensor(1) tensor(1) tensor(2) tensor(7)
tensor(9) tensor(8) tensor(1) tensor(3) tensor(1) tensor(1) tensor(8) tensor(2)
tensor(8) tensor(5) tensor(5) tensor(9) tensor(7) tensor(4) tensor(0) tensor(0)
tensor(5) tensor(4) tensor(2) tensor(2) tensor(1) tensor(7) tensor(8) tensor(8)
tensor(1) tensor(2) tensor(2) tensor(3) tensor(2) tensor(7) tensor(2) tensor(8)
tensor(9) tensor(8) tensor(5) tensor(8) tensor(1) tensor(5) tensor(8) tensor(8)
tensor(5) tensor(0) tensor(8) tensor(4) tensor(0) tensor(4) tensor(1) tensor(1)
tensor(6) tensor(8) tensor(4) tensor(6) tensor(3) tensor(8) tensor(5) tensor(9)
test_correct:98.4375%