天天看點

PyTorch | (4)神經網絡模型搭建和參數優化

PyTorch | (1)初識PyTorch PyTorch | (2)PyTorch 入門-張量 PyTorch | (3)Tensor及其基本操作 PyTorch | (4)神經網絡模型搭建和參數優化

基于PyTorch深度學習架構用簡單快捷的方式搭建出複雜的神經網絡模型,同時讓模型參數的優化方法趨于高效。如同使用PyTorch中的自動梯度方法一樣,在搭建複雜的神經網絡模型的時候,我們也可以使用PyTorch中已定義的類和方法,這些類和方法覆寫了神經網絡中的線性變換、激活函數、卷積層、全連接配接層、池化層等常用神經網絡結構的實作。在完成模型的搭建之後,我們還可以使用PyTorch提供的類型豐富的優化函數來完成對模型參數的優化,除此之外,還有很多防止模型在模型訓練過程中發生過拟合的類。

深度學習中的常見概念

1:批量

批量,即Batch,是深度學習中的一個重要概念。批量通常是指兩個不同的概念——如果對應的是模型訓練方法,那麼批量指的是将所有資料處理完以後一次性更新權重或者參數的估計,如果對應的是模型訓練中的資料,那麼對應的是一次輸入供模型計算用的資料量。這兩個概念有着緊密的關系。

基于批量概念的模型訓練通常按照如下步驟:

(1) 初始化參數

(2) 重複以下步驟:處理所有資料 ,更新參數

和批量算法相對應的是遞增算法,其步驟如下:

(2) 重複以下步驟:處理一個或者一組資料點,更新參數

我們看到,這裡的主要差別是批量算法一次處理所有的資料;而在遞增算法中,每處理一個或者數個觀測值就要更新一次參數。這裡“處理”和“更新”二詞根據算法的不同有不同的含義。在後向傳播算法中,“處理”對應的具體操作就是在計算損失函數的梯度變化曲線。如果是批量算法,則計算平均或者總的損失函數的梯度變化曲線;而如果是遞增算法,則計算損失函數僅在對應于該觀測值或者數個觀測值時的梯度變化曲線。“更新”則是從已有的參數值中減去梯度變化率和學習速率的乘積。

2:線上學習和離線學習

在深度學習中,另外兩個常見的概念是線上學習(Online Learning)和離線學習(Offline Learning)。在離線學習中,所有的資料都可以被反複擷取,比如上面的批量學習就是離線學習的一種。而在線上學習中,每個觀測值在處理以後會被遺棄,同時參數得到更新。線上學習永遠是遞增算法的一種,但是遞增算法卻既可以離線學習也可以線上學習。

離線學習有如下幾個優點:

  1. 對于任何固定個數的參數,目标函數都可以直接被計算出來,是以很容易驗證模型

    訓練是否在朝着所需要的方向發展。

  2. 計算精度可以達到任意合理的程度。

  3. 可以使用各種不同的算法來避免出現局部最優的情況。

  4. 可以采用訓練、驗證、測試三分法對模型的普适度進行驗證。

  5. 可以計算預測值及其置信區間。

線上學習無法實作上述功能,因為資料并沒有被存儲,不能反複擷取,是以對于任何固定的參數集,無法在訓練集上計算損失函數,也無法在驗證集上計算誤差。這就造成線上算法一般來說比離線算法更加複雜和不穩定。但是離線遞增算法并沒有線上算法的問題,是以有必要了解線上學習和遞增算法的差別。

3:偏移/門檻值

在深度學習中,采用sigmoid激活函數的隐藏層或者輸出層的神經元通常在計算網絡輸入時加入一個偏移值,稱為Bias。對于線性輸出神經元,偏移項就是回歸中的截距項。

跟截距項的作用類似,偏移項可以被視為一個由特殊神經元引出的連結權重,這是因為偏移項通常連結到一個取固定機關值的偏移神經元。比如在一個多層感覺器(MLP)神經網絡中,某一個神經元的輸入變量為N維,那麼這個神經元在這個高維空間中根據參數畫一個超平面,一邊是正值,一邊為負值。所使用的參數決定了這個超平面在輸入空間中的相對位置。如果沒有偏移項,這個超平面的位置就被限制住了,必須通過原點;如果多個神經元都需要其各自的超平面,那麼就嚴重限制了模型的靈活性。這就好比一個沒有截距項的回歸模型,其斜率的估計值在大多數情況下會大大偏移最優估計值,因為生成的拟合曲線必須通過原點。是以,如果缺少偏移項,多層感覺器的普适拟合能力就幾乎不存在了。

