声明
开始之前
本文中,我们要干三件事:
- 初始化参数:
1.1、使用0来初始化参数。
1.2、使用随即数来初始化参数。
1.3、使用抑梯度异常初始化参数。 - 正则化模型:
2.1、使用二范数对二分类模型正则化,尝试避免过拟合。
2.2、使用随即删除节点的方法精简模型,尝试避免过拟合。 - 梯度校验:
3.1、对模型使用梯度校验,检测它是否在梯度下降的过程中出现误差过大的情况。
首先,导入相关库
import numpy as np
import matplotlib.pyplot as plt
import sklearn
import sklearn.datasets
#from init_utils import sigmoid,relu,compute_loss,forward_propagation,backward_propagation
#from init_utils import update_parameters,predict,load_dataset,plot_decision_boundary,predict_dec
import init_utils #第一部分,初始化
import reg_utils #第二部分,正则化
import gc_utils #第三部分,梯度校验
%matplotlib inline
plt.rcParams['figure.figsize']=(7.0,4.0)
plt.rcParams['image.interpolation']='nearest'
plt.rcParams['image.cmap']='gray'
初始化参数
读取并绘制数据
train_X,train_Y,test_X,test_Y = init_utils.load_dataset()
结果:
我们将要建立一个分类器把蓝点和红点分开,在之前已经实现过一个3层的神经网络,将对它进行初始化:
我们将尝试下面三种初始化方法:
- 初始化为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]))
- 抑梯度异常初始化:权重初始化为大的随机值。参数名为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,learning_rate=0.01, num_iterations = 10000, print_cost = True, initialization = "he",is_plot=True):
"""
实现一个三层神经网络:linear->relu->linear->relu->linear->sigmoid
参数:
X - 输入数据,维度(2,样本数)
Y - 标签,[0|1],维度为(1,样本数)
learning_rate - 学习速率
num_iterations - 迭代次数
print_cost - 是否打印成本值
initialization - 字符串类型,初始化的类型['zeros'|'random'|'he']
is_plot - 是否绘制梯度下降的曲线图
返回:
parameters - 学习后的参数
"""
grads = {}
costs = []
m = X.shape[1]
layers_dims = [X.shape[0],10,5,1]
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(0,num_iterations):
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,learning_rate)
if i%1000 == 0:
costs.append(cost)
if print_cost:
print('i = ',i,',cost = ',cost)
if is_plot:
plt.plot(costs)
plt.ylabel('cost')
plt.xlabel('iteration(per hundreds)')
plt.title('learning rate = '+str(learning_rate))
plt.show()
return parameters
初始化为零
def initialize_parameters_zeros(layers_dims):
"""
将模型的参数全部设置为0
参数:
layers_dims - 列表,模型的层数和对应每一层的节点的数量
返回:
parameters - 包含了所有W和b的字典
W1 - 权重矩阵,维度为(layers_dims[1],layers_dims[0])
b1 - 偏置向量,维度为(layers_dims[1],1)
...
WL - 权重矩阵,维度为(layers_dims[L],layers_dims[L-1])
bL - 偏置向量,维度为(layers_dims[L],1)
"""
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))
return parameters
测试:
parameters = initialize_parameters_zeros([3,2,1])
print(parameters['W1'],parameters['b1'],parameters['W2'],parameters['b2'])
结果:
[[0. 0. 0.]
[0. 0. 0.]] [[0.]
[0.]] [[0. 0.]] [[0.]]
看到W和b全部被初始化为0了,那么使用这些参数来训练模型,会是什么样的结果呢?
parameters = model(train_X,train_Y,initialization = "zeros",is_polt=True)
结果
i = 0 ,cost = 0.6931471805599453
i = 1000 ,cost = 0.6931471805599453
i = 2000 ,cost = 0.6931471805599453
i = 3000 ,cost = 0.6931471805599453
i = 4000 ,cost = 0.6931471805599453
i = 5000 ,cost = 0.6931471805599453
i = 6000 ,cost = 0.6931471805599453
i = 7000 ,cost = 0.6931471805599453
i = 8000 ,cost = 0.6931471805599453
i = 9000 ,cost = 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
性能确实较差,而且成本并没有真正降低,算法的性能也比随机猜测要好。为什么?让我们看看预测和决策边界的细节:
print('prediction_train = '+str(predictions_train))
print('prediction_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)
"""
结果
prediction_train = [[1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1]]
prediction_test = [[1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1]]
分类失败,该模型预测每个都为1。通常来说,零初始化会导致神经网络无法打破对称性,最终导致的结果就是无论网络有多少层,最终只能得到和Logistic函数相同的效果。
随即初始化
为了打破对称性,我们可以随机地把参数赋值。在随机初始化以后,每个神经元可以开始学习其输入的不同功能,还会设置比较大的参数值,看看会发生什么:
def initialize_parameters_random(layers_dims):
"""
将模型的参数设置为随即值
参数:
layers_dims - 列表,模型的层数和对应每一层的节点的数量
返回:
parameters - 包含了所有W和b的字典
W1 - 权重矩阵,维度为(layers_dims[1],layers_dims[0])
b1 - 偏置向量,维度为(layers_dims[1],1)
...
WL - 权重矩阵,维度为(layers_dims[L],layers_dims[L-1])
bL - 偏置向量,维度为(layers_dims[L],1)
"""
np.random.seed(3)
parameters = {}
L = len(layers_dims)
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))
return parameters
测试:
parameters = initialize_parameters_random([3,2,1])
print(parameters['W1'],parameters['b1'],parameters['W2'],parameters['b2'])
结果:
[[ 17.88628473 4.36509851 0.96497468]
[-18.63492703 -2.77388203 -3.54758979]] [[0.]
[0.]] [[-0.82741481 -6.27000677]] [[0.]]
看到W和b全部被随机,那么使用这些参数来训练模型,会是什么样的结果呢?
parameters = model(train_X,train_Y,initialization = "random",is_polt=True)
print("训练集:")
predictions_train = init_utils.predict(train_X,train_Y,parameters)
print('测试集')
predictions_test = init_utils.predict(test_X,test_Y,parameters)
print('prediction_train = '+str(predictions_train))
print('prediction_test = '+str(predictions_test))
结果
i = 0 ,cost = inf
i = 1000 ,cost = 0.6240194973662918
i = 2000 ,cost = 0.5978755800081139
i = 3000 ,cost = 0.5636137012849799
i = 4000 ,cost = 0.550120052655226
i = 5000 ,cost = 0.5444068732918267
i = 6000 ,cost = 0.5374092464457688
i = 7000 ,cost = 0.4736615125287398
i = 8000 ,cost = 0.3977530246475065
i = 9000 ,cost = 0.39346821959121897
训练集:
Accuracy: 0.8266666666666667
测试集
Accuracy: 0.85
prediction_train = [[1 0 1 1 0 0 1 1 1 1 1 0 1 0 0 1 0 1 1 0 1 0 1 0 1 1 1 1 1 1 0 1 1 0 0 1
1 1 1 1 1 1 1 0 1 1 1 1 1 1 0 1 1 1 1 0 0 1 1 1 1 0 1 1 0 1 0 1 1 1 1 0
0 0 0 0 1 0 1 0 1 1 1 0 0 1 1 1 1 1 1 0 0 1 1 1 0 1 1 0 1 0 1 1 0 1 1 0
1 0 1 1 0 0 1 0 0 1 1 0 1 1 1 0 1 0 0 1 0 1 1 1 1 1 1 1 0 1 1 0 0 1 1 0
0 0 1 0 1 0 1 0 1 1 1 0 0 1 1 1 1 0 1 1 0 1 0 1 1 0 1 1 1 1 1 1 0 1 1 1
1 0 1 0 1 0 1 1 1 1 0 1 1 0 1 1 0 1 1 0 1 0 1 1 1 0 1 1 1 0 1 0 1 0 0 1
0 1 1 0 1 1 0 1 1 0 1 1 1 0 1 1 1 1 0 1 0 0 1 1 0 1 1 1 0 0 0 1 1 0 1 1
1 1 0 1 1 0 1 1 1 0 0 1 0 0 0 1 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 0 0 1 1 1
1 1 1 1 0 0 0 1 1 1 1 0]]
prediction_test = [[1 1 1 1 0 1 0 1 1 0 1 1 1 0 0 0 0 1 0 1 0 0 1 0 1 1 1 1 1 1 1 0 0 0 0 1
0 1 1 0 0 1 1 1 1 1 0 1 1 1 0 1 0 1 1 0 1 0 1 0 1 1 1 1 1 1 1 1 1 0 1 0
1 1 1 1 1 0 1 0 0 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 0]]
结论:误差开始很高,这是因为由于具有较大的随机权重,最后一个激活sigmoid输出的结果非常接近0或1,而当它出现错误时,会导致非常高的损失,初始化参数如果没有很好的话会导致梯度消失、爆炸,会减慢优化算法。如果对这个网络进行更长时间的训练,将看到更好的结果,但是使用过大的随机数初始化会减慢优化的速度。
总之,将权重初始化为非常大的的值其实效果并不好,下面试试小一点的参数值。
抑梯度异常初始化
def initialize_parameters_he(layers_dims):
"""
参数:
layers_dims - 列表,模型的层数和对应每一层的节点的数量
返回:
parameters - 包含了所有W和b的字典
W1 - 权重矩阵,维度为(layers_dims[1],layers_dims[0])
b1 - 偏置向量,维度为(layers_dims[1],1)
...
WL - 权重矩阵,维度为(layers_dims[L],layers_dims[L-1])
bL - 偏置向量,维度为(layers_dims[L],1)
"""
np.random.seed(3)
parameters = {}
L = len(layers_dims)
for l in range(1,L):
parameters['W'+str(l)] = np.random.randn(layers_dims[l],layers_dims[l-1])*np.sqrt(2/layers_dims[l-1])
parameters['b'+str(l)] = np.zeros((layers_dims[l],1))
return parameters
测试:
parameters = initialize_parameters_he([3,2,1])
print(parameters['W1'],parameters['b1'],parameters['W2'],parameters['b2'])
结果:
[[ 1.46040903 0.3564088 0.07878985]
[-1.52153542 -0.22648652 -0.28965949]] [[0.]
[0.]] [[-0.08274148 -0.62700068]] [[0.]]
看到W和b全部被初始化为很小的值,那么使用这些参数来训练模型,会是什么样的结果呢?
parameters = model(train_X,train_Y,initialization = "he",is_polt=True)
print("训练集:")
predictions_train = init_utils.predict(train_X,train_Y,parameters)
print('测试集')
predictions_test = init_utils.predict(test_X,test_Y,parameters)
print('prediction_train = '+str(predictions_train))
print('prediction_test = '+str(predictions_test))
结果
i = 0 ,cost = 0.8830537463419761
i = 1000 ,cost = 0.6879825919728063
i = 2000 ,cost = 0.6751286264523371
i = 3000 ,cost = 0.6526117768893807
i = 4000 ,cost = 0.6082958970572938
i = 5000 ,cost = 0.5304944491717495
i = 6000 ,cost = 0.4138645817071794
i = 7000 ,cost = 0.3117803464844441
i = 8000 ,cost = 0.23696215330322562
i = 9000 ,cost = 0.18597287209206834
训练集:
Accuracy: 0.9833333333333333
测试集
Accuracy: 0.96
prediction_train = [[1 0 1 1 0 0 1 0 1 1 1 0 1 0 0 0 0 1 1 0 1 0 1 0 0 0 1 0 1 1 0 1 1 0 0 0
0 1 0 1 1 1 1 0 0 1 1 1 0 1 0 1 1 1 1 0 0 1 1 1 1 1 0 1 0 1 0 1 0 1 1 0
0 0 0 0 1 0 0 0 1 0 1 0 0 1 1 0 1 1 1 0 0 0 1 1 0 1 1 0 1 0 0 1 0 1 1 0
0 0 1 1 0 0 1 0 0 1 0 0 1 1 1 0 0 0 0 1 0 1 1 0 1 1 1 1 0 1 1 0 0 0 0 0
0 0 1 0 1 0 1 0 1 1 1 0 0 1 1 0 1 0 1 1 0 1 0 1 1 0 1 0 1 1 0 1 0 0 1 0
1 0 0 0 1 0 1 1 1 0 0 1 1 0 0 1 0 1 0 0 1 0 1 1 0 1 1 1 1 0 1 0 1 0 0 1
0 1 0 0 0 1 1 1 1 0 1 0 0 1 1 0 0 1 0 1 0 0 0 0 0 1 1 0 0 0 0 1 1 0 1 0
1 1 0 1 1 0 1 0 0 0 0 0 0 0 0 1 0 0 0 1 1 1 1 0 0 0 0 0 1 1 0 0 1 1 1 1
1 1 1 0 0 0 0 1 1 0 1 0]]
prediction_test = [[1 0 1 0 0 0 0 0 0 0 1 1 1 0 0 0 1 0 1 1 0 0 1 0 0 0 0 1 1 1 1 0 0 0 0 1
0 1 1 0 0 1 1 1 1 0 0 0 1 1 0 1 0 1 1 0 1 0 1 0 1 1 1 1 1 1 1 1 1 1 1 0
1 1 1 1 0 0 1 1 0 1 0 0 0 0 1 0 1 1 0 0 0 1 1 0 0 0 0 0]]
结论:可以看到误差越来越小,初始化的模型将蓝色和红色的点在少量的迭代中很好地分离出来,总结:
- 不同的初始化方法可能导致性能最终不同。
- 随即初始化有助于打破对称性,使得不同隐藏层的单元可以学习到不同的参数。
- 初始化时,初始值不宜过大。
- he初始化搭配relu激活函数常常得到不错的效果。
在深度学习中,如果数据集没有足够大,可能会导致过拟合,过拟合导致的结果就是在训练集上有着很高的精确度,但在遇到新样本时,精确度下降会很严重,为了避免过拟合的问题,接下来讲解的就是正则化。
正则化模型
问题:假设你现在是一个AI专家,需要设计一个模型,可以用于推荐在足球场中守门员将球发至哪个位置可以让本队的球员抢到球的可能性更大。简单来说,就是一个二分类,,一本是己方抢到球,一半是对方抢到球,来看看这个图。
读取并绘制数据集
train_X,train_Y,test_X,test_Y = reg_utils.load_2D_dataset()
每一个点代表球落下的可能的位置,蓝方代表己方的球员会抢到球,红色代表对手的球员会抢到球,我们要做的就是使用模型来画出一条线,来找到适合我方球员能抢到球的位置。我们将做以下三件事,来对比出不同模型的优劣:
- 不是用正则化
- 使用正则化
2.1、使用L2正则化
2.2、使用dropout随即节点删除
首先,来看一下我们的模型:
- 正则化模式:将lambd输入设置为非零值。
- 随即删除节点:将keep_prob设置为小于1的值。
def model(X,Y,learning_rate=0.3,num_iteration=10000,print_cost=True,is_plot=True,lambd=0,keep_prob=1):
"""
实现一个三层的神经网络:linear->relu->linear->relu->linear->sigmoid
参数:
X - 输入数据,维度为(2,样本数)
Y - 标签,[0|1],维度为(1,样本数)
learning_rate - 学习速率
num_iterations - 迭代次数
print_cost - 是否打印成本值
is_plot - 是否绘制梯度下降曲线图
lambd - 正则化的超参数,实数
keep_prob - 随机删除节点的概率
返回:
parameters - 学习后的参数
"""
grads = {}
costs = []
m = X.shape[1]
layers_dims = [X.shape[0],20,3,1]
parameters = reg_utils.initialize_parameters(layers_dims)
for i in range(0,num_iteration):
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("keep_prob参数错误!程序退出。")
exit
if lambd == 0:
cost = reg_utils.compute_cost(A3,Y)
else:
cost = compute_cost_with_regularization(A3,Y,parameters,lambd)
if lambd == 0 and keep_prob == 1:
grads = reg_utils.backward_propagation(X,Y,cache)
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)
parameters = reg_utils.update_parameters(parameters,grads,learning_rate)
if i%1000 == 0:
costs.append(cost)
if print_cost:
print('i=',i,',cost=',cost)
if is_plot:
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,is_plot=True,lambd=0)
print("训练集:")
predictions_train = reg_utils.predict(train_X,train_Y,parameters)
print("测试集:")
predictions_test = reg_utils.predict(test_X,test_Y,parameters)
结果
i= 0 ,cost= 0.7105962752873438
i= 1000 ,cost= 0.035015680368265356
i= 2000 ,cost= 0.018029603240057788
i= 3000 ,cost= 0.017047489958612817
i= 4000 ,cost= 0.014820514844691785
i= 5000 ,cost= 0.013944845300970002
i= 6000 ,cost= 0.013041624682599938
i= 7000 ,cost= 0.011805974427927586
i= 8000 ,cost= 0.010993606051378356
i= 9000 ,cost= 0.010331123029474434
训练集:
Accuracy: 0.9933333333333333
测试集:
Accuracy: 0.93
可以看出,在无正则化时,分割曲线有了明显的过拟合特征。
使用正则化
使用L2正则化
def compute_cost_with_regularization(A3,Y,parameters,lambd):
"""
实现L2正则化计算成本
参数:
A3 - 正向传播的输出结果,维度为(输出节点数量,样本数)
Y - 标签,[0|1],维度为(1,样本数)
parameters - 学习后的参数的字典
lambd - 正则化的超参数,实数
返回:
cost - 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
def backward_propagation_with_regularization(X,Y,cache,lambd):
"""
实现了添加L2正则化的模型的后向传播。
参数:
X - 输入数据,维度为(2,样本数)
Y - 标签,[0|1],维度为(1,样本数)
cache - 来自forward_propagation()的cache输出
lambd - 正则化的超参数,实数
返回:
gradients - 包含每个参数、激活值和预激活值变量的梯度的字典
"""
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
在模型中跑:
parameters = model(train_X,train_Y,is_plot=True,lambd=0.7)
print("使用正则化,训练集:")
predictions_train = reg_utils.predict(train_X,train_Y,parameters)
print("使用正则化,测试集:")
predictions_test = reg_utils.predict(test_X,test_Y,parameters)
结果:
i= 0 ,cost= 0.7399303371527456
i= 1000 ,cost= 0.17212033312563896
i= 2000 ,cost= 0.16882325680402693
i= 3000 ,cost= 0.19626035625767696
i= 4000 ,cost= 0.1641843606927927
i= 5000 ,cost= 0.16440506766399476
i= 6000 ,cost= 0.16630290434891343
i= 7000 ,cost= 0.16395132281033184
i= 8000 ,cost= 0.1767690202116719
i= 9000 ,cost= 0.16955690783394725
使用正则化,训练集:
Accuracy: 0.9933333333333333
使用正则化,测试集:
Accuracy: 0.94
λ
\lambda
λ值可以使用开发集调整时的超参数。L2正则化会使决策边界更加平滑。如果
λ
\lambda
λ太大,也可能会“过渡平滑”,从而导致模型高偏差。L2正则化实际上在做什么?L2正则化依赖于较小权重的模型比具有较大权重的模型更简单这样的假设,因此,通过削弱成本函数中权重的平方值,可以将所有权重值逐渐改变到较小的值。权值数值高的话会有更平滑的模型,其中输入变化时输出变化更慢,但是你需要花费更多的时间。L2正则化对以下内容有影响。
- 成本计算:正则化的计算需要添加到成本函数中。
- 反向传播功能:在权值居中方面,梯度计算时也要依据正则化来做出相应的计算。
- 重量变小(“重量衰减”):权重被逐渐改变到较小的值。
随即删除节点
使用dropout来进行正则化,dropout的原理就是每次迭代过程中随即将其中的一些节点失效。当我们关闭一些节点时,我们实际上修改了我们的模型。背后的想法是,在每次迭代时,我们都会训练一个只使用一部分神经元的不同模型。随着迭代次数的增加,我们的模型的节点会对其他特定节点的激活变得不那么敏感,因为其他节点可能在任何时候会失效。
下面我们将关闭第一层和第三层的一些节点,需要做以下四步:
1、在视频中,吴恩达老师讲解了使用np.random.randn()
来初始化和
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},\dots,d^{1}]
D[1]=[d1,d1,…,d1]。
2、如果
D
[
1
]
D^{[1]}
D[1]低于(keep_prob
)的值我们就把它设置为0,如果高于(keep_prob
)的值我们就设置为1。
3、把
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。
4、使用
A
[
1
]
A^{[1]}
A[1]除以keep_prob
。这样做的话我们通过缩放就在计算成本的时候仍然具有相同的期望值,这叫做反向dropout。
def forward_propagation_with_dropout(X,parameters,keep_prob=0.5):
"""
实现具有随机舍弃节点的前向传播。
linear -> relu + dropout -> linear -> relu + dropout -> linear -> sigmoid
参数:
X - 输入数据,维度为(2,样本数)
parameters - 学习后的参数的字典
W1 - 权重矩阵,维度为(20,2)
b1 - 偏向量,维度为(20,1)
W2 - 权重矩阵,维度为(3,20)
b2 - 偏向量,维度为(3,1)
W3 - 权重矩阵,维度为(1,3)
b3 - 偏向量,维度为(1,1)
keep_preb - 随机删除的概率,实数
返回:
A3 - 最后的激活值,维度为(1,1),正向传播的输出
cache -存储了一些用于计算反向传播的数值的元组
"""
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 = reg_utils.relu(Z1)
D1 = np.random.rand(A1.shape[0],A1.shape[1])
D1 = D1 < keep_prob
A1 = A1 * D1
A1 = A1 / keep_prob
Z2 = np.dot(W2,A1) + b2
A2 = reg_utils.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 = 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]将舍弃的节点信息添加到第一个和第二个隐藏层。
def backward_propagation_with_dropout(X,Y,cache,keep_prob):
"""
实现随机删除的模型的后向传播。
参数:
X - 输入数据,维度为(2,样本数)
Y - 标签,[0|1],维度为(1,样本数)
cache - 来自forward_propagation_with_dropout()的cache输出
keep_prob - 随机删除的概率,实数
返回:
gradients - 包含每个参数、激活值和预激活值变量的梯度的字典
"""
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
前向和后向传播的函数都写好了,现在用dropout运行模型(keep_prob=0.86)跑一次。这意味着在每次迭代中,程序都可以24%的概率关闭第一层和第二层的每个神经元。调用的时候:
- 使用forwa_propagation_with_dropout
- 使用backward_propagation_with_dropout
检测:
parameters = model(train_X,train_Y,keep_prob=0.86,is_plot=True,learning_rate=0.3)
print("使用随机删除节点,训练集:")
predictions_train = reg_utils.predict(train_X,train_Y,parameters)
print("使用随机删除节点,测试集:")
predictions_test = reg_utils.predict(test_X,test_Y,parameters)
结果:
i= 0 ,cost= 0.7147401913684602
i= 1000 ,cost= 0.1435789778090651
i= 2000 ,cost= 0.07322120398526878
i= 3000 ,cost= 0.057537042062446885
i= 4000 ,cost= 0.05241357733348299
i= 5000 ,cost= 0.05038859427215001
i= 6000 ,cost= 0.04941488893305485
i= 7000 ,cost= 0.048922543432744416
i= 8000 ,cost= 0.048512370969579166
i= 9000 ,cost= 0.04836416003562005
使用正则化,训练集:
Accuracy: 0.9366666666666666
使用正则化,测试集:
Accuracy: 0.91
结论:可以看出,正则化会把训练集的准确度降低,但是测试集的准确度提高了,所以这个成功了。
梯度校验
问题:假设你现在是一个全球移动支付团队中的一员,现在需要建立一个深度学习模型去判断用户账户在进行付款的时候是否被黑客入侵。
但是,在我们执行反向传播的计算过程中,反向传播函数的计算过程是比较复杂的。为了验证我们得到的反向传播函数的是否正确,现在需要编写一些代码来验证反向传播函数的正确性。
反向传播计算梯度
∂
J
∂
θ
\frac{\partial J}{\partial \theta}
∂θ∂J,
θ
\theta
θ表示模型中的参数,使用前向传播和损失函数计算J,因为前向传播相对容易实现,所以你确信自己得到了正确的结果,所以你几乎100%确定你正确计算了J的成本。因此,你可以使用你的代码来计算J来验证计算的代码
∂
J
∂
θ
\frac{\partial J}{\partial \theta}
∂θ∂J。
一维线性
def forward_propagation(x,theta):
"""
实现图中呈现的线性前向传播(计算J)(J(theta) = theta*x)
参数:
x - 一个实值输入
theta - 参数,也是一个实数
返回:
J - 函数J的值,用公式J(theta) = theta*x 计算
"""
J = np.dot(theta,x)
return J
测试:
x,theta = 2,4
J = forward_propagation(x,theta)
print('J = ' + str(J))
结果:
J = 8
def backward_propagation(x,theta):
"""
计算J相对于θ的导数。
参数:
x - 一个实值输入
theta - 参数,也是一个实数
返回:
dtheta - 相对于θ的成本梯度
"""
dtheta = x
return dtheta
测试:
x,theta = 2,4
dtheta = backward_propagation(x,theta)
print('dtheta = ' + str(dtheta))
结果:
dtheta = 2
梯度检测的步骤如下:
1、
θ
+
=
θ
+
ξ
\theta ^{+}=\theta + \xi
θ+=θ+ξ
2、
θ
+
=
θ
−
ξ
\theta ^{+}=\theta - \xi
θ+=θ−ξ
3、
J
+
=
J
(
θ
+
)
J ^{+}=J(\theta ^{+})
J+=J(θ+)
4、
J
−
=
J
(
θ
−
)
J ^{-}=J(\theta ^{-})
J−=J(θ−)
5、
g
r
a
d
a
p
p
r
o
x
=
J
+
−
J
−
2
ξ
gradapprox =\frac{J ^{+} - J ^{-}}{2\xi}
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{||grad-gradapprox||_2}{||grad||_2 + ||gradapprox||_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):
"""
实现图中的反向传播。
参数:
x - 一个实值输入
theta - 参数,也是一个实数
epsilon - 为了计算近似梯度而输入的微小偏移
返回:
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('right')
else:
print('error')
return difference
测试:
x,theta = 2,4
difference = gradient_check(x,theta)
print('difference = '+ str(difference))
结果:
right
difference = 2.919335883291695e-10
高维
def forward_propagation_n(X,Y,parameters):
"""
实现图中的前向传播(并计算成本)。
参数:
X - 训练集为m个例子
Y - m个示例的标签
parameters - 学习后的参数的字典
W1 - 权重矩阵,维度为(5,4)
b1 - 偏向量,维度为(5,1)
W2 - 权重矩阵,维度为(3,5)
b2 - 偏向量,维度为(3,1)
W3 - 权重矩阵,维度为(1,3)
b3 - 偏向量,维度为(1,1)
返回:
cache - 成本函数(logistic)
"""
m = X.shape[1]
W1 = parameters['W1']
b1 = parameters['b1']
W2 = parameters['W2']
b2 = parameters['b2']
W3 = parameters['W3']
b3 = parameters['b3']
# LINEAR -> RELU -> LINEAR -> RELU -> LINEAR -> SIGMOID
Z1 = np.dot(W1,X) + b1
A1 = gc_utils.relu(Z1)
Z2 = np.dot(W2,A1) + b2
A2 = gc_utils.relu(Z2)
Z3 = np.dot(W3,A2) + b3
A3 = gc_utils.sigmoid(Z3)
logprobs = np.multiply(-np.log(A3),Y) + np.multiply(-np.log(1-A3),1-Y)
cost = (1/m)*np.sum(logprobs)
cache = (Z1,A1,W1,b1,Z2,A2,W2,b2,Z3,A3,W3,b3)
return cost,cache
def back_propagation_n(X,Y,cache):
"""
实现图中所示的反向传播。
参数:
X - 训练集为m个例子
Y - m个示例的标签
cache - 来自forward_propagation_n()的cache输出
返回:
gradients - 包含每个参数、激活值和预激活值变量的梯度的字典
"""
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)
dW3 = 1/m * np.dot(dZ3,A2.T)
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)
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)
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
如果想比较‘gradapprox’与反向传播计算的梯度,该公式仍然是:
lim
n
→
+
∞
J
(
θ
+
ξ
)
−
J
(
θ
−
ξ
)
2
ξ
\lim_{n \to +\infty} \frac{J(\theta + \xi)-J(\theta - \xi)}{2\xi} \quad
n→+∞lim2ξJ(θ+ξ)−J(θ−ξ)
然而,此时
θ
\theta
θ不再是一个标量,而是一个矢量。
类似的是,对于多维度而言,实现的过程如下:
1、计算gradapprox
- 计算
J_plus[i]
:
1.把 θ + \theta ^{+} θ+设置为np.copy(parameters_values
2.把 θ i + \theta_i ^{+} θi+设置为 θ i + + ξ \theta_i ^{+} + \xi θi++ξ
3.使用forward_propagation_n(x,y,vector_to_dictionary(
θ \theta θ))
来计算 J i + J_i ^{+} Ji+ - 计算
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\xi} 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(parameters,gradients,X,Y,epsilon=1e-7):
"""
检查back_propagation_n是否正确计算forward_propagation_n输出的成本梯度。
参数:
parameters - 包含参数“W1”,“b1”,“W2”,“b2”,“W3”,“b3”的python字典
gradients - 输出包含与参数相关的成本梯度。
X - 输入数据点,维度为(输入节点数量,1)
Y - 标签
epsilon - 计算输入的微小偏移以计算近似梯度
"""
parameters_values,keys = gc_utils.dictionary_to_vector(parameters)
grad = gc_utils.gradients_to_vector(gradients)
num_parameters = parameters_values.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(parammters_values)
thetaplus[i][0] = thetaplus[i][0]+epsilon
J_plus[i],cache = forward_propagation_n(X,Y,gc_utils.vector_to_dictionary(thetaplus))
thetaminus = np.copy(parameters_values)
thetaminus[i][0] = thetaminus[i][0] - epsilon
J_minus[i],cache = forward_propagation_n(X,Y,gc_utils.vector_to_dictionary(thetaminus))
gradapprox[i] = (J_plus[i]-J_minus[i])/(2*epsilon)
numerator = np.linalg.norm(grad - pradapprox)
denominator = np.linalg.norm(grad) + np.linalg.norm(gradapprox)
difference = numerator/denominator
if differencce<1e-7:
print('right')
else:
print('error')
return difference
总结
线索:
1、初始化
- 初始化为0
- 初始化随即数–较大的随即数
- 初始化为抑梯度
2、正则化
- L2正则化–代价函数、后向传播求梯度
- 随即节点消失–前向传播、后向传播
3、梯度校验
在前向传播过程中,算某个点的导数,用高数上偏微分方式求某点导数为gradapprox,再用后向传播算出梯度grad,再用求精度的公式算出gradapprox与grad之间的difference,当difference<
1
0
−
7
10^{-7}
10−7时代表后向传播的梯度算得正确。