Pytorch使用卷積神經網絡對CIFAR10圖片進行分類

神經網絡

如下所示爲一個基本的卷積神經網絡的模型,將圖像輸入之後經過卷積操作提取特徵,再經過降採樣操作後輸出到下一層。經過多次多個卷積、池化層之後結果輸出到全連接層,經過全連接映射到最終結果。
在這裏插入圖片描述一個神經網絡的典型訓練過程可以分爲如下幾步:

  1. 定義神經網絡,包含一些可學習參數(或者叫權重)
  2. 將數據輸入網絡進行訓練,並計算損失值
  3. 將梯度反向傳播給網絡的參數,據此更新網絡的權重,並再次訓練

定義網絡

如下所示爲我們定義的神經網絡類NeuralNet。

首先它繼承自父類nn.Module,從import可以看到從torch中分別引入了torch.nntorch.functional,其中nn用於保存常用的神經網絡類,而functional庫中則是一些網絡操作。nn.Module類有兩個子類必須重寫的方法,初始化方法__init__用於定義網絡結構,forward()中定義網絡訓練操作,當網絡對象被調用時會自動執行該方法。

在構造函數__init__中我們定義網絡的結構,這裏定義了網絡的兩個卷積層爲torch.nn庫中的二維卷積函數Conv2d()nn.Conv2d(1, 6, (5, 5))代表輸入數據的通道數爲1,輸出通道數爲5,卷積核爲5×5,卷積核長和寬一致的話可以簡寫爲5。用nn.Linear實現全連接操作,輸入數據長度爲16 * 5 * 5,這是由於之前conv2輸出的16通道的5×5的數據,輸出長度120的數據。經過三個全連接層輸出長度爲10

forward()方法中實現網絡的訓練過程,將輸入數據input_x經過conv1的卷積操作後經過激活函數relu,最後經過池化操作max_pool2d得到第一個卷積層的輸出layer1,同樣操作後得到第二個卷積層layer2。將卷積的結果通過flat_features()降維,經過第二個卷積層layer2爲四維數據[1, 16, 5, 5],通過tensor.size()[1:]選擇第一個維度以後的維度相乘得到features爲1655=400,通過tensor.view(-1,400)將其轉化爲長度400的二維數據。最後經過三個全連接層後輸出。

import torch
from torch import nn
from torch.nn import functional as Func


class NeuralNet(nn.Module):
    def __init__(self):
        super(NeuralNet, self).__init__()
        # 兩個卷積層
        self.conv1 = nn.Conv2d(1, 6, (5, 5))
        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, input_x):
        # 進行兩次卷積、池化操作
        layer1 = Func.max_pool2d(Func.relu(self.conv1(input_x)), (2, 2))
        layer2 = Func.max_pool2d(Func.relu(self.conv2(layer1)), (2, 2))
        # 降維
        flat = self.flat_features(layer2)
        # 經過三個全連接層
        fc1 = Func.relu(self.fc1(flat))
        fc2 = Func.relu(self.fc2(fc1))
        fc3 = self.fc3(fc2)
        return fc3

    def flat_features(self, tensor):
        features = 1
        for size in tensor.size()[1:]:
            features *= size
        flat = tensor.view(-1, features)
        return flat

# 創建一個neural_net對象並打印
neural_net = NeuralNet()
print(neural_net)
'''
NeuralNet(
  (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
  (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)
)
'''

訓練網絡

如下所示爲將數據送入網絡訓練並計算損失值的過程。

首先通過torch.randn()生成四維的隨機數據,由於我們之前定義網絡中Conv2d()函數接收的數據要求是四維的,其中第一維度代表樣本數據的個數,第二維代表數據的通道數,第3、4維代表數據大小,這裏是32×32的網格。然後將生成的數據送入neural_net(),這裏會自動調用該對象的forward()方法進行模型訓練並輸出結果。

得到輸出結果y_output之後通過和目標值進行比較即可得出損失值,這裏仍然使用randn創建目標值y_target,注意目標值要和輸出值維度相同,我們輸入的樣本數量爲1,最後經全連接層fc3產生的結果長度爲10,所以y_output維度爲(1, 10),因此y_target也是二維1×10的數據。定義評價函數criterion爲nn.MSELoss(),即計算輸出和目標的均方誤差(mean-squared error)。

