赞
踩
本文主要是了解如何处理数据,并完成一个小型的图像识别项目。首先,介绍Pytorch如恶化加载数据集,以CIFAR-10为例,并基于CIFAR-10完成一个图像分类模型;其次,介绍如何使用GPU加速模型训练;最后,介绍图像领域使用较为广发的ImageNet数据集和一些常用的图像识别模型。
通常来说,当处理图像、文本、语音或者视频数据时,我们可以使用标准Python包将数据加载成Numpy数组格式,然后将这个数组转换成torch.Tensor。
·对于图像,可以用Pillow和OpenCV;
·对于文本,可以直接用Python或Cython基础数据加载模块,或者用NLTK和spaCy;
·对于语音,可以用SciPy和Librosa。
考虑到这一点,为了给开发者提供更加放白你的加载方式,Pytorch已经创建了一个叫作torchvision的包,该包含有支持加载类似ImageNet、CIFAR-10、MNIST等公共数据集的数据加载模块torchvision.datasets和支持加载图像数据的数据转换模块torch.utils.data.DataLoader。者提供了极大的便利,并且避免了编写“模板代码”。
本文内容将使用CIFAR-10数据集,它包含10个类别:plane、car、bird、cat、deer、dog、frog、horse、ship、truck。CIFAR-10中的图像尺寸为3*32*32,也就是RGB的3层颜色通道,图像的宽和高都为32.
1.1 CIFAR-10数据集简介
CIFAR-10数据集共有60000张32*32的RGB彩色图片, 分为10个类别,每个类别有6000张图片。其中训练集图片为50000张,测试集有10000张图片。训练集和测试集的生成方法是,分别从每个类别中随机挑选1000张图片加入测试集,其余图片便加入训练集。与MNIST手写字符数据集比较来看,CIFAR-10数据集是彩色图片,图片内容是真实世界的物体,噪声更大,物体的比例也不一样,所以在识别上比MNIST困难很多。
训练分类器的步骤如下:
(1)使用视觉工具包torchvision加载并且归一化CIFAR-10的训练和测试数据集;
(2)定义一个卷积神经网络;
(3)定义一个损失函数;
(4)在训练样本数据上训练神经网络;
(5)在测试样本数据上测试神经网络。
1.2 加载数据集
在加载CIFAR-10数据集时,推荐使用Pytorch提供的市局工具包torchvision。torchvision可以加载许多视觉数据集,在加载时就完成了归一化的操作。使用torchvision包可以非常方便地构建出DataLoader对象。代码如下:
- import torch
- import torchvision
- import torchvision.transforms as transforms
-
使用torchvision,数据集的输出是范围为[0,1]的PILImage,我们将他们转换成归一化范围为[-1,1]的张量。加载数据集的代码如下:
- transform = transforms.Compose(
- [transforms.ToTensor(),
- transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
-
- trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
- trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2)
-
- testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
- testloader = torch.utils.data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=2)
-
- classes = ('plane', 'car', 'bird', 'cat','deer', 'dog', 'frog', 'horse', 'ship', 'truck')
如果是第一次下载数据集,则会出现进度条。数据集下载完毕后,我们呢来展示一些训练图片,代码如下:
- import matplotlib.pyplot as plt
- import numpy as np
-
- # functions to show an image
-
- def imshow(img):
- img = img / 2 + 0.5 # unnormalize
- npimg = img.numpy()
- plt.imshow(np.transpose(npimg, (1, 2, 0)))
- plt.show()
-
- # get some random training images
- dataiter = iter(trainloader)
- images, labels = dataiter.next()
-
- # show images
- imshow(torchvision.utils.make_grid(images))
- # print labels
- print(' '.join('%5s' % classes[labels[j]] for j in range(4)))

这里的代码运行过程中如果报错的内容和datater.next()有关,改成datater.__next__()就可以。
1.3 定义卷积神经网络
复制MNIST手写数字识别项目的神经网络模块相关的代码,并修改为三通道的图片(在此之前它被定义为单通道),代码如下:
- import torch.nn as nn
- import torch.nn.functional as F
-
-
- class Net(nn.Module):
- def __init__(self):
- super(Net, self).__init__()
- # 输入图片为三通道,输出为六通道,卷积核为5*5
- self.conv1 = nn.Conv2d(3, 6, 5)
- self.pool = nn.MaxPool2d(2, 2)
- self.conv2 = nn.Conv2d(6, 16, 5)
- self.fc1 = nn.Linear(16 * 5 * 5, 120)
- self.fc2 = nn.Linear(120, 84)
- self.fc3 = nn.Linear(84, 10)
-
- def forward(self, x):
- x = self.pool(F.relu(self.conv1(x)))
- x = self.pool(F.relu(self.conv2(x)))
- x = x.view(-1, 16 * 5 * 5)
- x = F.relu(self.fc1(x))
- x = F.relu(self.fc2(x))
- x = self.fc3(x)
- return x
-
-
- net = Net()

