PyTorch學習之 圖像分類器

PyTorch學習之 圖像分類器

學習網站

http://pytorch123.com/SecondSection/neural_networks/

訓練一個圖像分類器

通過前面的章節,我們已經知道怎樣定義一個神經網絡,以及計算其損失函數,並且更新網絡的權重
現在,我們將要學習怎樣去處理數據。
一般來說,當處理圖像,文本,音頻,視頻這些數據時,可以使用標準的python包來下載這些數據,
並轉換成numpy數組格式。然後,將這些數組轉換成 “torch.Tensor”格式

  • 對於圖像,可以使用Pillow,OpenCV包
  • 對於音頻,可以使用 scipy, librosa包
  • 對於文本,可以使用Python或者Cyphon直接加載,或者使用NLTK和SpaCy

對於視覺,PytorCh中創建了一個“torchvision”包,裏面包含一些常見的數據集,例如
Imagenet, CIFAR10, MNIST等,以及一些圖像轉換模塊:torchvision.datasets, torch.utils.data.DataLoader
下面會使用CIFAR10數據集作爲例子,進行圖像分類:

CIFAR10: ‘airplane’, ‘automobile’, ‘bird’, ‘cat’, ‘deer’,‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’.
尺寸:3*32*32

圖像分類一般分爲以下5個步驟

  1. 使用torchvision加載並且歸一化CIFAR10的訓練和測試數據集
  2. 定義一個卷積神經網絡
  3. 定義一個損失函數
  4. 在訓練樣本數據上訓練網絡
  5. 在測試樣本數據上測試網絡

1. 下載並歸一化CIFAR10數據集

import torch
import torchvision
import torchvision.transforms as transforms

########################################################################
# torchvision加載的數據都是PILImage的數據類型,在[0, 1]之間
# 對上述類型的數據集進行歸一化爲[-1, 1]範圍的tensors
# 歸一化方法: (X-mean)/std
transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) # mean, std
# 檢驗是否已經存在,若不存在,則下載數據集
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=0)

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

classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

顯示數據中的一些圖像

########################################################################
# 顯示數據集中的一些圖像

import matplotlib.pyplot as plt
import numpy as np

def imshow(img):
    img = img / 2 + 0.5     # unnormalize, 因爲前面是將圖像進行了歸一化,即 x = (X-0.5)/0.5
    npimg = img.numpy()
    image = np.transpose(npimg, (1, 2, 0))
    plt.imshow(image)    # 1 是和第二個軸交換,2,是和第2個軸交換,0是和第一個軸交換image[Height, Width, Dim]
    plt.show()

# get some random training images
dataiter = iter(trainloader)     # 使得 trainloader 變成迭代器
images, labels = dataiter.next()

# show images
imshow(torchvision.utils.make_grid(images))  # 將若干圖像拼成一幅圖像
# print labels
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))

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

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)   # 輸出 6*28*28
        self.pool = nn.MaxPool2d(2, 2)    # 6*14*14
        self.conv2 = nn.Conv2d(6, 16, 5)  # 16*10*10
        self.fc1 = nn.Linear(16 * 5 * 5, 120)  # conv2經過 pooling 後,變成 5*5 map, 所以 16*5*5個全連接神經元
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))   # 卷積 -> Relu -> Pool
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5)             # view函數將張量x變形成一維的向量形式,作爲全連接的輸入
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

net = Net()

3. 定義損失函數與優化器

import torch.optim as optim

criterion = nn.CrossEntropyLoss()  # 損失函數
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)  # 優化器 SGD with momentum

4. 訓練網絡

for epoch in range(2):  # 訓練集訓練次數

    running_loss = 0.0
    # enumerate()用於可迭代\可遍歷的數據對象組合爲一個索引序列,
    # 同時列出數據和數據下標.上面代碼的0表示從索引從0開始,
    for i, data in enumerate(trainloader, 0):
        # 獲得輸入
        inputs, labels = data
        # 初始化參數梯度
        optimizer.zero_grad()
        # 前饋 + 後饋 + 優化
        outputs = net(inputs)
        loss = criterion(outputs, labels)    # labels 會進行二值化,即[1 0 0 0 0 0 0 0 0]
        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')

