目录
一、基础知识
我们将继续学习如何有效运作神经网络,内容涉及超参数调优,如何构建数据,以及如何确保优化算法快速运行,从而使学习算法在合理时间内完成自我学习。
1.1 数据的划分
机器学习时代,如果我们不设置验证集,那么一般训练集占70%,测试集占30%;如果我们设置验证集,那他们的比例一般为60%,20%,20%。
如果只有 100 条,1000 条或者 1 万条数据,那么上述比例划分是非常合理的。
深度学习时代,数据量很大的情况下不需要三七分,假设我们有 100 万条数据,其中 1 万条作为验证集,1 万条作为测试集, 100 万里取 1 万,比例是 1%,即:训练集占 98%,验证集和测试集各占 1%。对于数据量过 百万的应用,训练集可。
总结:
总结一下,在机器学习中,我们通常将样本分成训练集,验证集和测试集三部分,数据 集规模相对较小,适用传统的划分比例,数据集规模较大的,验证集和测试集要小于数据总 量的 20%或 10%
建议:
确保验证集和测试集的数据来自同一分布
1.2 偏差,方差(Bias /Variance)
理解偏差和方 差的两个关键数据是训练集误差(Train set error)和验证集误差(Dev set error)。
对于高偏差和高方差在错误率上的体现如下表所示:
总结:
通过分析在训练集上训练算法产生的误差和验证集上验证算法产生的误差来诊断算法是否存在高偏差和高方差,是否两个值都高,或者两个值都不高,根 据算法偏差和方差的具体情况决定接下来你要做的工作。
1.3 机器学习基础
如何处理 Bias偏差较大的问题(欠拟合)
1、增加特征数量
2、使用更复杂的模型
3、去掉正则化
如何处理 Variance 方差较大的问题(过拟合)
1、减少特征数量
2、使用更简单的模型
3、增大你的训练数据集
4、使用正则化
1.4 正则化
深度学习可能存在过拟合问题——高方差,有两个解决方法,一个是正则化,另一个是 准备更多的数据,这是非常可靠的方法,但你可能无法时时刻刻准备足够多的训练数据或者 获取更多数据的成本很高,但正则化通常有助于避免过拟合或减少你的网络误差。
L2正则化:实际上就是多加了一个权重矩阵的二范数的平方。 ( 目的是让权重更加接近原点,其他叫法也有岭回归或者Tikhonov正则)
L1正则化:相比L2正则,L1正则会产生更加稀疏的解。一般被用来特征选择机制。
1.5 为什么正则化有利于预防过拟合呢?
直观上理解就是如果正则化𝜆设置得足够大,权重矩阵𝑊被设置为接近于 0 的值,直观 理解就是把多隐藏单元的权重设为 0,于是基本上消除了这些隐藏单元的许多影响。如果是这种情况,这个被大大简化了的神经网络会变成一个很小的网络,小到如同一个逻辑回归单 元,可是深度却很大,它会使这个网络从过度拟合的状态更接近左图的高偏差状态。
但是𝜆会存在一个中间值,于是会有一个接近“Just Right”的中间状态。
直观理解就是𝜆增加到足够大,𝑊会接近于 0,实际上是不会发生这种情况的,我们尝 试消除或至少减少许多隐藏单元的影响,最终这个网络会变得更简单,这个神经网络越来越 接近逻辑回归,我们直觉上认为大量隐藏单元被完全消除了,其实不然,实际上是该神经网 络的所有隐藏单元依然存在,但是它们的影响变得更小了。神经网络变得更简单了,貌似这 样更不容易发生过拟合,因此我不确定这个直觉经验是否有用,不过在编程中执行正则化时, 你实际看到一些方差减少的结果。
1.6 Dropout正则化
还有一个非常有效的正则化方法:dropout(随机失活)。
我们设置一个keep_prob参数,假设值为0.8,那么意味着这个神经网络将会有20%的神经元被消去,80%神经元被保留。
d3 = np.random.rand(a3.shape[0], a3.shape[1]) < keep.prob
所有小于该值的神经元都会被置为0,其余置为1。
a3 = np.multiply(a3, d3)
a3 /= keep_prob
除以keep_prob它会大致矫正或者补足你丢失的20%,以确保a3的期望值仍然维持在同一水准。这也会使你在测试时轻松一点,因为你没有增加额外的缩放问题(scaling problem)。
测试时我们不需要执行Dropout操作,你没有必要随机化你的输出。如果你在测试阶段使用了Dropout,那只会增加你预测上的噪音。理论上,你可以多次用不用的隐藏单元运行随机化的Dropout,但也只会给你不用Dropout一样的结果。这会耗费了你的计算效率,但给了你同样的结果。
如下图,实际上dropout做的事情是使结果不会过分依赖与某一个结点,因为他们都会随机失效,也就防止了过拟合的发生,一般用在计算机视觉上偏多,因为网络常常有过拟合的问题。
1.7 理解 dropout
编写代码实现dropout过程
1.8 其他正则化
一.数据扩增
二.early stopping
由于刚开始初始化的时候w很小,随着迭代的次数通过观察训练集代价和验证集代价,当发现验证集代价开始升高的时候,说明接下来的迭代就可能是开始对训练集进行过拟合了,这时候早早的结束算法获得的往往是一个比较好的结果。
1.9 归一化输入
标准化训练集
将训练集的值均匀的散列在原点附近(类似于做正态分布)。
1.10 梯度消失/梯度爆炸
训练神经网络,尤其是深度神经所面临的一个问题就是梯度消失或梯度爆炸,也就是你训练神经网络的时候,导数或坡度有时会变得非常大,或者非常小,甚至于以指数方式变小, 这加大了训练的难度。
例子:
1.11 神经网络的权重初始化
1)随机初始化
2)抑梯度异常初始化
1.12 梯度的数值逼近
双边误差就是更改一下求导方法,以达到更优秀的精度。
具体推导如下:
我们知道导数的定义
使用泰勒展开的话如下
我们可以得出误差项与h同阶。
如果我们使用两个导数
我们发现现在的误差项变为h方同阶,精度得到了提升。
1.13 梯度检验
神经网络算法使用反向传播计算目标函数关于每个参数的梯度,可以看做解析梯度。由于计算过程中涉及到的参数很多,反向传播计算的梯度很容易出现误差,导致最后迭代得到效果很差的参数值。
为了确认代码中反向传播计算的梯度是否正确,可以采用梯度检验(gradient check)的方法。通过计算数值梯度,得到梯度的近似值,然后和反向传播得到的梯度进行比较,若两者相差很小的话则证明反向传播的代码是正确无误的
一般我们认为差距10^-7是比较好的,10^-5一般,可能产生bug,而10^-3可能就要仔细检查有没有bug的产生。
1.14 梯度检验应用的注意事项
- 不要在训练中使用梯度检验,仅仅是在调试阶段使用,因为它太慢了
- 如果检验不合格,要检查每一项的值以确定bug的位置
- 在实施梯度检验的时候不要忘记使用正则化
- 不要和dropout同时使用
二、课后测验
1. 如果你有10,000,000个例子,你会如何划分训练/开发/测试集?
训练集占98% , 开发集占1% , 测试集占1% 。
2. 开发和测试集应该:
来自同一分布。
3. 如果你的神经网络模型似乎有很高的方差,下列哪个尝试是可能解决问题的?
添加正则化, 获取更多的训练数据 。
4. 你在一家超市的自动结帐亭工作,正在为苹果,香蕉和橘子制作分类器。 假设您的分类器在训练集上有0.5%的错误,以及开发集上有7%的错误。 以下哪项尝试是有希望改善你的分类器的分类效果的?
增加正则化参数lambda, 获取更多的训练数据 。
5. 什么是权重衰减?
正则化技术(例如L2正则化)导致梯度下降在每次迭代时权重收缩。
6.当你增加正则化超参数lambda时会发生什么?
权重会变得更小(接近0)
7. 在测试时候使用dropout时
不要随机消除节点,也不要在训练中使用的计算中保留1 / keep_prob因子。
8. 将参数keep_prob从(比如说)0.5增加到0.6可能会导致以下情况
正则化效应被减弱、使神经网络在结束时会在训练集上表现好一些。
9. 以下哪些技术可用于减少方差(减少过拟合):
Dropout、 L2 正则化 、 扩充数据集 。
10.为什么我们要归一化输入x?
它使成本函数更快地进行优化。
三、编程作业
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
可视化数据,然后打印出来看一下数据效果。
if __name__ == '__main__':
plt.rcParams['figure.figsize'] = (7.0, 4.0) # set default size of plots
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'
train_X, train_Y, test_X, test_Y = load_dataset()
plt.show()
我们在这里尝试三种参数初始化方法,全部初始化为0、随机初始化与抑梯度异常初始化。
在这之前,我们先把模型搭建好,然后使用不同的参数初始化方法进行对比。
def model(X, Y, learning_rate=0.01, num_iterations=15000, print_cost=True, initialization="he"):
"""
三层神经网络的实现: LINEAR->RELU->LINEAR->RELU->LINEAR->SIGMOID.
参数:
X -- input data, of shape (2, number of examples)
Y -- true "label" vector (containing 0 for red dots; 1 for blue dots), of shape (1, number of examples)
learning_rate -- learning rate for gradient descent
num_iterations -- number of iterations to run gradient descent
print_cost -- if True, print the cost every 1000 iterations
initialization -- flag to choose which initialization to use ("zeros","random" or "he")
Returns:
parameters -- 学到的模型参数
"""
grads = {}
costs = [] # to keep track of the loss
m = X.shape[1] # number of examples
layers_dims = [X.shape[0], 10, 5, 1]
# Initialize parameters dictionary.
if initialization == "zero":
parameters = initialize_parameters_zeros(layers_dims)
elif initialization == "random":
parameters = initialize_parameters_random(layers_dims)
elif initialization == "he":
parameters = initialize_parameters_he(layers_dims)
# Loop (gradient descent)
for i in range(0, num_iterations):
# Forward propagation: LINEAR -> RELU -> LINEAR -> RELU -> LINEAR -> SIGMOID.
a3, cache = forward_propagation(X, parameters)
# Loss
cost = compute_loss(a3, Y)
# Backward propagation.
grads = backward_propagation(X, Y, cache)
# Update parameters.
parameters = update_parameters(parameters, grads, learning_rate)
# Print the loss every 1000 iterations
if print_cost and i % 1000 == 0:
print("Cost after iteration {}: {}".format(i, cost))
costs.append(cost)
# plot the loss
plt.plot(costs)
plt.ylabel('cost')
plt.xlabel('iterations (per hundreds)')
plt.title("Learning rate =" + str(learning_rate))
plt.show()
return parameters
1.1 权重初始化0:
def initialize_parameters_zeros(layers_dims):
"""
模型的参数全部初始化为0
:param layers_dims:
:return:
"""
parameters = {}
# 网络层数
L = len(layers_dims)
for i in range(1, L):
parameters['W' + str(i)] = np.zeros((layers_dims[i], layers_dims[i - 1]))
parameters['b' + str(i)] = np.zeros((layers_dims[i], 1))
return parameters
然后我们运行模型
if __name__ == '__main__':
plt.rcParams['figure.figsize'] = (7.0, 4.0) # set default size of plots
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'
train_X, train_Y, test_X, test_Y = load_dataset()
plt.show()
parameters = model(train_X, train_Y, initialization="zero")
print("训练集:")
predictions_train = predict(train_X, train_Y, parameters)
print("测试集:")
predictions_test = predict(test_X, test_Y, parameters)
输出如下:
第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
训练集:
Accuracy: 0.5
测试集:
Accuracy: 0.5
可以看到随着迭代次数的增加,代价没有任何变化。
就像之前说的,当参数初始化为0的时候,神经网络就退化成逻辑回归了。
1.2 随机初始化
def initialize_parameters_random(layers_dims):
"""
模型随机初始化参数
:param layers_dims:
:return:
"""
np.random.seed(3)
parameters = {}
L = len(layers_dims)
for i in range(1, L):
parameters['W' + str(i)] = np.random.randn(layers_dims[i], layers_dims[i - 1]) * 10
parameters['b' + str(i)] = np.zeros((layers_dims[i], 1))
return parameters
然后调用输出并绘制决策边界。
if __name__ == '__main__':
plt.rcParams['figure.figsize'] = (7.0, 4.0) # set default size of plots
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'
train_X, train_Y, test_X, test_Y = load_dataset()
plt.show()
parameters = model(train_X, train_Y, initialization="random")
print("训练集:")
predictions_train = predict(train_X, train_Y, parameters)
print("测试集:")
predictions_test = predict(test_X, test_Y, parameters)
plt.title("Model with large random initialization")
axes = plt.gca()
axes.set_xlim([-1.5, 1.5])
axes.set_ylim([-1.5, 1.5])
plot_decision_boundary(lambda x: predict_dec(parameters, x.T), train_X, train_Y)
第0次迭代,成本值为:inf
第1000次迭代,成本值为:0.6250982793959966
第2000次迭代,成本值为:0.5981216596703697
第3000次迭代,成本值为:0.5638417572298645
第4000次迭代,成本值为:0.5501703049199763
第5000次迭代,成本值为:0.5444632909664456
第6000次迭代,成本值为:0.5374513807000807
第7000次迭代,成本值为:0.4764042074074983
第8000次迭代,成本值为:0.39781492295092263
第9000次迭代,成本值为:0.3934764028765484
第10000次迭代,成本值为:0.3920295461882659
第11000次迭代,成本值为:0.38924598135108
第12000次迭代,成本值为:0.3861547485712325
第13000次迭代,成本值为:0.384984728909703
第14000次迭代,成本值为:0.3827828308349524
训练集:
Accuracy: 0.83
测试集:
Accuracy: 0.86
代价曲线:
决策边界:
初始化的时候乘了个10,如果我们将这个10去掉变成1,那么准确率就会达到96%。说明这个超参也能影响准确度,这与这个数变成多少比较好,下面会说。
1.3 抑梯度异常初始化
def initialize_parameters_he(layers_dims):
"""
Arguments:
layer_dims -- python array (list) containing the size of each layer.
Returns:
parameters -- python dictionary containing your parameters "W1", "b1", ..., "WL", "bL":
W1 -- weight matrix of shape (layers_dims[1], layers_dims[0])
b1 -- bias vector of shape (layers_dims[1], 1)
...
WL -- weight matrix of shape (layers_dims[L], layers_dims[L-1])
bL -- bias vector of shape (layers_dims[L], 1)
"""
np.random.seed(3)
parameters = {}
L = len(layers_dims) - 1 # integer representing the number of layers
for l in range(1, L + 1):
### START CODE HERE ### (≈ 2 lines of code)
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))
### END CODE HERE ###
return parameters
然后输出结果和绘制决策边界:
if __name__ == '__main__':
plt.rcParams['figure.figsize'] = (7.0, 4.0) # set default size of plots
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'
train_X, train_Y, test_X, test_Y = load_dataset()
plt.show()
parameters = model(train_X, train_Y, initialization="he")
print("训练集:")
predictions_train = predict(train_X, train_Y, parameters)
print("测试集:")
predictions_test = predict(test_X, test_Y, parameters)
plt.title("Model with large random initialization")
axes = plt.gca()
axes.set_xlim([-1.5, 1.5])
axes.set_ylim([-1.5, 1.5])
plot_decision_boundary(lambda x: predict_dec(parameters, x.T), train_X, train_Y)
第0次迭代,成本值为:0.8830537463419761
第1000次迭代,成本值为:0.6879825919728063
第2000次迭代,成本值为:0.6751286264523371
第3000次迭代,成本值为:0.6526117768893807
第4000次迭代,成本值为:0.6082958970572937
第5000次迭代,成本值为:0.5304944491717495
第6000次迭代,成本值为:0.4138645817071793
第7000次迭代,成本值为:0.3117803464844441
第8000次迭代,成本值为:0.23696215330322556
第9000次迭代,成本值为:0.18597287209206828
第10000次迭代,成本值为:0.15015556280371808
第11000次迭代,成本值为:0.12325079292273548
第12000次迭代,成本值为:0.09917746546525937
第13000次迭代,成本值为:0.08457055954024274
第14000次迭代,成本值为:0.07357895962677366
训练集:
Accuracy: 0.9933333333333333
测试集:
Accuracy: 0.96
代价变化曲线:
决策边界
我们发现准确度很高,并且决策边界和上面超参为1的时候很像。
处理完参数初始化,我们接下来学习正则化的相关知识。
2、正则化
如果训练数据集不够大,由于深度学习模型具有非常大的灵活性和容量,以至于过度拟合可能是一个严重的问题,为了解决这个问题,引入了正则化的这个方法。要在神经网络中加入正则化,除了在激活层中加入正则函数,应该dropout也是可以起到正则的效果。我们来试试吧
在使用之前,我们还要先导入所需的依赖包,和加载数据。
2.1 无正则化情况
导包:
# -*- coding: UTF-8 -*-
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 *
原始数据可视化:
模型函数
在这里编写一个model
函数,来测试和对比以下三种情况:
- 无正则化的情况
- 使用有正则化的激活激活函数
- 使用dropout
def model(X, Y, learning_rate=0.3, num_iterations=30000, print_cost=True, lambd=0, keep_prob=1):
"""
实现一个三层神经网络: LINEAR->RELU->LINEAR->RELU->LINEAR->SIGMOID.
Arguments:
X -- 输入数据、形状(输入大小、样本数量)
Y -- 真正的“标签”向量(红点的蓝色点/ 0),形状(输出大小,样本数量)
learning_rate -- 学习速率的优化
num_iterations -- 优化循环的迭代次数。
print_cost -- 如果是真的,打印每10000次迭代的成本。
lambd -- 正则化超参数,标量
keep_prob - 在dropout过程中保持神经元活跃的概率。
Returns:
parameters -- 由模型学习的参数。他们可以被用来预测。
"""
grads = {}
costs = [] # to keep track of the cost
m = X.shape[1] # 样本数目 211
layers_dims = [X.shape[0], 20, 3, 1] #[2,20,3,1] 设置每一层神经元的数目
# 初始化权重
parameters = initialize_parameters(layers_dims)
# 梯度下降 迭代次数
for i in range(0, num_iterations):
# 正向传播: LINEAR -> RELU -> LINEAR -> RELU -> LINEAR -> SIGMOID.
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)
# 反向传播
assert (lambd == 0 or keep_prob == 1) # 可以同时使用L2正则化和退出,但是这个任务只会一次探索一个。
if lambd == 0 and keep_prob == 1:
grads = 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 = update_parameters(parameters, grads, learning_rate)
# 每10000次迭代打印一次损失。
if print_cost and i % 10000 == 0:
print("Cost after iteration {}: {}".format(i, cost))
if print_cost and i % 1000 == 0:
costs.append(cost) # costs存储
# 可视化损失函数
plt.plot(costs)
plt.ylabel('cost')
plt.xlabel('iterations (x1,000)')
plt.title("Learning rate =" + str(learning_rate))
plt.show()
return parameters
无正则化:
下面就测试没有正则化的情况,直接运行项目就可以了。
if __name__ == '__main__':
plt.rcParams['figure.figsize'] = (7.0, 5.0) # 设置图形的大小 宽:7.0 高: 5.0
train_X, train_Y, test_X, test_Y = load_2D_dataset()
plt.show()
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)
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)# lambda x 为匿名函数
输出的相关日志,从中看到训练的准确率比较高,而测试的准确率比较低,这个是一种过拟合的体现:
Cost after iteration 0: 0.6557412523481002
Cost after iteration 10000: 0.16329987525724216
Cost after iteration 20000: 0.13851642423255986
On the training set:
Accuracy: 0.947867298578
On the test set:
Accuracy: 0.915
以图表显示Cost的情况:
下面是收敛情况,从这个图像中可以很直观看出已经存在过拟合情况了:
2.2 L2正则化情况
损失函数
如果L2正则的话,要修改损坏函的计算公式,如下:
损失函数:
带L2正则的损失函数:
损失函数的代码片段如下:
def compute_cost_with_regularization(A3, Y, parameters, lambd):
"""
用L2正则化实现成本函数。参见上面的公式。
Arguments:
A3 -- post-activation,前向传播输出,形状(输出尺寸,样本数量)
Y -- “true”标签向量,形状(输出大小,样本数量)
parameters -- 包含模型参数的python字典。
Returns:
cost - 正则化损失函数值
"""
m = Y.shape[1]
W1 = parameters["W1"]
W2 = parameters["W2"]
W3 = parameters["W3"]
cross_entropy_cost = compute_cost(A3, Y) # cost的交叉熵。
L2_regularization_cost = (1 / m) * (lambd / 2) * (
np.sum(np.square(W1)) + np.sum(np.square(W2)) + np.sum(np.square(W3)))
cost = cross_entropy_cost + L2_regularization_cost
return cost
反向传播
反向传播所需的更改以考虑正则化。这些变化只涉及dW1,dW2和dW3。对于每一个添加正则化项的梯度(
def backward_propagation_with_regularization(X, Y, cache, lambd):
"""
实现基线模型的反向传播,我们添加了L2正则化。
Arguments:
X -- 输入数据集,形状(输入大小,样本数量)
Y -- “true”标签向量,形状(输出大小,样本数量)
cache -- 缓存输出forward_propagation()
lambd -- 正则化超参数,标量
Returns:
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 / m) * W3
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 / m) * W2
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 / m) * W1
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
然后运行带有L2正则的模型,如下:
# -*- coding: UTF-8 -*-
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
from testCases import *
def model(X, Y, learning_rate=0.3, num_iterations=30000, print_cost=True, lambd=0, keep_prob=1):
"""
实现一个三层神经网络: LINEAR->RELU->LINEAR->RELU->LINEAR->SIGMOID.
Arguments:
X -- 输入数据、形状(输入大小、样本数量)
Y -- 真正的“标签”向量(红点的蓝色点/ 0),形状(输出大小,样本数量)
learning_rate -- 学习速率的优化
num_iterations -- 优化循环的迭代次数。
print_cost -- 如果是真的,打印每10000次迭代的成本。
lambd -- 正则化超参数,标量
keep_prob - 在dropout过程中保持神经元活跃的概率。
Returns:
parameters -- 由模型学习的参数。他们可以被用来预测。
"""
grads = {}
costs = [] # to keep track of the cost
m = X.shape[1] # 样本数目 211
layers_dims = [X.shape[0], 20, 3, 1] # [2,20,3,1] 设置每一层神经元的数目
# 初始化权重
parameters = initialize_parameters(layers_dims)
# 梯度下降 迭代次数
for i in range(0, num_iterations):
# 正向传播: LINEAR -> RELU -> LINEAR -> RELU -> LINEAR -> SIGMOID.
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)
# 反向传播
assert (lambd == 0 or keep_prob == 1) # 可以同时使用L2正则化和退出,但是这个任务只会一次探索一个。
if lambd == 0 and keep_prob == 1:
grads = 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 = update_parameters(parameters, grads, learning_rate)
# 每10000次迭代打印一次损失。
if print_cost and i % 10000 == 0:
print("Cost after iteration {}: {}".format(i, cost))
if print_cost and i % 1000 == 0:
costs.append(cost) # costs存储
# 可视化损失函数
plt.plot(costs)
plt.ylabel('cost')
plt.xlabel('iterations (x1,000)')
plt.title("Learning rate =" + str(learning_rate))
plt.show()
return parameters
def compute_cost_with_regularization(A3, Y, parameters, lambd):
"""
Implement the cost function with L2 regularization. See formula (2) above.
Arguments:
A3 -- post-activation, output of forward propagation, of shape (output size, number of examples)
Y -- "true" labels vector, of shape (output size, number of examples)
parameters -- python dictionary containing parameters of the model
Returns:
cost - value of the regularized loss function (formula (2))
"""
m = Y.shape[1]
W1 = parameters["W1"]
W2 = parameters["W2"]
W3 = parameters["W3"]
cross_entropy_cost = compute_cost(A3, Y) # This gives you the cross-entropy part of the cost
### START CODE HERE ### (approx. 1 line)
L2_regularization_cost = 1. / m * lambd / 2 * (
np.sum(np.square(W1)) + np.sum(np.square(W2)) + np.sum(np.square(W3)))
### END CODER HERE ###
cost = cross_entropy_cost + L2_regularization_cost
return cost
def backward_propagation_with_regularization(X, Y, cache, lambd):
"""
Implements the backward propagation of our baseline model to which we added an L2 regularization.
Arguments:
X -- input dataset, of shape (input size, number of examples)
Y -- "true" labels vector, of shape (output size, number of examples)
cache -- cache output from forward_propagation()
lambd -- regularization hyperparameter, scalar
Returns:
gradients -- A dictionary with the gradients with respect to each parameter, activation and pre-activation variables
"""
m = X.shape[1]
(Z1, A1, W1, b1, Z2, A2, W2, b2, Z3, A3, W3, b3) = cache
dZ3 = A3 - Y
### START CODE HERE ### (approx. 1 line)
dW3 = 1. / m * np.dot(dZ3, A2.T) + lambd / m * W3
### END CODE HERE ###
db3 = 1. / m * np.sum(dZ3, axis=1, keepdims=True)
dA2 = np.dot(W3.T, dZ3)
dZ2 = np.multiply(dA2, np.int64(A2 > 0))
### START CODE HERE ### (approx. 1 line)
dW2 = 1. / m * np.dot(dZ2, A1.T) + lambd / m * W2
### END CODE HERE ###
db2 = 1. / m * np.sum(dZ2, axis=1, keepdims=True)
dA1 = np.dot(W2.T, dZ2)
dZ1 = np.multiply(dA1, np.int64(A1 > 0))
### START CODE HERE ### (approx. 1 line)
dW1 = 1. / m * np.dot(dZ1, X.T) + lambd / m * W1
### END CODE HERE ###
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
if __name__ == '__main__':
plt.rcParams['figure.figsize'] = (7.0, 5.0) # 设置图形的大小 宽:7.0 高: 5.0
train_X, train_Y, test_X, test_Y = load_2D_dataset()
plt.show()
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)
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)
输出的日志信息如下,从日志信息来看,模型收敛得挺好,没有过拟合的情况:
Cost after iteration 0: 0.6974484493131264
Cost after iteration 10000: 0.2684918873282239
Cost after iteration 20000: 0.2680916337127301
On the train set:
Accuracy: 0.9383886255924171
On the test set:
Accuracy: 0.93
使用图表来显示Cost的话,如下:
下面是收敛情况,从这个图像来看,没有出现过拟合的情况:
L2正则化实际上在做什么:
L2正则化依赖于这样的假设,即具有较小权重的模型比具有较大权重的模型更简单。因此,通过惩罚成本函数中权重的平方值,可以将所有权重驱动到较小的值。拥有大权重的成本太高了!这导致更平滑的模型,其中输入变化时输出变化更慢。
L2正则化对以下内容的影响:
- 成本计算:
- 在成本中增加了正则化项。
- 反向传播功能:
- 在权重矩阵的梯度上有额外的项。
- 权重变小(“权重衰减”):
- 权重被推到较小的值。
2.3 Dropout
Dropout是一种广泛使用的专门针对深度学习的正规化技术。 它在每次迭代中随机关闭一些神经元。具体流程如下:
在第二个隐藏层上Dropout
带Dropout的前向传播
实施具有Dropout的前向传播。当正在使用3层神经网络,并将丢弃添加到第一个和第二个隐藏层,模型不会将Dropout应用于输入层或输出层。
def forward_propagation_with_dropout(X, parameters, keep_prob=0.5):
"""
实现了向前传播: LINEAR -> RELU + DROPOUT -> LINEAR -> RELU + DROPOUT -> LINEAR -> SIGMOID.
Arguments:
X -- 输入数据集,形状(2,样本数量)
parameters -- 包含参数的python字典 "W1", "b1", "W2", "b2", "W3", "b3":
W1 -- 形状权重矩阵(20,2)
b1 -- 形状偏差向量(20,1)
W2 -- 形状权重矩阵(3,20)
b2 -- 形状的偏差向量(3,1)
W3 -- 形状权重矩阵(1,3)
b3 -- 形状的偏差向量(1,1)
keep_prob - 在dropout过程中保持神经元活跃的概率。
Returns:
A3 -- 最后一个激活值,向前传播的输出,形状(1,1)
cache -- 元组,用于计算反向传播的信息。
"""
np.random.seed(1)
# retrieve parameters
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 = relu(Z1)
# Step 1: 初始化矩阵 D1
D1 = np.random.rand(A1.shape[0], A1.shape[1])
D1 = (D1 < keep_prob) # Step 2: 将D1的条目转换为0或1(使用keep_prob作为阈值)
A1 = A1 * D1 # Step 3: 关闭A1的一些神经元。
A1 = A1 / keep_prob # Step 4: 测量那些没有被关闭的神经元的价值。
Z2 = np.dot(W2, A1) + b2
A2 = relu(Z2)
D2 = np.random.rand(A2.shape[0], A2.shape[1]) # Step 1: 初始化矩阵D2
D2 = (D2 < keep_prob) # Step 2: 将D2的条目转换为0或1(使用keep_prob作为阈值)
A2 = A2 * D2 # Step 3: 关闭A2的一些神经元。
A2 = A2 / keep_prob # Step 4: 测量那些没有被关闭的神经元的价值。
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
带Dropout的反向传播
实施具有Dropout的反向传播。和上面一样,当正在训练一个3层网络。将dropout添加到第一个和第二个隐藏层
def backward_propagation_with_dropout(X, Y, cache, keep_prob):
"""
实现我们的基线模型的反向传播,我们增加了dropout率。
Arguments:
X -- 输入数据集,形状(2,样本数量)
Y -- “true”标签向量,形状(输出大小,样本数量)
cache -- 缓存输出forward_propagation_with_dropout()
keep_prob - 在dropout过程中保持神经元活跃的概率。
Returns:
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 # Step 1: 在向前传播过程中,应用mask D2关闭相同的神经元。
dA2 = dA2 / keep_prob # Step 2: 测量那些没有被关闭的神经元的值。
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 # Step 1: 使用mask D1关闭与转发传播时相同的神经元。
dA1 = dA1 / keep_prob # Step 2: 测量那些没有被关闭的神经元的价值。
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的模型,如下:
if __name__ == '__main__':
plt.rcParams['figure.figsize'] = (7.0, 5.0) # 设置图形的大小 宽:7.0 高: 5.0
train_X, train_Y, test_X, test_Y = load_2D_dataset()
plt.show()
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)
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)
输出的日志如下:
Cost after iteration 10000: 0.061016986574905605
Cost after iteration 20000: 0.060582435798513114
On the train set:
Accuracy: 0.9289099526066351
On the test set:
Accuracy: 0.95
使用图表显示Cost如下:
下面是收敛情况,从这个图像来看,也没有出现过拟合的情况:
3、梯度校验
3.1 问题描述
假设你是致力于在全球范围内提供移动支付的团队的一员,并被要求构建一个深度学习模型来检测欺诈行为的存在,即每当有人付款时,你要查看付款是否可能是欺诈性的,比如用户的帐户已被黑客接管。
但是反向传播实施起来非常具有挑战性,有时还会出现错误。由于这是一项很关键的任务应用程序,贵公司的首席执行官希望确定你执行的反向传播是正确的。你的老板会说,“给我一个证据证明你的反向传播真的有效!” 为了保证这一点,我们会使用“梯度校验”。
3.2 梯度校验如何工作
在反向传播过程中需要计算梯度,表示模型中的参数,要用到前向传播和损失函数来计算。由于前向传播实现起来相对容易一些,所以我们默认是百分百正确的,因此这里会用来计算梯度。
OK,再来回顾一下导数(或梯度)的定义:
这里我们要知道:
要追求的计算正确的值是
在确定代价函数 正确的前提下,我们可以通过计算来校验
一维梯度校验:
针对一维线性模型 梯度校验计算过程:
即首先在前向传播中计算
,然后在反向传播中计算
#前向传播 J = theta * x
def forward_propagation(x, theta):
J = np.dot(theta,x)
return J
#反向传播dtheta即为导数(梯度)
def backward_propagation(x, theta):
dtheta = x
return dtheta
来看一下详细的计算步骤:
首先是计算导数(梯度)值"gradapprox"
def gradient_check(x, theta, epsilon = 1e-7):
# Compute gradapprox using left side of formula (1). epsilon is small enough, you don't need to worry about the limit.
thetaplus = theta + epsilon # Step 1
thetaminus = theta - epsilon # Step 2
J_plus = forward_propagation(x, thetaplus) # Step 3
J_minus = forward_propagation(x, thetaminus) # Step 4
gradapprox = (J_plus - J_minus) / (2 * epsilon) # Step 5
# Compute grad in the backward propagation
grad = backward_propagation(x, theta)
# Check if gradapprox is close enough to the output of backward_propagation()
numerator = np.linalg.norm(grad - gradapprox) # Step 1'
denominator = np.linalg.norm(grad) + np.linalg.norm(gradapprox) # Step 2'
difference = numerator / denominator # Step 3'
if difference < 1e-7:
print ("The gradient is correct!")
else:
print ("The gradient is wrong!")
return difference
测试一下
if __name__ == '__main__':
x, theta = 2, 4
difference = gradient_check(x, theta)
print("difference = " + str(difference))
结果如下
The gradient is correct!
difference = 2.919335883291695e-10
可以看到,结果显示梯度的计算是正确的,因为误差小于阈值。
那么,在一般的情况下,输入都是高于一维的。在训练神经网络的时候,实际上模型参数都是多维矩阵形式的权重 和偏置 ,接下来是多维梯度校验的内容!
多维梯度校验
下图表示的是一个三层神经网络的梯度校验过程:
# DNN的前向传播
def forward_propagation_n(X, Y, parameters):
# retrieve parameters
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 = relu(Z1)
Z2 = np.dot(W2, A1) + b2
A2 = relu(Z2)
Z3 = np.dot(W3, A2) + b3
A3 = sigmoid(Z3)
# Cost
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
# DNN的反向传播
def backward_propagation_n(X, Y, cache):
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)
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) * 2 # Hint
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 = 4./m * np.sum(dZ1, axis=1, keepdims = True) # Hint
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"与反向传播的梯度,公式仍然是
这里不同的是,不再是标量,而是一个名为"parameters"的字典。下面是一个函数"dictionary_to_vector()",它通过将参数(W1, b1, W2, b2, W3, b3)整形并连接来完成从字典"parameters"到向量"values"的转换。
同样的,在反向传播中会产生字典"gradients",我会用函数"gradients_to_vector()"将它转换为向量"grad",实现代码如下
# 向量转换为字典
def vector_to_dictionary(theta):
parameters = {}
parameters["W1"] = theta[:20].reshape((5,4))
parameters["b1"] = theta[20:25].reshape((5,1))
parameters["W2"] = theta[25:40].reshape((3,5))
parameters["b2"] = theta[40:43].reshape((3,1))
parameters["W3"] = theta[43:46].reshape((1,3))
parameters["b3"] = theta[46:47].reshape((1,1))
return parameters
def gradients_to_vector(gradients):
count = 0
for key in ["dW1", "db1", "dW2", "db2", "dW3", "db3"]:
# flatten parameter
new_vector = np.reshape(gradients[key], (-1,1))
if count == 0:
theta = new_vector
else:
theta = np.concatenate((theta, new_vector), axis=0)
count = count + 1
return theta
下面是高维梯度校验的详细步骤:
For each i in num_parameters:
这里会得到梯度的近似计算值向量"gradapprox",通过反向传播会得到梯度值向量"grad",比较两个向量并计算误差即可。
def gradient_check_n(parameters, gradients, X, Y, epsilon = 1e-7):
# Set-up variables
parameters_values, _ = dictionary_to_vector(parameters)
grad = 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))
# Compute gradapprox
for i in range(num_parameters):
# Compute J_plus[i]. Inputs: "parameters_values, epsilon". Output = "J_plus[i]".
# "_" is used because the function you have to outputs two parameters but we only care about the first one
thetaplus = np.copy(parameters_values) # Step 1
thetaplus[i][0] = thetaplus[i][0] + epsilon # Step 2
J_plus[i], _ = forward_propagation_n(X,Y,vector_to_dictionary(thetaplus)) # Step 3
# Compute J_minus[i]. Inputs: "parameters_values, epsilon". Output = "J_minus[i]".
thetaminus = np.copy(parameters_values) # Step 1
thetaminus[i][0] = thetaminus[i][0] - epsilon # Step 2
J_minus[i], _ = forward_propagation_n(X,Y,vector_to_dictionary(thetaminus))# Step 3
# Compute gradapprox[i]
gradapprox[i] = (J_plus[i] - J_minus[i]) / (2 * epsilon)
# Compare gradapprox to backward propagation gradients by computing difference.
numerator = np.linalg.norm(grad - gradapprox) # Step 1'
denominator = np.linalg.norm(grad) + np.linalg.norm(gradapprox) # Step 2'
difference = numerator / denominator # Step 3'
if difference > 1e-7:
print ("There is a mistake in the backward propagation! difference = " + str(difference))
else:
print ("Your backward propagation works perfectly fine! difference = " + str(difference) )
return difference
测试一下 测试数据集下载
X, Y, parameters = gradient_check_n_test_case()
cost, cache = forward_propagation_n(X, Y, parameters)
gradients = backward_propagation_n(X, Y, cache)
difference = gradient_check_n(parameters, gradients, X, Y)
结果如下
There is a mistake in the backward propagation! difference = 0.2850931567761624
可以看到,这里梯度校验显示出错,因为吴恩达老师在反向传播"backward_propagation_n()"里面故意写了两个错误,感兴趣的朋友可以找一下,改了之后的输出就是下面这样的:
Your backward propagation works perfectly fine! difference = 1.1890913023330276e-07
注意
- 梯度校验是用来检验反向传播的梯度与梯度的数值近似解(用前向传播来计算)之间的接近程度;
- 要在没有应用dropout功能的情况下运行梯度校验算法,否则会出现参数向量长度错误;
- 梯度校验很慢,因此不会在每次训练迭代时运行。通常梯度校验只是用来确保代码的正确性,然后再将梯度校验关闭进行实际的神经网络训练
四、总结
最后使用一个表格来总结一下我们的模型情况,如表所示,使用了正则化可以提供测试的准确率。