上一篇博客先搭建了基礎環境,並熟悉了基礎知識,本節基於此,再進行深一步的學習。
接下來看看如何基於PyTorch深度學習框架用簡單快捷的方式搭建出復雜的神經網絡模型,同時讓模型參數的優化方法趨於高效。如同使用PyTorch中的自動梯度方法一樣,在搭建復雜的神經網絡模型的時候,我們也可以使用PyTorch中已定義的類和方法,這些類和方法覆蓋了神經網絡中的線性變換、激活函數、卷積層、全連接層、池化層等常用神經網絡結構的實現。在完成模型的搭建之后,我們還可以使用PyTorch提供的類型豐富的優化函數來完成對模型參數的優化,除此之外,還有很多防止模型在模型訓練過程中發生過擬合的類。
知識儲備——深度學習中的常見概念
1:批量
批量,即Batch,是深度學習中的一個重要概念。批量通常是指兩個不同的概念——如果對應的是模型訓練方法,那么批量指的是將所有數據處理完以后一次性更新權重或者參數的估計,如果對應的是模型訓練中的數據,那么對應的是一次輸入供模型計算用的數據量。這兩個概念有着緊密的關系。
基於批量概念的模型訓練通常按照如下步驟:
(1) 初始化參數
(2) 重復以下步驟:處理所有數據 ,更新參數
和批量算法相對應的是遞增算法,其步驟如下:
(1) 初始化參數
(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包來簡化我們之前的代碼,開始部分的代碼變化不大,如下所示:
#_*_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) )
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這三個類進行詳細介紹
1.2.1 torch.nn.Sequential
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()
前兩句代碼和之前的代碼沒有多大區別,只是單純地增加了學習速率和訓練次數,學習速率現在是0.0001,訓練次數增加到了10000次,這樣做是為了讓最終得到的結果更好。不過計算損失函數的代碼發生了改變,現在使用的是在torch.nn包中已經定義好的均方誤差函數類torch.nn.MSELoss來計算損失值,而之前的代碼是根據損失函數的計算公式來編寫的。
下面簡單介紹在torch.nn包中常用的損失函數的具體用法,如下所述:
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中的優化函數之后,我們就可以對自己建立的神經網絡模型進行訓練並對參數進行優化了,代碼如下:
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
3.1 PyTorch中的torch.transforms
在前面講到過,在torch.transforms中提供了豐富的類對載入的數據進行變換,現在讓我們看看如何進行變換。我們知道,在計算機視覺中處理的數據集有很大一部分是圖片類型的,而在PyTorch中實際進行計算的是Tensor數據類型的變量,所以我們首先需要解決的是數據類型轉換的問題,如果獲取的數據是格式或者大小不一的圖片,則還需要進行歸一化和大小縮放等操作,慶幸的是,這些方法在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類看作是一種容器,它能夠同時對多種數據變換進行組合。傳入的參數是一個列表,列表中的元素就是對載入的數據進行的各種變換操作。
在以上的代碼中,在torchvision.transforms.Compose類中只是用了一個類型的轉換變化transfroms.ToTensor和一個數據標准化變換transforms.Normalize。這里使用的是標准化變換也叫標准差變換法,這種方法需要使用原始數據的均值(Mean)和標准差(Standard Deviation)來進行數據的標准化,在經過標准化變換之后,數據全部符合均值為0,標准差為1的標准正態分布,計算公式入選:
不過這里我們偷了一個懶,均值和標准差的值並非來自原始數據的,而是自行定義了一個,不過仍然能夠達到我們的目的。
下面看看在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方法的效果。
在上圖中的打叉的神經節點就是被隨機抽中並丟棄的神經連接,正是因為選取的方式的隨機性,所以在模型的每輪訓練中選擇丟棄的神經連接也是不同的,這樣做是為了讓我們最后訓練出來的模型對各部分的權重參數不產生過度依賴,從而防止過擬合,對於torch.nn.Dropout類,我們可以對隨機概率值的大小進行設置,如果不足任何設置,我們就使用默認的概率值0.5。
最后說一下代碼中前向傳播forward函數中的內容,首先經過self.conv1進行卷積處理,然后進行x.view(-1,14*14*128),對參數實現扁平化,因為之后緊接着的就是全連接層,所以如果不進行扁平化,則全連接層的實際輸出的參數維度和其定義輸入的維度將不匹配,程序將會報錯,最后通過self.dense定義的全連接層進行最后的分類。