初始化、正则化、梯度校验
一、目的
- 初始化参数:
1.1 使用0来初始化参数;
1.2 使用随机数(高斯分布)来初始化参数;
1.3 使用抑梯度异常初始化参数(参见视频中的梯度消失和梯度爆炸);
观察不同初始化的训练结果。 - 正则化模型:
2.1 使用二范数对二分类模型正则化,尝试避免过拟合;
2.2 使用随机删除节点(dropout)的方法精简模型,同样是为了尝试避免过拟合; - 梯度校验:对模型使用梯度校验,检测它是否在梯度下降的过程中出现误差过大的情况,确认反向传播的梯度计算正确;
二、训练集与测试集
三、编程
这是本次编程需要用的的所有模块和工具类
import numpy as np
import matplotlib.pyplot as plt
import sklearn
import sklearn.datasets
import init_utils
import reg_utils
import gc_utils
import testCase
不清这些模块的作用看我以前的文章
3.1 初始化参数
在我们初始化参数之前,我们先看一下数据集是怎样的
# 加载并查看数据集
train_X, train_Y, test_X, test_Y = init_utils.load_dataset(is_plot=True)
plt.show()
我们将要建立一个三层的神经网络,用作分类器,尝试使用三种初始化参数方法:
- 初始化为0:在输入参数中全部初始化为0,参数名为initialization = “zeros”,核心代码:
parameters['W' + str(l)] = np.zeros((layers_dims[l], layers_dims[l - 1]))
- 初始化为随机数:把输入参数设置为随机值,权重初始化为比较大的随机值。参数名为initialization = “random”,核心代码:
parameters['W' + str(l)] = np.random.randn(layers_dims[l], layers_dims[l - 1]) * 10
- 抑梯度异常初始化:参见梯度消失和梯度爆炸的那一个视频,参数名为initialization = “he”,核心代码:
parameters['W' + str(l)] = np.random.randn(layers_dims[l], layers_dims[l - 1]) * np.sqrt(2 / layers_dims[l - 1])
下面建立我们的三层神经网络模型
# 三层神经网络模型
def model(X, Y, n_iters, alpha, initialization, print_cost, isPlot):
"""
搭建三层神经网络模型,LINEAR ->RELU -> LINEAR -> RELU -> LINEAR -> SIGMOID
:param isPlot:
:param X: 输入数据,维度(n_x, m)
:param Y: 标签向量,维度(1, m)
:param n_iters: 迭代次数
:param alpha: 学习速率
:param initialization: 可选初始化参数,zeros/random/he
:param print_cost: 成本打印控制参数
:param isPlot: 绘图控制参数
:return: parameters:训练后的最优参数,字典值
"""
# 各层节点数量
layers_dims=[X.shape[0], 10, 5, 1]
costs = []
parameters = {}
# 参数初始化
if initialization == "zeros":
parameters = initialize_parameters_zeros(layers_dims)
elif initialization == "random":
parameters = initialize_parameters_random(layers_dims)
elif initialization == "he":
parameters = initialize_parameters_he(layers_dims)
else:
print("初始化参数错误!")
exit()
for i in range(n_iters):
# 正向传播
A3, cache = init_utils.forward_propagation(X, parameters)
# 计算陈本
cost = init_utils.compute_loss(A3, Y)
# 反向传播
grads = init_utils.backward_propagation(X, Y, cache)
# 更新参数
parameters = init_utils.update_parameters(parameters, grads, alpha)
# 成本打印控制
if i % 1000 == 0:
costs.append(cost)
if print_cost:
print("迭代次数:", i, "成本为:", cost)
# 成本绘图控制
if isPlot:
plt.figure("1")
plt.plot(costs)
plt.xlabel("pre thousand iters")
plt.ylabel("cost")
plt.show()
return parameters
下面我们开始尝试这三种初始化
3.1.1 零初始化
# 零初始化
def initialize_parameters_zeros(layers_dims):
"""
零初始化参数
:param layers_dims: 各层节点数量
:return: parameters:初始化后的参数,字典值
"""
np.random.seed(3)
parameters = {}
L = len(layers_dims)
for l in range(1, L):
parameters["W" + str(l)] = np.zeros((layers_dims[l], layers_dims[l - 1]))
parameters["b" + str(l)] = np.zeros((layers_dims[l], 1))
assert (parameters["W" + str(l)].shape == (layers_dims[l], layers_dims[l - 1]))
assert (parameters["b" + str(l)].shape == (layers_dims[l], 1))
return parameters
测试一下:
# 测试initialize_parameters_zeros
parameters = initialize_parameters_zeros([3, 2, 1])
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))
测试结果:
W1 = [[0. 0. 0.]
[0. 0. 0.]]
b1 = [[0.]
[0.]]
W2 = [[0. 0.]]
b2 = [[0.]]
使用这些参数训练模型
# 零初始化模型
parameters = model(train_X, train_Y, n_iters=15000, alpha=0.01, initialization="zeros", print_cost=True, isPlot=True)
执行结果:
迭代次数: 0 成本为: 0.6931471805599453
迭代次数: 1000 成本为: 0.6931471805599453
迭代次数: 2000 成本为: 0.6931471805599453
迭代次数: 3000 成本为: 0.6931471805599453
迭代次数: 4000 成本为: 0.6931471805599453
迭代次数: 5000 成本为: 0.6931471805599453
迭代次数: 6000 成本为: 0.6931471805599453
迭代次数: 7000 成本为: 0.6931471805599453
迭代次数: 8000 成本为: 0.6931471805599453
迭代次数: 9000 成本为: 0.6931471805599453
迭代次数: 10000 成本为: 0.6931471805599455
迭代次数: 11000 成本为: 0.6931471805599453
迭代次数: 12000 成本为: 0.6931471805599453
迭代次数: 13000 成本为: 0.6931471805599453
迭代次数: 14000 成本为: 0.6931471805599453
从成本变化曲线上可以看到,成本始终保持不变,说明在该参数下模型根本没进行学习,我们再来看看预测的结果如何
# 性能
print("训练集:")
predictions_train = init_utils.predict(train_X, train_Y, parameters)
print("测试集:")
predictions_test = init_utils.predict(test_X, test_Y, parameters)
执行结果:
训练集:
Accuracy: 0.5
测试集:
Accuracy: 0.5
性能很差,对于二分类0.5的准确率相当于随机猜测,我们再看看预测结果和决策边界的细节
# 预测结果
print("predictions_train = " + str(predictions_train))
print("predictions_test = " + str(predictions_test))
# 决策边界
plt.title("Model with Zeros initialization")
axes = plt.gca()
axes.set_xlim([-1.5, 1.5])
axes.set_ylim([-1.5, 1.5])
init_utils.plot_decision_boundary(lambda x: init_utils.predict_dec(parameters, x.T), train_X, train_Y)
执行结果:
predictions_train = [[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0]]
predictions_test = [[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]]
以上得知,分类失败,该模型的每个预测值均为0,这是为什么?因为当所有参数都为0时,这时模型无法打破对称性,无论神经网络有多少层,最终的效果和Logistic相同。
3.1.2 随机初始化
为了打破对称性,我们可以随机地(搞死分布)给参数赋值。在随机初始化之后,每个神经元可以开始学习其输入的不同功能,我们设置比较大的参数值,看看会发生什么。
# 随机初始化
def initialize_parameters_random(layers_dims):
"""
随机初始化参数
:param layers_dims: 各层节点数量
:return: parameters:初始化后的参数,字典值
"""
L = len(layers_dims)
parameters = {}
for l in range(1, L):
parameters["W" + str(l)] = np.random.randn(layers_dims[l], layers_dims[l - 1]) * 10
parameters["b" + str(l)] = np.zeros((layers_dims[l], 1))
assert (parameters["W" + str(l)].shape == (layers_dims[l], layers_dims[l - 1]))
assert (parameters["b" + str(l)].shape == (layers_dims[l], 1))
return parameters
测试一下:
# 测试initialize_parameters_random
parameters = initialize_parameters_random([3, 2, 1])
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))
测试结果:
W1 = [[ 17.88628473 4.36509851 0.96497468]
[-18.63492703 -2.77388203 -3.54758979]]
b1 = [[0.]
[0.]]
W2 = [[-0.82741481 -6.27000677]]
b2 = [[0.]]
这些参数看起来比较大,我们来看看实际运行会如何
# 随机初始化模型
parameters = model(train_X, train_Y, n_iters=15000, alpha=0.01, initialization="random", print_cost=True, isPlot=True)
# 性能
print("训练集:")
predictions_train = init_utils.predict(train_X, train_Y, parameters)
print("测试集:")
predictions_test = init_utils.predict(test_X, test_Y, parameters)
执行结果:
迭代次数: 0 成本为: inf
E:\py\DL\DL_work\004_wz_course2_week1\init_utils.py:50: RuntimeWarning: divide by zero encountered in log
logprobs = np.multiply(-np.log(a3),Y) + np.multiply(-np.log(1 - a3), 1 - Y)
E:\py\DL\DL_work\004_wz_course2_week1\init_utils.py:50: RuntimeWarning: invalid value encountered in multiply
logprobs = np.multiply(-np.log(a3),Y) + np.multiply(-np.log(1 - a3), 1 - Y)
迭代次数: 1000 成本为: 0.6238752898567104
迭代次数: 2000 成本为: 0.5981823702488288
迭代次数: 3000 成本为: 0.5639260898138578
迭代次数: 4000 成本为: 0.5502002121249503
迭代次数: 5000 成本为: 0.5445041940208237
迭代次数: 6000 成本为: 0.5374846467184754
迭代次数: 7000 成本为: 0.4786183145900604
迭代次数: 8000 成本为: 0.3978648824400712
迭代次数: 9000 成本为: 0.39349010190237194
迭代次数: 10000 成本为: 0.392036570490362
迭代次数: 11000 成本为: 0.38928537988509276
迭代次数: 12000 成本为: 0.3861532565273933
迭代次数: 13000 成本为: 0.38499529808336796
迭代次数: 14000 成本为: 0.38280883365395
训练集:
Accuracy: 0.83
测试集:
Accuracy: 0.86
注:这里的警告是因为sigmoid函数进行exp(-z)运算时,因为输入得z值太大(正值)或太小(负值),产生了内存溢出,最终得到的结果是nan,所以在cost函数中的log计算引发此警告,但是该警告不影响梯度的计算。
再看看决策边界
# 决策边界
plt.title("Model with large random initialization")
axes = plt.gca()
axes.set_xlim([-1.5, 1.5])
axes.set_ylim([-1.5, 1.5])
init_utils.plot_decision_boundary(lambda x: init_utils.predict_dec(parameters, x.T), train_X, train_Y)
这个预测结果并不合格,成本曲线可以看到,在最开始误差很大,这是因为我们将权重初始值设置的比较大,而最后一个激活函数(sigmoid)的输出非常接近于0/1,当预测出现错误是,它会造成非常高损失。初始化参数如果没有设置的合适,将会导致梯度消失、爆炸,这回降低优化算法的效率。当然,我们可以对该模型进行长时间的训练,能达到一个更好的效果,但是使用较大的随机初始值会减慢优化的速度。所有将初始值设置的比较大并不好,下面我们试试比较小的数值。
3.1.3 抑梯度异常初始化
# 抑梯度异常初始化
def initialize_parameters_he(layers_dims):
"""
抑梯度异常初始化参数
:param layers_dims: 各层节点数量
:return: parameters:初始化后的参数,字典值
"""
np.random.seed(3)
L = len(layers_dims)
parameters = {}
for l in range(1, L):
parameters["W" + str(l)] = np.random.randn(layers_dims[l], layers_dims[l - 1]) * np.sqrt(np.divide(2, layers_dims[l - 1]))
parameters["b" + str(l)] = np.zeros((layers_dims[l], 1))
assert (parameters["W" + str(l)].shape == (layers_dims[l], layers_dims[l - 1]))
assert (parameters["b" + str(l)].shape == (layers_dims[l], 1))
return parameters
测试一下:
# 测试initialize_parameters_he
parameters = initialize_parameters_he([2, 4, 1])
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))
测试结果:
W1 = [[ 1.78862847 0.43650985]
[ 0.09649747 -1.8634927 ]
[-0.2773882 -0.35475898]
[-0.08274148 -0.62700068]]
b1 = [[0.]
[0.]
[0.]
[0.]]
W2 = [[-0.03098412 -0.33744411 -0.92904268 0.62552248]]
b2 = [[0.]]
可以看到参数W均在1附近,现在我们来看一看效果
# 抑梯度异常初始化模型
parameters = model(train_X, train_Y, n_iters=15000, alpha=0.01, initialization="he", print_cost=True, isPlot=True)
# 性能
print("训练集:")
predictions_train = init_utils.predict(train_X, train_Y, parameters)
print("测试集:")
init_utils.predictions_test = init_utils.predict(test_X, test_Y, parameters)
执行结果:
迭代次数: 0 成本为: 0.8830537463419761
迭代次数: 1000 成本为: 0.6879825919728063
迭代次数: 2000 成本为: 0.6751286264523371
迭代次数: 3000 成本为: 0.6526117768893807
迭代次数: 4000 成本为: 0.6082958970572938
迭代次数: 5000 成本为: 0.5304944491717495
迭代次数: 6000 成本为: 0.41386458170717944
迭代次数: 7000 成本为: 0.3117803464844441
迭代次数: 8000 成本为: 0.23696215330322562
迭代次数: 9000 成本为: 0.18597287209206836
迭代次数: 10000 成本为: 0.1501555628037181
迭代次数: 11000 成本为: 0.12325079292273548
迭代次数: 12000 成本为: 0.09917746546525934
迭代次数: 13000 成本为: 0.08457055954024276
迭代次数: 14000 成本为: 0.07357895962677366
训练集:
Accuracy: 0.9933333333333333
测试集:
Accuracy: 0.96
再看决策边界
# 决策边界
plt.title("Model with He initialization")
axes = plt.gca()
axes.set_xlim([-1.5, 1.5])
axes.set_ylim([-1.5, 1.5])
init_utils.plot_decision_boundary(lambda x: init_utils.predict_dec(parameters, x.T), train_X, train_Y)
执行结果:
抑梯度异常初始化的模型将蓝色和红色的点在少量的迭代中很好地分离出来,总结一下:
- 不同的初始化方法可能导致性能最终不同;
- 随机初始化有助于打破对称,使得不同隐藏层的单元可以学习到不同的参数;
- 初始化时,初始值不宜过大;
- he初始化搭配ReLU激活函数常常可以得到不错的效果;
在深度学习中,如果数据集没有足够大的话,可能会导致一些过拟合的问题。过拟合导致的结果就是在训练集上有着很高的精确度,但是在遇到新的样本时,精确度下降会很严重。为了避免过拟合的问题,接下来我们要讲解的方式就是正则化。
3.2 正则化
问题描述:假设你现在是一个AI专家,你需要设计一个模型,可以用于推荐在足球场中守门员将球发至哪个位置可以让本队的球员抢到球的可能性更大。说白了,实际上就是一个二分类,一半是己方抢到球,一半就是对方抢到球,我们来看一下这个图:
先加载并绘制数据集看看
# 加载并绘制数据集
train_X, train_Y, test_X, test_Y = reg_utils.load_2D_dataset(is_plot=True)
plt.show()
执行结果:
每一个点代表球落下的可能的位置,蓝色代表己方的球员会抢到球,红色代表对手的球员会抢到球,我们要做的就是使用模型来画出一条线,来找到适合我方球员能抢到球的位置。
我们要做以下三件事,来对比出不同的模型的优劣:
- 不使用正则化;
- 使用正则化;
2.1 使用L2正则化;
2.2 使用随机节点(dropout)删除;
我们来看一下我们的模型:
正则化模式:将lambd输入设置为非零值;
随机删除节点:将keep_prob设置为小于1的值;
注: 我们使用“lambd”而不是“lambda”,因为“lambda”是Python中的保留关键字。
# 三层神经网络模型
def model(X, Y, n_iters, alpha, lambd, keep_prob, print_cost, isPlot):
"""
搭建三层神经网络模型,LINEAR ->RELU -> LINEAR -> RELU -> LINEAR -> SIGMOID
:param X: 输入数据,维度(n_x, m)
:param Y: 标签向量,维度(1, m)
:param n_iters: 迭代次数
:param alpha: 学习速率
:param lambd: 正则化的超参数,实数
:param keep_prob: dropout的超参数,0-1之间
:param print_cost: 成本打印控制参数
:param isPlot: 成本绘图控制参数
:return: parameters:训练后的最佳参数,字典值
"""
costs = []
parameters = {}
# 各层节点数量
layers_dims = [X.shape[0], 20, 3, 1]
# 参数初始化
parameters = reg_utils.initialize_parameters(layers_dims)
for i in range(n_iters):
# 正向传播
# 是否使用dropout
if keep_prob == 1:
# 不使用
A3, cache = reg_utils.forward_propagation(X, parameters)
elif keep_prob < 1:
# 使用
A3, cache = forward_propagation_with_dropout(X, parameters, keep_prob)
else:
print("dropout参数错误!")
exit()
# 计算成本
# 是否使用L2正则化
if lambd == 0:
# 不使用
cost = reg_utils.compute_cost(A3, Y)
else:
# 使用
cost = compute_cost_with_L2(A3, Y, lambd)
# 反向传播
# 可以同时使用L2正则化和随机删除节点,但是本次编程不同时使用
if lambd == 0 and keep_prob == 1:
# 均不使用
grads = reg_utils.backward_propagation(X, Y, cache)
elif lambd != 0:
# 使用L2正则化
grads = backward_propagation_with_L2(X, Y, cache, lambd)
elif keep_prob < 1:
# 使用dropout
grads = backward_propagation_with_dropout(X, Y, cache, keep_prob)
# 更新参数
parameters = reg_utils.update_parameters(parameters, grads, alpha)
# 成本打印控制
if i % 1000 == 0:
costs.append(cost)
if print_cost:
print("迭代次数:", i, "成本为:", cost)
# 成本绘图控制
if isPlot:
plt.figure("1")
plt.plot(costs)
plt.xlabel("pre thousand iters")
plt.ylabel("cost")
plt.show()
return parameters
我们看看不使用正则化的效果
3.2.1 不使用正则化
# 不使用正则化
parameters = model(train_X, train_Y, n_iters=30000, alpha=0.3, lambd=0, keep_prob=1, print_cost=True, isPlot=True)
# 性能
print("训练集:")
predictions_train = reg_utils.predict(train_X, train_Y, parameters)
print("测试集:")
predictions_test = reg_utils.predict(test_X, test_Y, parameters)
执行结果:
迭代次数: 0 成本为: 0.6557412523481002
迭代次数: 1000 成本为: 0.2222669386845115
迭代次数: 2000 成本为: 0.20288702607598855
迭代次数: 3000 成本为: 0.1825149792468696
迭代次数: 4000 成本为: 0.1805397830621775
迭代次数: 5000 成本为: 0.17620471758400458
迭代次数: 6000 成本为: 0.1683273039211542
迭代次数: 7000 成本为: 0.16583593654672968
迭代次数: 8000 成本为: 0.16312671914460503
迭代次数: 9000 成本为: 0.1594261225324512
迭代次数: 10000 成本为: 0.16329987525724213
迭代次数: 11000 成本为: 0.16098614487789206
迭代次数: 12000 成本为: 0.15764474148193003
迭代次数: 13000 成本为: 0.1521359964422285
迭代次数: 14000 成本为: 0.14843713518977328
迭代次数: 15000 成本为: 0.147964009225741
迭代次数: 16000 成本为: 0.1463487630634989
迭代次数: 17000 成本为: 0.14634375863076843
迭代次数: 18000 成本为: 0.14335088537380342
迭代次数: 19000 成本为: 0.13936488362302485
迭代次数: 20000 成本为: 0.13851642423238042
迭代次数: 21000 成本为: 0.141186783517487
迭代次数: 22000 成本为: 0.13629319468767007
迭代次数: 23000 成本为: 0.1315186013745369
迭代次数: 24000 成本为: 0.13032326002051173
迭代次数: 25000 成本为: 0.13285370211491684
迭代次数: 26000 成本为: 0.12814812861868885
迭代次数: 27000 成本为: 0.13263846518792582
迭代次数: 28000 成本为: 0.12678384582611732
迭代次数: 29000 成本为: 0.1240877609099884
训练集:
Accuracy: 0.9478672985781991
测试集:
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])
reg_utils.plot_decision_boundary(lambda x: reg_utils.predict_dec(parameters, x.T), train_X, train_Y)
执行结果:
在训练集上准确率94.7%,测试集上准确率为91.5%,再结合决策边界,明显数据过拟合了,接下来我们使用正则化来看看效果。
3.2.2 使用正则化
3.2.2.1 使用L2正则化
修改现在的成本函数,为其加上一个经过放缩的二范数
当前成本函数:
J = − 1 m ∑ i = 1 m ( y ( i ) log ( a [ L ] ( i ) ) + ( 1 − y ( i ) ) log ( 1 − a [ L ] ( i ) ) ) J=-\frac{1}{m} \sum_{i=1}^{m}\left(y^{(i)} \log \left(a^{[L](i)}\right)+\left(1-y^{(i)}\right) \log \left(1-a^{[L](i)}\right)\right) J=−m1i=1∑m(y(i)log(a[L](i))+(1−y(i))log(1−a[L](i)))
L2成本函数:
J 正则化 = − 1 m ∑ i = 1 m ( y ( i ) log ( a [ L ] ( i ) ) + ( 1 − y ( i ) ) log ( 1 − a [ L ] ( i ) ) ) ⏟ 交叉滴成本 + 1 m λ 2 ∑ l ∑ k ∑ j W k , j [ l ] 2 ⏟ L2正则化成本 J_{\text {正则化 }}=\underbrace{-\frac{1}{m} \sum_{i=1}^{m}\left(y^{(i)} \log \left(a^{[L](i)}\right)+\left(1-y^{(i)}\right) \log \left(1-a^{[L](i)}\right)\right)}_{\text {交叉滴成本 }}+\underbrace{\frac{1}{m} \frac{\lambda}{2} \sum_{l} \sum_{k} \sum_{j} W_{k, j}^{[l] 2}}_{\text {L2正则化成本 }} J正则化 =交叉滴成本 −m1i=1∑m(y(i)log(a[L](i))+(1−y(i))log(1−a[L](i)))+L2正则化成本 m12λl∑k∑j∑Wk,j[l]2
计算 ∑ k ∑ j W k , j [ l ] 2 \sum_{k} \sum_{j} W_{k, j}^{[l] 2} ∑k∑jWk,j[l]2的代码如下
np.sum(np.square(W[l]))
需要注意的是在正向传播中我们对 W [ 1 ] W^{[1]} W[1], W [ 2 ] W^{[2]} W[2]和 W [ 3 ] W^{[3]} W[3]这三个项进行操作,将这三个项相加并乘以 1 m λ 2 \frac{1}{m} \frac{\lambda}{2} m12λ ,在反向传播中,使用 d d W ( 1 2 λ m W 2 ) = λ m W \frac{d}{dW} ( \frac{1}{2}\frac{\lambda}{m} W^2) = \frac{\lambda}{m} W dWd(21mλW2)=mλW计算梯度。
# L2正则化计算成本
def compute_cost_with_L2(A3, Y, parameters, lambd):
"""
L2正则化计算成本
:param A3: 正向传播输出结果,维度(输出节点的数量, m)
:param Y: 标签向量,维度(1, m)
:param parameters: 参数字典
:param lambd: 二范数的超参数
:return: L2正则化后的成本
"""
m = Y.shape[1]
W1 = parameters["W1"]
W2 = parameters["W2"]
W3 = parameters["W3"]
cross_entropy_cost = reg_utils.compute_cost(A3, Y)
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
# L2反向传播计算梯度
def backward_propagation_with_L2(X, Y, cache, lambd):
"""
L2正则化的反向传播
:param X: 输入数据,维度(n_x, m)
:param Y: 标签向量,维度(1, m)
:param cache: 正向传播保存的线性结果,元组值
:param lambd: 二范数的超参数
:return: grads:L2正则化后的各层梯度值,字典值
"""
m = Y.shape[1]
(A1, A1, W1, b1, Z2, A2, W2, b2, Z3, A3, W3, b3) = cache
dZ3 = A3 - Y
dW3 = np.divide(np.dot(dZ3, A2.T), m) + ((lambd * W3) / m)
db3 = np.divide(np.sum(dZ3, axis=1, keepdims=True), m)
dA2 = np.dot(W3.T, dZ3)
dZ2 = np.multiply(dA2, np.int64(A2 > 0))
dW2 = np.divide(np.dot(dZ2, A1.T), m) + ((lambd * W2) / m)
db2 = np.divide(np.sum(dZ2, axis=1, keepdims=True), m)
dA1 = np.dot(W2.T, dZ2)
dZ1 = np.multiply(dA1, np.int64(A1 > 0))
dW1 = np.divide(np.dot(dZ1, X.T), m) + ((lambd * W1) / m)
db1 = np.divide(np.sum(dZ1, axis=1, keepdims=True), m)
grads = {
"dZ3": dZ3, "dW3": dW3, "db3": db3,
"dA2": dA2, "dZ2": dZ2, "dW2": dW2, "db2": db2,
"dA1": dA1, "dZ1": dZ1, "dW1": dW1, "db1": db1
}
return grads
在模型中看看效果
# L2正则化
parameters = model(train_X, train_Y, n_iters=30000, alpha=0.3, lambd=0.7, keep_prob=1, print_cost=True, isPlot=True)
# 性能
print("使用正则化,训练集:")
predictions_train = reg_utils.predict(train_X, train_Y, parameters)
print("使用正则化,测试集:")
predictions_test = reg_utils.predict(test_X, test_Y, parameters)
执行结果:
迭代次数: 0 成本为: 0.6974484493131264
迭代次数: 1000 成本为: 0.27304563479326766
迭代次数: 2000 成本为: 0.26985760798332925
迭代次数: 3000 成本为: 0.2695082806412659
迭代次数: 4000 成本为: 0.2692654449361622
迭代次数: 5000 成本为: 0.26904304743497043
迭代次数: 6000 成本为: 0.2688974560734563
迭代次数: 7000 成本为: 0.2687751717855726
迭代次数: 8000 成本为: 0.268673250206026
迭代次数: 9000 成本为: 0.2685595174585008
迭代次数: 10000 成本为: 0.2684918873282239
迭代次数: 11000 成本为: 0.2684141282822975
迭代次数: 12000 成本为: 0.268370115369031
迭代次数: 13000 成本为: 0.2683177401855035
迭代次数: 14000 成本为: 0.26826704893477976
迭代次数: 15000 成本为: 0.2682199033729047
迭代次数: 16000 成本为: 0.26816870547972715
迭代次数: 17000 成本为: 0.2681517100224052
迭代次数: 18000 成本为: 0.26813316999149156
迭代次数: 19000 成本为: 0.2680892379993576
迭代次数: 20000 成本为: 0.26809163371273015
迭代次数: 21000 成本为: 0.2680382189950235
迭代次数: 22000 成本为: 0.26806045797644007
迭代次数: 23000 成本为: 0.26782909050763876
迭代次数: 24000 成本为: 0.2678953595054706
迭代次数: 25000 成本为: 0.26794285663887024
迭代次数: 26000 成本为: 0.26785446611618824
迭代次数: 27000 成本为: 0.2679162550106563
迭代次数: 28000 成本为: 0.26789526792627955
迭代次数: 29000 成本为: 0.2678261099093218
使用正则化,训练集:
Accuracy: 0.9383886255924171
使用正则化,测试集:
Accuracy: 0.93
再看决策边界
λ(lambd)的值是可以使用验证集调整时的超参数,L2正则化会使决策边界更加平滑,如果λ太大,也可能会“过度平滑”,从而导致模型高偏差。L2正则化实际上在做什么?L2正则化依赖于较小权重的模型比具有较大权重的模型更简单这样的假设,因此,通过削减成本函数中权重的平方值,可以将所有权重值逐渐改变到到较小的值。权值数值高的话会有更平滑的模型,其中输入变化时输出变化更慢,但是你需要花费更多的时间。L2正则化对以下内容有影响:
- 成本计算 : 正则化的计算需要添加到成本函数中
- 反向传播功能 :在权重矩阵方面,梯度计算时也要依据正则化来做出相应的计算
- 权重变小(“权重衰减”) :权重被逐渐改变到较小的值
3.2.2.2 使用dropout正则化
Dropout的原理就是每次迭代过程中随机将其中的一些节点失效。当我们关闭一些节点时,我们实际上修改了我们的模型,背后的想法是,在每次迭代时,我们都会训练一个只使用一部分神经元的不同模型,随着迭代次数的增加,我们的模型的节点会对其他特定节点的激活变得不那么敏感,因为其他节点可能在任何时候会失效。
上图中使用了dropout,使第二层节点失效,在每一次迭代中,关闭(设置值为零)一层的每个神经元,概率为
1
−
k
e
e
p
_
p
r
o
b
1 - keep\_prob
1−keep_prob,我们在这里保持概率为
k
e
e
p
_
p
r
o
b
keep\_prob
keep_prob,丢弃的节点都不参与迭代时的前向和后向传播。
上图在第一层和第三层启用随机删除(dropout),
1
s
t
1^{st}
1st 平均40%(1-keep_prob)节点被删除,
3
r
d
3^{rd}
3rd 平均删除了20%的节点。
对于我们的三层神经网络,我们将关闭第一层和第二层的一些节点,我们需要做以下四步:
- 在视频中,吴恩达老师讲解了使用np.random.rand() 来初始化和 a [ 1 ] a^{[1]} a[1]具有相同维度的 d [ 1 ] d^{[1]} d[1] ,在这里,我们将使用向量化实现,我们先来实现一个和 A [ 1 ] A^{[1]} A[1]相同的随机矩阵 D [ 1 ] = [ d 1 d 1 … d 1 ] D^{[1]} = [d^{1} d^{1} … d^{1}] D[1]=[d1d1…d1];
- 如果
D
[
1
]
D^{[1]}
D[1]低于
keep_prob
的值我们就把它设置为0,如果高于keep_prob
的值我们就设置为1; - 把 A [ 1 ] A^{[1]} A[1]更新为 A [ 1 ] ∗ D [ 1 ] A ^{[1]}∗D^{[1]} A[1]∗D[1] (这样我们就已经关闭了该层的一些节点),我们可以使用 D [ 1 ] D^{[1]} D[1]作为掩码,做矩阵相乘的时候,关闭的那些节点(值为0)就会不参与计算,因为0乘以任何值都为0;
- 使用 A [ 1 ] A^{[1]} A[1]除以 keep_prob,这样做的话我们通过缩放就在计算成本的时候仍然具有相同的期望值,这叫做反向dropout;
注:这里解释一下为什么最后
A
[
1
]
A^{[1]}
A[1]要除于keep_prob
,dropout(实际是Inverted-dropout)的基本实现原理是在训练阶段每次迭代过程中,以keep_prob
的概率保留一个神经元(也就是以1-keep_prob
的概率关闭一个神经元),利用numpy的具体实现方式为:用np.random.rand(A1.shape[0], A1.shape[1]) < keep_prob
得到一个mask,再用神经元输出的激活值乘以这个mask,D1
是一个布尔值数组,当其元素值小于keep_prob
时是True
,大于keep_prob
时是False
。那么后面为什么还要除以keep_prob
呢?吴恩达老师在课里讲的是为了保证神经元输出激活值的期望值与不使用dropout时一致,我们结合概率论的知识来具体看一下:假设一个神经元的输出激活值为a,在不使用dropout的情况下,其输出期望值为a,如果使用了dropout,神经元就可能有保留和关闭两种状态,把它看作一个离散型随机变量,它就符合概率论中的0-1分布,其输出激活值的期望变为p*a+(1-p)*0=pa,此时若要保持期望和不使用dropout时一致,就要除以p。
# dropout正则化正向传播
def forward_propagation_with_dropout(X, parameters, keeep_prob):
"""
实现dropout前向传播
:param X: 输入数据,维度(n_x, m)
:param parameters: 初始化参数, 字典值
:param keep_prob: dropout的超参数
:return: A3:最后一层的激活值,维度(1, m)
cache:存储了正向传播的线性结果,用于反向传播,元组值
"""
np.random.seed(1)
m = X.shape[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 = reg_utils.relu(Z1)
# 步骤1:初始化矩阵D1 = np.random.rand(..., ...)
D1 = np.random.rand(A1.shape[0], A1.shape[1])
# 步骤2:将D1的值转换为False(0)或True(1)(使用keep_prob作为阈值)
D1 = D1 < keeep_prob
# 步骤3:舍弃A1的一些节点(将它的值变为0)
A1 = A1 * D1
# 步骤4:缩放未舍弃的节点(不为0)的值
A1 = A1 / keeep_prob
Z2 = np.dot(W2, A1) + b2
A2 = reg_utils.relu(Z2)
D2 = np.random.rand(A2.shape[0], A2.shape[1])
D2 = D2 < keeep_prob
A2 = A2 * D2
A2 = A2 / keeep_prob
Z3 = np.dot(W3, A2) + b3
A3 = reg_utils.sigmoid(Z3)
cache = (Z1, D1, A1, W1, b1, Z2, D2, A2, W2, b2, Z3, A3, W3, b3)
return A3, cache
我们改变了正向传播的算法,也需要改变反向传播的算法,使用存储在缓存中的掩码 D [ 1 ] D^{[1]} D[1]和 D [ 2 ] D^{[2]} D[2]将舍弃的节点位置信息添加到第一个和第二个隐藏层。
# dropout正则化反向传播
def backward_propagation_with_dropout(X, Y, cache, keep_prob):
"""
实现dropout的反向传播
:param X: 输入数据,维度(n_x, m)
:param Y: 标签数据,维度(1, m)
:param cache: 正向传播保存的线性结果
:param keep_prob: dropout的超参数
:return: 各层参数的梯度值,字典值
"""
m = X.shape[1]
(Z1, D1, A1, W1, b1, Z2, D2, A2, W2, b2, Z3, A3, W3, b3) = cache
dZ3 = A3 - Y
dW3 = np.divide(np.dot(dZ3, A2.T), m)
db3 = np.divide(np.sum(dZ3, axis=1, keepdims=True), m)
dA2 = np.dot(W3.T, dZ3)
# 步骤1:使用正向传播期间相同的节点,舍弃那些关闭的节点(因为任何数乘以0或者False都为0或者False)
dA2 = dA2 * D2
# 步骤2:缩放未舍弃的节点(不为0)的值
dA2 = dA2 / keep_prob
dZ2 = np.multiply(dA2, np.int64(A2 > 0))
dW2 = np.divide(np.dot(dZ2, A1.T), m)
db2 = np.divide(np.sum(dZ2, axis=1, keepdims=True), m)
dA1 = np.dot(W2.T, dZ2)
dA1 = dA1 * D1
dA1 = dA1 / keep_prob
dZ1 = np.multiply(dA1, np.int64(A1 > 0))
dW1 = np.divide(np.dot(dZ1, X.T), m)
db1 = np.divide(np.sum(dZ1, axis=1, keepdims=True), m)
grads = {
"dZ3": dZ3, "dW3": dW3, "db3": db3,
"dA2": dA2, "dZ2": dZ2, "dW2": dW2, "db2": db2,
"dA1": dA1, "dZ1": dZ1, "dW1": dW1, "db1": db1
}
return grads
我们dropout前向和后向传播的函数都写好了,现在用dropout正则化运行模型(keep_prob = 0.86)测试一下,这意味着在每次迭代中,程序都可以14%的概率关闭第1层和第2层的每个神经元。
# 使用dropout正则化
parameters = model(train_X, train_Y, n_iters=30000, alpha=0.3, lambd=0, keep_prob=0.86, print_cost=True, isPlot=True)
# 性能
print("使用随机删除节点,训练集:")
predictions_train = reg_utils.predict(train_X, train_Y, parameters)
print("使用随机删除节点,测试集:")
reg_utils.predictions_test = reg_utils.predict(test_X, test_Y, parameters)
执行结果:
迭代次数: 0 成本为: 0.6543912405149825
迭代次数: 1000 成本为: 0.1759784892976791
迭代次数: 2000 成本为: 0.10396707442900774
迭代次数: 3000 成本为: 0.07661297787357767
E:\py\DL\DL_work\004_wz_course2_week1\reg_utils.py:121: RuntimeWarning: divide by zero encountered in log
logprobs = np.multiply(-np.log(a3), Y) + np.multiply(-np.log(1 - a3), 1 - Y)
E:\py\DL\DL_work\004_wz_course2_week1\reg_utils.py:121: RuntimeWarning: invalid value encountered in multiply
logprobs = np.multiply(-np.log(a3), Y) + np.multiply(-np.log(1 - a3), 1 - Y)
迭代次数: 4000 成本为: 0.0671989126981358
迭代次数: 5000 成本为: 0.06466905008519826
迭代次数: 6000 成本为: 0.06240987259698133
迭代次数: 7000 成本为: 0.06187873531315629
迭代次数: 8000 成本为: 0.0614332181524981
迭代次数: 9000 成本为: 0.061192143757634855
迭代次数: 10000 成本为: 0.0610169865749056
迭代次数: 11000 成本为: 0.06093245082616846
迭代次数: 12000 成本为: 0.06082640196596112
迭代次数: 13000 成本为: 0.0608626412998495
迭代次数: 14000 成本为: 0.06083521537021214
迭代次数: 15000 成本为: 0.060664572161287754
迭代次数: 16000 成本为: 0.06064947237748143
迭代次数: 17000 成本为: 0.06062585662115728
迭代次数: 18000 成本为: 0.060658745055966724
迭代次数: 19000 成本为: 0.060596513302999366
迭代次数: 20000 成本为: 0.060582435798513114
迭代次数: 21000 成本为: 0.060559199380913156
迭代次数: 22000 成本为: 0.06057898390156484
迭代次数: 23000 成本为: 0.06056461466372936
迭代次数: 24000 成本为: 0.060503590269931996
迭代次数: 25000 成本为: 0.06050179002362491
迭代次数: 26000 成本为: 0.06052211741257311
迭代次数: 27000 成本为: 0.06047314739225407
迭代次数: 28000 成本为: 0.06047206692105942
迭代次数: 29000 成本为: 0.060485966719343975
使用随机删除节点,训练集:
Accuracy: 0.9289099526066351
使用随机删除节点,测试集:
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])
reg_utils.plot_decision_boundary(lambda x: reg_utils.predict_dec(parameters, x.T), train_X, train_Y)
执行结果:
可以看到,dropout正则化把训练集的准确度降低了,但是测试集的准确度提高了,所以我们还是缓解了过拟合情况。
3.3 梯度检验
假设你现在是一个全球移动支付团队中的一员,现在需要建立一个深度学习模型去判断用户账户在进行付款的时候是否是被黑客入侵的。但是,在我们执行反向传播的计算过程中,反向传播函数的计算过程是比较复杂的,为了验证我们得到的反向传播函数是否正确,现在你需要编写一些代码来验证反向传播函数的正确性。
反向传播计算梯度 ∂ J ∂ θ \frac{\partial J}{\partial \theta} ∂θ∂J, θ \theta θ表示模型中的参数,使用正向传播和损失函数计算 J J J,因为正向传播相对容易实现,所以你确信自己得到了正确的结果,所以你几乎100%确定你正确计算了 J J J的成本。 因此,您可以使用您的代码来计算 J J J来验证计算的代码 ∂ J ∂ θ \frac{\partial J}{\partial \theta} ∂θ∂J。
让我们再看一下导数(或梯度)的定义:
∂ J ∂ θ = lim ε → 0 J ( θ + ε ) − J ( θ − ε ) 2 ε \frac{\partial J}{\partial \theta}=\lim _{\varepsilon \rightarrow 0} \frac{J(\theta+\varepsilon)-J(\theta-\varepsilon)}{2 \varepsilon} ∂θ∂J=ε→0lim2εJ(θ+ε)−J(θ−ε)
- ∂ J ∂ θ \frac{\partial J}{\partial \theta} ∂θ∂J是你想确保你的计算正确的值。
- 你可以计算 J ( θ + ε ) J(\theta + \varepsilon) J(θ+ε)和 J ( θ − ε ) J(\theta - \varepsilon) J(θ−ε)(在 θ \theta θ是一个实数的情况下),因为你确信你对 J J J的实现是正确的。
我们先来看一下一维线性模型的梯度检查计算过程:
3.3.1 一维线性的梯度检查
现在我们实现一维线性的梯度检查
正向传播
def forward_propagation(X, theta):
"""
实现一维正向传播,计算J = theta * X
:param X: 实数
:param theta: 实数
:return: J:实数
"""
J = np.dot(theta, X)
return J
测试一下:
# 测试forward_propagation
print("-----------------测试forward_propagation-----------------")
x, theta = 2, 4
J = forward_propagation(x, theta)
print("J = " + str(J))
测试结果:
J = 8
反向传播
def backward_propagation(X, theta):
"""
实现一维反向传播,计算J相对于theta的导数
:param X: 实数
:param theta: 实数
:return: dtheta:实数
"""
dtheta = X
return dtheta
测试一下:
# 测试backward_propagation
print("-----------------测试backward_propagation-----------------")
x, theta = 2, 4
dtheta = backward_propagation(x, theta)
print("dtheta = " + str(dtheta))
测试结果:
dtheta = 2
梯度检查,步骤如下:
- θ + = θ + ε \theta^{+} = \theta + \varepsilon θ+=θ+ε
- θ − = θ − ε \theta^{-} = \theta - \varepsilon θ−=θ−ε
- J + = J ( θ + ) J^{+} = J(\theta^{+}) J+=J(θ+)
- J − = J ( θ − ) J^{-} = J(\theta^{-}) J−=J(θ−)
- g r a d a p p r o x = J + − J − 2 ε gradapprox = \frac{J^{+} - J^{-}}{2 \varepsilon} gradapprox=2εJ+−J−
接下来,计算梯度的反向传播值,最后计算误差:
d i f f e r e n c e = ∣ ∣ g r a d − g r a d a p p r o x ∣ ∣ 2 ∣ ∣ g r a d ∣ ∣ 2 + ∣ ∣ g r a d a p p r o x ∣ ∣ 2 difference = \frac {\mid\mid grad - gradapprox \mid\mid_2}{\mid\mid grad \mid\mid_2 + \mid\mid gradapprox \mid\mid_2} difference=∣∣grad∣∣2+∣∣gradapprox∣∣2∣∣grad−gradapprox∣∣2
当difference小于 1 0 − 7 10^{-7} 10−7时,我们通常认为我们计算的结果是正确的。
def gradient_check(X, theta, epsilon=1e-7):
"""
一维线性梯度检查实现
:param X: 实数
:param theta: 实数
:param epsilon: 偏移量
:return: difference:近似梯度与反向传播梯度之间的差异,实数
"""
thetaplus = theta + epsilon
thetaminus = theta - epsilon
J_plus = forward_propagation(X, thetaplus)
J_minus = forward_propagation(X, thetaminus)
gradapprox = (J_plus - J_minus) / (2 * epsilon)
grad = backward_propagation(X, theta)
numerator = np.linalg.norm(grad - gradapprox)
denominator = np.linalg.norm(grad) + np.linalg.norm(gradapprox)
difference = numerator / denominator
if difference < 1e-7:
print("梯度检查,梯度正常!")
else:
print("梯度检查,差异超过阈值!")
return difference
测试一下:
# 测试gradient_check
print("-----------------测试gradient_check-----------------")
X, theta = 2, 4
difference = gradient_check(X, theta)
print("difference = " + str(difference))
测试结果:
梯度检查,梯度正常!
difference = 2.919335883291695e-10
高维参数是怎样计算的呢?
接下来我们实现高维参数的梯度检查
3.3.2 高维参数的梯度检查
正向传播
def forward_propagation_n(X, Y, parameters):
"""
实现n维正向传播,计算陈本
:param X: 输入数据,维度(n_x, m)
:param Y: 标签向量,维度(1, m)
:param parameters: 初始化参数,字典值
:return: cost:成本,实数
cache:各层的线性结果值,用于反向传播,字典值
"""
m = X.shape[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 = reg_utils.relu(Z1)
Z2 = np.dot(W2, A1) + b2
A2 = reg_utils.relu(Z2)
Z3 = np.dot(W3, A2) + b3
A3 = reg_utils.sigmoid(Z3)
logprobs = np.multiply(Y, np.log(A3)) + np.multiply((1 - Y), np.log(1 - A3))
cost = -np.divide(np.sum(logprobs), m)
cache = (Z1, A1, W1, b1, Z2, A2, W2, b2, Z3, A3, W3, b3)
return cost, cache
反向传播
def backward_propagation_n(X, Y, cache, epsilon=1e-7):
"""
实现n维反向传播,计算梯度
:param X: 输入数据,维度(n_x, m)
:param Y: 标签数据,维度(1, m)
:param cache: 各层线性结果
:return: grads:各层参数梯度,字典值
"""
m = X.shape[1]
(Z1, A1, W1, b1, Z2, A2, W2, b2, Z3, A3, W3, b3) = cache
dZ3 = A3 - Y
dW3 = np.divide(np.dot(dZ3, A2.T), m)
db3 = np.divide(np.sum(dZ3, axis=1, keepdims=True), m)
dA2 = np.dot(W3.T, dZ3)
dZ2 = np.multiply(dA2, np.int64(A2 > 0))
dW2 = np.divide(np.dot(dZ2, A1.T), m)
db2 = np.divide(np.sum(dZ2, axis=1, keepdims=True), m)
dA1 = np.dot(W2.T, dZ2)
dZ1 = np.multiply(dA1, np.int64(A1 > 0))
dW1 = np.divide(np.dot(dZ1, X.T), m)
db1 = np.divide(np.sum(dZ1, axis=1, keepdims=True), m)
grads = {
"dZ3": dZ3, "dW3": dW3, "db3": db3,
"dA2": dA2, "dZ2": dZ2, "dW2": dW2, "db2": db2,
"dA1": dA1, "dZ1": dZ1, "dW1": dW1, "db1": db1
}
return grads
梯度检查,比较“gradapprox”与反向传播计算的梯度。 该公式仍然是:
d i f f e r e n c e = ∣ ∣ g r a d − g r a d a p p r o x ∣ ∣ 2 ∣ ∣ g r a d ∣ ∣ 2 + ∣ ∣ g r a d a p p r o x ∣ ∣ 2 difference = \frac {\mid\mid grad - gradapprox \mid\mid_2}{\mid\mid grad \mid\mid_2 + \mid\mid gradapprox \mid\mid_2} difference=∣∣grad∣∣2+∣∣gradapprox∣∣2∣∣grad−gradapprox∣∣2
然而,这里
θ
\theta
θ不再是标量,而是一个长向量,它是由名为parameters
的字典,经过gc_utils中的一个函数dictionary_to_vector()
,它将parameters
字典转换为一个称为values
的向量,通过将所有参数
(
W
1
,
b
1
,
W
2
,
b
2
,
W
3
,
b
3
)
(W1,b1,W2,b2,W3,b3)
(W1,b1,W2,b2,W3,b3)整形为向量并将它们连接起来而获得;反函数是vector_to_dictionary()
,它返回parameters
字典。
下面是伪代码,可以帮助你实现梯度检查:
for i in num_parameters:
- 计算
J_plus[i]
:- 把
θ
+
\theta^{+}
θ+设置为
np.copy(parameters_values)
- 把 θ i + \theta^{+}_i θi+设置为 θ i + + ε \theta^{+}_i + \varepsilon θi++ε
- 使用
forward_propagation_n(x, y, vector_to_dictionary(
θ i + \theta^{+}_i θi+))
来计算 J + J^{+} J+
- 把
θ
+
\theta^{+}
θ+设置为
- 计算
J_minus[i]
: 使用相同的方法计算 θ − \theta^{-} θ− - 计算 g r a d a p p r o x [ i ] = J i + − J i − 2 ε gradapprox[i] = \frac{J^{+}_i - J^{-}_i}{2 \varepsilon} gradapprox[i]=2εJi+−Ji−
- 计算梯度
- 计算误差:
d i f f e r e n c e = ∥ g r a d − g r a d a p p r o x ∥ 2 ∥ g r a d ∥ 2 + ∥ g r a d a p p r o x ∥ 2 difference = \frac {\| grad - gradapprox \|_2}{\| grad \|_2 + \| gradapprox \|_2 } difference=∥grad∥2+∥gradapprox∥2∥grad−gradapprox∥2
def gradient_check_n(X, Y, parameters, grads, epsilon=1e-7):
"""
实现n维梯度检验
:param X: 输入数据,维度(n_x, m)
:param Y: 标签数据,维度(1, m)
:param grads: 各层参数梯度值
:param parameters: 初始化参数
:param epsilon: 偏移量
:return: difference:近似梯度与反向传播梯度之间的差异量
"""
parameters_value, keys = gc_utils.dictionary_to_vector(parameters)
grad = gc_utils.gradients_to_vector(grads)
num_parameters = parameters_value.shape[0]
J_plus = np.zeros((num_parameters, 1))
J_minus = np.zeros((num_parameters, 1))
gradapprox = np.zeros((num_parameters, 1))
for i in range(num_parameters):
thetaplus = np.copy(parameters_value)
thetaplus[i][0] = thetaplus[i][0] + epsilon
J_plus[i][0], cache = forward_propagation_n(X, Y, gc_utils.vector_to_dictionary(thetaplus))
thetaminus = np.copy(parameters_value)
thetaminus[i][0] = thetaminus[i][0] - epsilon
J_minus[i][0], cache = forward_propagation_n(X, Y, gc_utils.vector_to_dictionary(thetaminus))
gradapprox[i][0] = (J_plus[i] - J_minus[i]) / (2 * epsilon)
numerator = np.linalg.norm(grad - gradapprox)
denominator = np.linalg.norm(grad) + np.linalg.norm(gradapprox)
difference = numerator / denominator
if difference < 1e-7:
print("梯度检查,梯度正确!")
else:
print("梯度检查,差异超过阈值!")
return difference
测试一下:
# 测试gradient_check_n
X, Y, parameters = testCase.gradient_check_n_test_case()
cost, cache = forward_propagation_n(X, Y, parameters)
grads = backward_propagation_n(X, Y, cache)
difference = gradient_check_n(X, Y, parameters, grads, epsilon=1e-7)
print("difference:" + str(difference))
测试结果:
梯度检查,梯度正确!
difference:1.3407408838767896e-08
3.4 结果分析
3.4.1 初始化参数结果分析
参数初始化方式 | 成本曲线 | 结果 | 总结 |
---|---|---|---|
零初始化 | ![]() | 训练集: 0.5;测试集: 0.5 | 所有参数都为0时,模型无法打破对称性,模型无法学习到有效信息,成本保持不变 |
随机初始化 | ![]() | 训练集: 0.83;测试集: 0.86 | 权重初始较大或较小都会造成梯度爆炸、消失的问题,需要增加迭代次数来解决,意味着需要寻找合适的权重值 |
抑梯度异常初始化 | ![]() | 训练集: 0.99;测试集: 0.96 | 这种初始化可以得到合适的权重,he初始化搭配ReLU激活函数常常可以得到不错的效果 |
3.4.2 正则化结果分析
正则化方式 | 决策边界 | 结果 | 总结 |
---|---|---|---|
不使用正则化 | ![]() | 训练集: 0.947;测试集: 0.915 | 明显数据过拟合 |
使用L正则化 | ![]() | 训练集: 0.938;测试集: 0.930 | 数据过拟合现象减弱,其二范数作用于成本函数,影响成本计算和反向传播梯度的计算,会使得权重衰弱,这便是减少过拟合的关键 |
使用dropout正则化 | ![]() | 训练集: 0.928;测试集: 0.95 | 训练集准确率下降了,但测试集准确率上升了,比较有效的缓解了过拟合,其作用于不同层的神经节点,使得每个神经节点都不是那么重要,有效缓解了权重问题 |
3.4.3 梯度检验
梯度检验可以非常有效的帮助你在测试的时候排除bug
3.5 源码
四、总结
参数初始化如果很合适,可以有效的加快我们模型的学习速度,为我们开一个好的开始;正则化可以有效的避免找不到更多可用数据集而导致的过拟合问题;梯度检验可以帮助我们确认模型学习的正确性和帮助我们排除bug;这些方法对于神经网络的实践应用是非常有效的,我们应该掌握这些方法。最后,感谢文驰和特征两位兄弟,不然这篇文章早就写完了。
五、参考文章
神经网络正则化方法dropout细节探索
【中文】【吴恩达课后编程作业】Course 2 - 改善深层神经网络 - 第一周作业(1&2&3)