# AI 深度学习之正则化和 Dropout

## 正则化

import numpy as np
import matplotlib.pyplot as plt
from reg_utils import sigmoid, relu, plot_decision_boundary, initialize_parameters, load_2D_dataset, predict_dec
from reg_utils import compute_cost, predict, forward_propagation, backward_propagation, update_parameters
import sklearn
import sklearn.datasets
import scipy.io
from testCases import *

%matplotlib inline
plt.rcParams['figure.figsize'] = (7.0, 4.0)
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'

**图1** : **足球场（蓝点是法国足球员）**

train_X, train_Y, test_X, test_Y = load_2D_dataset()

• 如果点是蓝色的，那么表示接住球的是法国队员。
• 如果是红色的点，表示接住球的是对方球队的球员。

## 1 - 不加正则化的模型

• 如果要添加 L2正则化 -- 那么就将参数lambd设置为非0的值。
• 如果要使用 dropout -- 那么就设置keep_prob为小于1的数。

def model(X, Y, learning_rate = 0.3, num_iterations = 30000, print_cost = True, lambd = 0, keep_prob = 1):
costs = []
m = X.shape[1]
layers_dims = [X.shape[0], 20, 3, 1]

parameters = initialize_parameters(layers_dims)

for i in range(0, num_iterations):
if keep_prob == 1:
a3, cache = forward_propagation(X, parameters)
elif keep_prob < 1:
a3, cache = forward_propagation_with_dropout(X, parameters, keep_prob)

if lambd == 0:
cost = compute_cost(a3, Y)
else:
cost = compute_cost_with_regularization(a3, Y, parameters, lambd)

# 防止同时使用L2正则化和dropout。
# 其实是可以同时使用它们的，但是为了教学目的，突出重点，所以单次只允许用其中一个。
assert(lambd == 0 or keep_prob == 1)

if lambd == 0 and keep_prob == 1:
elif lambd != 0:
grads = backward_propagation_with_regularization(X, Y, cache, lambd)
elif keep_prob < 1:
grads = backward_propagation_with_dropout(X, Y, cache, keep_prob)

if print_cost and i % 10000 == 0:
print("Cost after iteration {}: {}".format(i, cost))
if print_cost and i % 1000 == 0:
costs.append(cost)

plt.plot(costs)
plt.ylabel('cost')
plt.xlabel('iterations (x1,000)')
plt.title("Learning rate =" + str(learning_rate))
plt.show()

return parameters

parameters = model(train_X, train_Y)
print("On the training set:")
predictions_train = predict(train_X, train_Y, parameters)
print("On the test set:")
predictions_test = predict(test_X, test_Y, parameters)
Cost after iteration 0: 0.6557412523481002
Cost after iteration 10000: 0.16329987525724204
Cost after iteration 20000: 0.13851642423255658

On the training set:
Accuracy: 0.9478672985781991
On the test set:
Accuracy: 0.915

plt.title("Model without regularization")
axes = plt.gca()
axes.set_xlim([-0.75, 0.40])
axes.set_ylim([-0.75, 0.65])
plot_decision_boundary(lambda x: predict_dec(parameters, x.T), train_X, train_Y.ravel())

## 2 - L2正则化

L2正则化是解决过拟合的常用方法之一.

L2主要有两部分组成，第一步是在成本函数后面加个尾巴,下面是常规的成本函数:

$$J = -\frac{1}{m} \sum\limits_{i = 1}^{m} \large{(}\small y^{(i)}\log\left(a^{[L] (i)}\right) + (1-y^{(i)})\log\left(1- a^{[L] (i)}\right) \large{)} \tag{1}$$

$$J{regularized} = \small \underbrace{-\frac{1}{m} \sum\limits{i = 1}^{m} \large{(}\small y^{(i)}\log\left(a^{[L] (i)}\right) + (1-y^{(i)})\log\left(1- a^{[L] (i)}\right) \large{)} }_\text{cross-entropy cost} + \underbrace{\frac{1}{m} \frac{\lambda}{2} \sum\limits_l\sum\limits_k\sum\limitsj W{k,j}^{[l]2} }_\text{L2尾巴} \tag{2}$$

