DEEP LEARNING WITH PYTORCH: A 60 MINUTE BLITZ | TRAINING A CLASSIFIER

Deep_RS發表於2022-01-22

你已經知道怎樣定義神經網路,計算損失和更新網路權重。現在你可能會想,

那麼,資料呢?

通常,當你需要解決有關影像、文字或音訊資料的問題,你可以使用python標準庫載入資料並轉換為numpy array。然後將其轉換為 torch.Tensor

  • 對於影像,例如Pillow,OpenCV
  • 對於音訊,例如scipy和librosa
  • 對於文字,原生Python或基於Cython的載入,或NLTK和SpaCy

針對視覺領域,我們建立了一個名為 torchvision 的包,擁有用於ImageNet、CIFAR10、MNIST等常見資料集載入的data loaders,以及用於圖片變換的data transfomer,即,torchvision.datasetstorch.utils.data.DataLoader

這提供了很大的方便,避免重複造輪子。

在本教程中,我們將使用CIFAR10資料集。它包括:“airplane”,“automobile”,“bird”,“cat”,“deer”,“dog”,“frog”,“horse”,“ship”,“truck”。CIFAR-10的圖片大小是3x32x32,也就是3通道、大小32x32畫素。

cifar10

訓練一個圖片分類器

我們將依次進行以下步驟

  1. 使用torchvision載入CIFAR10訓練集和測試集,並進行標準化
  2. 定義一個卷積神經網路
  3. 定義一個損失函式
  4. 在訓練集上訓練網路
  5. 在測試集上測試網路

1. 載入和標準化CIFAR10

使用 torchvision,載入CIFAR10非常容易。

import torch
import torchvision
import torchvision.transforms as transforms

torchvision資料集輸出的是範圍[0, 1]、PILImage格式的圖片。我們將其歸一化到[-1, 1],並轉換為Tensor。

注意:如果你在Windows上執行,並出現BrokenPipeError,嘗試設定torch.utils.data.DataLoader()的num_worker為0

transform = transform.Compose(
    [transforms.ToTensor(),
    # 逐通道標準化,這裡傳入的mean=std=(0.5,0.5,0.5)是固定值,這一做法可以使傳入的[0,1]的tensor
    # 轉換為[-1,1],但可能不符合正態分佈,除非傳入的是根據實際資料計算的mean和std
     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')

輸出:

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

檢視一些訓練圖片

import matplotlib.pyplot as plt
import numpy as np

# functions to show an image
def imshow(img):
    # 這裡僅僅是將[-1,1]的值轉換為[0,1]
    img = img / 2 + 0.5 # unnormalize
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))

# get some random training images
dataiter = iter(trainloader)
images, labels = dataiter.next() # 取1個batch

# show images
# .make_grid()將4D(BxCxHxW)mini-batch的Tensor或同樣大小的圖片list拼成一副圖片
imshow(torchvision.utils.maker_grid(images))
# 列印labels
# 迴圈讀取batch中每個圖片的label(數字),並把其對應的類別列印出來(字串)
print(' '.join('%5s' % classes[labels[j]] for j in range(batch_size)))

輸出:

   cat plane  bird  ship

2. 定義一個卷積神經網路

拷貝之前的“Neural Network”節內的神經網路,並且修改成接受3-channel的圖片(代替之前定義的1-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()

3. 定義損失函式和優化器

使用Classification Cross-Entropy損失函式和SGD with momentum優化器。

import torch.optim as optim

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

4. 訓練網路

事情變得有趣了起來,我們只需要迴圈遍歷資料迭代器,並將輸入提供給網路進行優化即可。

for epoch in range(2): # loop over the dataset multiple times
    
    running_loss = 0.0
    # 沿著第一個維度(batchs)列舉
    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
            print('[%d, %5d] loss: %.3f' % 
                   (epoch + 1, i + 1, running_loss / 2000))
            running_loss = 0.0
print('Finished Training')

輸出:

[1,  2000] loss: 2.128
[1,  4000] loss: 1.793
[1,  6000] loss: 1.649
[1,  8000] loss: 1.555
[1, 10000] loss: 1.504
[1, 12000] loss: 1.444
[2,  2000] loss: 1.379
[2,  4000] loss: 1.344
[2,  6000] loss: 1.336
[2,  8000] loss: 1.327
[2, 10000] loss: 1.294
[2, 12000] loss: 1.280
Finished Training

快速儲存模型

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

這裡有關於儲存PyTorch模型更多的細節。

5. Test the network on the test data

我們已經遍歷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)]))

