pytorch學習日記(一)——之CIFAR10圖像訓練測試實戰

神經網絡NN編程實現,往往需要以下幾個步驟:

1)定義NN,初始化NN的參數(權重和偏置)

2)準備好輸入數據集

3)讓輸入通過NN,得到輸出

4)計算輸出和理想輸出的loss

5)採用隨機梯度下降方法(SGD),後向傳播更新NN的權重和偏置,更新規則:

weight = weight - learning_rate * gradient

下面,將根據這些步驟進行編程:

導入庫

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable
import torch
import torchvision
import torchvision.transforms as transforms
import torch.optim as optim
import matplotlib.pyplot as plt
import numpy as np

 

  • 1. 定義NN,初始化NN的參數
class Net(nn.Module):
    # 定義Net的初始化函數,這個函數定義了該神經網絡的基本結構
    def __init__(self):
        super(Net, self).__init__()  # 複製並使用Net的父類的初始化方法,即先運行nn.Module的初始化函數
        self.conv1 = nn.Conv2d(3, 6, 5)  # 定義conv1函數的是圖像卷積函數:輸入爲圖像(3個頻道,即RGB圖),輸出爲 6張特徵圖, 卷積核爲5x5正方形
        self.conv2 = nn.Conv2d(6, 16, 5)  # 定義conv2函數的是圖像卷積函數:輸入爲6張特徵圖,輸出爲16張特徵圖, 卷積核爲5x5正方形
        self.fc1 = nn.Linear(16 * 5 * 5, 120)  # 定義fc1(fullconnect)全連接函數1爲線性函數:y = Wx + b,並將16*5*5個節點連接到120個節點上。
        self.fc2 = nn.Linear(120, 84)  # 定義fc2(fullconnect)全連接函數2爲線性函數:y = Wx + b,並將120個節點連接到84個節點上。
        self.fc3 = nn.Linear(84, 10)  # 定義fc3(fullconnect)全連接函數3爲線性函數:y = Wx + b,並將84個節點連接到10個節點上。

    # 定義該神經網絡的向前傳播函數,該函數必須定義,一旦定義成功,向後傳播函數也會自動生成(autograd)
    def forward(self, x):
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))  # 輸入x經過卷積conv1之後,經過激活函數ReLU,使用2x2的窗口進行最大池化Max pooling,然後更新到x。
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)  # 輸入x經過卷積conv2之後,經過激活函數ReLU,使用2x2的窗口進行最大池化Max pooling,然後更新到x。
        x = x.view(-1, self.num_flat_features(x))  # view函數將張量x變形成一維的向量形式,總特徵數並不改變,爲接下來的全連接作準備。
        x = F.relu(self.fc1(x))  # 輸入x經過全連接1,再經過ReLU激活函數,然後更新x
        x = F.relu(self.fc2(x))  # 輸入x經過全連接2,再經過ReLU激活函數,然後更新x
        x = self.fc3(x)  # 輸入x經過全連接3,然後更新x
        return x

    # 使用num_flat_features函數計算張量x的總特徵量(把每個數字都看出是一個特徵,即特徵總量),比如x是4*2*2的張量,那麼它的特徵總量就是16。
    def num_flat_features(self, x):
        size = x.size()[1:]  # 這裏爲什麼要使用[1:],是因爲pytorch只接受批輸入,也就是說一次性輸入好幾張圖片,那麼輸入數據張量的維度自然上升到了4維。
        # 【1:】讓我們把注意力放在後3維上面,是因爲 x.size() 會 return [nSamples, nChannels, Height, Width]。我們只需要展開後三項成爲一個一維的 tensor。
        num_features = 1
        for s in size:
            num_features *= s
        return num_features

 這裏你可以用下面的代碼打印出網絡的結構看看:

net = Net()
# 以下代碼是爲了看一下我們需要訓練的參數的數量
print(net)

params = list(net.parameters())
k = 0
for i in params:
     l = 1
     #i type is <class 'torch.nn.parameter.Parameter'>
     print("該層的結構:" + str(list(i.size())))
     for j in i.size():
         l *= j
     print("參數和:" + str(l))
     k = k + l

print("總參數和:" + str(k))

 

這裏特別說下在__init__(self)裏的 self.fc1 = nn.Linear(16 * 5 * 5, 120)  ,爲什麼輸入是16*5*5?

1)有必要提下CIFAR10數據集:

