版权为吴恩达老师所有,参考Koala_Tree的博客,部分根据自己实践添加
使用google翻译,部分手工翻译
你可能需要的参考资料https://pan.baidu.com/s/18Br4OAowA3yMKEADiC24kg
优化方法
到目前为止,您始终使用Gradient Descent更新参数并最大限度地降低成本。在这个笔记本中,您将学习更多高级优化方法,这些方法可以加快学习速度,甚至可以让您获得更好的成本函数最终值。拥有一个好的优化算法可能是等待天数与短短几个小时之间的差异,以获得良好的结果。
梯度下降在成本函数上“下坡”。把它想象成试图这样做:
图1 :最小化成本就像找到丘陵景观中的最低点在
训练的每一步,您都会按照某个方向更新参数,以尝试达到最低点。
符号:像往常一样,对于任何变量a
首先,运行以下代码以导入您需要的库。
import numpy as np
import matplotlib.pyplot as plt
import math
import scipy.io
import sklearn
import sklearn.datasets
from opt_utils import load_params_and_grads,initialize_parameters,forward_propagation,backward_propagation
from opt_utils import compute_cost,predict,predict_dec,plot_decision_boundary,load_dataset
from testCases import *
plt.rcParams['figure.figsize'] = (7.0, 4.0) # set default size of plots
plt.rcParams['image.interpolation'] = 'nearest'
plt.rcParams['image.cmap'] = 'gray'
1 - 梯度下降
机器学习中的一种简单优化方法是梯度下降(GD)。当您一次对所有m个示例采取梯度下降步骤时,也称为Batch Gradient Descen。
热身练习:实施梯度下降更新规则。梯度下降规则是,对于:
其中L是层数和α是学习率。所有参数都应存储在parameters
字典中。请注意,迭代器l
在for
循环中从0开始,而第一个参数是和。在编程时您需要将l
转换到l+1
。
def update_parameters_with_gd(parameters,grads,learning_rate):
L=len(parameters)//2
for l in range(L):
parameters["W"+str(l+1)]=parameters["W"+str(l+1)]-learning_rate*grads["dW"+str(l+1)]
parameters["b"+str(l+1)]=parameters["b"+str(l+1)]-learning_rate*grads["db"+str(l+1)]
return parameters
parameters, grads, learning_rate = update_parameters_with_gd_test_case()
parameters = update_parameters_with_gd(parameters, grads, learning_rate)
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))
其中一个变体是随机梯度下降(SGD),相当于mini-batch梯度下降,其中每个mini-batch只有一个例子。您刚刚实施的更新规则不会更改。您将在一次只计算一个训练示例而不是整个训练集上计算梯度的变化是什么。下面的代码示例说明了随机梯度下降和(batch)梯度下降之间的差异。
- (batch)渐变下降:
X=data_input
Y=labels
parameteres=initialize_parameters(layers_dims)
for i in range(0,num_iterations):
a,caches=forward_propagation(X,parameteres)
cost=compute_cost(a,Y)
grads=backward_propagation(a,caches,parameteres)
parameteres=update_parameters(parameteres,grads)
- 随机梯度下降:
X=data_input
Y=labels
parameteres=initialize_parameters(layers_dims)
for i in range(0,num_iterations):
for j in range(0,m):
a,caches=forward_propagations(X[:,j],parameteres)
cost=compute_cost(a,Y[:,j])
grads=backward_propagation(a,caches,parameteres)
parameteres=update_parameters(parameteres,grads)
在Stochastic Gradient Descent中,在更新渐变之前,您只使用1个训练示例。当训练集很大时,SGD可以更快。但是参数将“振荡”到最小值而不是平滑地收敛。以下是对此的说明:
图1 : SGD与GD
“+”表示成本的最小值。SGD导致许多振荡达到收敛。但是,为SGD计算每一步的速度比使用GD的速度快得多,因为它仅使用一个训练示例(相对于GD的整批)。
另请注意,实现SGD总共需要3个for循环:
1.迭代次数
2.m个训练示例
3.在各层(更新所有参数,从到
实际上,如果您既不使用整个训练集,也不使用一个训练示例来执行每次更新,则通常会获得更快的结果。mini-batch梯度下降使用每个步骤的中间数量的示例。通过mini-batch梯度下降,您可以循环使用mini-batch,而不是循环遍历各个训练示例。
图2 : SGD与小批量GD “+”表示成本的最小值。在优化算法中使用小批量通常可以加快优化速度。
您应该记住:
- 梯度下降,mini-batch梯度下降和随机梯度下降之间的差异是您用于执行一个更新步骤的示例数。
- 你必须调整学习率超参数α。
- 具有良好的mini-batch大小,通常它优于梯度下降或随机梯度下降(特别是当训练集很大时)。
2 - Mini-Batch Gradient下降
让我们学习如何从训练集(X,Y)构建mini-batches。
有两个步骤:
- 重新分布:创建训练集(X,Y)的混洗版本,如下所示。X和Y的每一列代表一个训练样例。注意,随机改组是在X和Y之间同步完成的。这样在重新分布之后X的列是对应于 Y中的标签。重新分布步骤确保将示例随机分成不同的mini-batches。
- 分区:将混洗(X,Y)分区为
mini_batch_size
(此处为64)。请注意,训练示例的数量并不总是可以被整除mini_batch_size
。最后一个mini-batches可能更小,但您不必担心这一点。当最终的mini-batches小于满的时候mini_batch_size
,它看起来像这样:
练习:实施random_mini_batches
。我们为您编写了重新分布部分。为了帮助您完成分区步骤,我们为您提供以下代码,用于选择1的索引为和的mini-batches:
first_mini_batch_X = shuffled_X[:, 0 : mini_batch_size]
second_mini_batch_X = shuffled_X[:, mini_batch_size : 2 * mini_batch_size]
...
请注意,最后一个mini-batches可能小于mini_batch_size=64
。让代表s向下舍入到最接近的整数(这是math.floor(s)
在Python中)。如果实例的总数不是mini_batch_size=64
的倍数,则会有个具有完整的64个例子的mini-batch,最后的mini-batch的例子数量将是.
def random_mini_batches(X,Y,mini_batch_size=64,seed=0):
np.random.seed(seed)
m=X.shape[1]
mini_batches=[]
permutation=list(np.random.permutation(m))
shuffled_X=X[:,permutation]
shuffled_Y=Y[:,permutation].reshape((1,m))
num_complete_minibatches=math.floor(m/mini_batch_size)
for k in range(0,num_complete_minibatches):
mini_batch_X=shuffled_X[:,k*mini_batch_size:(k+1)*mini_batch_size]
mini_batch_Y=shuffled_Y[:,k*mini_batch_size:(k+1)*mini_batch_size]
mini_batch=(mini_batch_X,mini_batch_Y)
mini_batches.append(mini_batch)
if m%mini_batch_size!=0:
mini_batch_X=shuffled_X[:,num_complete_minibatches*mini_batch_size:m]
mini_batch_Y=shuffled_Y[:,num_complete_minibatches*mini_batch_size:m]
mini_batch=(mini_batch_X,mini_batch_Y)
mini_batches.append(mini_batch)
return mini_batches
X_assess, Y_assess, mini_batch_size = random_mini_batches_test_case()
mini_batches = random_mini_batches(X_assess, Y_assess, mini_batch_size)
print ("shape of the 1st mini_batch_X: " + str(mini_batches[0][0].shape))
print ("shape of the 2nd mini_batch_X: " + str(mini_batches[1][0].shape))
print ("shape of the 3rd mini_batch_X: " + str(mini_batches[2][0].shape))
print ("shape of the 1st mini_batch_Y: " + str(mini_batches[0][1].shape))
print ("shape of the 2nd mini_batch_Y: " + str(mini_batches[1][1].shape))
print ("shape of the 3rd mini_batch_Y: " + str(mini_batches[2][1].shape))
print ("mini batch sanity check: " + str(mini_batches[0][0][0][0:3]))
您应该记住的事项:
- 重新分布和分区是构建mini-batch所需的两个步骤
- 通常选择2的指数作为mini-batch大小,例如16,32,64,128。
3 - Momentum
因为mini-batch梯度下降在仅仅看到一个例子的子集之后进行参数更新,所以更新的方向具有一些变化,因此mini-batch梯度下降所采用的路径将“摆动”着收敛。使用momentum可以减少这些振荡。
Momentum考虑了过去的梯度以平滑更新。我们将先前梯度的“方向”存储在变量v中。形式上,这将是先前步骤的梯度的指数加权平均值。你也可以想到v 作为球下坡的“速度”,根据坡度/坡度的方向建立速度(和momentum动量)。
图3:红色箭头显示了具有动量的小批量梯度下降的一步所采取的方向。蓝点表示每一步的梯度方向(相对于当前的小批量)。我们让梯度影响v,而不仅仅是跟随梯度v然后向v的方向迈出一步v.
练习:初始化速度。速度v是一个python字典,需要用零数组初始化。它的键与grads
字典中的键相同,即:
v["dW" + str(l+1)] = ...
v["db" + str(l+1)] = ...
注意,迭代器l在for循环中从0开始,而第一个参数是v [“dW1”]和v [“db1”](上标是“1”)。这就是我们在for
循环中将l转换为l + 1的原因。
def initialize_velocity(parameters):
L=len(parameters)//2
v={}
for l in range(L):
v["dW"+str(l+1)]=np.zeros(parameters["W"+str(l+1)].shape)
v["db"+str(l+1)]=np.zeros(parameters["b"+str(l+1)].shape)
return v
parameters = initialize_velocity_test_case()
v = initialize_velocity(parameters)
print("v[\"dW1\"] = " + str(v["dW1"]))
print("v[\"db1\"] = " + str(v["db1"]))
print("v[\"dW2\"] = " + str(v["dW2"]))
print("v[\"db2\"] = " + str(v["db2"]))
练习:现在,用momentum实现参数更新。momentum更新规则是,对于:
其中L是层数,β是momentum和α是学习率。所有参数都应存储在parameters
字典中。请注意,迭代器l
在for
循环中从0开始,而第一个参数是和。在编程时您需要将l
转换到l+1
def update_parameters_with_momentum(parameters,grads,v,beta,learning_rate):
L=len(parameters)//2
for l in range(L):
v["dW"+str(l+1)]=beta*v["dW"+str(l+1)]+(1-beta)*grads["dW"+str(l+1)]
v["db"+str(l+1)]=beta*v["db"+str(l+1)]+(1-beta)*grads["db"+str(l+1)]
parameters["W"+str(l+1)]=parameters["W"+str(l+1)]-learning_rate*v["dW"+str(l+1)]
parameters["b"+str(l+1)]=parameters["b"+str(l+1)]-learning_rate*v["db"+str(l+1)]
return parameters,v
parameters, grads, v = update_parameters_with_momentum_test_case()
parameters, v = update_parameters_with_momentum(parameters, grads, v, beta = 0.9, learning_rate = 0.01)
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))
print("v[\"dW1\"] = " + str(v["dW1"]))
print("v[\"db1\"] = " + str(v["db1"]))
print("v[\"dW2\"] = " + str(v["dW2"]))
print("v[\"db2\"] = " + str(v["db2"]))
请注意:
- 使用零初始化速度。因此,该算法将需要几次迭代来“建立”速度并开始采取更大的步骤。
- 如果β=0那么这只是没有momentum的标准梯度下降。
你怎么选择β?
- momentum β越大,更新越平滑,因为我们考虑过去的梯度越多。但如果β 太大了,它也可以过多地消除更新。
- β的常见值b范围从0.8到0.999。如果你不想调整这个,β=0.9通常是合理的默认值。
- 调整最佳β对于您的模型可能需要尝试多个值,检验最佳的β的最有效的方法是查看减少的成本函数J的值.
你应该记住的:
- momentum将过去的梯度考虑在内,以平滑梯度下降的步骤。它可以应用于batch 梯度下降,mini-batch梯度下降或随机梯度下降。
- 你必须调整momentum超参数β和学习率α
4 - Adam
Adam是用于训练神经网络的最有效的优化算法之一。它结合了RMSProp(在课程中描述)和Momentum的想法。
Adam是如何工作的?
1.它计算过去梯度的指数加权平均值,并将其存储在变量(在偏差校正之前)和(偏差校正)。
2.它计算过去梯度的平方的指数加权平均值,并将其存储在变量(在偏差校正之前)和(偏差校正)。
3.它基于来自“1”和“2”的组合信息更新方向上的参数。
更新规则是,对于:
其中:
- t计算Adam采取的次数
- L是层数
- 和是超参数,控制两个指数加权平均值。
- α是学习率
- ε是一个非常小的数字,以避免除以零
像往常一样,我们将所有参数存储在parameters
字典中
练习:初始化Adam变量v,s跟踪过去的信息。
说明:变量v,s是需要使用零数组初始化的python词典。它们的键是相同的grads
,即:
对于
v["dW" + str(l+1)] = ...
v["db" + str(l+1)] = ...
s["dW" + str(l+1)] = ...
s["db" + str(l+1)] = ...
def initialize_adam(parameters):
L=len(parameters)//2
v={}
s={}
for l in range(L):
v["dW"+str(l+1)]=np.zeros(parameters["W"+str(l+1)].shape)
v["db"+str(l+1)]=np.zeros(parameters["b"+str(l+1)].shape)
s["dW"+str(l+1)]=np.zeros(parameters["W"+str(l+1)].shape)
s["db"+str(l+1)]=np.zeros(parameters["b"+str(l+1)].shape)
return v,s
parameters = initialize_adam_test_case()
v, s = initialize_adam(parameters)
print("v[\"dW1\"] = " + str(v["dW1"]))
print("v[\"db1\"] = " + str(v["db1"]))
print("v[\"dW2\"] = " + str(v["dW2"]))
print("v[\"db2\"] = " + str(v["db2"]))
print("s[\"dW1\"] = " + str(s["dW1"]))
print("s[\"db1\"] = " + str(s["db1"]))
print("s[\"dW2\"] = " + str(s["dW2"]))
print("s[\"db2\"] = " + str(s["db2"]))
练习:现在,使用Adam实现参数update。回想一下一般的更新规则是,对于:
请注意,迭代器l
在for
循环中从0开始,而第一个参数是和。在编程时您需要将l
转换到l+1
。
def update_parameters_with_adam(parameters,grads,v,s,t,learning_rate=0.01,beta1=0.9,beta2=0.999,epsilon=1e-8):
L=len(parameters)//2
v_corrected={}
s_corrected={}
for l in range(L):
v["dW"+str(l+1)]=beta1*v["dW"+str(l+1)]+(1-beta1)*grads["dW"+str(l+1)]
v["db"+str(l+1)]=beta1*v["db"+str(l+1)]+(1-beta1)*grads["db"+str(l+1)]
v_corrected["dW"+str(l+1)]=v["dW"+str(l+1)]/(1-beta1**t)
v_corrected["db"+str(l+1)]=v["db"+str(l+1)]/(1-beta1**t)
s["dW"+str(l+1)]=beta2*s["dW"+str(l+1)]+(1-beta2)*(grads["dW"+str(l+1)]**2)
s["db"+str(l+1)]=beta2*s["db"+str(l+1)]+(1-beta2)*(grads["db"+str(l+1)]**2)
s_corrected["dW"+str(l+1)]=s["dW"+str(l+1)]/(1-beta2**t)
s_corrected["db"+str(l+1)]=s["db"+str(l+1)]/(1-beta2**t)
parameters["W"+str(l+1)]=parameters["W"+str(l+1)]-learning_rate*(v_corrected["dW"+str(l+1)]/(np.sqrt(s_corrected["dW"+str(l+1)])+epsilon))
parameters["b"+str(l+1)]=parameters["b"+str(l+1)]-learning_rate*(v_corrected["db"+str(l+1)]/(np.sqrt(s_corrected["db"+str(l+1)])+epsilon))
return parameters,v,s
parameters, grads, v, s = update_parameters_with_adam_test_case()
parameters, v, s = update_parameters_with_adam(parameters, grads, v, s, t = 2)
print("W1 = " + str(parameters["W1"]))
print("b1 = " + str(parameters["b1"]))
print("W2 = " + str(parameters["W2"]))
print("b2 = " + str(parameters["b2"]))
print("v[\"dW1\"] = " + str(v["dW1"]))
print("v[\"db1\"] = " + str(v["db1"]))
print("v[\"dW2\"] = " + str(v["dW2"]))
print("v[\"db2\"] = " + str(v["db2"]))
print("s[\"dW1\"] = " + str(s["dW1"]))
print("s[\"db1\"] = " + str(s["db1"]))
print("s[\"dW2\"] = " + str(s["dW2"]))
print("s[\"db2\"] = " + str(s["db2"]))
您现在有三种工作优化算法(mini-batch梯度下降,Momentum,Adam)。让我们用这些优化器中的每一个实现一个模型并观察差异。
5 - 具有不同优化算法的模型
让我们使用以下“moons”数据集来测试不同的优化方法。(数据集名为“moons”,因为来自两个类中的每一个的数据看起来有点像新月形月亮。)
train_X, train_Y = load_dataset()
我们已经实现了3层神经网络。您将训练它:
- Mini-batch Gradient Descent:它将调用您的功能:
- update_parameters_with_gd()
- Mini-batch Momentum:它将调用您的功能:
- initialize_velocity()
和update_parameters_with_momentum()
- Mini-batch Adam:它将调用您的功能:
- initialize_adam()
和update_parameters_with_adam()
def model(X,Y,layer_dims,optimizer,learning_rate=0.0007,mini_batch_size=64,beta=0.9,beta1=0.9,beta2=0.999,epsilon=1e-8,num_epochs=10000,print_cost=True):
L=len(layer_dims)
costs=[]
t=0
seed=10
parameters=initialize_parameters(layer_dims)
if optimizer=="gd":
pass
elif optimizer=="momentum":
v=initialize_velocity(parameters)
elif optimizer=="adam":
v,s=initialize_adam(parameters)
for i in range(num_epochs):
seed=seed+1
minibatches=random_mini_batches(X,Y,mini_batch_size,seed)
for minibatch in minibatches:
(minibatch_X,minibatch_Y)=minibatch
a3,caches=forward_propagation(minibatch_X,parameters)
cost=compute_cost(a3,minibatch_Y)
grads=backward_propagation(minibatch_X,minibatch_Y,caches)
if optimizer=="gd":
parameters=update_parameters_with_gd(parameters,grads,learning_rate)
elif optimizer=="momentum":
parameters,v=update_parameters_with_momentum(parameters,grads,v,beta,learning_rate)
elif optimizer=="adam":
t=t+1
parameters,v,s=update_parameters_with_adam(parameters,grads,v,s,t,learning_rate,beta1,beta2,epsilon)
if print_cost and i%1000 == 0:
print("Cost after epoch %i: %f"%(i,cost))
if print_cost and i%100 == 0:
costs.append(cost)
plt.close()
plt.plot(costs)
plt.ylabel("cost")
plt.xlabel("epochs (per 100)")
plt.title("Learning rate = "+str(learning_rate))
plt.show()
return parameters
现在,您将使用3种优化方法中的每一种运行此3层神经网络。
5.1 - Mini-batch梯度下降
运行以下代码以查看模型如何使用Mini-batch梯度下降。
layers_dims = [train_X.shape[0], 5, 2, 1]
parameters = model(train_X, train_Y, layers_dims, optimizer = "gd")
predictions = predict(train_X, train_Y, parameters)
plt.title("Model with Gradient Descent optimization")
axes = plt.gca()
axes.set_xlim([-1.5,2.5])
axes.set_ylim([-1,1.5])
plot_decision_boundary(lambda x: predict_dec(parameters, x.T), train_X, train_Y)
5.2 - 具有momemtum的Mini-batch梯度下降
运行以下代码以查看模型如何处理momemtum。因为这个例子相对简单,使用momemtum的收益很小; 但对于更复杂的问题,您可能会看到更大的收益。
layers_dims = [train_X.shape[0], 5, 2, 1]
parameters = model(train_X, train_Y, layers_dims, beta = 0.9, optimizer = "momentum")
predictions = predict(train_X, train_Y, parameters)
plt.title("Model with Momentum optimization")
axes = plt.gca()
axes.set_xlim([-1.5,2.5])
axes.set_ylim([-1,1.5])
plot_decision_boundary(lambda x: predict_dec(parameters, x.T), train_X, train_Y)
5.3 - 具有Adam模式的Mini-batch
运行以下代码以查看模型如何处理Adam。
layers_dims = [train_X.shape[0], 5, 2, 1]
parameters = model(train_X, train_Y, layers_dims, optimizer = "adam")
predictions = predict(train_X, train_Y, parameters)
plt.title("Model with Adam optimization")
axes = plt.gca()
axes.set_xlim([-1.5,2.5])
axes.set_ylim([-1,1.5])
plot_decision_boundary(lambda x: predict_dec(parameters, x.T), train_X, train_Y)
5.4 - 总结
**优化方法** | **准确性** | **成本形状** |
梯度下降 | 79.7% | 振荡 |
Momentum | 79.7% | 振荡 |
Adam | 94% | 平滑 |
Momentum通常有所帮助,但鉴于学习率较低且数据集过于简单,其影响几乎可以忽略不计。此外,您在成本中看到的巨大振荡来自于这样的事实:一些小型计算机比其他小型计算机更难以用于优化算法。
另一方面,Adam明显优于mini-batch梯度下降和Momentum。如果您在此简单数据集上运行模型以获得更多世代,则所有这三种方法都将产生非常好的结果。但是,你已经看到Adam收敛得更快。
Adam的一些优点包括:
- 相对较低的内存要求(虽然高于梯度下降和有Momentum的梯度下降)
- 通常即使很少调整超参数也能很好地工作(除了α)