在深度神經網絡中最常用的方法是Regularization和dropout。 在本文中,我們將一起理解這兩種方法並在python中實現它們
Regularization 正則化
正則化通過在損失函數的末尾添加額外的懲罰項來幫助防止模型過度擬合。
其中m是批次大小。 所示的正則化稱為L2正則化,而L2對權重應用平方,而L1正則化則採用絕對值,其形式為| W |。
當權重過多或權重太大時,附加的額外項會增加損失,並且可調整因子λ著重說明了我們要對權重進行多少懲罰。
為什麼添加懲罰會有助於防止過度擬合?
直觀的理解是,在最小化新損失函數的過程中,某些權重將減小至接近零,因此相應的神經元將對我們的結果產生非常小的影響,就好像我們正在使用 更少的神經元。
前向傳播:在前進過程中,我們只需更改損失函數。
def compute_loss(A, Y, parameters, reg=True, lambd=.2):""" With L2 regularization parameters: dict with 'W1', 'b1', 'W2', ... """ assert A.shape == Y.shape n_layer = len(parameters)//2 m = A.shape[1] s = np.dot(Y, np.log(A.T)) + np.dot(1-Y, np.log((1 - A).T)) loss = -s/m if reg: p = 0 for i in range(1, n_layer+1): p += np.sum(np.square(parameters['W'+str(i)])) loss += (1/m)*(lambd/2)*p return np.squeeze(loss)
反向傳播:L2正則化的反向傳播實際上是直接的,我們只需要添加L2項的梯度即可。
def backward(params, cache, X, Y, lambd=0.2):""" params: weight [W, b] cache: result [A, Z] Y: shape (1, m) """ grad = {} n_layers = int(len(params)/2) m = Y.shape[1] cache['A0'] = X for l in range(n_layers, 0, -1): A, A_prev, Z = cache['A' + str(l)], cache['A' + str(l-1)], cache['Z' + str(l)] W = params['W'+str(l)] if l == n_layers: dA = -np.divide(Y, A) + np.divide(1 - Y, 1 - A) if l == n_layers: dZ = np.multiply(dA, sigmoid_grad(A, Z)) else: dZ = np.multiply(dA, relu_grad(A, Z)) # with an extra gradient at the end, other terms would remain the same dW = np.dot(dZ, A_prev.T)/m + (lambd/m)*W db = np.sum(dZ, axis=1, keepdims=True)/m dA = np.dot(W.T, dZ) grad['dW'+str(l)] = dW grad['db'+str(l)] = db return grad
訓練過程:像往常一樣,我們在二元分類的情況下測試我們的模型,並比較有無正則化的模型。
沒有正則化的模型
有正則化的模型
實際上,當迭代次數增加時,該模型將繼續過擬合,從而導致除法運算出錯,造成這種問題的原因可能是在正向過程中,結果A太接近於0。
相反,具有正則化的模型不會過擬合。
Dropout
Dropout通過隨機關閉某些輸出單元來防止過度擬合。
在上述過程中,在每次迭代中,層[2]上的某些單元將被隨機關閉,這意味著在正向過程中將工作的神經元更少,因此簡化了神經網絡的整體結構。
同時,訓練後的模型將更加健壯,因為該模型不再可以依賴任何特定的神經元(因為在此過程中它們可能會被靜音),因此所有其他神經元都需要在訓練中學習。
前向傳播:你可以理解為Dropout是在前向傳播過程中增加了一層。
一般情況下,正向傳播方程如下:
其中g是激活函數。 現在,通過Dropout將一個額外的圖層應用於A ^ [l]。
添加了Dropout如下:
其中D是Dropout層。 Dropout層中的關鍵因素是keepprob參數,該參數指定保留每個單元的可能性。 假設keepprob = 0.8,我們將有80%的機會保持每個輸出單位不變,而有20%的機會將其設置為0。
該實現將為結果A添加一個額外的掩碼。假設我們有一個包含四個元素的輸出A ^ {[l]},如下所示,
我們希望在保持其餘部分不變的情況下使第三個單元關閉,我們需要的是形狀相同的矩陣,並按以下方式進行元素逐次乘法,
前向傳播:
def forward(X):# intermediate layer use relu as activation # last layer use sigmoid n_layers = int(len(params)/2) A = X cache = {} for i in range(1, n_layers): W, b = params['W'+str(i)], params['b'+str(i)] Z = np.dot(W, A) + b A = relu(Z) # dropout keep_prob = keep_probs[i-1] D = np.random.rand(A.shape[0], A.shape[1]) D = (D < keep_prob).astype(int) A = np.multiply(D, A) # rescale A = A/keep_prob cache['Z'+str(i)] = Z cache['A'+str(i)] = A cache['D'+str(i)] = D # last layer W, b = params['W'+str(i+1)], params['b'+str(i+1)] Z = np.dot(W, A) + b A = sigmoid(Z) cache['Z'+str(i+1)] = Z cache['A'+str(i+1)] = A return cache, A
在這裡,我們將D初始化為與A相同的形狀,並根據keep_prob將其轉換為0和1矩陣。
請注意,dropout後,結果A需要重新縮放! 由於在此過程中某些神經元被靜音,因此需要增加左神經元以匹配預期值。
反向傳播:過程是將相同的函數D屏蔽為相應的dA。
# dummy code, full version needs to be inside a Classdef backward(self, cache, X, Y, keep_probs):""" cache: result [A, Z] Y: shape (1, m) """ grad = {} n_layers = int(len(self.params)/2) m = Y.shape[1] cache['A0'] = X for l in range(n_layers, 0, -1): A, A_prev, Z = cache['A' + str(l)], cache['A' + str(l-1)], cache['Z' + str(l)] W = self.params['W'+str(l)] if l == n_layers: dA = -np.divide(Y, A) + np.divide(1 - Y, 1 - A) if l == n_layers: dZ = np.multiply(dA, self.sigmoid_grad(A, Z)) else: # dropout version D = cache['D' + str(l)] dA = np.multiply(dA, D) # rescale dA = dA/keep_probs[l-1] dZ = np.multiply(dA, self.relu_grad(A, Z)) dW = np.dot(dZ, A_prev.T)/m db = np.sum(dZ, axis=1, keepdims=True)/m dA = np.dot(W.T, dZ) grad['dW'+str(l)] = dW grad['db'+str(l)] = db return grad
反向傳播方程式與一般的神經網絡網絡中引入的方程式相同。 唯一的區別在於矩陣D。 除最後一層外,所有其他具有丟失的層將對dA施加相應的蒙版D。
注意,在反向傳播中,dA也需要重新縮放。
結論
正則化和dropout都被廣泛採用以防止過度擬合,正則化通過在損失函數的末尾添加一個額外的懲罰項來實現,並通過在正向過程中隨機地使某些神經元靜音來使其退出以使網絡更加簡潔來實現正則化。
最後所有的代碼都可以在這裡找到:github/MJeremy2017/deep-learning/tree/main/regularization
作者:Jeremy Zhang
deephub翻譯組