【PyTorch官方教程中文版学习笔记04】PyTorch 图像分类器


前言:

  通常来说,当你处理图像,文本,语音或者视频数据时,你可以使用标准 python 包将数据加载成
  numpy 数组格式,然后将这个数组转换成 torch.*Tensor

  • 对于图像,可以用 Pillow,OpenCV
  • 对于语音,可以用 scipy,librosa
  • 对于文本,可以直接用 Python 或 Cython 基础数据加载模块,或者用 NLTK 和 SpaCy

对于视觉,我们已经创建了一个叫做 totchvision 的包,该包含有支持加载类似Imagenet,CIFAR10,MNIST 等公共数据集的数据加载模块torchvision.datasets 和支持加载图像数据数据转换模块 torch.utils.data.DataLoader。

torchvision.models库提供了众多经典的网络结构与预训练模型,例如VGG、ResNet和Inception等,利用这些模型可以快速搭建物体检测网络,不需要逐层手动实现。

详见torchvision — Torchvision 0.12 documentation (pytorch.org)

torchvision包与PyTorch相独立,需要通过pip指令进行安装。

pip install torchvision -i https://pypi.tuna.tsinghua.edu.cn/simple #对比发现,使用清华源下载torchvision包相对来说是最快的

对于本教程,我们将使用CIFAR10数据集,它包含十个类别:‘airplane’, ‘automobile’, ‘bird’, ‘cat’,
‘deer’, ‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’。CIFAR-10 中的图像尺寸为3*32*32,也就是RGB的3层颜色
通道,每层通道内的尺寸为32*32。

 Step1: 加载数据集CIFAR10

  • 引用到的工具包
1 import torch
2 import torchvision
3 import torchvision.transforms as transforms
4 import matplotlib.pyplot as plt
5 import numpy as np
  • 加载并归一化 CIFAR10 
 1 transform = transforms.Compose(
 2 [transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
 3     #transforms.Compose():即组合几个变换方法,按顺序变换相应数据,对应于问题描述中代码,即先应用ToTensor()使[0-255]变换为[0-1],再应用Normalize自定义标准化
 4     #transforms.ToTensor():转换一个PIL库的图片或者numpy的数组为tensor张量类型;转换从[0,255]->[0,1];原理即各值除以255
 5     #transforms.Normalize():通过平均值和标准差来标准化一个tensor图像,公式为:output[channel] = (input[channel] - mean[channel]) / std[channel]
 6                         #由于ToTensor()已经将图像变为[0,1],我们使其变为[-1,1]
 7                         #第一个(0.5,0.5,0.5) 即三个通道的平均值
 8                         #第二个(0.5,0.5,0.5) 即三个通道的标准差值
 9                         #ps:三个通道是因为CIFAR-10 中的图像尺寸为3*32*32
10                         #以第一个通道为例,将最大与最小值代入公式
11                                 #(0-0.5)/0.5=-1
12                                 #(1-0.5)/0.5=1
13 
14 #训练集
15 trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
16 download=True, transform=transform)
17     #torchvision.datasets.CIFAR10 就指定了 CIFAR-10 这个数据集,这个模块定义了它如何去下载数据集,及如何从本地加载现成的数据。
18     #root 指定了数据集存放的位置,train 指定是否是训练数据集。
19 trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
20 shuffle=True, num_workers=2)
21     #数据集需要配合 DataLoader 使用,DataLoader 从数据集中不断提取数据然后送往 Model 进行训练和预测。
22     #在这里,指定了 batch size 为 4,也就是 mini-batch 单批次图片的数量为 4.
23     #shuffle = True 表明提取数据时,随机打乱顺序,因为我们都是基于随机梯度下降的方式进行训练优化,但测试的时候因为不需要更新参数,所以就无须打乱顺序了。
24     #num_workers = 2 指定了工作线程的数量。
25 
26 #测试集
27 testset = torchvision.datasets.CIFAR10(root='./data', train=False,
28 download=True, transform=transform)
29 #同上,但train=False
30 testloader = torch.utils.data.DataLoader(testset, batch_size=4,
31 shuffle=False, num_workers=2)
32 #同上
33 
34 classes = ('plane', 'car', 'bird', 'cat',
35 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
36 #标签数组
  • 显示数据集(使用到matplotlib,安装详情移步主页)
def imshow(img):
    img = img / 2 + 0.5 # unnormalize
    npimg = img.numpy()
    #img转换成numpy
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    #因为在plt.imshow在现实的时候输入的是(imagesize,imagesize,channels)
    # 但是现在imshow中的参数img的格式为(channels,imagesize,imagesize),
    # 这两者的格式不一致,我们需要调用一次np.transpose函数,即np.transpose(npimg,(1,2,0)),
    # 将npimg的数据格式由(channels,imagesize,imagesize)转化为(imagesize,imagesize,channels),进行格式的转换后方可进行显示。
    plt.show()


dataiter = iter(trainloader)
# get some random training images
images, labels = dataiter.next()
# show all images as one image grid
imshow(torchvision.utils.make_grid(images))
# Show the real labels on the screen
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))

 Step2: 搭建神经网络

  这里用的是上一篇搭建好的神经网络,修改它为3通道的图片(在此之前它被定义为1通道)

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        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()

 Step3: 定义一个损失函数和优化器

 1 criterion = nn.CrossEntropyLoss()
 2     #分类交叉熵Cross-Entropy 作损失函数
 3 optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)#lr learning rate 学习速率,学习速率0.01比较小的时候比较稳定
 4 
 5 for epoch in range(2): # loop over the dataset multiple times,进行2轮的学习训练
 6     running_loss = 0.0
 7     for i, data in enumerate(trainloader, 0):#enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。                                     
 8         # get the inputs
 9         inputs, labels = data
10         # zero the parameter gradients
11         optimizer.zero_grad()
12         # forward + backward + optimize
13         outputs = net(inputs)
14         loss = criterion(outputs, labels)
15         loss.backward()
16         optimizer.step()
17         # print statistics
18         running_loss += loss.item()
19         if i % 2000 == 1999: # print every 2000 mini-batches
20             print('[%d, %5d] loss: %.3f' %(epoch + 1, i + 1, running_loss / 2000))
21             running_loss = 0.0
22 print('Finished Training')

 Step4: 训练网络

#得到结果(训练时间可能会比较长)
[1,  2000] loss: 2.226
[1,  4000] loss: 1.925
[1,  6000] loss: 1.711
[1,  8000] loss: 1.567
[1, 10000] loss: 1.510
[1, 12000] loss: 1.457
[2,  2000] loss: 1.400
[2,  4000] loss: 1.370
[2,  6000] loss: 1.350
[2,  8000] loss: 1.330
[2, 10000] loss: 1.303
[2, 12000] loss: 1.274
Finished Training

 Step5: 在测试网络上测试数据集

outputs = net(images)
_, predicted = torch.max(outputs.data, 1)
#其中这个 1代表行,0的话代表列。
# 不加_,返回的是一行中最大的数。
# 加_,则返回一行中最大数的位置。
print('Predicted: ', ' '.join('%5s' % classes[predicted[j]]
                              for j in range(4)))

这里只训练了一轮,精确度很低。

 Step6: 检验精确度

#总体精确度
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个种类各自的精确度
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]))

 Step7: Trainning on GPU

将神经网络传到GPU上

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:0

接下来我们都是假设设备是有CUDA的设备。

然后这些方法将会递归的遍历所有Modules并且将他们的参数和缓存传入到CUDA tensors中:

net.to(device)

记住,你还必须将每一步的inputs和targets传入GPU中:

inputs, labels = input.to(device), labels.to(device)