輸出:

GroundTruth:   cat  ship  ship plane

接下來,把我們儲存的模型載入進來(注意:儲存和重新載入在這裡不是必須的,我們只是去展示怎麼做):

net = Net()
net.load_state_dict(torch.load(PATH))

現在,讓我們來看一下神經網路對以上樣例是怎麼判斷的:

outputs = net(images)

outputs是10類對應的分數。某類的分數越高,那麼網路就越認為圖片對應該類。所以,讓我們獲取最高分數的索引:

# outputs的維度應該是4x10(4個樣本,每個樣本10個類別分數)
# 這裡沿著第二個維度取最大值及其索引
_, predicted = torch.max(outputs, 1)

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

輸出:

Predicted:  frog  ship  ship  ship

結果看起來還不錯
讓我們來看看網路在整個資料集上表現如何

correct = 0
total =0
# 因為我們不是訓練,所以不需要計算梯度
with torch.no_grad():
    for data in testloader:
        images, labels = data
        # 將圖片輸入網路計算outputs
        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))

輸出:

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

看起來比隨機的準確率10%(隨機從10類裡面選擇1類)要好得多。看起來網路學到了一些東西。

Hmmm,哪些類表現得好一點,哪些類表現得不好:

# 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)
        # 依然是求出最大值的索引,label也是類別的索引,所以可以直接比較
        # 並且均跟classes是對應的,所以就能找到對應的類別了
        _, 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
for classname, correct_count in correct_pred.items():
    accuracy = 100 * float(correct_count) / total_pred[classname]
    print("Accuracy for class {:5s} is: {:.1f} %".format(classname, accuracy))

輸出:

Accuracy for class plane is: 59.4 %
Accuracy for class car   is: 66.7 %
Accuracy for class bird  is: 22.7 %
Accuracy for class cat   is: 52.7 %
Accuracy for class deer  is: 59.1 %
Accuracy for class dog   is: 28.9 %
Accuracy for class frog  is: 70.8 %
Accuracy for class horse is: 57.6 %
Accuracy for class ship  is: 67.4 %
Accuracy for class truck is: 62.2 %

好的,那麼接下來呢?

我們怎麼將神經網路執行在GPU上嗯?

在GPU上訓練

將神經網路轉移到GPU上就像如何將Tensor移到GPU上一樣。
如果CUDA可用,首先定義裝置為第一個可見裝置:

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

# 假設是在一個CUDA機器上,那麼應該列印一個CUDA裝置:
print(device)

輸出:

cuda:0

本節剩下的部分假設 device 是一個CUDA裝置

然後下面的方法將遞迴遍歷模型的所有部分,並將它們的引數和快取轉換為CUDA tensors:

net.to(device)

記住你還必須把每一步的inputs和targets送到GPU上。

inputs, labels = data[0].to(device), data[1].to(device)

為什麼我沒有看到相比CPU有巨大的速度提升?因為網路實在太小了。
練習:嘗試提高網路width(第一個 nn.Conv2d 的第二個引數,第二個 nn.Conv2d 的第一個引數,它們必須一樣),觀察速度提升的如何。

目標達成:

  • 高度理解PyTorch的Tensor庫和神經網路
  • 訓練一個小型神經網路分類圖片

在多GPUs上訓練

如果你想看到使用所有GPUs帶來更多的速度提升,檢視Optional:Data Parallelism

相關文章