過擬合指的是隻能擬合訓練數據,但不能很好地擬合不包含訓練數據的其他數據。神經網絡要求模型能夠具有較高的泛化能力,也就是對不包含訓練數據的未觀測數據也能進行正確識別。所以在訓練表現力強的模型同時,抑制過擬合的技巧也很重要。
防止過擬合的方法:(1)人爲增加訓練數據集;(2)正則化(權值衰減);(3)Dropout
1、人爲增加訓練數據集
發生過擬合的原因主要有:
- 模型擁有大量參數,表現力強;
- 訓練數據少。
所以通過增加訓練數據可以抑制過擬合,提高模型的泛化能力。例如增加圖像數據集可以對原始圖像進行旋轉、鏡像。
2、正則化(權值衰減)
權值衰減是一直以來經常被使用的一種抑制過擬合的方法。該方法通過在學習過程中對大的權重進行懲罰,來抑制過擬合。因爲很多過擬合原本就是因爲權重參數取值過大才發生的。
神經網絡的習目的是減小損失函數的值。這時,例如爲損失函數加上權值的平方範數(L2範數)。這樣就可以抑制權重變大。用符號表示的話,如果將權重記爲W,L2範數的權值衰減就是,然後將加到損失函數上。這裏,是控制正則化強度的超參數。設置的越大,對大的權重施加懲罰就越重。此外,開頭的是用於將的求導結果變成的調整用常量。
對於所有權重,權值衰減方法都會爲損失函數加上。因此在求權重梯度的計算中,要爲之前的誤差反向傳播算法的結果加上正則化項的導數。正則化項可以使用L1,L2,L∞,這裏使用比較常用的L2。
所以用python搭建神經網絡時,損失函數和計算梯度的代碼要改爲:
def loss(self, x, t):
# 前向傳播
y = self.predict(x)
weight_decay = 0
# 計算所有權重的L2範數
for idx in range(1, self.hidden_layer_num + 2):
W = self.params['W' + str(idx)]
weight_decay += 0.5 * self.weight_decay_lambda * np.sum(W ** 2)
return self.last_layer.forward(y, t) + weight_decay
def gradient(self, x, t):
# 計算損失函數
self.loss(x, t)
# backward
dout = 1
dout = self.last_layer.backward(dout)
layers = list(self.layers.values())
layers.reverse()
for layer in layers:
dout = layer.backward(dout)
# 梯度
grads = {}
for idx in range(1, self.hidden_layer_num+2):
grads['W' + str(idx)] = self.layers['Affine' + str(idx)].dW + self.weight_decay_lambda * self.layers['Affine' + str(idx)].W
grads['b' + str(idx)] = self.layers['Affine' + str(idx)].db
return grads
3、Dropout
上面介紹的損失函數加上權重的L2範數的權值衰減方法,在某種程度上可以抑制過擬合。但是,如果網絡模型變得複雜,只有權值衰減就顯得力不從心了。在這種情況下,往往使用Dropout方法。
Dropout是一種在學習過程中隨機刪除神經元的方法。訓練時,隨機選出隱藏層的神經元,然後將其刪除。被刪除的神經元不再進行信號傳遞。如下圖所示。訓練時,每傳遞一次數據,就會隨機選擇要刪除的神經元。然後,測試時,雖然會傳遞所有的神經元信號,但是對於各個神經元的輸出,要乘上訓練時的刪除比例後再輸出。
下面來實現Dropout,這裏注重理解實現的方法比較簡單:
class Dropout:
def __init__(self, dropout_ratio=0.5):
self.dropout_ratio = dropout_ratio
self.mask = None
def forward(self, x, train_flg=True):
if train_flg:
self.mask = np.random.rand(*x.shape) > self.dropout_ratio
return x * self.mask
def backward(self, dout):
return dout * self.mask
說明:每次正向傳播時,self.mask中都會以False的形式保存要刪除的神經元。self.mask會隨機生成和x形狀相同的數組,並將值比dropout_ratio大的元素設爲True。傳播行爲和RuLU相同。
在搭建神經網絡時可以這樣使用Dropout:
def __init__(self, input_size, hidden_size_list, output_size,
activation='relu', weight_init_std='relu', weight_decay_lambda=0,
use_dropout = False, dropout_ration = 0.5):
self.input_size = input_size
self.output_size = output_size
self.hidden_size_list = hidden_size_list
self.hidden_layer_num = len(hidden_size_list)
self.use_dropout = use_dropout
self.weight_decay_lambda = weight_decay_lambda
self.params = {}
# 權重初始化方法
self.__init_weight(weight_init_std)
# 每層網絡生成
activation_layer = {'sigmoid': Sigmoid, 'relu': Relu}
self.layers = OrderedDict()
for idx in range(1, self.hidden_layer_num+1):
self.layers['Affine' + str(idx)] = Affine(self.params['W' + str(idx)],
self.params['b' + str(idx)])
if self.use_dropout:
self.layers['Dropout' + str(idx)] = Dropout(dropout_ration)
idx = self.hidden_layer_num + 1
self.layers['Affine' + str(idx)] = Affine(self.params['W' + str(idx)], self.params['b' + str(idx)])
self.last_layer = SoftmaxWithLoss()
也就是一個全連接層後面使用Dropout。
拓展:
機器學習中經常使用集成學習。所謂集成學習,就是讓多個模型單獨進行學習,推理時再取多個模型的輸出的平均值。用神經網絡的語境來說,比如,準備5個結構相同的網絡,分別進行學習,測試時,以這5個網絡的輸出的平均值作爲答案。通過實驗可以發現,使用集成學習,神經網絡的識別精度可以提高好幾個百分點。
這個集成學習與Dropout有密切的關係。這是因爲可以將Dropout理解爲,通過在學習過程中隨機刪除神經元,從而每一次都讓不同的模型進行學習。並且,推理時,通過對神經元的輸出乘以刪除比例(比如0.5),可以取模型的平均值。也就是說,可以理解成,Dropout將集成學習的效果通過一個網絡實現了。