x = torch.randn(1, 1, 32, 32)   # 隨機產生輸入數據
y_output = neural_net(x)  # 輸入數據並進行訓練

y_target = torch.randn(1, 10)   # 隨機產生目標數據
criterion = nn.MSELoss()    # 定義評價函數
loss = criterion(y_output, y_target)  # 計算損失值

反向傳播

由之前定義的網絡可知我們的從輸入到輸出,數據經過的函數操作如下,

input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
        -> view -> linear -> relu -> linear -> relu -> linear
        -> MSELoss
        -> loss

通過tensor的grad_fn屬性記錄了這些函數操作,例如從loss向前回退查看grad_fn

print(loss.grad_fn)  # MSELoss
print(loss.grad_fn.next_functions[0][0])  # Linear
print(loss.grad_fn.next_functions[0][0].next_functions[0][0])  # ReLU

我們進行反向傳播操作,然後就可以查看各層網絡參數的梯度

neural_net.zero_grad()  # 清零所有參數(parameter)的梯度緩存
loss.backward()  # 反向傳播
print(neural_net.conv1.bias.grad)  # 查看梯度
# tensor([-0.0046, -0.0087,  0.0390,  0.0045, -0.0096,  0.0028])

根據得到的梯度對網絡的參數進行更新,例如這裏使用隨機梯度下降法進行更新,其公式爲weight = weight - learning_rate * gradient,即在原有權重的基礎上,根據學習率learning_rate減少一定梯度。如下所示遍歷網絡的所有參數neural_net.parameters並對其進行更新

learning_rate = 0.01
for param in neural_net.parameters():
    param.data.sub_(param.grad.data * learning_rate)

然而在使用神經網絡時,我們可能希望使用各種不同的更新規則,如SGD、Nesterov-SGD、Adam、RMSProp等。在torch.optim庫實現了所有的這些方法,如下所示,首先創建優化器optimizer,然後進行多次迭代訓練

import torch.optim as optim

# 創建優化器(optimizer)
optimizer = optim.SGD(neural_net.parameters(), lr=0.01)

# 在訓練的迭代中:
for epoch in range(100):
    optimizer.zero_grad()  # 清零梯度緩存
    output = neural_net(x)
    loss = criterion(y_output, y_target)
    loss.backward()
    optimizer.step()  # 自動更新參數

CIFAR10圖片識別

卷積神經網絡一個常用的領域就是圖片分類,而圖片分類中最經典的就是對CIFAR10圖片數據集進行分類。它是一個包含“飛機”,“汽車”,“鳥”,“貓”,“鹿”,“狗”,“青蛙”,“馬”,“船”,“卡車”10中類別的圖片庫。在CIFAR-10裏面的圖片數據大小是3x32x32,即:三通道彩色圖像,圖像大小是32x32像素。

準備數據

pytorch的torchvision提供了CIFAR10庫,通過torchvision.datasets.CIFAR10加載該數據集。root爲數據集的路徑,如果該路徑下沒有數據,則會從指定站點下載並保存到該路徑,train屬性標誌是訓練集還是測試集數據。transform指定了對數據進行的預處理操作。例如這裏將兩個預處理操作通過Compose放在了transform中,第一步ToTensor將數據轉化爲張量,第二步通過Normalize()將數據化爲正態分佈值。前面的(0.5,0.5,0.5)是 R G B 三個通道上的均值,後面(0.5, 0.5, 0.5)是三個通道的標準差,Normalize對每個通道執行以下操作:image =(圖像-平均值)/ std。當mean,std都是0.5時將使圖像在[-1,1]範圍內歸一化。例如,最小值0將轉換(0-0.5)/0.5=-1

接着使用DataLoader將數據分爲多個批次,batch_size指定每個批次包含幾個圖片,shuffle爲是否打亂圖片,num_workers指定多個線程去加載數據。當訓練很快、加載數據時間過慢時會導致模型等待數據加載而變慢,這時可以採用多線程來加載數據。

import torch
import torchvision