通常來說,每個隐藏層和輸出層的神經元都有自己的偏移項。但是如果輸入資料已經被等比例轉換到一個有限值域中,比如[0,1]區間,那麼第一個隐藏層的神經元設定了偏移項以後,後面任何層跟這些具備偏移項的神經元有連結的其他神經元就不需要再額外設定偏移項了。

4:标準化資料

在機器學習和深度學習中,常常會出現對資料标準化這個動作。那麼什麼是标準化資料呢?其實這裡是用“标準化”這個詞代替了幾個類似的但又不同的動作。下面詳細講解三個常見的“标準化”資料處理動作。

  1. 重放縮(Rescaling):通常指将一個向量加上或者減去一個常量,再乘

    以或者除以一個常量。比如将華氏溫度轉換為攝氏溫度就是一個重放縮的過程。

  2. 規範化(Normalization):通常指将一個向量除以其範數,比如采用歐

    式空間距離,則用向量的方差作為範數來規範化向量。在深度學習中,規範化通

    常采用極差為範數,即将向量減去最小值,并除以其極差,進而使數值範圍在0

    到1之間。

  3. 标準化(Standardization):通常指将一個向量移除其位置和規模的度

    量。比如一個服從正态分布的向量,可以減去其均值,并除以其方差來标準化數

    據,進而獲得一個服從标準正态分布的向量。

那麼在深度學習中是否應該進行以上任何一種資料處理呢?答案是依照情況而定。一般來講,如果激活函數的值域在0到1之間,那麼規範化資料到[0,1]的值域區間是比較好的。另外一個考慮是規範化資料能使計算過程更加穩定,特别是在資料值域範圍差別較大的時候,規範化資料總是相對穩健的一個選擇。而且很多算法的初始值設定也是針對使規範化以後的資料更有效來設計的。

一:PyTorch之torch.nn

PyTorch中的 torch.nn包提供了很多與實作神經網絡中的具體功能相關的類,這些類涵蓋了深度神經網絡模型在搭建和參數優化過程中的常用内容,比如神經網絡中的卷積層、池化層、全連接配接層這類層次構造的方法、防止過拟合的參數歸一化方法、Dropout 方法,還有激活函數部分的線性激活函數、非線性激活函數相關的方法,等等。在學會使用PyTorch的 torch.nn進行神經網絡模型的搭建和參數優化後,我們就會發現實作一個神經網絡應用并沒有我們想象中那麼難。

1.1 導入包

下面使用PyTorch的torch.nn包來簡化我們之前的代碼,開始部分的代碼變化不大,如下所示:

  1. 對于任何固定個數的參數,目标函數都可以直接被計算出來,是以很容易驗證模型

    訓練是否在朝着所需要的方向發展。

  2. 計算精度可以達到任意合理的程度。

  3. 可以使用各種不同的算法來避免出現局部最優的情況。

  4. 可以采用訓練、驗證、測試三分法對模型的普适度進行驗證。

  5. 可以計算預測值及其置信區間。

  1. 重放縮(Rescaling):通常指将一個向量加上或者減去一個常量,再乘

    以或者除以一個常量。比如将華氏溫度轉換為攝氏溫度就是一個重放縮的過程。

  2. 規範化(Normalization):通常指将一個向量除以其範數,比如采用歐

    式空間距離,則用向量的方差作為範數來規範化向量。在深度學習中,規範化通

    常采用極差為範數,即将向量減去最小值,并除以其極差,進而使數值範圍在0

    到1之間。

  3. 标準化(Standardization):通常指将一個向量移除其位置和規模的度

    量。比如一個服從正态分布的向量,可以減去其均值,并除以其方差來标準化數

    據,進而獲得一個服從标準正态分布的向量。

 那麼在深度學習中是否應該進行以上任何一種資料處理呢?答案是依照情況而定。一般來講,如果激活函數的值域在0到1之間,那麼規範化資料到[0,1]的值域區間是比較好的。另外一個考慮是規範化資料能使計算過程更加穩定,特别是在資料值域範圍差別較大的時候,規範化資料總是相對穩健的一個選擇。而且很多算法的初始值設定也是針對使規範化以後的資料更有效來設計的。

#_*_coding:utf-8_*_
import torch
from torch.autograd import Variable
 
# 批量輸入的資料量
batch_n = 100
# 通過隐藏層後輸出的特征數
hidden_layer = 100
# 輸入資料的特征個數
input_data = 1000
# 最後輸出的分類結果數
output_data = 10
 