CIFAR10,該數據集共有60000張彩色圖像,這些圖像是32*32×3(記住這個32*32很重要),分爲10個類,每類6000張圖。這裏面有50000張用於訓練,構成了5個訓練批,每一批10000張圖;另外10000用於測試,單獨構成一批。測試批的數據裏,取自10類中的每一類,每一類隨機取1000張。抽剩下的就隨機排列組成了訓練批。注意一個訓練批中的各類圖像並不一定數量相同,總的來看訓練批,每一類都有5000張圖。

 下面這幅圖就是列舉了10各類,每一類展示了隨機的10張圖片:

 你可以打印出圖片看看:

# matplotlib inline
def imshow(img):
    img = img / 2 + 0.5  # unnormalize
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    plt.show()


# show some random training images
dataiter = iter(trainloader)
images, labels = dataiter.next()
imshow(torchvision.utils.make_grid(images))
# print labels
print(' '.join('%5s' % classes[labels[j]] for j in range(5)))

2)有必要了解上面code定義的NN結構:

conv1->max_pool1->conv2->max_pool2->fc1->fc2->fc3

總體結構是這樣的,當然relu只是激活函數,不作爲一層。

上面我們提過輸入是32*32*3的圖像,我們先看看nn.Conv2d函數參數的意義:

class torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True) 

可以看到,步長stride=1,默認爲1,填充padding=0默認爲0。好了,看下我們定義的self.conv1 = nn.Conv2d(3, 6, 5),即輸入爲圖像(3個頻道,即RGB圖),輸出爲 6張特徵圖, 卷積核爲5x5正方形。

那麼通過conv1層輸出的feature_map尺寸計算公式爲:[ (原圖片尺寸 -卷積核尺寸)/ 步長 ] + 1(牢記),不懂原理的看這篇博客https://blog.csdn.net/m0_37673307/article/details/81166266

因此,

  1. conv1層self.conv1 = nn.Conv2d(3, 6, 5):輸入是32*32*3,計算(32-5)/ 1 + 1 = 28,那麼通過conv1輸出的結果是28*28*6;
  2. max_pool1層F.max_pool2d(F.relu(self.conv1(x)), (2, 2)):輸入是28*28*6,窗口2*2,計算28 / 2 = 14,那麼通過max_pool1層輸出結果是14*14*6;
  3. conv2層 self.conv2 = nn.Conv2d(6, 16, 5):輸入是14*14*6,計算(14 - 5)/ 1 + 1 = 10,那麼通過conv2輸出的結果是10*10*16;
  4. max_pool2層F.max_pool2d(F.relu(self.conv2(x)), 2):輸入是10*10*16,窗口2*2,計算10 /  2 = 5,那麼通過max_pool2層輸出結果是5*5*16.

那麼接下來就是fc1層的輸入,也就是爲啥是self.fc1 = nn.Linear(16 * 5 * 5, 120) 的原因了。

  •  2. 準備好輸入數據集
# torchvision輸出的是PILImage,值的範圍是[0, 1].
# 我們將其轉化爲tensor數據,並歸一化爲[-1, 1]。
transform = transforms.Compose([transforms.ToTensor(),
                                transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
                                ])

# 訓練集,將相對目錄./data下的cifar-10-batches-py文件夾中的全部數據(50000張圖片作爲訓練數據)加載到內存中,若download爲True時,會自動從網上下載數據並解壓
trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)

# 將訓練集的50000張圖片劃分成12500份,每份4張圖,用於mini-batch輸入。shffule=True在表示不同批次的數據遍歷時,打亂順序。num_workers=2表示使用兩個子進程來加載數據
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=False, num_workers=2)

#測試集,將相對目錄./data下的cifar-10-batches-py文件夾中的全部數據(10000張圖片作爲測試數據)加載到內存中,若download爲True時,會自動從網上下載數據並解壓
testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)

# 將測試集的10000張圖片劃分成2500份,每份4張圖,用於mini-batch輸入。
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')

 

  • 3.輸入通過NN,得到輸出,計算輸出和理想輸出的loss,採用隨機梯度下降方法(SGD),後向傳播更新NN的權重和偏置

這裏三個步驟放在一起,因爲pytorch進行了高度封裝,一個函數就能做成一件事。

criterion = nn.CrossEntropyLoss()#叉熵損失函數
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)#使用SGD(隨機梯度下降)優化,學習率爲0.001,動量爲0.9
for epoch in range(10):  # 遍歷數據集10次

    running_loss = 0.0
    # enumerate(sequence, [start=0]),i序號,data是數據
    for i, data in enumerate(trainloader, 0):
        # get the inputs
        #data is list
        inputs, labels = data  # data的結構是:[4x3x32x32的張量,長度4的張量]
        # wrap them in Variable
        inputs, labels = Variable(inputs), Variable(labels)  # 把input數據從tensor轉爲variable
        # zero the parameter gradients
        optimizer.zero_grad()  # 將參數的grad值初始化爲0

        # forward + backward + optimize
        outputs = net(inputs)
        loss = criterion(outputs, labels)  # 將output和labels使用叉熵計算損失
        loss.backward()  # 反向傳播
        optimizer.step()  # 用SGD更新參數

        # 每2000批數據打印一次平均loss值
        running_loss += loss.item()  # loss本身爲Variable類型,所以要使用data獲取其Tensor,因爲其爲標量,所以取0  或使用loss.item()
        if i % 2000 == 1999:  # 每2000批打印一次
            print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 2000))
            running_loss = 0.0