5. 在數據集上測試網絡結構

上面已經在訓練集上進行了2次完整的訓練循環,但是我們需要檢查網絡是否真正的學到了一些什麼東西。測試的方式是,將網絡輸出的結果與數據集的ground-truth進行對比.

  1. 首先,顯示一些圖像
dataiter = iter(testloader)
images, labels = dataiter.next()
# 輸出圖像
imshow(torchvision.utils.make_grid(images))
print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))
print : 
GroundTruth:    cat  ship  ship plane
  1. 查看網絡輸出結果
outputs = net(images)
outputs
tensor([[-1.3145, -2.4341, -0.7362,  6.8300,  0.5993,  2.2841, -0.9894, -0.9424,
          1.3211, -3.0649],
        [ 4.2055,  8.5567, -2.8397, -2.3198, -3.1733, -4.6069, -8.4125, -2.9534,
         10.5395,  5.7375],
        [ 1.3612,  1.1350,  0.3872, -0.3729, -0.1908, -1.1665, -3.7862, -0.3712,
          3.3340, -0.1305],

outputs 是10種類別分別預測出來的能量值,即某一類的能量值越高,其被認爲是該種類的概率越大。因此,我們需要獲得outputs中類被能量的最大值所對應的種類。

_, predicted = torch.max(outputs, 1)   # predicted 對應的種類
print('Predicted: ', ' '.join('%5s' % classes[predicted[j]]
                              for j in range(4)))
Predicted:    cat  ship  ship  ship
tensor([3, 8, 8, 8])

從上面的輸出結果看,檢測結果似乎還是不錯的。
下面,我們看一下訓練的網絡在整個數據集上的表現。

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

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

因爲隨機預測的概率是10%(10類中預測一類),所以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()   # 將shape中爲1的維度去掉
        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 : 52 %
Accuracy of   car : 70 %
Accuracy of  bird : 44 %
Accuracy of   cat : 28 %
Accuracy of  deer : 54 %
Accuracy of   dog : 41 %
Accuracy of  frog : 66 %
Accuracy of horse : 60 %
Accuracy of  ship : 65 %
Accuracy of truck : 68 %

6. 在GPU上訓練數據

在GPU上訓練神經網絡,就像將一個Tensor轉移到GPU上一樣。
首先來定義我們的device作爲第一個可見的cuda device。

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# 如果程序運行在CUDA機器上,下面會輸出一個device的id
print(device)
cuda:0

設定好之後,這些方法就會遞歸的遍歷所有模塊,並把他們的參數和buffers轉換爲cuda的tensors
下面的語句是必不可少的:

net.to(device)

同時,也必須每一個步驟都要向gpu中發送inputs和targets.

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

當網絡非常小的時候,感覺不帶速度的變化,可以把卷積1的輸出改爲128,卷積2的輸入改爲128,觀察效果。改爲128後,訓練2次後的準確率爲

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

更多的特徵圖的結果似乎要比6個特徵圖要好一些,下面是每個類的輸出結果

Accuracy of plane : 73 %
Accuracy of   car : 82 %
Accuracy of  bird : 27 %
Accuracy of   cat : 32 %
Accuracy of  deer : 53 %
Accuracy of   dog : 55 %
Accuracy of  frog : 75 %
Accuracy of horse : 76 %
Accuracy of  ship : 70 %
Accuracy of truck : 54 %

備註:
改成GPU運行方式後會出現如下報錯:

TypeError: can't convert CUDA tensor to numpy. Use Tensor.cpu() to copy the tensor to host memory first.

解決方法:

將 npimg = img.numpy() 改爲
npimg = img.cpu().numpy()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章