x = Variable(torch.randn(batch_n , input_data) , requires_grad = False)
y = Variable(torch.randn(batch_n , output_data) , requires_grad = False)      

之前的代碼如下:   和之前一樣,這裡首先導入必要的包、類并定義了4個變量,不過這裡僅定義了輸入和輸出的變量,之前定義神經網絡模型中的權重參數的代碼被删減了,這和我們之後在代碼中使用的torch.nn包中的類有關,因為這個類能夠幫助我們自動生成和初始化對應次元的權重參數。

#_*_coding:utf-8_*_
import torch
from torch.autograd import Variable
 
# 批量輸入的資料量
batch_n = 100
# 通過隐藏層後輸出的特征數
hidden_layer = 100
# 輸入資料的特征個數
input_data = 1000
# 最後輸出的分類結果數
output_data = 10
 
x = Variable(torch.randn(batch_n , input_data) , requires_grad = False)
y = Variable(torch.randn(batch_n , output_data) , requires_grad = False)
 
w1 = Variable(torch.randn(input_data,hidden_layer),requires_grad = True)
w2 = Variable(torch.randn(hidden_layer,output_data),requires_grad = True)      

1.2 模型搭建

models = torch.nn.Sequential(
    # 首先通過其完成從輸入層到隐藏層的線性變換
    torch.nn.Linear(input_data,hidden_layer),
    # 經過激活函數
    torch.nn.ReLU(),
    # 最後完成從隐藏層到輸出層的線性變換
    torch.nn.Linear(hidden_layer,output_data)
)      

1.2.1 torch.nn.Sequential   torch.nn.Sequential括号内的内容就是我們搭建的神經網絡模型的具體結構,這裡首先通過torch.nn.Linear(input_data, hidden_layer)完成從輸入層到隐藏層的線性變換,然後經過激活函數及torch.nn.Linear(hidden_layer, output_data)完成從隐藏層到輸出層的線性變換。下面分别對在以上代碼中使用的torch.nn.Sequential、torch.nn.Linear和torch.nn.RelU這三個類進行詳細介紹

torch.nn.Sequential類是torch.nn中的一種序列容器,通過在容器中嵌套各種實作神經網絡中具體功能相關的類,來完成對神經網絡模型的搭建,最主要的是,參數會按照我們定義好的序列自動傳遞下去。我們可以将嵌套在容器中的各個部分看作各種不同的子產品,這些子產品可以自由組合。子產品的加入一般有兩種方式,一種是在以上代碼中使用的直接嵌套,另一種是以orderdict有序字典的方式進行傳入,這兩種方式的唯一差別是,使用後者搭建的模型的每個子產品都有我們自定義的名字,而前者預設使用從零開始的數字序列作為每個子產品的名字。下面通過示例來直覺地看一下使用這兩種方式搭建的模型之間的差別。

首先,使用直接嵌套搭建的模型代碼如下:

#_*_coding:utf-8_*_
import torch
from torch.autograd import Variable
 
# 批量輸入的資料量
batch_n = 100
# 通過隐藏層後輸出的特征數
hidden_layer = 100
# 輸入資料的特征個數
input_data = 1000
# 最後輸出的分類結果數
output_data = 10
 
x = Variable(torch.randn(batch_n , input_data) , requires_grad = False)
y = Variable(torch.randn(batch_n , output_data) , requires_grad = False)
 
models = torch.nn.Sequential(
    # 首先通過其完成從輸入層到隐藏層的線性變換
    torch.nn.Linear(input_data,hidden_layer),
    # 經過激活函數
    torch.nn.ReLU(),
    # 最後完成從隐藏層到輸出層的線性變換
    torch.nn.Linear(hidden_layer,output_data)
)
print(models)      

這裡對模型的結構進行列印輸出,結果如下:

Sequential(
  (0): Linear(in_features=1000, out_features=100, bias=True)
  (1): ReLU()
  (2): Linear(in_features=100, out_features=10, bias=True)
)      

 使用orderdict有序字典進行傳入來搭建的模型代碼如下:

#_*_coding:utf-8_*_
import torch
from torch.autograd import Variable
from collections import OrderedDict
 
# 批量輸入的資料量
batch_n = 100
# 通過隐藏層後輸出的特征數
hidden_layer = 100
# 輸入資料的特征個數
input_data = 1000
# 最後輸出的分類結果數
output_data = 10
 
models = torch.nn.Sequential(OrderedDict([
    ("Linel",torch.nn.Linear(input_data,hidden_layer)),
    ("ReLU1",torch.nn.ReLU()),
    ("Line2",torch.nn.Linear(hidden_layer,output_data))
])
)
print(models)      

