Pytorch使用卷積神經(jīng)網(wǎng)絡(luò)對(duì)CIFAR10圖片進(jìn)行分類方式
神經(jīng)網(wǎng)絡(luò)
如下所示為一個(gè)基本的卷積神經(jīng)網(wǎng)絡(luò)的模型,將圖像輸入之后經(jīng)過(guò)卷積操作提取特征,再經(jīng)過(guò)降采樣操作后輸出到下一層。
經(jīng)過(guò)多次多個(gè)卷積、池化層之后結(jié)果輸出到全連接層,經(jīng)過(guò)全連接映射到最終結(jié)果。

一個(gè)神經(jīng)網(wǎng)絡(luò)的典型訓(xùn)練過(guò)程可以分為如下幾步:
- 定義神經(jīng)網(wǎng)絡(luò),包含一些可學(xué)習(xí)參數(shù)(或者叫權(quán)重)
- 將數(shù)據(jù)輸入網(wǎng)絡(luò)進(jìn)行訓(xùn)練,并計(jì)算損失值
- 將梯度反向傳播給網(wǎng)絡(luò)的參數(shù),據(jù)此更新網(wǎng)絡(luò)的權(quán)重,并再次訓(xùn)練
定義網(wǎng)絡(luò)
如下所示為我們定義的神經(jīng)網(wǎng)絡(luò)類NeuralNet。
首先它繼承自父類nn.Module,從import可以看到從torch中分別引入了torch.nn和torch.functional,其中nn用于保存常用的神經(jīng)網(wǎng)絡(luò)類,而functional庫(kù)中則是一些網(wǎng)絡(luò)操作。nn.Module類有兩個(gè)子類必須重寫(xiě)的方法,初始化方法__init__用于定義網(wǎng)絡(luò)結(jié)構(gòu),forward()中定義網(wǎng)絡(luò)訓(xùn)練操作,當(dāng)網(wǎng)絡(luò)對(duì)象被調(diào)用時(shí)會(huì)自動(dòng)執(zhí)行該方法。
在構(gòu)造函數(shù)__init__中我們定義網(wǎng)絡(luò)的結(jié)構(gòu),這里定義了網(wǎng)絡(luò)的兩個(gè)卷積層為torch.nn庫(kù)中的二維卷積函數(shù)Conv2d(),nn.Conv2d(1, 6, (5, 5))代表輸入數(shù)據(jù)的通道數(shù)為1,輸出通道數(shù)為6,卷積核為5×5,卷積核長(zhǎng)和寬一致的話可以簡(jiǎn)寫(xiě)為5。用nn.Linear實(shí)現(xiàn)全連接操作,輸入數(shù)據(jù)長(zhǎng)度為16 * 5 * 5,這是由于之前conv2輸出的16通道的5×5的數(shù)據(jù),輸出長(zhǎng)度120的數(shù)據(jù)。經(jīng)過(guò)三個(gè)全連接層輸出長(zhǎng)度為10
在forward()方法中實(shí)現(xiàn)網(wǎng)絡(luò)的訓(xùn)練過(guò)程,將輸入數(shù)據(jù)input_x經(jīng)過(guò)conv1的卷積操作后經(jīng)過(guò)激活函數(shù)relu,最后經(jīng)過(guò)池化操作max_pool2d得到第一個(gè)卷積層的輸出layer1,同樣操作后得到第二個(gè)卷積層layer2。
將卷積的結(jié)果通過(guò)flat_features()降維,經(jīng)過(guò)第二個(gè)卷積層layer2為四維數(shù)據(jù)[1, 16, 5, 5],通過(guò)tensor.size()[1:]選擇第一個(gè)維度以后的維度相乘得到features為1655=400,通過(guò)tensor.view(-1,400)將其轉(zhuǎn)化為長(zhǎng)度400的二維數(shù)據(jù)。最后經(jīng)過(guò)三個(gè)全連接層后輸出。
import torch
from torch import nn
from torch.nn import functional as Func
class NeuralNet(nn.Module):
def __init__(self):
super(NeuralNet, self).__init__()
# 兩個(gè)卷積層
self.conv1 = nn.Conv2d(1, 6, (5, 5))
self.conv2 = nn.Conv2d(6, 16, 5)
# 三個(gè)全連接層
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):
# 進(jìn)行兩次卷積、池化操作
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)
# 經(jīng)過(guò)三個(gè)全連接層
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
# 創(chuàng)建一個(gè)neural_net對(duì)象并打印
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)
)
'''
訓(xùn)練網(wǎng)絡(luò)
如下所示為將數(shù)據(jù)送入網(wǎng)絡(luò)訓(xùn)練并計(jì)算損失值的過(guò)程。
首先通過(guò)torch.randn()生成四維的隨機(jī)數(shù)據(jù),由于我們之前定義網(wǎng)絡(luò)中Conv2d()函數(shù)接收的數(shù)據(jù)要求是四維的,其中第一維度代表樣本數(shù)據(jù)的個(gè)數(shù),第二維代表數(shù)據(jù)的通道數(shù),第3、4維代表數(shù)據(jù)大小,這里是32×32的網(wǎng)格。然后將生成的數(shù)據(jù)送入neural_net(),這里會(huì)自動(dòng)調(diào)用該對(duì)象的forward()方法進(jìn)行模型訓(xùn)練并輸出結(jié)果。
得到輸出結(jié)果y_output之后通過(guò)和目標(biāo)值進(jìn)行比較即可得出損失值,這里仍然使用randn創(chuàng)建目標(biāo)值y_target,注意目標(biāo)值要和輸出值維度相同,我們輸入的樣本數(shù)量為1,最后經(jīng)全連接層fc3產(chǎn)生的結(jié)果長(zhǎng)度為10,所以y_output維度為(1, 10),因此y_target也是二維1×10的數(shù)據(jù)。
定義評(píng)價(jià)函數(shù)criterion為nn.MSELoss(),即計(jì)算輸出和目標(biāo)的均方誤差(mean-squared error)。
x = torch.randn(1, 1, 32, 32) # 隨機(jī)產(chǎn)生輸入數(shù)據(jù) y_output = neural_net(x) # 輸入數(shù)據(jù)并進(jìn)行訓(xùn)練 y_target = torch.randn(1, 10) # 隨機(jī)產(chǎn)生目標(biāo)數(shù)據(jù) criterion = nn.MSELoss() # 定義評(píng)價(jià)函數(shù) loss = criterion(y_output, y_target) # 計(jì)算損失值
反向傳播
由之前定義的網(wǎng)絡(luò)可知我們的從輸入到輸出,數(shù)據(jù)經(jīng)過(guò)的函數(shù)操作如下,
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
-> view -> linear -> relu -> linear -> relu -> linear
-> MSELoss
-> loss
通過(guò)tensor的grad_fn屬性記錄了這些函數(shù)操作,例如從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
我們進(jìn)行反向傳播操作,然后就可以查看各層網(wǎng)絡(luò)參數(shù)的梯度
neural_net.zero_grad() # 清零所有參數(shù)(parameter)的梯度緩存 loss.backward() # 反向傳播 print(neural_net.conv1.bias.grad) # 查看梯度 # tensor([-0.0046, -0.0087, 0.0390, 0.0045, -0.0096, 0.0028])
根據(jù)得到的梯度對(duì)網(wǎng)絡(luò)的參數(shù)進(jìn)行更新,例如這里使用隨機(jī)梯度下降法進(jìn)行更新,其公式為weight = weight - learning_rate * gradient,即在原有權(quán)重的基礎(chǔ)上,根據(jù)學(xué)習(xí)率learning_rate減少一定梯度。
如下所示遍歷網(wǎng)絡(luò)的所有參數(shù)neural_net.parameters并對(duì)其進(jìn)行更新
learning_rate = 0.01
for param in neural_net.parameters():
param.data.sub_(param.grad.data * learning_rate)
然而在使用神經(jīng)網(wǎng)絡(luò)時(shí),我們可能希望使用各種不同的更新規(guī)則,如SGD、Nesterov-SGD、Adam、RMSProp等。
在torch.optim庫(kù)實(shí)現(xiàn)了所有的這些方法,如下所示,首先創(chuàng)建優(yōu)化器optimizer,然后進(jìn)行多次迭代訓(xùn)練
import torch.optim as optim
# 創(chuàng)建優(yōu)化器(optimizer)
optimizer = optim.SGD(neural_net.parameters(), lr=0.01)
# 在訓(xùn)練的迭代中:
for epoch in range(100):
optimizer.zero_grad() # 清零梯度緩存
output = neural_net(x)
loss = criterion(y_output, y_target)
loss.backward()
optimizer.step() # 自動(dòng)更新參數(shù)
CIFAR10圖片識(shí)別
卷積神經(jīng)網(wǎng)絡(luò)一個(gè)常用的領(lǐng)域就是圖片分類,而圖片分類中最經(jīng)典的就是對(duì)CIFAR10圖片數(shù)據(jù)集進(jìn)行分類。
它是一個(gè)包含“飛機(jī)”,“汽車”,“鳥(niǎo)”,“貓”,“鹿”,“狗”,“青蛙”,“馬”,“船”,“卡車”10中類別的圖片庫(kù)。
在CIFAR-10里面的圖片數(shù)據(jù)大小是3x32x32,即:三通道彩色圖像,圖像大小是32x32像素。
準(zhǔn)備數(shù)據(jù)
pytorch的torchvision提供了CIFAR10庫(kù),通過(guò)torchvision.datasets.CIFAR10加載該數(shù)據(jù)集。root為數(shù)據(jù)集的路徑,如果該路徑下沒(méi)有數(shù)據(jù),則會(huì)從指定站點(diǎn)下載并保存到該路徑,train屬性標(biāo)志是訓(xùn)練集還是測(cè)試集數(shù)據(jù)。transform指定了對(duì)數(shù)據(jù)進(jìn)行的預(yù)處理操作。
例如這里將兩個(gè)預(yù)處理操作通過(guò)Compose放在了transform中,第一步ToTensor將數(shù)據(jù)轉(zhuǎn)化為張量,第二步通過(guò)Normalize()將數(shù)據(jù)化為正態(tài)分布值。
前面的(0.5,0.5,0.5)是 R G B 三個(gè)通道上的均值,后面(0.5, 0.5, 0.5)是三個(gè)通道的標(biāo)準(zhǔn)差,Normalize對(duì)每個(gè)通道執(zhí)行以下操作:image =(圖像-平均值)/ std。當(dāng)mean,std都是0.5時(shí)將使圖像在[-1,1]范圍內(nèi)歸一化。
例如,最小值0將轉(zhuǎn)換(0-0.5)/0.5=-1
接著使用DataLoader將數(shù)據(jù)分為多個(gè)批次,batch_size指定每個(gè)批次包含幾個(gè)圖片,shuffle為是否打亂圖片,num_workers指定多個(gè)線程去加載數(shù)據(jù)。
當(dāng)訓(xùn)練很快、加載數(shù)據(jù)時(shí)間過(guò)慢時(shí)會(huì)導(dǎo)致模型等待數(shù)據(jù)加載而變慢,這時(shí)可以采用多線程來(lái)加載數(shù)據(jù)。
import torch
import torchvision
# 定義數(shù)據(jù)預(yù)處理操作
transform=torchvision.transforms.Compose([
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
# 加載數(shù)據(jù)
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)
# 封裝為批數(shù)據(jù)
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)
# 定義標(biāo)簽值
classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
我們通過(guò)matplotlib打印其中的一個(gè)批次圖片和標(biāo)簽。由于之前將圖片標(biāo)準(zhǔn)化,所以需要進(jìn)行反標(biāo)準(zhǔn)化操作。
由于CiFAR10的圖片數(shù)據(jù)為3×32×32,需要使用transpose將其轉(zhuǎn)為32×32×3
import matplotlib.pyplot as plt
import numpy as np
# 輸出圖像的函數(shù)
def imshow(img):
img = img / 2 + 0.5 # 反標(biāo)準(zhǔn)化
npimg = img.numpy()
plt.imshow(np.transpose(npimg, (1, 2, 0)))
plt.show()
# 獲取一個(gè)批次的訓(xùn)練圖片、標(biāo)簽
images, labels = iter(train_loader).next()
# 顯示圖片
imshow(torchvision.utils.make_grid(images))
# 打印圖片標(biāo)簽
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
顯示結(jié)果如下:

定義網(wǎng)絡(luò)和參數(shù)
如下定義卷積網(wǎng)絡(luò)類并創(chuàng)建一個(gè)對(duì)象net,以及定義訓(xùn)練的損失函數(shù)和優(yōu)化器
nn.Conv2d(3, 6, 5)代表使用pytorch自帶的二維卷積網(wǎng)絡(luò),輸入通道為3,輸出通道為6,卷積核大小為5×5。
輸入的一個(gè)批次有四張32×32的3通道圖片[4, 3, 32, 32],輸出大小=(輸入大小-卷積核+padding)/stride+1,這里默認(rèn)padding為0,stride為1,所以卷積后為[4, 6, 28, 28]
nn.MaxPool2d(2, 2)為最大池化函數(shù),池化窗口大小為2,步長(zhǎng)也為2,由于窗口大小為2,所以圖片大小會(huì)/2,即池化后為[4, 6, 14, 14]
之后經(jīng)過(guò)view()將x轉(zhuǎn)化為[4, 400]的二維張量,再經(jīng)過(guò)由nn.Linear()實(shí)現(xiàn)的三個(gè)全連接層,由400->120->84->10映射為10個(gè)分類
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
# 定義卷積網(wǎng)絡(luò)
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:[4, 3, 32, 32]
x = self.pool(F.relu(self.conv1(x))) # x:[4, 6, 14, 14]
x = self.pool(F.relu(self.conv2(x))) # x:[4, 16, 5, 5]
x = x.view(-1, 16 * 5 * 5) # x:[4, 400]
x = F.relu(self.fc1(x)) # x:[4, 120]
x = F.relu(self.fc2(x)) # x:[4, 84]
x = self.fc3(x) # x:[4, 10]
return x
net = Net()
criterion = nn.CrossEntropyLoss() # 損失函數(shù)
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9) # 優(yōu)化器
訓(xùn)練并保存模型
如下所示進(jìn)行兩輪疊代訓(xùn)練,每輪按批次取出訓(xùn)練集的數(shù)據(jù)投入模型進(jìn)行訓(xùn)練
for epoch in range(2): # 進(jìn)行兩輪迭代訓(xùn)練
running_loss = 0.0
# 按批次取出數(shù)據(jù)進(jìn)行訓(xùn)練
for i, data in enumerate(train_loader, 0):
inputs, labels = data # 獲取數(shù)據(jù)和標(biāo)簽
optimizer.zero_grad() # 清零梯度緩存
outputs = net(inputs) # 得到預(yù)測(cè)結(jié)果
loss = criterion(outputs, labels) # 計(jì)算損失
loss.backward() # 反向傳播
optimizer.step() # 更新參數(shù)
# 每隔兩千次輸出一次平均損失值
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')
# 保存訓(xùn)練好的模型
MODEL_PATH = './cifar_net.pth'
torch.save(net.state_dict(), MODEL_PATH)
載入模型進(jìn)行測(cè)試
模型以及訓(xùn)練好并保存了,那么模型的預(yù)測(cè)效果如何呢?
這就需要在測(cè)試集數(shù)據(jù)上進(jìn)行檢測(cè)了,如下所示,我們首先讀取保存的模型,然后將測(cè)試集的圖片數(shù)據(jù)images投入模型進(jìn)行預(yù)測(cè),然后取得預(yù)測(cè)值predicted,將其和測(cè)試集的標(biāo)簽labels進(jìn)行比對(duì),統(tǒng)計(jì)預(yù)測(cè)正確的個(gè)數(shù)correct,除以總數(shù)total就是準(zhǔn)去率了
# 加載模型
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) # 將測(cè)試集圖片投入模型
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0) # 統(tǒng)計(jì)測(cè)試集總樣本數(shù)
correct += (predicted == labels).sum().item() # 累計(jì)預(yù)測(cè)正確的個(gè)數(shù)
print('在整個(gè)測(cè)試集上的準(zhǔn)確率為: %d %%' % (100 * correct / total))
使用GPU
Nvidia顯卡具有的CUDA加速可以更快地進(jìn)行神經(jīng)網(wǎng)絡(luò)的訓(xùn)練,torch.cuda包集成了相關(guān)的操作函數(shù)。例如通過(guò)is_available()可以查看顯卡是否可用,device_count()統(tǒng)計(jì)具有cuda功能的顯卡個(gè)數(shù),get_device_name(i)查看第i個(gè)顯卡名字。
如果需要使用GPU進(jìn)行網(wǎng)絡(luò)訓(xùn)練,需要將模型net和訓(xùn)練集的數(shù)據(jù)images、標(biāo)簽labels都放到GPU設(shè)備上,通過(guò)model.to(device)可以將模型或張量放到指定設(shè)備上?;蛘咧苯邮褂?code>model.cuda(i)放到第i塊CUDA顯卡上。
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # 獲取GPU設(shè)備
print(device)
net = Net()
net.to(device) # 將模型放在GPU上
for epoch in range(2): # 進(jìn)行兩輪迭代訓(xùn)練
running_loss = 0.0
# 按批次取出數(shù)據(jù)進(jìn)行訓(xùn)練
for i, data in enumerate(train_loader, 0):
inputs, labels = data
inputs, labels = inputs.to(device),labels.to(device) # 將訓(xùn)練數(shù)據(jù)放到GPU
......
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
DES加密解密算法之python實(shí)現(xiàn)版(圖文并茂)
這篇文章主要介紹了DES加密解密算法之python實(shí)現(xiàn)版,圖文并茂的為大家分享一下,需要的朋友可以參考下2018-12-12
python實(shí)現(xiàn)將Word文檔中的文字轉(zhuǎn)換成語(yǔ)音的操作步驟
在Python中實(shí)現(xiàn)文字轉(zhuǎn)語(yǔ)音(Text-to-Speech, TTS)功能,能夠廣泛應(yīng)用于多種場(chǎng)景,如語(yǔ)音助手、有聲讀物、無(wú)障礙閱讀等,本文將結(jié)合具體案例,詳細(xì)介紹如何在Python中實(shí)現(xiàn)文字轉(zhuǎn)語(yǔ)音功能,需要的朋友可以參考下2024-08-08
Python+Selenium實(shí)現(xiàn)表單自動(dòng)填充和提交
你是不是也厭倦了每天重復(fù)表單填寫(xiě)的工作,是時(shí)候讓技術(shù)來(lái)幫助我們解放雙手了,下面小編就為大家介紹一下如何使用Selenium和Python來(lái)自動(dòng)填充和提交表單2023-09-09
python利用hook技術(shù)破解https的實(shí)例代碼
python利用hook技術(shù)破解https的實(shí)例代碼,需要的朋友可以參考一下2013-03-03
Python自動(dòng)化之批量生成含指定數(shù)據(jù)的word文檔
在平時(shí)工作當(dāng)中,經(jīng)常需要處理文件,特別是Word,我們常常會(huì)機(jī)械的重復(fù)打開(kāi)、修改、保存文檔等一系列操作。本文將主要介紹如何通過(guò)Python批量生成含指定數(shù)據(jù)的word文檔,感興趣的同學(xué)可以來(lái)看一看2021-11-11
Python交互環(huán)境下打印和輸入函數(shù)的實(shí)例內(nèi)容
在本篇文章里小編給大家分享的是關(guān)于Python交互環(huán)境下打印和輸入函數(shù)的實(shí)例內(nèi)容,有興趣的朋友們可以學(xué)習(xí)下。2020-02-02