print('Finished Training')

那麼由以上訓練的模型,我們需要測試下預測效果怎樣:

correct = 0
total = 0
with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(Variable(images))
        # print outputs.data
        # print(outputs.data)
        # print(labels)
        value, predicted = torch.max(outputs.data, 1)  # outputs.data是一個4x10張量,將每一行的最大的那一列的值和序號各自組成一個一維張量返回,第一個是值的張量,第二個是序號的張量。
        #label.size(0) 是一個數
        total += labels.size(0)
        correct += (predicted == labels).sum()  # 兩個一維張量逐行對比,相同的行記爲1,不同的行記爲0,再利用sum(),求總和,得到相同的個數。

print('Accuracy of the network on the 10000 test images: %d %%' % (100 * correct / total))

看看我的運行效果,訓練了10次,測試準確度爲57%,多訓練幾次,就能降低loss,但也會出現過擬合。

當然你也可以看看每一類的分類情況:

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

運行結果:

===========================================================================================

這裏給出完整代碼:

# coding=utf-8
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable
import torch
import torchvision
import torchvision.transforms as transforms
import torch.optim as optim

# torchvision輸出的是PILImage,值的範圍是[0, 1].
# 我們將其轉化爲tensor數據,並歸一化爲[-1, 1]。
transform = transforms.Compose([transforms.ToTensor(),
                                transforms.Normalize(mean = (0.5, 0.5, 0.5), std = (0.5, 0.5, 0.5)),
                                ])

# 訓練集,將相對目錄./data下的cifar-10-batches-py文件夾中的全部數據(50000張圖片作爲訓練數據)加載到內存中,若download爲True時,會自動從網上下載數據並解壓
trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)

# 將訓練集的50000張圖片劃分成12500份,每份4張圖,用於mini-batch輸入。shffule=True在表示不同批次的數據遍歷時,打亂順序。num_workers=2表示使用兩個子進程來加載數據
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=False, num_workers=2)


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

# print(type(trainloader))
# print(len(trainset))
# print(len(trainloader))
# print(len(testloader))

# 下面是代碼只是爲了給小夥伴們顯示一個圖片例子,讓大家有個直覺感受。
# functions to show an image
import matplotlib.pyplot as plt
import numpy as np


# matplotlib inline
def imshow(img):
    img = img / 2 + 0.5  # unnormalize
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    plt.show()


# show some random training images
# dataiter = iter(trainloader)
# images, labels = dataiter.next()

# dataiter = iter(trainloader)
# try:
#     # show some random training images
#     while True:
#         images, labels = dataiter.next()
#         # print images
#         imshow(torchvision.utils.make_grid(images))
#         # print labels
#         print(' '.join('%5s' % classes[labels[j]] for j in range(5)))
# except:
#     print ('done')