這裡對該模型的結構進行列印輸出,結果如下:

Sequential(
  (Linel): Linear(in_features=1000, out_features=100, bias=True)
  (ReLU1): ReLU()
  (Line2): Linear(in_features=100, out_features=10, bias=True)
)      

通過對這兩種方式進行比較,我們會發現,對子產品使用自定義的名稱可讓我們更便捷地找到模型中相應的子產品并進行操作。

1.2.2 torch.nn.Linear

torch.nn.Linear類用于定義模型的線性層,即完成前面提到的不同的層之間的線性變換。torch.nn.Linear類接收的參數有三個,分别是輸入特征數、輸出特征數和是否使用偏置,設定是否使用偏置的參數是一個布爾值,預設為True,即使用偏置。在實際使用的過程中,我們隻需将輸入的特征數和輸出的特征數傳遞給torch.nn.Linear類,就會自動生成對應次元的權重參數和偏置,對于生成的權重參數和偏置,我們的模型預設使用了一種比之前的簡單随機方式更好的參數初始化方法。

根據我們搭模組化型的輸入、輸出和層次結構需求,它的輸入是在一個批次中包含100個特征數為1000的資料,最後得到100個特征數為10的輸出資料,中間需要經過兩次線性變換,是以要使用兩個線性層,兩個線性層的代碼分别是torch.nn.Linear(input_data,hidden_layer)和torch.nn.Linear(hidden_layer, output_data)。可看到,其代替了之前使用矩陣乘法方式的實作,代碼更精煉、簡潔。

1.2.3 torch.nn.RelU

 torch.nn.ReLU類屬于非線性激活分類,在定義時預設不需要傳入參數。當然,在 torch.nn包中還有許多非線性激活函數類可供選擇,比如之前講到的PReLU、LeakyReLU、Tanh、Sigmoid、Softmax等。

在掌握torch.nn.Sequential、torch.nn.Linear和torch.nn.RelU的使用方法後,快速搭建更複雜的多層神經網絡模型變為可能,而且在整個模型的搭建過程中不需要對在模型中使用到的權重參數和偏置進行任何定義和初始化說明,因為參數已經完成了自動生成。

1.3 優化模型

epoch_n = 10000
learning_rate = 1e-4
loss_fn = torch.nn.MSELoss()      

下面簡單介紹在torch.nn包中常用的損失函數的具體用法,如下所述:   前兩句代碼和之前的代碼沒有多大差別,隻是單純地增加了學習速率和訓練次數,學習速率現在是0.0001,訓練次數增加到了10000次,這樣做是為了讓最終得到的結果更好。不過計算損失函數的代碼發生了改變,現在使用的是在torch.nn包中已經定義好的均方誤差函數類torch.nn.MSELoss來計算損失值,而之前的代碼是根據損失函數的計算公式來編寫的。

1.3.1 torch.nn.MSELoss

torch.nn.MSELoss類使用均方誤差函數對損失值進行計算,在定義類的對象時不用傳入任何參數,但在使用執行個體時需要輸入兩個次元一樣的參數方可進行計算。示例如下

import torch
from torch.autograd import Variable
 
loss_f = torch.nn.MSELoss()
x = Variable(torch.randn(100,100))
y = Variable(torch.randn(100,100))
loss = loss_f(x,y)
print(loss)      

以上代碼首先通過随機方式生成了兩個次元都是(100,100)的參數,然後使用均方誤差函數來計算兩組參數的損失值,列印輸出的結果如下:

tensor(2.0121)      

1.3.2 torch.nn.L1Loss

torch.nn.L1Loss類使用平均絕對誤差函數對損失值進行計算,同樣,在定義類的對象時不用傳入任何參數,但在使用執行個體時需要輸入兩個次元一樣的參數進行計算。示例如下:

import torch
from torch.autograd import Variable
 
loss_f = torch.nn.L1Loss()
x = Variable(torch.randn(100,100))
y = Variable(torch.randn(100,100))
loss = loss_f(x,y)
print(loss)      

以上代碼也是通過随機方式生成了兩個次元都是(100,100)的參數,然後使用平均絕對誤差函數來計算兩組參數的損失值,列印輸出的結果如下:

tensor(1.1294)      

1.3.3 torch.nn.CrossEntropyLoss

 torch.nn.CrossEntropyLoss類用于計算交叉熵,在定義類的對象時不用傳入任何參數,在使用執行個體時需要輸入兩個滿足交叉熵的計算條件的參數,代碼如下:

