在訓練CNN網絡的時候,常常會使用dropout來使得模型具有更好的泛化性,並防止過擬合。而dropout的實質則是以一定概率使得輸入網絡的數據某些維度上變為0,這樣可以使得模型訓練更加有效。但是我們需要注意dropout層在訓練和測試的時候,模型架構是不同的。為什么會產生這種差別呢?
一、訓練和測試的不同
標准的網絡連接如下圖:
增加了dropout層的網絡結構如下圖:
此處的$r_j^{(l)}$是服從參數為p的伯努利分布的隨機數(0或者1),用一句話來說就是y這個向量的每一個維度都會以概率p變為0。
問題來了,在訓練的時候我們是有dropout層的,那我們測試的時候還需要么?答案是不需要dropout層了,而是直接把y輸入進網絡。這時候問題又來了,如果直接把y輸入進網絡,在訓練的時候y由於經歷了dropout層,意味着y的數據分布的期望應該是會乘以p的(舉個例子如果y的每一個維度都是1,那么經過dropout層之后,有些維度變成了0,那此時真正進入網絡的數值分布其期望應該是p),而測試時沒有經過dropout層那就意味着訓練時輸入和測試時輸入的期望是不同的,那這個訓練好的權重將無法達到最優秀的狀態。那該如何處理才能保證這個期望的一致性呢?答案就是在測試時,將網絡的權重全部乘以p。如下圖:
這就是含有dropout層的訓練與測試不同的地方。
二、Pytorch實現dropout
前文講了dropout訓練和測試時的不同,接下來我們講講如何在Pytorch建模時使用這個層。
pytorch實現dropout的方式主要有兩個,第一個是F.dropout(out, p=0.5),第二個是nn.Dropout(p=0.5),這兩者的區別其實就是F和nn的區別。第一個是一個函數,第二個是一個nn.model類。那在實際使用中我們該使用什么呢?在構建網絡時我們該使用第二個,因為前面說到了dropout在訓練和測試時是不同的,因此我們的測試時使用model.eval()時,如果只使用的是F.dropout(out, p=0.5)那么不會有變化,但是nn.Dropout(p=0.5)已經被模型注冊成了nn.model類,因此此時會改變權重。
如果非要使用F.dropout(out, p=0.5),那可以增加一個參數,根據文檔顯示:
也就是out = nn.functional.dropout(out, p=0.5, training=self.training)。
接下來是推薦代碼:
class MyNet(nn.Module): def __init__(self, input_size, num_classes): super(MyNet, self).__init__() self.fc1 = nn.Linear(input_size, num_classes) # 輸入層到輸出層 self.dropout = nn.Dropout(p=0.5) # dropout訓練 def forward(self, x): out = self.dropout(x) print('dropout層的輸出:', out) out = self.fc1(out) return out input_size = 10 num_classes = 5 model = MyNet(input_size, num_classes)
大家可以使用以下代碼進行測試:
x = torch.arange(0,10).reshape(-1).float() print('輸入向量', x) model.train() print("訓練模式下:", model(x)) model.eval() print("測試模式下:", model(x))
出現問題了,畫紅框部分應該是訓練模式下dropout層的輸出,確實有些維度變成了0,但是怎么會出現8,10,仔細一看可以發現,dropout在屏蔽一些維度的數值同時,會將沒有屏蔽的數值進行調整(縮放)乘以$\frac{1}{1-p}$,此處的p=0.5,因此代入公式可以知道,沒有被屏蔽的數值應該會被乘以2。這是為什么呢?前文提到了dropout由於會改變輸入數據的均值,所以需要對權重進行改變,其實還有一種方式,在屏蔽一些數值的時候同時對其他沒有屏蔽的數值進行縮放使其的縮放后的均值依然保持原來均值,這樣在測試的時候就不用調整權重了。這就是nn.Dropout的方式(其實F.dropout也是進行了這個樣的rescaled)。
參考網頁:
理解dropout_張雨石的博客-CSDN博客_dropout