在深度神經(jīng)網(wǎng)絡(luò)中最常用的方法是Regularization和dropout。在本文中,我們將一起理解這兩種方法并在python中實(shí)現(xiàn)它們
Regularization 正則化
正則化通過(guò)在損失函數(shù)的末尾添加額外的懲罰項(xiàng)來(lái)幫助防止模型過(guò)度擬合。
其中m是批次大校所示的正則化稱為L(zhǎng)2正則化,而L2對(duì)權(quán)重應(yīng)用平方,而L1正則化則采用絕對(duì)值,其形式為| W |。
當(dāng)權(quán)重過(guò)多或權(quán)重太大時(shí),附加的額外項(xiàng)會(huì)增加損失,并且可調(diào)整因子λ著重說(shuō)明了我們要對(duì)權(quán)重進(jìn)行多少懲罰。
為什么添加懲罰會(huì)有助于防止過(guò)度擬合?
直觀的理解是,在最小化新?lián)p失函數(shù)的過(guò)程中,某些權(quán)重將減小至接近零,因此相應(yīng)的神經(jīng)元將對(duì)我們的結(jié)果產(chǎn)生非常小的影響,就好像我們正在使用 更少的神經(jīng)元。
前向傳播:在前進(jìn)過(guò)程中,我們只需更改損失函數(shù)。
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正則化的反向傳播實(shí)際上是直接的,我們只需要添加L2項(xiàng)的梯度即可。
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
訓(xùn)練過(guò)程:像往常一樣,我們?cè)诙诸惖那闆r下測(cè)試我們的模型,并比較有無(wú)正則化的模型。
沒(méi)有正則化的模型
有正則化的模型
實(shí)際上,當(dāng)?shù)螖?shù)增加時(shí),該模型將繼續(xù)過(guò)擬合,從而導(dǎo)致除法運(yùn)算出錯(cuò),造成這種問(wèn)題的原因可能是在正向過(guò)程中,結(jié)果A太接近于0。
相反,具有正則化的模型不會(huì)過(guò)擬合。
Dropout
Dropout通過(guò)隨機(jī)關(guān)閉某些輸出單元來(lái)防止過(guò)度擬合。
在上述過(guò)程中,在每次迭代中,層[2]上的某些單元將被隨機(jī)關(guān)閉,這意味著在正向過(guò)程中將工作的神經(jīng)元更少,因此簡(jiǎn)化了神經(jīng)網(wǎng)絡(luò)的整體結(jié)構(gòu)。
同時(shí),訓(xùn)練后的模型將更加健壯,因?yàn)樵撃P筒辉倏梢砸蕾嚾魏翁囟ǖ纳窠?jīng)元(因?yàn)樵诖诉^(guò)程中它們可能會(huì)被靜音),因此所有其他神經(jīng)元都需要在訓(xùn)練中學(xué)習(xí)。
前向傳播:你可以理解為Dropout是在前向傳播過(guò)程中增加了一層。
一般情況下,正向傳播方程如下:
其中g(shù)是激活函數(shù)。現(xiàn)在,通過(guò)Dropout將一個(gè)額外的圖層應(yīng)用于A ^ [l]。
添加了Dropout如下:
其中D是Dropout層。Dropout層中的關(guān)鍵因素是keep_prob參數(shù),該參數(shù)指定保留每個(gè)單元的可能性。假設(shè)keep_prob = 0.8,我們將有80%的機(jī)會(huì)保持每個(gè)輸出單位不變,而有20%的機(jī)會(huì)將其設(shè)置為0。
該實(shí)現(xiàn)將為結(jié)果A添加一個(gè)額外的掩碼。假設(shè)我們有一個(gè)包含四個(gè)元素的輸出A ^ {[l]},如下所示,
我們希望在保持其余部分不變的情況下使第三個(gè)單元關(guān)閉,我們需要的是形狀相同的矩陣,并按以下方式進(jìn)行元素逐次乘法,
前向傳播:
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相同的形狀,并根據(jù)keep_prob將其轉(zhuǎn)換為0和1矩陣。
請(qǐng)注意,dropout后,結(jié)果A需要重新縮放!由于在此過(guò)程中某些神經(jīng)元被靜音,因此需要增加左神經(jīng)元以匹配預(yù)期值。
反向傳播:過(guò)程是將相同的函數(shù)D屏蔽為相應(yīng)的dA。
# dummy code, full version needs to be inside a Class
def 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
反向傳播方程式與一般的神經(jīng)網(wǎng)絡(luò)網(wǎng)絡(luò)中引入的方程式相同。唯一的區(qū)別在于矩陣D。除最后一層外,所有其他具有丟失的層將對(duì)dA施加相應(yīng)的蒙版D。
注意,在反向傳播中,dA也需要重新縮放。
結(jié)論
正則化和dropout都被廣泛采用以防止過(guò)度擬合,正則化通過(guò)在損失函數(shù)的末尾添加一個(gè)額外的懲罰項(xiàng)來(lái)實(shí)現(xiàn),并通過(guò)在正向過(guò)程中隨機(jī)地使某些神經(jīng)元靜音來(lái)使其退出以使網(wǎng)絡(luò)更加簡(jiǎn)潔來(lái)實(shí)現(xiàn)正則化。
最后所有的代碼都可以在這里找到:https://github.com/MJeremy2017/deep-learning/tree/main/regularization
作者:Jeremy Zhang
deephub翻譯組