# 定義數據預處理操作
transform=torchvision.transforms.Compose([
    torchvision.transforms.ToTensor(),
    torchvision.transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
# 加載數據
data_path = 'D:/Temp/MachineLearning/data'
train_set = torchvision.datasets.CIFAR10(root=data_path, train=True, download=True, transform=transform)
test_set = torchvision.datasets.CIFAR10(root=data_path, train=False, download=True, transform=transform)
# 封裝爲批數據
train_loader = torch.utils.data.DataLoader(train_set, batch_size=4, shuffle=True, num_workers=2)
test_loader = torch.utils.data.DataLoader(test_set, batch_size=4, shuffle=False, num_workers=2)
# 定義標籤值
classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

我們通過matplotlib打印其中的一個批次圖片和標籤。由於之前將圖片標準化,所以需要進行反標準化操作。由於CiFAR10的圖片數據爲3×32×32,需要使用transpose將其轉爲32×32×3

import matplotlib.pyplot as plt
import numpy as np

# 輸出圖像的函數
def imshow(img):
    img = img / 2 + 0.5     # 反標準化
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    plt.show()


# 獲取一個批次的訓練圖片、標籤
images, labels = iter(train_loader).next()
# 顯示圖片
imshow(torchvision.utils.make_grid(images))
# 打印圖片標籤
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))

顯示結果如下:
在這裏插入圖片描述

定義網絡和參數

如下定義卷積網絡類並創建一個對象net,以及定義訓練的損失函數和優化器

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

# 定義卷積網絡
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()

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

訓練並保存模型

如下所示進行兩輪疊代訓練,每輪按批次取出訓練集的數據投入模型進行訓練

for epoch in range(2):  # 進行兩輪迭代訓練
    running_loss = 0.0
    # 按批次取出數據進行訓練
    for i, data in enumerate(train_loader, 0):
        inputs, labels = data   # 獲取數據和標籤
        optimizer.zero_grad()   # 清零梯度緩存
        outputs = net(inputs)   #  得到預測結果
        loss = criterion(outputs, labels)   # 計算損失
        loss.backward()     # 反向傳播
        optimizer.step()    # 更新參數

        # 每隔兩千次輸出一次平均損失值
        running_loss += loss.item()
        if i % 2000 == 1999:
            print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 2000))
            running_loss = 0.0

print('Finished Training')
# 保存訓練好的模型
MODEL_PATH = './cifar_net.pth'
torch.save(net.state_dict(), MODEL_PATH)

載入模型進行測試

模型以及訓練好並保存了,那麼模型的預測效果如何呢?這就需要在測試集數據上進行檢測了,如下所示,我們首先讀取保存的模型,然後將測試集的圖片數據images投入模型進行預測,然後取得預測值predicted,將其和測試集的標籤labels進行比對,統計預測正確的個數correct,除以總數total就是準去率了

# 加載模型
net = Net()
net.load_state_dict(torch.load(MODEL_PATH))

correct = 0
total = 0
with torch.no_grad():
    for data in test_loader:
        images, labels = data
        outputs = net(images)   # 將測試集圖片投入模型
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)     # 統計測試集總樣本數
        correct += (predicted == labels).sum().item()   # 累計預測正確的個數

print('在整個測試集上的準確率爲: %d %%' % (100 * correct / total))

使用GPU

Nvidia顯卡具有的CUDA加速可以更快地進行神經網絡的訓練,torch.cuda包集成了相關的操作函數。例如通過is_available()可以查看顯卡是否可用,device_count()統計具有cuda功能的顯卡個數,get_device_name(i)查看第i個顯卡名字。
如果需要使用GPU進行網絡訓練,需要將模型net和訓練集的數據images、標籤labels都放到GPU設備上,通過model.to(device)可以將模型或張量放到指定設備上。或者直接使用model.cuda(i)放到第i塊CUDA顯卡上。

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")	# 獲取GPU設備
print(device)
net = Net()
net.to(device)		# 將模型放在GPU上

for epoch in range(2):  # 進行兩輪迭代訓練
    running_loss = 0.0
    # 按批次取出數據進行訓練
    for i, data in enumerate(train_loader, 0):
        inputs, labels = data   
        inputs, labels = inputs.to(device),labels.to(device)	# 將訓練數據放到GPU
        ......
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章