使用 PyTorch 进行深度学习-训练分类器

之前的内容已经学习了如何定义神经网络、计算损失和更新网络的权重。 现在我们可以完整的训练一个分类器。

我们将按顺序执行以下步骤:

  1. 使用torchvision加载和规范化 CIFAR10 训练和测试数据集
  2. 定义卷积神经网络
  3. 定义损失函数
  4. 在训练数据上训练网络
  5. 在测试数据上测试网络

获取数据

通常,当我们必须处理图像、文本、音频或视频数据时, 可以使用将数据加载到 numpy 数组中的标准 python 包。 然后将此数组转换为 torch.*Tensor类型。

  • 对于图像,Pillow、OpenCV 等软件包很有用
  • 对于音频,scipy 和 librosa 等软件包
  • 对于文本,基于原始 Python 或 Cython 的加载,或 NLTK 和 SpaCy 很有用

例如针对计算机视觉可以使用名为 torchvision 的包,它具有用于常见数据集的数据加载器,比如ImageNet、CIFAR10、MNIST 数据集等。和用于图像的数据转换器, torchvision.datasetstorch.utils.data.DataLoader

这为我们提供了极大的便利并避免了编写模板代码。 对于本次代码,将使用 CIFAR10 数据集。 它有:'飞机','汽车','鸟','猫','鹿', “狗”、“青蛙”、“马”、“船”、“卡车”十个类别。 CIFAR-10 中的图像尺寸是 3x32x32,即 32x32 像素大小的 3 通道彩色图像。

1、加载并规范化 CIFAR10

 使用 torchvision,加载 CIFAR10

import torch
import torchvision
import torchvision.transforms as transforms

 torchvision 数据集的输出是范围 [0, 1] 的 PILImage图像。 最后我们将它标准化转换为范围 [-1, 1] 的张量。

transform = transforms.Compose(     #图像预处理包。一般用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=0)

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=0)

classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')#定义其中的类别

 其中ToTensor()能够把灰度范围从0-255变换到0-1之间,ToTensor() shape (H, W, C)nump.ndarray img 转为 shape (C, H, W) tensor,再将每一个数值归一化到[0,1],其归一化方法比较简单,直接除以255即可。

transforms.Normalize(std=(0.5,0.5,0.5),mean=(0.5,0.5,0.5)),则其作用就是先将输入归一化到(0,1),再使用公式"(x-mean)/std",将每个元素分布到(-1,1)之间,对每个通道而言,执行image=(image-mean)/std。每个样本图像变成了均值为0  方差为1 的标准正态分布。有时 std mean 中的数据是从imagenet训练集中抽样算出来的

 为了更加直观,让我们展示一些训练图像:

import matplotlib.pyplot as plt #和matlab类似的绘图API,方便用户快速绘制2D图表。
import numpy as np
def imshow(img):
    img = img / 2 + 0.5     # 逆标准化,Normalize的逆过程,img = img * std + mean
    npimg = img.numpy()     # 将张量转化成numpy的ndarray类型
    plt.imshow(np.transpose(npimg, (1, 2, 0)))#ndarray对象的形状(C, H, W)转换为(H, W, C)
    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)))
#join()返回被子字符串连接的字符串。5s表示空格大小

 其中transpose()函数的实质作用是改变序列。根据维度(shape)索引决定的,就是调换数组的行列值的索引值。

Python numpy.transpose 详解_November丶Chopin的博客-CSDN博客_numpy transpose

 输出结果:                               标签:car   frog  truck deer

使用 PyTorch 进行深度学习-训练分类器_第1张图片

 2、定义卷积神经网络

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) # 展平层、展平除批次外的所有尺寸
        x = F.relu(self.fc1(x))#全连接层+激活函数
        x = F.relu(self.fc2(x))
        x = self.fc3(x)#全连接层
        return x
net = Net()
print(net)

 输出结果:

Net(
  (conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))
  (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
  (fc1): Linear(in_features=400, out_features=120, bias=True)
  (fc2): Linear(in_features=120, out_features=84, bias=True)
  (fc3): Linear(in_features=84, out_features=10, bias=True)
)

 3、定义损失函数和优化器

使用具有动量的分类交叉熵损失函数随机梯度下降SGD优化器。

import torch.optim as optim

criterion = nn.CrossEntropyLoss()#定义交叉熵损失函数
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

其中torch.optim是一个实现了各种优化算法的库。SGD就是optim中的一个算法(优化器):随机梯度下降算法。torch.optim.SGD参数学习率lr、动量momentum、权重衰减weight_decay的解析_jjw_zyfx的博客-CSDN博客_pytorch sgd参数

第一个参数 net.parameters() 包括权重w,和偏置b等是神经网络中的参数,也是SGD优化的重点。

第二个参数lr是学习率。sgd中的学习率lr的作用可以理解为: p ′ = p − l r ∗ d p 其中p就是模型中的参数比如:权重(w), 偏置(b)。dp 就是对p的一阶求导, lr 即学习率, p ′为p的另一种形式,用来替换上一次的p。