def compute_cost_with_regularization(A3, Y, parameters, lambd):
m = Y.shape[1]
W1 = parameters["W1"]
W2 = parameters["W2"]
W3 = parameters["W3"]

# 获得常规的成本
cross_entropy_cost = compute_cost(A3, Y)

#计算L2尾巴
L2_regularization_cost = lambd * (np.sum(np.square(W1)) + np.sum(np.square(W2)) + np.sum(np.square(W3))) / (2 * m)

cost = cross_entropy_cost + L2_regularization_cost

return cost
# 单元测试
A3, Y_assess, parameters = compute_cost_with_regularization_test_case()

print("cost = " + str(compute_cost_with_regularization(A3, Y_assess, parameters, lambd = 0.1)))
cost = 1.7864859451590758

def backward_propagation_with_regularization(X, Y, cache, lambd):

m = X.shape[1]
(Z1, A1, W1, b1, Z2, A2, W2, b2, Z3, A3, W3, b3) = cache

dZ3 = A3 - Y

dW3 = 1. / m * np.dot(dZ3, A2.T) + (lambd * W3) / m
db3 = 1. / m * np.sum(dZ3, axis=1, keepdims=True)

dA2 = np.dot(W3.T, dZ3)
dZ2 = np.multiply(dA2, np.int64(A2 > 0))
dW2 = 1. / m * np.dot(dZ2, A1.T) + (lambd * W2) / m
db2 = 1. / m * np.sum(dZ2, axis=1, keepdims=True)

dA1 = np.dot(W2.T, dZ2)
dZ1 = np.multiply(dA1, np.int64(A1 > 0))
dW1 = 1. / m * np.dot(dZ1, X.T) + (lambd * W1) / m
db1 = 1. / m * np.sum(dZ1, axis=1, keepdims=True)

gradients = {"dZ3": dZ3, "dW3": dW3, "db3": db3, "dA2": dA2,
"dZ2": dZ2, "dW2": dW2, "db2": db2, "dA1": dA1,
"dZ1": dZ1, "dW1": dW1, "db1": db1}

return gradients
X_assess, Y_assess, cache = backward_propagation_with_regularization_test_case()

grads = backward_propagation_with_regularization(X_assess, Y_assess, cache, lambd=0.7)
print ("dW1 = " + str(grads["dW1"]))
print ("dW2 = " + str(grads["dW2"]))
print ("dW3 = " + str(grads["dW3"]))
dW1 = [[-0.25604646  0.12298827 -0.28297129]
[-0.17706303  0.34536094 -0.4410571 ]]
dW2 = [[ 0.79276486  0.85133918]
[-0.0957219  -0.01720463]
[-0.13100772 -0.03750433]]
dW3 = [[-1.77691347 -0.11832879 -0.09397446]]

• compute_cost_with_regularization 取代了 compute_cost
• backward_propagation_with_regularization 取代了 backward_propagation
parameters = model(train_X, train_Y, lambd=0.7)
print("On the train set:")
predictions_train = predict(train_X, train_Y, parameters)
print("On the test set:")
predictions_test = predict(test_X, test_Y, parameters)
Cost after iteration 0: 0.6974484493131264
Cost after iteration 10000: 0.2684918873282238
Cost after iteration 20000: 0.2680916337127301

On the train set:
Accuracy: 0.9383886255924171
On the test set:
Accuracy: 0.93

plt.title("Model with L2-regularization")
axes = plt.gca()
axes.set_xlim([-0.75,0.40])
axes.set_ylim([-0.75,0.65])
plot_decision_boundary(lambda x: predict_dec(parameters, x.T), train_X, train_Y.ravel())

• 参数$\lambda$是可以调试的超参数.
• 对比不添加正则化的模型，我们可以发现，L2使预测的线条更加平滑了。如果将参数$\lambda$设置得太大，那么线条就会平滑过度，就会出现对训练数据集欠拟合。建议你自己调试一下$\lambda$玩玩看。

## 3 - Dropout

dropout也是一个被深度学习领域经常用到的解决过拟合的方法。