import torch
from torch.autograd import Variable
 
loss_f = torch.nn.CrossEntropyLoss()
x = Variable(torch.randn(3,5))
y = Variable(torch.LongTensor(3).random_(5))
loss = loss_f(x,y)
print(loss)      

這裡生成的第1組參數是一個随機參數,次元為(3,5);第2組參數是3個範圍為0~4的随機數字。計算這兩組參數的損失值,列印輸出的結果如下

tensor(1.6983)
       

在學會使用PyTorch中的優化函數之後,我們就可以對自己建立的神經網絡模型進行訓練并對參數進行優化了,代碼如下:   這裡生成的第1組參數是一個随機參數,次元為(3,5);第2組參數是3個範圍為0~4的随機數字。計算這兩組參數的損失值,列印輸出的結果如下

for epoch in range(epoch_n):
    y_pred = models(x)
    loss = loss_fn(y_pred,y)
    if epoch%1000 == 0:
        print("Epoch:{},Loss:{:.4f}".format(epoch,loss.data[0]))
    models.zero_grad()
 
    loss.backward()
 
    for param in models.parameters():
        param.data -= param.grad.data*learning_rate      

以上代碼中的絕大部分和之前訓練和優化部分的代碼是一樣的,但是參數梯度更新的方式發生了改變。因為使用了不同的模型搭建方法,是以通路模型中的全部參數是通過對“models.parameters()”進行周遊完成的,然後才對每個周遊的參數進行梯度更新。其列印輸入結果的方式是每完成1000次訓練,就列印輸出目前的loss值.

1.4 結果及分析

Epoch:0,Loss:1.0140
Epoch:1000,Loss:0.9409
Epoch:2000,Loss:0.8776
Epoch:3000,Loss:0.8216
Epoch:4000,Loss:0.7716
Epoch:5000,Loss:0.7263
Epoch:6000,Loss:0.6850
Epoch:7000,Loss:0.6468
Epoch:8000,Loss:0.6109
Epoch:9000,Loss:0.5773
 
Process finished with exit code 0      

從結果可以看出,參數的優化效果比較理想,loss值被控制在相對較小的範圍之内,這和我們增強了訓練次數有很大關系。

完整代碼如下:

#_*_coding:utf-8_*_
import torch
from torch.autograd import Variable
 
# 批量輸入的資料量
batch_n = 100
# 通過隐藏層後輸出的特征數
hidden_layer = 100
# 輸入資料的特征個數
input_data = 1000
# 最後輸出的分類結果數
output_data = 10
 
x = Variable(torch.randn(batch_n , input_data) , requires_grad = False)
y = Variable(torch.randn(batch_n , output_data) , requires_grad = False)
 
models = torch.nn.Sequential(
    # 首先通過其完成從輸入層到隐藏層的線性變換
    torch.nn.Linear(input_data,hidden_layer),
    # 經過激活函數
    torch.nn.ReLU(),
    # 最後完成從隐藏層到輸出層的線性變換
    torch.nn.Linear(hidden_layer,output_data)
)
# print(models)
 
epoch_n = 10000
learning_rate = 1e-4
loss_fn = torch.nn.MSELoss()
 
for epoch in range(epoch_n):
    y_pred = models(x)
    loss = loss_fn(y_pred,y)
    if epoch%1000 == 0:
        print("Epoch:{},Loss:{:.4f}".format(epoch,loss.data[0]))
    models.zero_grad()
 
    loss.backward()
 
    for param in models.parameters():
        param.data -= param.grad.data*learning_rate      

二:PyTorch之torch.optim

到目前為止,代碼中的神經網絡權重的參數優化和更新還沒有實作自動化,并且目前使用的優化方法都有固定的學習速率,是以優化函數相對簡單,如果我們自己實作一些進階的參數優化算法,則優化函數部分的代碼會變得較為複雜。在PyTorch的torch.optim包中提供了非常多的可實作參數自動優化的類,比如SGD、AdaGrad、RMSProp、Adam等,這些類都可以被直接調用,使用起來也非常友善。

2.1 優化模型

我們使用自動化的優化函數實作方法對之前的代碼進行替換,新的代碼如下:

#_*_coding:utf-8_*_
import torch
from torch.autograd import Variable
 
# 批量輸入的資料量
batch_n = 100
# 通過隐藏層後輸出的特征數
hidden_layer = 100
# 輸入資料的特征個數
input_data = 1000
# 最後輸出的分類結果數
output_data = 10
 