第三个参数momentum是冲量,在普通的梯度下降法x+=v中,每次x的更新量v为v=−dx∗lr,其中dx为目标函数func(x)对x的一阶导数,当使用冲量时,把每次x的更新量v考虑为本次的梯度下降量−dx∗lr与上次x的更新量v乘上介于[0,1][0,1]因子momentum的和,即v′=−dx∗lr+v∗momemtum

 4、训练网络

遍历我们的数据迭代器,并将输入提供给网络和优化器。

for epoch in range(2):  # 在数据集上循环多次

    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):# data中包含输入图像张量inputs,张量标签labels
        inputs, labels = data                # 获取输入;数据是[输入、标签]的列表
        # 将优化器参数梯度归零
        optimizer.zero_grad()
        # 前向传播 + 计算损失 + 反向传播 + 优化
        outputs = net(inputs)        # 输入图像张量进网络,得到输出张量outputs
        loss = criterion(outputs, labels) # 利用网络的输出outputs和标签labels计算损失值
        loss.backward()        # 反向传播+参数更新,是标准代码的标准流程
        optimizer.step()
        # 打印轮次和损失值
        running_loss += loss.item()
        if i % 2000 == 1999:    # 每2000小批量打印一次
            print(f'[{epoch + 1}, {i + 1:5d}] loss: {running_loss / 2000:.3f}')
            running_loss = 0.0
print('Finished Training')

 输出结果:

[1,  2000] loss: 2.253
[1,  4000] loss: 1.901
[1,  6000] loss: 1.699
[1,  8000] loss: 1.564
[1, 10000] loss: 1.507
[1, 12000] loss: 1.454
[2,  2000] loss: 1.363
[2,  4000] loss: 1.347
[2,  6000] loss: 1.313
[2,  8000] loss: 1.297
[2, 10000] loss: 1.251
[2, 12000] loss: 1.288
Finished Training

 快速保存我们训练好的模型(参数):

PATH = './cifar_net.pth'#首先设定模型的保存路径
torch.save(net.state_dict(), PATH)#保存模型的状态字典

 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)))
# 首先实例化模型的类的对象
net = Net()
net.load_state_dict(torch.load(PATH))# 加载训练阶段保存好的模型的状态字典

outputs = net(images)# 利用模型对图片进行预测

_, predicted = torch.max(outputs, 1)# 共有十个类别,采用模型计算出的概率最大的作为预测的类别
print('Predicted: ', ' '.join(f'{classes[predicted[j]]:5s}'# 打印预测标签的结果
                                  for j in range(4)))

输出结果:                     GroundTruth:    cat  ship  ship plane
                                       Predicted:    cat  ship  ship  ship

使用 PyTorch 进行深度学习-训练分类器_第2张图片

 现在看看网络在整个数据集上的表现。

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(f'Accuracy of the network on the 10000 test images: {100 * correct // total} %')

 输出结果:

Accuracy of the network on the 10000 test images: 54 %

分析结果:对于拥有10个类别的数据集,随机猜测的准确率是10%,模型达到了54%,说明模型学到了真实的东西。为了更细致的看一下模型在哪些类上表现得更好,在哪些类上表现得更差,我们分类别的进行准确率的计算

# 准备计算每个类别的预测
correct_pred = {classname: 0 for classname in classes}#字典生成器
total_pred = {classname: 0 for classname in classes}#创建包含10个类别的字典

# 同样不需要梯度
with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(images)
        _, predictions = torch.max(outputs, 1)
        # 收集每个类别的正确预测
        for label, prediction in zip(labels, predictions):
            if label == prediction:
                correct_pred[classes[label]] += 1#给此标签对应的类别加1
            total_pred[classes[label]] += 1#此类别的总数加1
# 打印每一个类别的准确率
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} %')

其中 zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。

输出结果:

Accuracy of plane : 100 %
Accuracy of   car : 100 %
Accuracy of  bird : 100 %
Accuracy of   cat : 100 %
Accuracy of  deer : 100 %
Accuracy of   dog : 100 %
Accuracy of  frog : 100 %
Accuracy of horse : 100 %
Accuracy of  ship : 100 %
Accuracy of truck : 100 %

6、在 GPU 上训练

就像如何将张量转移到 GPU 上一样,转移神经网络到GPU上。如果有GPU,让我们首先将我们的设备定义为第一个可见的 cuda 设备 可用的 CUDA:

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

假设 device是一个 CUDA 设备。当训练模型的时候,只需要将模型转移到GPU上,同时将输入的图片和标签页转移到GPU上即可。然后这些方法将递归地遍历所有模块并转换它们的 CUDA 张量的参数和缓冲区:

net.to(device)#将模型转移到GPU上
inputs, labels = data[0].to(device), data[1].to(device) 
#将输入的图片张量和标签张量转移到GPU上

你可能感兴趣的:(深度学习,pytorch,人工智能)