class Net(nn.Module):
    # 定義Net的初始化函數,這個函數定義了該神經網絡的基本結構
    def __init__(self):
        super(Net, self).__init__()  # 複製並使用Net的父類的初始化方法,即先運行nn.Module的初始化函數
        self.conv1 = nn.Conv2d(3, 6, 5)  # 定義conv1函數的是圖像卷積函數:輸入爲圖像(1個頻道,即灰度圖),輸出爲 6張特徵圖, 卷積核爲5x5正方形
        self.conv2 = nn.Conv2d(6, 16, 5)  # 定義conv2函數的是圖像卷積函數:輸入爲6張特徵圖,輸出爲16張特徵圖, 卷積核爲5x5正方形
        self.fc1 = nn.Linear(16 * 5 * 5, 120)  # 定義fc1(fullconnect)全連接函數1爲線性函數:y = Wx + b,並將16*5*5個節點連接到120個節點上。
        self.fc2 = nn.Linear(120, 84)  # 定義fc2(fullconnect)全連接函數2爲線性函數:y = Wx + b,並將120個節點連接到84個節點上。
        self.fc3 = nn.Linear(84, 10)  # 定義fc3(fullconnect)全連接函數3爲線性函數:y = Wx + b,並將84個節點連接到10個節點上。

    # 定義該神經網絡的向前傳播函數,該函數必須定義,一旦定義成功,向後傳播函數也會自動生成(autograd)
    def forward(self, x):
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))  # 輸入x經過卷積conv1之後,經過激活函數ReLU,使用2x2的窗口進行最大池化Max pooling,然後更新到x。
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)  # 輸入x經過卷積conv2之後,經過激活函數ReLU,使用2x2的窗口進行最大池化Max pooling,然後更新到x。
        x = x.view(-1, self.num_flat_features(x))  # view函數將張量x變形成一維的向量形式,總特徵數並不改變,爲接下來的全連接作準備。
        x = F.relu(self.fc1(x))  # 輸入x經過全連接1,再經過ReLU激活函數,然後更新x
        x = F.relu(self.fc2(x))  # 輸入x經過全連接2,再經過ReLU激活函數,然後更新x
        x = self.fc3(x)  # 輸入x經過全連接3,然後更新x
        return x

    # 使用num_flat_features函數計算張量x的總特徵量(把每個數字都看出是一個特徵,即特徵總量),比如x是4*2*2的張量,那麼它的特徵總量就是16。
    def num_flat_features(self, x):
        size = x.size()[1:]  # 這裏爲什麼要使用[1:],是因爲pytorch只接受批輸入,也就是說一次性輸入好幾張圖片,那麼輸入數據張量的維度自然上升到了4維。
        # 【1:】讓我們把注意力放在後3維上面,是因爲 x.size() 會 return [nSamples, nChannels, Height, Width]。我們只需要展開後三項成爲一個一維的 tensor。
        num_features = 1
        for s in size:
            num_features *= s
        return num_features


net = Net()
# # 以下代碼是爲了看一下我們需要訓練的參數的數量
# # print(net)
# #net.parameters() type is generator
# params = list(net.parameters())
# k = 0
# for i in params:
#     l = 1
#     #i type is <class 'torch.nn.parameter.Parameter'>
#     print("該層的結構:" + str(list(i.size())))
#     for j in i.size():
#         l *= j
#     print("參數和:" + str(l))
#     k = k + l
#
# print("總參數和:" + str(k))

#====================================================================================

criterion = nn.CrossEntropyLoss()#叉熵損失函數
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)#使用SGD(隨機梯度下降)優化,學習率爲0.001,動量爲0.9
for epoch in range(10):  # 遍歷數據集兩次

    running_loss = 0.0
    # enumerate(sequence, [start=0]),i序號,data是數據
    for i, data in enumerate(trainloader, 0):
        # get the inputs
        #data is list
        # print(data[0].size())
        # print(i)
        inputs, labels = data  # data的結構是:[4x3x32x32的張量,長度4的張量]
        # wrap them in Variable
        inputs, labels = Variable(inputs), Variable(labels)  # 把input數據從tensor轉爲variable
        # print(input.grad_fn)
        # zero the parameter gradients
        optimizer.zero_grad()  # 將參數的grad值初始化爲0

        # forward + backward + optimize
        outputs = net(inputs)
        loss = criterion(outputs, labels)  # 將output和labels使用叉熵計算損失
        loss.backward()  # 反向傳播
        optimizer.step()  # 用SGD更新參數

        # 每2000批數據打印一次平均loss值
        running_loss += loss.item()  # loss本身爲Variable類型,所以要使用data獲取其Tensor,因爲其爲標量,所以取0  或使用loss.item()
        if i % 2000 == 1999:  # 每2000批打印一次
            print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 2000))
            running_loss = 0.0
print('Finished Training')

#測試集,將相對目錄./data下的cifar-10-batches-py文件夾中的全部數據(10000張圖片作爲測試數據)加載到內存中,若download爲True時,會自動從網上下載數據並解壓
testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)

# 將測試集的10000張圖片劃分成2500份,每份4張圖,用於mini-batch輸入。
testloader = torch.utils.data.DataLoader(testset, batch_size=4,
                                         shuffle=False, num_workers=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)))
#
correct = 0
total = 0
with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(Variable(images))
        # print outputs.data
        # print(outputs.data)
        # print(labels)
        value, predicted = torch.max(outputs.data, 1)  # outputs.data是一個4x10張量,將每一行的最大的那一列的值和序號各自組成一個一維張量返回,第一個是值的張量,第二個是序號的張量。
        #label.size(0) 是一個數
        total += labels.size(0)
        correct += (predicted == labels).sum()  # 兩個一維張量逐行對比,相同的行記爲1,不同的行記爲0,再利用sum(),求總和,得到相同的個數。

print('Accuracy of the network on the 10000 test images: %d %%' % (100 * correct / total))

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章