x = Variable(torch.randn(batch_n , input_data) , requires_grad = False)
y = Variable(torch.randn(batch_n , output_data) , requires_grad = False)
 
models = torch.nn.Sequential(
    # 首先通過其完成從輸入層到隐藏層的線性變換
    torch.nn.Linear(input_data,hidden_layer),
    # 經過激活函數
    torch.nn.ReLU(),
    # 最後完成從隐藏層到輸出層的線性變換
    torch.nn.Linear(hidden_layer,output_data)
)
# print(models)
 
epoch_n = 20
learning_rate = 1e-4
loss_fn = torch.nn.MSELoss()
 
optimzer = torch.optim.Adam(models.parameters(),lr = learning_rate)      

這裡使用了torch.optim包中的torch.optim.Adam類作為我們的模型參數的優化函數,在torch.optim.Adam類中輸入的是被優化的參數和學習速率的初始值,如果沒有輸入學習速率的初始值,那麼預設使用0.001這個值。因為我們需要優化的是模型中的全部參數,是以傳遞給torch.optim.Adam類的參數是models.parameters。另外,Adam優化函數還有一個強大的功能,就是可以對梯度更新使用到的學習速率進行自适應調節,是以最後得到的結果自然會比之前的代碼更理想。

2.2 訓練模型

進行模型訓練的代碼如下:

#進行模型訓練
for epoch in range(epoch_n):
    y_pred = models(x)
    loss = loss_fn(y_pred,y)
    print("Epoch:{}, Loss:{:.4f}".format(epoch, loss.data[0]))
    optimzer.zero_grad()
 
    loss.backward()
 
    #進行梯度更新
    optimzer.step()      

 在以上代碼中有幾處代碼和之前的訓練代碼不同,這是因為我們引入了優化算法,是以通過直接調用optimzer.zero_grad來完成對模型參數梯度的歸零;并且在以上代碼中增加了optimzer.step,它的主要功能是使用計算得到的梯度值對各個節點的參數進行梯度更新。

2.3 列印結果

這裡隻進行20次訓練并列印每輪訓練的loss值,結果如下:

Epoch:0, Loss:1.1289
Epoch:1, Loss:1.1073
Epoch:2, Loss:1.0862
Epoch:3, Loss:1.0656
Epoch:4, Loss:1.0455
Epoch:5, Loss:1.0258
Epoch:6, Loss:1.0065
Epoch:7, Loss:0.9877
Epoch:8, Loss:0.9692
Epoch:9, Loss:0.9513
Epoch:10, Loss:0.9338
Epoch:11, Loss:0.9166
Epoch:12, Loss:0.8997
Epoch:13, Loss:0.8833
Epoch:14, Loss:0.8672
Epoch:15, Loss:0.8514
Epoch:16, Loss:0.8360
Epoch:17, Loss:0.8209
Epoch:18, Loss:0.8061
Epoch:19, Loss:0.7917
 
Process finished with exit code 0      

在看到這個結果後我們會很驚訝,因為使用torch.optim.Adam類進行參數優化 後僅僅進行了20次訓練,得到的loss值就已經遠遠低于之前進行10000次優化訓練的 結果。是以,如果對torch.optim中的優化算法類使用得當,就更能幫助我們優化好 模型中的參數。

2.4 完整的代碼如下:

#_*_coding:utf-8_*_
import torch
from torch.autograd import Variable
 
# 批量輸入的資料量
batch_n = 100
# 通過隐藏層後輸出的特征數
hidden_layer = 100
# 輸入資料的特征個數
input_data = 1000
# 最後輸出的分類結果數
output_data = 10
 
x = Variable(torch.randn(batch_n , input_data) , requires_grad = False)
y = Variable(torch.randn(batch_n , output_data) , requires_grad = False)
 
models = torch.nn.Sequential(
    # 首先通過其完成從輸入層到隐藏層的線性變換
    torch.nn.Linear(input_data,hidden_layer),
    # 經過激活函數
    torch.nn.ReLU(),
    # 最後完成從隐藏層到輸出層的線性變換
    torch.nn.Linear(hidden_layer,output_data)
)
# print(models)
 
epoch_n = 20
learning_rate = 1e-4
loss_fn = torch.nn.MSELoss()
 
optimzer = torch.optim.Adam(models.parameters(),lr = learning_rate)
 
#進行模型訓練
for epoch in range(epoch_n):
    y_pred = models(x)
    loss = loss_fn(y_pred,y)
    print("Epoch:{}, Loss:{:.4f}".format(epoch, loss.data[0]))
    optimzer.zero_grad()
 
    loss.backward()
 
    #進行梯度更新
    optimzer.step()      

 三:torch和torchvision