• 你的朋友: "为什么你需要所有的神经元来参与神经网络模型的训练?".
• 你: "因为每一个神经元都能学到一个特定的知识，例如一类线条一种颜色一些形状。。。我有越多的神经元，那么我的神经网络就能学到越多的知识!"
• 你的朋友: "我明白，但是你确定你的所有神经元都学到了不同的知识吗，而不是所有的碰巧都只学会了认识红色而没有一个神经元学会了识别蓝色？"
• 你: "我了个去，你的这个问题问得太好了。。。同一层的神经元相互之间没有联系，无法互相交流，所以极有可能它们都学会了同一种知识点。。。如果每次都随机地删除一些神经元，那么网络结构发生了变化，那么它们学到相同知识点的概率就很低了"

dropout在训练的每个回合中都随机地删除一些神经元，下面的视频直观地展示了dropout的过程!

### 3.1 - dropout的前向传播

1. 在前面的文章中，我们提到了使用 np.random.rand()来创建一个与 $a^{[1]}$维度相同的 $d^{[1]}$。np.random.rand()会生成0到1之间的随机数。下面的函数中，我们实现了一个向量化的版本，创建了与 $A^{[1]}$维度相同的矩阵 $D^{[1]}$,$D^{[1]} = [d^{[1] (1)} d^{[1] (2)} ... d^{[1] (m)}]$.

2. 函数中会通过设置阈值的方式来将$D^{[1]}$中(1-keep_prob)百分比个元素设置为0，将占比keep_prob个元素设置为1。 例如，如果你想将矩阵X的所有元素都设置为0（当元素原来的值 < 0.5时）或1（如果元素原来的值>0.5），那么可以用这个公式 X = (X < 0.5). 这个0.5就是一个阈值，我们这里将keep_prob作为阈值。另外，在python中，False和True相当于0和1.

3. 然后让 $A^{[1]}$ 等于 $A^{[1]} * D^{[1]}$. 这样$A^{[1]}$某些元素与$D^{[1]}$中的0元素相乘之后，$A^{[1]}$中的相应元素也变成0了。相应的a被设置为0后，它对应的神经元就等于被删除掉了。

4. 最后一步，将 $A^{[1]}$ 除以 keep_prob. 通过这一步使运用了dropout的神经网络的期望值和没有用dropout的神经网络的期望值保持一样。这种dropout我们称为inverted dropout。详情温故我前面的文章。

def forward_propagation_with_dropout(X, parameters, keep_prob=0.5):

np.random.seed(1)

W1 = parameters["W1"]
b1 = parameters["b1"]
W2 = parameters["W2"]
b2 = parameters["b2"]
W3 = parameters["W3"]
b3 = parameters["b3"]

Z1 = np.dot(W1, X) + b1
A1 = relu(Z1)

D1 = np.random.rand(A1.shape[0], A1.shape[1])     # 第一步，创建与A1相同的维度
D1 = D1 < keep_prob                               # 第二步，如果小于阀值，则值为False(即为0)
A1 = A1 * D1                                      # 第三步，A1对应位置的为0

# 除以 keep_prob. 通过这一步使运用了dropout的神经网络的期望值和没有用dropout的神经网络的期望值保持一样
A1 = A1 / keep_prob                               # 第四步

Z2 = np.dot(W2, A1) + b2
A2 = relu(Z2)

D2 = np.random.rand(A2.shape[0], A2.shape[1])
D2 = D2 < keep_prob
A2 = A2 * D2
A2 = A2 / keep_prob

Z3 = np.dot(W3, A2) + b3
A3 = sigmoid(Z3)

cache = (Z1, D1, A1, W1, b1, Z2, D2, A2, W2, b2, Z3, A3, W3, b3)

return A3, cache
X_assess, parameters = forward_propagation_with_dropout_test_case()

A3, cache = forward_propagation_with_dropout(X_assess, parameters, keep_prob=0.7)
print ("A3 = " + str(A3))
A3 = [[0.36974721 0.00305176 0.04565099 0.49683389 0.36974721]]

### 3.2 - 带dropout的反向传播

