• 【PyTorch笔记】60分钟入门PyTorch——训练一个图片分类器


    训练一个分类器

    原文链接

    1. 数据

    通常来说,当你处理图像、文本、音频和视频数据时,可以使用标准的Python包将数据加载为numpy array形式,然后将数组转换成torch.*Tensor

    • 图像数据:Pillow、Opencv
    • 音频数据:scipy、librosa
    • 文本数据:原始Python或Cpython来加载,或者使用NLTK、SpaCy

    视觉数据有一些特殊,有一个包torchvision,可以加载常见数据集,比如ImageNet、CIFAR10、MINIST等,也包括图像转换器,即torchvision.datasetstorch.utils.data.DataLoader

    这给我们提供了极大的便利,同时避免样板代码(boilerplate code)

    在本教程中,使用CIFAR10数据集。它包含类: ‘airplane’, ‘automobile’, ‘bird’, ‘cat’, ‘deer’, ‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’。CIFAR10的图像大小为3×32×32,即3个颜色通道,像素大小32×32.

    cifar10


    2. 训练一个图片分类器

    按序执行如下步骤

    • 使用torchvision加载并标准化CIFAR10的训练集和测试集
    • 定义一个卷积神经网络
    • 定义一个损失函数
    • 在训练数据上训练网络
    • 在测试数据上测试网络

    (1)加载和标准化CIFAR10

    import torch
    import torchvision
    import torchvision.transforms as transforms
    
    • 1
    • 2
    • 3

    torchvision数据集的输出是[0,1]的PILImage图像,将它们转换成Tensor类型,并标准化在[-1,1]范围内

    如果在Windows上运行出现了BrokenPipeError,尝试将torch.utils.data.DataLoader()的num_worker设置为0

    transform = transforms.Compose(
        [transforms.ToTensor(),
        transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))]
    )
    batch_size = 4
    trainset = torchvision.datasets.CIFAR10(root='./data',train=True,download=True,transform=transform)
    trainloader = torch.utils.data.DataLoader(trainset,batch_size=batch_size,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=batch_size,shuffle=False,num_workers=2)
    classes = ('plane', 'car', 'bird', 'cat',
               'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    Downloading https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz to ./data/cifar-10-python.tar.gz
    Extracting ./data/cifar-10-python.tar.gz to ./data
    Files already downloaded and verified
    
    • 1
    • 2
    • 3

    展示一些有趣的训练图像

    import matplotlib.pyplot as plt
    import numpy as np
    
    def imshow(img):
        img = img/2 + 0.5  # 未标准化
        npimg = img.numpy()
        # 将数组的值以图片的形式展示出来
        plt.imshow(np.transpose(npimg,(1,2,0)))
        plt.show()
    
    # 得到一些随机的训练图片
    dataiter = iter(trainloader)
    images,labels = dataiter.next()
    
    # 展示图片
    imshow(torchvision.utils.make_grid(images))
    # 输出标签
    print(' '.join(f'{classes[labels[j]]:5s}' for j in range(batch_size)))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    ../../_images/sphx_glr_cifar10_tutorial_001.png

    car   bird  cat   cat
    
    • 1

    (2)定义一个卷积神经网络

    复制神经网络教程的神经网络代码,并将它修改成适用于3个channel图片的形式

    import torch.nn as nn
    import torch.nn.functional as F
    
    
    class Net(nn.Module):
        def __init__(self):
            super().__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 = torch.flatten(x, 1) # flatten all dimensions except batch
            x = F.relu(self.fc1(x))
            x = F.relu(self.fc2(x))
            x = self.fc3(x)
            return x
    
    
    net = Net()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    (3)定义损失函数和优化器

    使用交叉熵损失分类,含有momentum的SGD优化

    import torch.optim as optim
    
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.SGD(net.parameters(),lr=0.001,momentum=0.9)
    
    • 1
    • 2
    • 3
    • 4

    (4)训练网络

    这时候开始变得有趣,我们只需简单地在迭代数据上循环,并将输入数据输送到网络并优化

    for epoch in range(2):  # 循环数据集多次
        running_loss = 0.0
    
        for i , data in enumerate(trainloader,0):
            # 得到输入数据,数据是[inputs,labels]形式的列表
            inputs,labels =data
            # 参数梯度置为0
            optimizer.zero_grad()
    
            # 前向传播 + 后向传播 + 优化
            outputs = net(inputs)
            loss = criterion(outputs,labels)
            loss.backward()
            optimizer.step()
    
            #输出统计数据
            running_loss += loss.item()
            if i%2000 == 1999: # 每2000个mini-batchs输出一次
                print(f'[{epoch+1},{i+1:5d}] loss:{running_loss/2000:.3f}')
                running_loss = 0.0
    print('完成训练')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    [1,  2000] loss: 2.144
    [1,  4000] loss: 1.828
    [1,  6000] loss: 1.659
    [1,  8000] loss: 1.579
    [1, 10000] loss: 1.530
    [1, 12000] loss: 1.491
    [2,  2000] loss: 1.405
    [2,  4000] loss: 1.379
    [2,  6000] loss: 1.359
    [2,  8000] loss: 1.329
    [2, 10000] loss: 1.309
    [2, 12000] loss: 1.299
    Finished Training
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    快速保存好我们训练的模型

    PATH = './cifar_net.pth'
    torch.save(net.state_dict(),PATH)
    
    • 1
    • 2

    (5)在测试集上测试网络

    我们已经在训练集上训练了2次网络,但是我们需要检查网络有没有学到东西

    网络的输出作为预测的类标签,检查它和真实值之间的差距,如果预测正确,我们将样本添加到正确预测的列表中

    第一步,展示测试集的图片熟悉下内容

    dataiter = iter(testloader)
    images,labels = dataiter.next()
    
    # 输出图片
    imshow(torchvision.utils.make_grid(images))
    print('GroundTruth: ', ' '.join(f'{classes[labels[j]]:5s}' for j in range(4)))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    GroundTruth:  cat   ship  ship  plane
    
    • 1

    ../../_images/sphx_glr_cifar10_tutorial_002.png

    接下来,加载回存储过的模型(注意:存储并且再加载模型不是必要的步骤,这里这是说明如何做)

    net = Net()
    net.load_state_dict(torch.load(PATH))
    
    • 1
    • 2

    现在,让我们来看看神经网络认为以上图片是什么

    outputs = net(images)
    
    • 1

    输出10个标签的概率,一个类别的概率越大,神经网络越认为它是这个类别,所以我们得到最高概率的标签

    _, predicted = torch.max(outputs, 1)
    
    print('Predicted: ', ' '.join(f'{classes[predicted[j]]:5s}'
                                  for j in range(4)))
    
    • 1
    • 2
    • 3
    • 4
    Predicted:  cat   car   car   plane
    
    • 1

    结果看起来不错

    让我们看看在整个数据集上网络的表现效果

    # 网络在整个数据集上的表现效果
    correct = 0
    total = 0
    # 不训练,就不需要计算输出的梯度
    with torch.no_grad():
        images,labels = data
        # 将图片传进网络计算输出
        outputs = net(images)
        # 在哪个类别上概率最高,就是预测的类别结果
        _,predicted = torch.max(outputs.data,1)
        total += labels.size(0)
        correct = (predicted==labels).sum().item()
    
    print(f"神经网络在10000条测试图像上的准确率为:{100 * correct // total}%")
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    Accuracy of the network on the 10000 test images: 54 %
    
    • 1

    结果看起来比随机猜测效果要好,因为随机猜测的准确率是10%,网络似乎学到了一些东西

    在哪些类上的预测效果比较好,哪些不好呢?

    # prepare to count predictions for each class
    correct_pred = {classname: 0 for classname in classes}
    total_pred = {classname: 0 for classname in classes}
    
    # again no gradients needed
    with torch.no_grad():
        for data in testloader:
            images, labels = data
            outputs = net(images)
            _, predictions = torch.max(outputs, 1)
            # collect the correct predictions for each class
            for label, prediction in zip(labels, predictions):
                if label == prediction:
                    correct_pred[classes[label]] += 1
                total_pred[classes[label]] += 1
    
    
    # print accuracy for each class
    for classname, correct_count in correct_pred.items():
        accuracy = 100 * float(correct_count) / total_pred[classname]
        print(f'Accuracy for class: {classname:5s} is {accuracy:.1f} %')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    Accuracy for class: plane is 54.2 %
    Accuracy for class: car   is 85.1 %
    Accuracy for class: bird  is 41.1 %
    Accuracy for class: cat   is 33.9 %
    Accuracy for class: deer  is 38.7 %
    Accuracy for class: dog   is 55.8 %
    Accuracy for class: frog  is 66.7 %
    Accuracy for class: horse is 70.5 %
    Accuracy for class: ship  is 55.3 %
    Accuracy for class: truck is 44.6 %
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    如何在GPU上训练神经网络呢?

    3. 在GPU上训练

    就像是如何把一个tensor转换到GPU上,也同样把神经网络转换到GPU上

    如果我们有可用的CUDA,首先将我们的设备定义为一个可用的CUDA设备

    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
    # 假设我们在CUDA机器上,应该打印一个CUDA设备
    print(device)
    
    • 1
    • 2
    • 3
    cuda:0
    
    • 1

    下面的device就是一个CUDA设备

    然后,这些方法将递归遍历所有模块,并且将它们的参数和缓冲区转换成CUDA tensor

    net.to(device)
    # 必须在每个步骤中将输入和目标发送到GPU
    inputs,labels = data[0].to(device),data[1].to(device)
    
    • 1
    • 2
    • 3

    为什么没注意到和CPU相比要较大的速度提升呢?因为模型太小了。

    实践练习

    尝试提升网络的宽度(第一个nn.Conv2d的第二个参数,第二个nn.Conv2d的第一个参数,它们应该是相同的数字),看看你得到了什么样的加速

    已实现目标

    • 深入了解PyTorch的Tensor库和神经网络
    • 训练一个小的网络去分类图片

    4. 在多个GPU上训练

    如果你希望使用所有的GPU来获得更大的加快速度,请看选读:数据并行

  • 相关阅读:
    什么是DNS劫持?如何进行有效应对?
    电子学会青少年软件编程 Python编程等级考试三级真题解析(选择题)2020年9月
    面试常问:HTTPS的加密过程 ----- 光明和黑暗的恩怨情仇
    C语言的基本概念
    5G-A 商用加速,赋能工业互联网
    谣言检测(DUCK)《DUCK: Rumour Detection on Social Media by Modelling User and Comment Propagation Networks》
    二叉树的遍历方式和代码
    浅谈Oracle数据库调优(3完)
    mac下nodejs的卸载与安装
    Angular, React,Vus三大主流前端开发框架Setup
  • 原文地址:https://blog.csdn.net/weixin_45640009/article/details/125873799