卷積神經網絡學習——第二部分:卷積神經網絡訓練的基本流程
一、序言
本文承接第一部分,基於對卷積神經網絡網絡組成的認識,開始學習如何去使用卷積神經網絡進行對應的訓練。模型評估作為優化部分,我們將放在第三個部分中再好好講他的作用以及意義~
訓練的基本流程主要是數據集引入、訓練及參數設置、驗證及反饋這三個步驟,我們現在分三個步驟來認識一下這個訓練的基本流程。
PS:我更新真是快啊~
——2020年11月20日於北郵教三539
二、訓練流程
1、數據集引入
本文根據對應的實驗要求,主要采用的是Pytorch中自帶的MNIST數據集。MNIST數據集由於比較基礎,歷年來都是被各種玩壞的主要對象~
引入數據集的時候主要需要注意的是預處理的一個操作,在這里主要用的是ToTensor和Normalize兩個函數進行歸一化處理。其實也不一定需要Normalize這個函數,因為訓練其實都是可以進行的。
但是這里需要注意一下,因為導入數據集的時候操作是固定的。所以為了保證這個操作固定,就最好是用Compose把他們固定起來,不然在后續操作中可能就會添麻煩。
如果你在做自己的手寫圖像識別,並且老是正確率比較低,那么一定注意一下這幾個點。
第一個是圖像的前后的前后處理的時候是不一樣的,很容易直接用自己的圖像直接拿去識別了,但是因為之前訓練集中的都是經過Compose結合后的組合處理后的圖像。但是你直接拿去處理的圖像是沒有經過處理的,輸入到模型中的和此前的格式是不一樣的。
第二個就是因為你手寫的時候,導出的文件無論是png還是jpg,他們基本都是彩色圖片。(是的,哪怕你看到的都是黑色,但他們本身還都是彩色圖片)這個時候可以使用transforms.Grayscale函數先將你的圖像灰度處理,不然在用Normalize的時候還是會帶來問題。由於預處理不同,所以你在前后訓練的素材和你最后手寫的素材不是一個格式,難免會導致你的准確率很低。預處理函數的設置是后面新增自定義素材時的必要保障。
(2020.11.22補充:識別率和筆觸的關系較大,可以參考訓練集中圖像的大小和筆觸進行書寫;在一定程度上,黑底白字比起白底黑字來說,准確率更高——by絢佬)
關於transforms中包含有多少函數,有什么對應的作用,可以參考:二十二種 transforms 圖片數據預處理方法
import torch
import torchvision
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torch.autograd import Variable
from torchvision import datasets, transforms
# 步驟一:數據載入
# 1.transforms.Compose()將各種預處理操作組合到一起
# 2.transforms.ToTensor()將圖片轉換成 PyTorch 中處理的對象 Tensor.在轉化的過程中 PyTorch 自動將圖片標准化了,也就是說Tensor的范用是(0,1)之間
# 3.transforms.Normalize()要傳入兩個參數:均值、方差,做的處理就是減均值,再除以方差。將圖片轉化到了(-1,1)之間
# 4.注意因為圖片是灰度圖,所以只有一個通道,如果是彩色圖片,有三個通道,transforms.Normalize([a,b,c],[d,e,f])來表示每個通道對應的均值和方差。
data_tf = transforms.Compose([transforms.ToTensor(),
transforms.Normalize([0.5],[0.5])
])
# PyTorch 的內置函數 torchvision.datasets.MNIST 導入數據集
# 這里存儲的還是MNIST數據集的格式,但是不一樣的是這個數據集當中的元素是以tensor格式存儲的
train_dataset = datasets.MNIST(
root = '/Users/air/Desktop/【2020秋】數據科學基礎/第三次作業',
train = True,
transform = data_tf,
download = True
)
test_dataset = datasets.MNIST(
root = '/Users/air/Desktop/【2020秋】數據科學基礎/第三次作業',
train = False,
transform = data_tf
)
# 定義超參數
BATCH_SIZE = 128 # 訓練的包的大小,通過將訓練包分為2的倍數以加快訓練過程的方式
LR = 1e-2 # 學習率,學習率太小會減慢訓練效果,學習率太高會導致准確率降低
EPOCHS = 5 # 定義循環次數,避免因為次數太多導致時間過長
# torch.utils.data.DataLoader 建立一個數據迭代器,傳入數據集和 batch size, 通過 shuffle=True 來表示每次迭代數據的時候是否將數據打亂。
# 測試集無需打亂順序;訓練集打亂順序,為了增加訓練模型的泛化能力
# 但是由於這里的訓練集本身就已經滿足要求,所以打亂順序對於泛化能力本身的提升並不是必要的
train_loader = torch.utils.data.DataLoader(train_dataset,
batch_size = BATCH_SIZE,
shuffle = True)
test_loader = torch.utils.data.DataLoader(test_dataset,
batch_size = BATCH_SIZE,
shuffle = False)
2、構建網絡
我們在第一部分的基礎上,我們再重新定義一個網絡,這里我們分別定義一個全連接層網絡,再定義一個三層卷積神經網絡。也借此復習一下網絡定義的相關注意事項。
(1)四層卷積神經網絡
我們在第一部分的基礎上,我們再重新定義一個網絡,這里我們分別定義一個全連接層網絡,再定義一個三層卷積神經網絡。也借此復習一下網絡定義的相關注意事項。
class ConvNet(nn.Module):
def __init__(self):
super(ConvNet, self).__init__()
self.conv1 = nn.Sequential(
nn.Conv2d(1,10,3) ,
nn.ReLU(inplace=True))
self.conv2 = nn.Sequential(
nn.Conv2d(10,20,3) ,
nn.ReLU(inplace=True) ,
nn.MaxPool2d(kernel_size=2 , stride=2))
self.conv3 = nn.Sequential(
nn.Conv2d(20,40,3) ,
nn.ReLU(inplace=True))
self.conv4 = nn.Sequential(
nn.Conv2d(40,80,3) ,
nn.ReLU(inplace=True) ,
nn.MaxPool2d(kernel_size=2 , stride=2))
self.fc = nn.Sequential(nn.Linear(80*4*4,1600) ,
nn.ReLU(inplace=True) ,
nn.Linear(1600,400) ,
nn.ReLU(inplace=True) ,
nn.Linear(400,10) )
def forward(self, x):
in_size = x.size(0)
x = self.conv1(x)
x = self.conv2(x)
x = self.conv3(x)
x = self.conv4(x)
x = x.view(in_size , -1)
out = self.fc(x)
out = F.log_softmax(out, dim = 1)
return out
在定義的時候我們只需要注意幾個點,一個是我們在定義的時候,務必保證我們的每一個Linear之間存在着輸入輸出通道對應的關系要相對應。第一個Linear函數的輸入需要符合 深度x高度x寬度 的相關信息。
其實這里還有幾個沒有解決的問題:Linear函數的數量該如何確定,他們數目會不會影響訓練效果;log_softmax函數對於整體效果影響有多大等~(如果之后解決了我再寫上去(嗯!
(2)兩層全連接層網絡
同卷積神經網絡不太一樣的是,全連接層網絡中就只含有Linear映射。從我們此前的文字,我們可以知道:全連接層是不含Conv2d、relu這些函數的,它的組成僅是簡單的Linear映射而已。所以我們定義全連接網絡如下:
class Net(nn.Module):
def __init__(self, in_dim, n_hidden_1, out_dim):
super(Net,self).__init__()
self.layer1 = nn.Linear(in_dim,n_hidden_1)
self.layer2 = nn.Linear(n_hidden_1,out_dim)
def forward(self,x):
hidden_1_out = self.layer1(x)
out = self.layer2(hidden_1_out)
return out
該網絡包含的參數有三個,第一個是輸入圖像的大小,第二個是中間層,最后一個是輸出。很明顯,輸入的大小就是28*28,並不需要我們再做過多的設計,輸出也是十通道輸出,所以也是固定的。中間層則是根據自己的需求進行定義的。
3、模型訓練
我們在第一部分的基礎上,我們再重新定義一個網絡,這里我們分別定義一個全連接層網絡,再定義一個三層卷積神經網絡。也借此復習一下網絡定義的相關注意事項。這一部分,也可以參考鏈接:PyTorch卷積神經網絡學習筆記進一步了解一下~博主寫的也是真的好
首先,按照國際慣例,我們先用一個流程圖來展示一下每一次訓練過程。
# 訓練模型
for epoch in range(EPOCHS):
running_loss = 0.0
running_accuracy = 0.0
# 訓練
for i, data in enumerate(train_loader, 1):
img, label = data
# 如果使用全連接網絡,需要加上下面這一句話,以讓整個網絡正常工作,因為全連接網絡的輸入是一維列向量,如果不降維,很可能是無法正常運行的
# img = img.view(img.size(0), -1)
if torch.cuda.is_available():
img = img.cuda()
label = label.cuda()
else:
img = Variable(img)
label = Variable(label)
# 向前傳播
out = model(img)
loss = criterion(out, label) # 這個損失是當前批次的平均損失
running_loss += loss.item() * label.size(0) # 累計損失大小,乘積表示當前批次的總損失
_ , pred = torch.max(out, 1)
num_correct = (pred == label).sum()
accuracy = (pred == label).float().mean()
running_accuracy += num_correct.item()
# 向后傳播
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 用於存儲訓練后的參數
torch.save(model.state_dict(), './params.pth')
如果是想要利用已經有的參數進行多次訓練,還可以使用如下語句。
torch.save(model.state_dict(), ‘./params.pth’)
為了加深對於整段代碼的理解,我們可以先了解一下其中比較重要但是又不太常見的幾個語句塊和函數:
- _ , pred = torch.max(out, 1):這句話需要先了解torch.max的用法,不太熟悉的可以參考torch.max用法先看一下。torch.max的定義格式為:
out = torch.max(input, dim)
輸入為input以及一個dim。dim指的是維度,0代表索引每列的最大值,1代表索引每行的最大值。他的輸出為最大值以及其索引。在這里的作用就是,在多分類問題的類別取概率最大的類別。
對於我們而言,經過模型輸出后,我們需要的是結果的第二列,也就是預測值。所以用 _ , pred 就可以只存下pred。除了這種方式以外,也可以用如下語句表示同樣的意思:
pred = torch.max(out, 1)[1]
- torch.cuda.is_available():看你的電腦的GPU是否可以被PyTorch調用
- item():得到一個元素張量里面的元素值,常用於將一個零維張量轉換成浮點數。
- optimizer.zero_grad():遍歷模型的所有參數,將上一次的梯度記錄被清空。
- loss.backward():進行誤差反向傳播。
- optimizer.step():執行一次優化步驟,通過梯度下降法來更新參數的值。以上三個函數均為反向傳播當中的必要函數,詳細可以參考鏈接反向傳播函數的理解進一步了解,這三個函數之間是相輔相成的。
4、模型評估
模型評估大體上的效果和步驟同模型訓練一致,只需要將部分代碼進行替換即可~這里就不貼代碼了,就將評估當成是基於以上的又一次訓練即可。
三、總結
總的來說,這一部分也是比較重要的。需要梳理流程之后再加深對於函數的理解才行。最難最難的就是,網上的大部分代碼都是來回搬運的(讓人極為無語)而可能最開始寫的那個大佬認為一些小的語句沒有必要寫,但是對於我們這樣的小白來說就處於:基本看不懂的狀態。所以最麻煩的還是:為什么有這個語句?他為什么在這里?總的來說是比較詭異且麻煩的。