1. 程式人生 > 實用技巧 >卷積神經網路學習——第二部分:卷積神經網路訓練的基本流程

卷積神經網路學習——第二部分:卷積神經網路訓練的基本流程

卷積神經網路學習——第二部分:卷積神經網路訓練的基本流程



一、序言

   本文承接第一部分,基於對卷積神經網路網路組成的認識,開始學習如何去使用卷積神經網路進行對應的訓練。模型評估作為優化部分,我們將放在第三個部分中再好好講他的作用以及意義~
   訓練的基本流程主要是資料集引入、訓練及引數設定、驗證及反饋這三個步驟,我們現在分三個步驟來認識一下這個訓練的基本流程。
   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卷積神經網路學習筆記進一步瞭解一下~博主寫的也是真的好
   首先,按照國際慣例,我們先用一個流程圖來展示一下每一次訓練過程。

Created with Raphaël 2.2.0 開始 將訓練集輸入到模型進行訓練 對結果採用交叉熵巡視計算模型 誤差,並將預測結果提取出來 預測結果等於實際標籤 成功預測數n += 1 反向傳播,更新引數 輸出準確率和實驗誤差 yes
# 訓練模型
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、模型評估

   模型評估大體上的效果和步驟同模型訓練一致,只需要將部分程式碼進行替換即可~這裡就不貼程式碼了,就將評估當成是基於以上的又一次訓練即可。



三、總結

   總的來說,這一部分也是比較重要的。需要梳理流程之後再加深對於函式的理解才行。最難最難的就是,網上的大部分程式碼都是來回搬運的(讓人極為無語)而可能最開始寫的那個大佬認為一些小的語句沒有必要寫,但是對於我們這樣的小白來說就處於:基本看不懂的狀態。所以最麻煩的還是:為什麼有這個語句?他為什麼在這裡?總的來說是比較詭異且麻煩的。