在PyTorch中有兩個核心的包,分别是torch和torchvision。我們之前已經接觸了torch包的一部分内容,比如使用了torch.nn中的線性層加激活函數配合torch.optim完成了神經網絡模型的搭建和模型參數的優化,并使用了 torch.autograd實作自動梯度的功能,接下來會介紹如何使用torch.nn中的類來搭建卷積神經網絡。

torchvision包的主要功能是實作資料的處理、導入和預覽等,是以如果需要對計算機視覺的相關問題進行處理,就可以借用在torchvision包中提供的大量的類來完成相應的工作。下面可以看看都導入了什麼包

import torch
# torchvision包的主要功能是實作資料的處理,導入和預覽等
import torchvision
from torchvision import datasets
from torchvision import transforms
from torch.autograd import Variable      

在前面講到過,在torch.transforms中提供了豐富的類對載入的資料進行變換,現在讓我們看看如何進行變換。我們知道,在計算機視覺中處理的資料集有很大一部分是圖檔類型的,而在PyTorch中實際進行計算的是Tensor資料類型的變量,是以我們首先需要解決的是資料類型轉換的問題,如果擷取的資料是格式或者大小不一的圖檔,則還需要進行歸一化和大小縮放等操作,慶幸的是,這些方法在torch.transforms中都能找到。

3.1 PyTorch中的torch.transforms

在torch.transforms中有大量的資料變換類,其中有很大一部分可以用于實作資料增強(Data Argumentation)。若在我們需要解決的問題上能夠參與到模型訓練中的圖檔資料非常有限,則這時就要通過對有限的圖檔資料進行各種變換,來生成新的訓練集了,這些變換可以是縮小或者放大圖檔的大小、對圖檔進行水準或者垂直翻轉等,都是資料增強的方法。不過在手寫數字識别的問題上可以不使用資料增強的方法,因為可用于模型訓練的資料已經足夠了。對資料進行載入及有相應變化的代碼如下:

transform = transforms.Compose([transforms.ToTensor(),
                                transforms.Normalize(mean=[0.5,0.5,0.5],
                                std=[0.5,0.5,0.5])])      

在以上的代碼中,在torchvision.transforms.Compose類中隻是用了一個類型的轉換變化transfroms.ToTensor和一個資料标準化變換transforms.Normalize。這裡使用的是标準化變換也叫标準差變換法,這種方法需要使用原始資料的均值(Mean)和标準差(Standard Deviation)來進行資料的标準化,在經過标準化變換之後,資料全部符合均值為0,标準差為1的标準正态分布,計算公式入選:   我們可以将上面代碼中的torchvision.transforms.Compose類看作是一種容器,它能夠同時對多種資料變換進行組合。傳入的參數是一個清單,清單中的元素就是對載入的資料進行的各種變換操作。

PyTorch | (4)神經網絡模型搭建和參數優化

不過這裡我們偷了一個懶,均值和标準差的值并非來自原始資料的,而是自行定義了一個,不過仍然能夠達到我們的目的。

下面看看在torchvision.transforms中常用的資料變換操作。

3.1.1  torchvision.transforms.Resize

用于對載入的圖檔資料按我們需求的大小進行縮放。傳遞給這個類的參數可以是一個整型資料,也可以是一個類似于(h ,w )的序列,其中,h 代表高度,w 代表寬度,但是如果使用的是一個整型資料,那麼表示縮放的寬度和高度都是這個整型資料的值。

3.1.2  torchvision.transforms.Scale

用于對載入的圖檔資料按我們需求的大小進行縮放,用法和torchvision.transforms.Resize類似。

3.1.3  torchvision.transforms.CenterCrop

用于對載入的圖檔以圖檔中心為參考點,按我們需要的大小進行裁剪。傳遞給這個類的參數可以是一個整型資料,也可以是一個類似于(h ,w )的序列。

3.1.4  torchvision.transforms.RandomCrop

用于對載入的圖檔按我們需要的大小進行随機裁剪。傳遞給這個類的參數可以是一個整型資料,也可以是一個類似于(h ,w )的序列。

3.1.5  torchvision.transforms.RandomHorizontalFlip

用于對載入的圖檔按随機機率進行水準翻轉。我們可以通過傳遞給這個類的參數自定義随機機率,如果沒有定義,則使用預設的機率值0.5。

3.1.6  torchvision.transforms.RandomVerticalFlip