将nn.Conv2d的第一个参数从1改成3,这个卷积模块就支持输入三通道的图片,之后就和MNIST手写数字识别项目所使用的模型结构一样了。需要注意的是,这里展示了pooling的另外一种使用方法,把MaxPooling操作定义成一个网络模块,而不像MNIST那样调用函数完成pooling操作。但两者的实现效果是一样的。关于代码中view函数的用法。
2.1.4 定义损失函数和优化器
使用分类交叉熵(CrossEntropy)作为损失函数,支持动量的SGD作为优化器,代码如下:
- import torch.optim as optim
-
- criterion = nn.CrossEntropyLoss()
- optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
这里设置动量为0.9,这也是深度学习中最常用的参数。
2.1.5 训练网络
我们只需要在数据迭代器上循环传给网络和优化器的输入就可以,训练模型代码如下:
- for epoch in range(2): # loop over the dataset multiple times
-
- running_loss = 0.0
- for i, data in enumerate(trainloader, 0):
- # get the inputs
- inputs, labels = data
-
- # zero the parameter gradients
- optimizer.zero_grad()
-
- # forward + backward + optimize
- outputs = net(inputs)
- loss = criterion(outputs, labels)
- loss.backward()
- optimizer.step()
-
- # print statistics
- running_loss += loss.item()
- if i % 2000 == 1999: # print every 2000 mini-batches
- print('[%d, %5d] loss: %.3f' %
- (epoch + 1, i + 1, running_loss / 2000))
- running_loss = 0.0
-
- print('Finished Training')

输出结果如下:
2.1.6 使用测试集评估
我们已经通过训练数据集对网络进行了两次训练,即两个epoch。现在我们来检查一下,这个网络模型是否已经学到了东西。我们将用神经网络的输出作为预测的类别来检查网络的预测性能,用样本的真是类别来校对。如果预测是正确的,我们将样本添加到正确预测的列表里。下面使用2.1.2节中定义的函数,从测试集中选取一些图片。
- dataiter = iter(testloader)
- images, labels = dataiter.__next__()
-
- # print images
- imshow(torchvision.utils.make_grid(images))
- print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))
现在让我们看看,训练好的网络模型认为这些样本应该能预测出什么。神经网络模型的最后一层是一个全连接层,输出的是预测的与10个类别的近似程度,值越大则表示与某一个类别的近似程度越高,网络就越认为图像属于这个类别。打印其中最相似类别的代码:
- outputs = net(images)
- _, predicted = torch.max(outputs, 1)
-
- print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4)))
预测结果如下:
接下来对测试集中的每一张图片都进行预测,并且计算整体的准确率。代码如下:
- correct = 0
- total = 0
- with torch.no_grad():
- for data in testloader:
- images, labels = data
- outputs = net(images)
- _, predicted = torch.max(outputs.data, 1)
- total += labels.size(0)
- correct += (predicted == labels).sum().item()
-
- print('Accuracy of the network on the 10000 test images: %d %%' % (
- 100 * correct / total))
如果模型全预测为一个类别,那么准确率应该是10%左右,而我们训练的模型(两个epoch),准确率为54%。这说明神经网络还是学到了一些东西。为了进行精细化分析,下面看模型在每一个类别上的准确率。代码如下:
- class_correct = list(0. for i in range(10))
- class_total = list(0. for i in range(10))
- with torch.no_grad():
- for data in testloader:
- images, labels = data
- outputs = net(images)
- _, predicted = torch.max(outputs, 1)
- c = (predicted == labels).squeeze()
- for i in range(4):
- label = labels[i]
- class_correct[label] += c[i].item()
- class_total[label] += 1
-
-
- for i in range(10):
- print('Accuracy of %5s : %2d %%' % (
- classes[i], 100 * class_correct[i] / class_total[i]))