1. 在前向传播时，我们通过使用掩码$D^{[1]}$与A1进行运算而删除了一些神经元。在反向传播时，我们也必须删除相同的神经元，这个可以通过使用相同的$D^{[1]}$与dA1进行运算来实现。
2. 在前向传播时，我们将A1除以了keep_prob。在反向传播时，我们也必须将dA1除以keep_prob。微积分层面的解释是：如果$A^{[1]}$被keep_prob进行了缩放，那么它的导数$dA^{[1]}$也应该被相应地缩放。
def backward_propagation_with_dropout(X, Y, cache, keep_prob):

m = X.shape[1]
(Z1, D1, A1, W1, b1, Z2, D2, A2, W2, b2, Z3, A3, W3, b3) = cache

dZ3 = A3 - Y
dW3 = 1. / m * np.dot(dZ3, A2.T)
db3 = 1. / m * np.sum(dZ3, axis=1, keepdims=True)
dA2 = np.dot(W3.T, dZ3)

dA2 = dA2 * D2              # 第一步
dA2 = dA2 / keep_prob              # 第二步

dZ2 = np.multiply(dA2, np.int64(A2 > 0))
dW2 = 1. / m * np.dot(dZ2, A1.T)
db2 = 1. / m * np.sum(dZ2, axis=1, keepdims=True)

dA1 = np.dot(W2.T, dZ2)

dA1 = dA1 * D1
dA1 = dA1 / keep_prob

dZ1 = np.multiply(dA1, np.int64(A1 > 0))
dW1 = 1. / m * np.dot(dZ1, X.T)
db1 = 1. / m * np.sum(dZ1, axis=1, keepdims=True)

gradients = {"dZ3": dZ3, "dW3": dW3, "db3": db3,"dA2": dA2,
"dZ2": dZ2, "dW2": dW2, "db2": db2, "dA1": dA1,
"dZ1": dZ1, "dW1": dW1, "db1": db1}

return gradients
X_assess, Y_assess, cache = backward_propagation_with_dropout_test_case()

gradients = backward_propagation_with_dropout(X_assess, Y_assess, cache, keep_prob=0.8)

print ("dA1 = " + str(gradients["dA1"]))
print ("dA2 = " + str(gradients["dA2"]))
dA1 = [[ 0.36544439  0.         -0.00188233  0.         -0.17408748]
[ 0.65515713  0.         -0.00337459  0.         -0.        ]]
dA2 = [[ 0.58180856  0.         -0.00299679  0.         -0.27715731]
[ 0.          0.53159854 -0.          0.53159854 -0.34089673]
[ 0.          0.         -0.00292733  0.         -0.        ]]

• forward_propagation_with_dropout取代了 forward_propagation.
• backward_propagation_with_dropout取代了 backward_propagation.
parameters = model(train_X, train_Y, keep_prob=0.86, learning_rate=0.3)

print("On the train set:")
predictions_train = predict(train_X, train_Y, parameters)
print("On the test set:")
predictions_test = predict(test_X, test_Y, parameters)
Cost after iteration 0: 0.6543912405149825
Cost after iteration 10000: 0.0610169865749056
Cost after iteration 20000: 0.060582435798513114

On the train set:
Accuracy: 0.9289099526066351
On the test set:
Accuracy: 0.95

plt.title("Model with dropout")
axes = plt.gca()
axes.set_xlim([-0.75, 0.40])
axes.set_ylim([-0.75, 0.65])
plot_decision_boundary(lambda x: predict_dec(parameters, x.T), train_X, train_Y.ravel())

• 如我前面的文章提到的，千万不要在实际使用时也运行dropout，应该只在训练模型时使用dropout。

• Dropout是一种正则化技术.
• 只能在训练模型时运行dropout，在使用模型时要把dropout关掉。
• 在前向传播和反向传播中都要实现dropout.
• 要记住在每层的前向传播和反向传播中都除以keep_prob来保证期望值不变.

## 4 - 总结

 **模型** **训练集准确度** **测试集准确度** L2正则化模型 94% 93% dropout模型 93% 95%

• 正则化能解决过拟合问题.
• 正则化会使权重变小.
• L2正则化和dropout是两个效率非常高的正则化技术