用于對載入的圖檔按随機機率進行垂直翻轉。我們可以通過傳遞給這個類的參數自定義随機機率,如果沒有定義,則使用預設的機率值0.5。

3.1.7  torchvision.transforms.ToTensor

用于對載入的圖檔資料進行類型轉換,将之前構成PIL圖檔的資料轉換成Tensor資料類型的變量,讓PyTorch能夠對其進行計算和處理。

3.1.8  torchvision.transforms.ToPILImage

用于将Tensor變量的資料轉換成PIL圖檔資料,主要是為了友善圖檔内容的顯示

3.2 PyTorch中的torch.nn

首先我們看一個卷積神經網絡模型搭建的代碼:

#模型搭建和參數優化
# 在順利完成資料裝載後,我們可以開始編寫卷積神經網絡模型的搭建和參數優化的代碼
#卷積層使用torch.nn.Conv2d類來搭建
# 激活層使用torch.nn.ReLU 類方法來搭建
# 池化層使用torch.nn.MaxPool2d類方法來搭建
# 全連接配接層使用 torch.nn.Linear 類方法來搭建
 
class Model(torch.nn.Module):
    def __init__(self):
        super(Model,self).__init__()
        self.conv1 = torch.nn.Sequential(
            torch.nn.Conv2d(1,64,kernel_size=3,stride=1,padding=1),
            torch.nn.ReLU(),
            torch.nn.Conv2d(64,128,kernel_size=3,stride=1,padding=1),
            torch.nn.ReLU(),
            torch.nn.MaxPool2d(stride=2,kernel_size=2))
 
        self.dense = torch.nn.Sequential(
            torch.nn.Linear(14*14*128,1024),
            torch.nn.ReLU(),
            torch.nn.Dropout(p = 0.5),
            torch.nn.Linear(1024,10)
        )
 
    def forward(self, x):
        x = self.conv1(x)
        x = x.view(-1,14*14*128)
        x = self.dense(x)
        return x      

上面我們選擇搭建了一個在結構層次上有所簡化的卷積神經網絡模型,在結構上使用了兩個卷積層:一個最大池化層和兩個全連接配接層,這裡對具體的使用方法進行補充說明。

3.2.1 torch.nn.Conv2d   

用于搭建卷積神經網絡的卷積層,主要的輸入參數有輸入通道數、輸出通道數、卷積核大小、卷積核移動步長和Paddingde值。其中,輸入通道數的資料類型是整型,用于确定輸入資料的層數;輸出通道數的資料類型也是整型,用于确定輸出資料的層數;卷積核大小的資料類型是整型,用于确定卷積核的大小;卷積核移動步長的資料類型是整型,用于确定卷積核每次滑動的步長;Paddingde 的資料類型是整型,值為0時表示不進行邊界像素的填充,如果值大于0,那麼增加數字所對應的邊界像素層數。

3.2.2  torch.nn.MaxPool2d

用于實作卷積神經網絡中的最大池化層,主要的輸入參數是池化視窗大小、池化視窗移動步長和Padding的值。同樣,池化視窗大小的資料類型是整型,用于确定池化視窗的大小。池化視窗步長的資料類型也是整型,用于确定池化視窗每次移動的步長。Padding的值和在torch.nn.Conv2d中定義的Paddingde值的用法和意義是一樣的。

3.2.3  torch.nn.Dropout

torch.nn.Dropout類用于防止卷積神經網絡在訓練的過程中發生過拟合,其工作原理簡單來說就是在模型訓練的過程中,以一定的随機機率将卷積神經網絡模型的部分參數歸零,以達到減少相鄰兩層神經連接配接的目的。下圖顯示了 Dropout方法的效果。

PyTorch | (4)神經網絡模型搭建和參數優化

在上圖中的打叉的神經節點就是被随機抽中并丢棄的神經連接配接,正是因為選取的方式的随機性,是以在模型的每輪訓練中選擇丢棄的神經連接配接也是不同的,這樣做是為了讓我們最後訓練出來的模型對各部分的權重參數不産生過度依賴,進而防止過拟合,對于torch.nn.Dropout類,我們可以對随機機率值的大小進行設定,如果不足任何設定,我們就使用預設的機率值0.5。

最後說一下代碼中前向傳播forward函數中的内容,首先經過self.conv1進行卷積處理,然後進行x.view(-1,14*14*128),對參數實作扁平化,因為之後緊接着的就是全連接配接層,是以如果不進行扁平化,則全連接配接層的實際輸出的參數次元和其定義輸入的次元将不比對,程式将會報錯,最後通過self.dense定義的全連接配接層進行最後的分類。