输出结果如下:
从输出结果可以清晰看到每个类别的准确率。如果想提高准确率,可以多训练多几个epoch。
2.1.7 使用GPU加速
在pytorch中,如何将模型运行在GPU上呢?就像把 Tensor转移到GPU上一样,我们只需要把神经网络模块也转移到GPU上即可。具体做法是,如果GPU能用,表示CUDA有(一种并行计算机平台和编程模型,它通过利用图形处理器GPU的处理能力,可大幅度提升计算性能)可用,则取得CUDA的设备标识。
- device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
-
- # Assume that we are on a CUDA machine, then this should print a CUDA device:
-
- print(device)
接着使用下面的命令将神经网络模块移到CUDA设备上,方法to会递归遍历网络中的所有模块,并将他们的参数(parameters)和缓存(buffers)转换为CUDA 张量。
net.to(device)
需要注意的是,当神经网络移到CUDA设备后,输入到网络中的张量也需要先一道CUDA设备上。因为Pytorch只能在同一设备上作矩阵操作。
inputs,labels = inputs.to(device),labels.to(device)
不过对于本文所定义的网络,因为其太小,所以用GPU加速效果可能不太明显。
二、 ImageNet和图像识别模型
ImageNet数据集是计算机视觉领域使用最广泛的数据集,基于这个数据集诞生了很多优秀的图像识别模型,推动了计算机视觉的发展。
2.2.1 ImageNet
ImageNet是一个大型图像数据集,用于拓展和改进可用于训练AI算法的数据。ImageNet包含了1400W张带有标注信息的图片,比如,图片的类别,图片中目标的边界框。对比CIFAR-10数据集,ImageNet的图片数量更多,分辨率也更高,包含了2万多个类别,几乎涵盖了生活中大部分物体,常见的如气球、草莓等,每个类别包含了数百万张图片。因为这些特点,从2010年到2017年,ImageNet项目每年都会举办依次大型的计算机视觉挑战赛,研究团队需要在给定的数据集上评估他们的算法,并在几项视觉识别任务上争夺更高的准确率。
2.2.2 基于ImageNet的图像识别模型
AlexNet是2012年ImageNet挑战赛的冠军模型。网络模型如下图,AlexNet可以分为上下两个部分,经过卷积得到特征图(feature map)后,模型分别经过上下两个子网络进行计算。总共有五个卷积层,分别是1个11*11,1个5*5,3个3*3卷积,部分卷积层后面使用了池化层,最后经过3个全连接层。AlexNet的贡献主要在于它的网络深度很深,证明了卷积神经网络在复杂模型里也有效,并且使用了GPU训练这一复杂模型,可以把时间控制在人类可以接收范围内,此外还是用了Dropout等技术。
在2014年imageNet挑战赛上,VGGNet深度学习网络成为人们关注的热点,因为它将AlexNet模型的错误率少了一半以上。从下图也可以看出,VGGNet学习网络的特点是连接的卷积层特别多。这里简单解释一下下图的含义。比如,conv3-64是指使用了3*3的卷积,通道为64。同理,conv1-256的含义是1*1的卷积,通道数是256。最大池化表示为maxpool,层与层之间使用maxpool分开;全连接层表示为“FC-神经元个数”,例如FC-4096表示包含4096个神经元的全连接层;最后是softmax层。VGG网络的特点:结构简洁;小卷积核;小池化核;通道数更多,特征度更宽;层数更深;全连接转卷积;模型参数:A、A-LRN、B、C、D、E这6种网络结构的深度虽然从11层增加至19层,但参数量变化不大,这是由于基本上都是采用了小卷积核,参数主要集中在全连接层。
时间来到2015年,深度残差网络(Deep Residual Network,ResNet)赢得了ImageNet挑战赛的冠军。这个模型比以往的所有模型都要深,它可以训练100层,甚至更多,1000层,错误率是VGGNet的一半左右,VGGNet的错误率为7%,ResNet为3.57%,并且ResNet的正确率首次超过了人类。在梯度反向传播的过程中,随着网络增多,从最后一层反转到前面网络层的梯度会越来越小,所以网络层越多,模型越不好训练,而深度残差网络打破了这一魔咒。深度残差网络通过使用跳过连接(skip connection)使得梯度可以无损地向后传播,这样就可以训练深层模型了。如图。
三、 总结
本文从一个图像分类器入手,首先,基于CIFAR-10数据集介绍了如何使用Pytorch处理数据;其次,介绍了如何使用GPU来加快模型的训练速度,完整地完成了一个图像分类项目;最后,介绍了计算机视觉领域使用较广泛的ImageNet数据集,以及三个经典的深度学习网络模型。
参考资料:
pytorch教程:21个项目玩转pytorch实战
感谢补充本文知识内容的链接的作者
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。