欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

1.4 训练一个分类器

程序员文章站 2022-07-08 08:59:21
...

训练一个分类器

上一讲中已经看到如何去定义一个神经网络,计算损失值和更新网络的权重。

你现在可能会在想。

什么是数据?

一般情况下处理图像、文本、音频和视频数据时,可以使用标准的Python包来加载数据到一个numpy数组中。 然后把这个数组转换成 torch.Tensor

  • 图像可以使用 Pillow, OpenCV 之类的包
  • 音频可以使用 scipy, librosa 之类的包
  • 文本可以使用原始 Python 和 Cython 来加载,或者使用 NLTK或 SpaCy 处理

特别的,对于图像任务,我们创建了一个包 torchvision,它包含了处理一些基本图像数据集的data loaders。这些数据集包括 Imagenet, CIFAR10, MNIST 等。**除了数据加载以外,torchvision 还包含了图像转换器, torchvision.datasetstorch.utils.data.DataLoader

torchvision包不仅提供了巨大的便利,也避免了代码的重复。

在这个教程中,我们使用CIFAR10数据集,它有如下10个类别 :‘airplane’, ‘automobile’, ‘bird’, ‘cat’, ‘deer’, ‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’。CIFAR-10的图像都是 3x32x32大小的,即,3颜色通道,32x32像素。
1.4 训练一个分类器

训练一个图像分类器

依次按照下列顺序进行:

  • 1.使用torchvision加载和归一化CIFAR10训练集和测试集
  • 2.定义一个卷积神经网络(Convolutional Network Network)
  • 3.定义损失函数(loss function)
  • 4.在训练集上训练网络
  • 在测试集上测试网络

1. 读取和归一化 CIFAR10

使用torchvision可以非常容易地加载CIFAR10。

import torch
import torchvision
import torchvision.transforms as transforms

torchvision的输出是 [0,1] 的PILImage图像,我们把它转换为归一化范围为[-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))])

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')
Using downloaded and verified file: ./data\cifar-10-python.tar.gz
Extracting ./data\cifar-10-python.tar.gz to ./data
Files already downloaded and verified

我们展示一些训练图像。

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()
print(labels)

# show images
imshow(torchvision.utils.make_grid(images))
# print labels
print(''.join('%5s' % classes[labels[j]] for j in range(4)))
tensor([0, 8, 1, 5])

1.4 训练一个分类器

plane ship  car  dog

2. 定义一个卷积神经网络

从之前的神经网络一节复制神经网络代码,并修改为输入3通道图像(之前是1通道图像)。

import torch.nn as nn
import torch.nn.functional as F

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()
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. 定义损失函数(Loss Function)和优化器(Optimizer)

我们使用交叉熵(Cross-Entropy)作为损失函数,使用带动量的随机梯度下降(SGD)进行优化。

import torch.optim as optim
import torch.nn as nn

criterion=nn.CrossEntropyLoss()
optimizer=optim.SGD(net.parameters(),lr=0.001,momentum=0.9)

4. Train the network(训练网路)

有趣的时刻开始了。 我们只需在数据迭代器上循环,将数据输入给网络,并优化(optimize)。

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;data is a list of [inputs,labels]  获取输入
        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   每2000循环一次
            print('[%d , %5d] loss:%.3f' % (epoch+1,i+1,running_loss/2000))
            
            running_loss=0.0
            
    print('Finished Training')
[1 , 20005d] loss:2.218
[1 , 40005d] loss:1.905
[1 , 60005d] loss:1.660
[1 , 80005d] loss:1.588
[1 , 100005d] loss:1.521
[1 , 120005d] loss:1.481
Finished Training
[2 , 20005d] loss:1.415
[2 , 40005d] loss:1.396
[2 , 60005d] loss:1.372
[2 , 80005d] loss:1.351
[2 , 100005d] loss:1.317
[2 , 120005d] loss:1.285
Finished Training

Let’s quickly save our trained model:

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

See here for more details on saving PyTorch models.

5. 在测试集上测试网络

我们在整个训练集上进行了2次训练,但是我们需要检查网络是否从数据集中学习到有用的东西。 通过预测神经网络输出的类别标签与实际情况标签进行对比来进行检测。 如果预测正确,我们把该样本添加到正确预测列表。

第一步,显示测试集中的图片并熟悉图片内容。

testiter=iter(testloader)
images,labels=testiter.next()

# print images
imshow(torchvision.utils.make_grid(images))
print('GroundTruth:',' '.join('%5s' % classes[labels[j]] for j in range(4)))

1.4 训练一个分类器
GroundTruth: cat ship ship plane

让我们看看神经网络认为以上图片是什么。

outputs = net(images)
outputs
tensor([[-1.5164, -2.4398,  0.7794,  3.4010, -0.7153,  2.6029,  0.3918, -0.4157,
         -1.0885, -1.0866],
        [ 3.9155,  7.5775, -0.9970, -2.6943, -3.8319, -4.0049, -4.3840, -4.4132,
          4.1887,  4.4919],
        [ 1.6441,  3.5423,  0.3699, -0.7701, -2.6172, -1.7461, -1.8804, -2.5832,
          1.2750,  2.5317],
        [ 3.0803,  0.8045,  0.0677, -1.1196, -0.7847, -2.6785, -2.5864, -1.6566,
          3.7456,  1.3289]], grad_fn=<AddmmBackward>)

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

_,predicted=torch.max(outputs,1)

print('Predicted:', ' '.join('%5s' % classes[predicted[j]] for j in range(4)))
Predicted:   cat   car   car  ship

结果看来不错。

接下来让看看网络在整个测试集上的结果如何。

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()#计算Accuracy
        
print('Accuracy of the network on the 10000 test images: %d %%' % (100 * correct / total))
Accuracy of the network on the 10000 test images: 55 %

结果看起来不错,至少比随机选择要好,随机选择的正确率为55%。 似乎网络学习到了一些东西。

在识别哪一个类的时候好,哪一个不好呢?

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]))
Accuracy of plane : 57 %
Accuracy of   car : 76 %
Accuracy of  bird : 36 %
Accuracy of   cat : 37 %
Accuracy of  deer : 35 %
Accuracy of   dog : 56 %
Accuracy of  frog : 70 %
Accuracy of horse : 59 %
Accuracy of  ship : 65 %
Accuracy of truck : 55 %

下一步?

我们如何在GPU上运行神经网络呢?

Training on GPU

把一个神经网络移动到GPU上训练就像把一个Tensor转换GPU上一样简单。

并且这个操作会递归遍历有所模块,并将其参数和缓冲区转换为CUDA张量。

device=torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')# 检查你的电脑是否支持GPU
print(device)
cpu

本节的其余部分假定device是CUDA设备。

然后这些方法将递归遍历所有模块并将模块的参数和缓冲区 转换成CUDA张量:

net.to(device)
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)
)

记住:inputs 和 targets 也要转换。

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

为什么我们没注意到GPU的速度提升很多?那是因为网络非常的小。

实践: 尝试增加你的网络的宽度(第一个nn.Conv2d的第2个参数,第二个nn.Conv2d的第一个参数,它们需要是相同的数字),看看你得到了什么样的加速。

实现的目标:

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

多GPU训练

如果你想使用所有的GPU得到更大的加速, 请查看数据并行处理

下一步?

相关标签: CIFAR10