1.任务 学习Gradient Descent 、Mini-Batch Gradient descent 、Momentum 、Adam(RMSprop和Momentum的结合)
2.准备工作
1 2 3 4 5 6 7 8 9 10 import numpy as npimport matplotlib.pyplot as pltimport scipy.ioimport mathimport sklearnimport sklearn.datasetsfrom opt_utils import load_params_and_grads, initialize_parameters, forward_propagation, backward_propagationfrom opt_utils import compute_cost, predict, predict_dec, plot_decision_boundary, load_datasetfrom testCases import *
3.四种算法介绍 3.1Gradient Descent Gradient Descent是机器学习中一个简单的优化算法,简称GD。当对每个步骤上的所有 示例采取梯度步骤时,它也称为批量梯度下降。
实现梯度下降更新规则。梯度下降规则是,对于 : 来说
在这段话中,“L”表示层数,“ ”表示学习率。所有参数应该存储在parameters
字典中。注意迭代器l
在for
循环中从0开始,而第一个参数是 和 。编码时需要将l
转换为l+1
。
下面我们实现这个算法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 def update_parameters_with_gd (parameters, grads, learning_rate ): """ 使用梯度下降的一步来更新参数 参数: parameters -- 包含要更新的参数的python字典: parameters['W' + str(l)] = Wl parameters['b' + str(l)] = bl grads -- 包含用于更新每个参数的梯度的python字典: grads['dW' + str(l)] = dWl grads['db' + str(l)] = dbl learning_rate -- 学习率,标量。 返回: parameters -- 包含更新后参数的python字典 """ L = len (parameters) // 2 for i in range (L): parameters["W" + str (i + 1 )] -= learning_rate * grads["dW" + str (i + 1 )] parameters["b" + str (i + 1 )] -= learning_rate * grads["db" + str (i + 1 )] return parameters
1 2 3 4 5 6 7 8 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" ]))
1 2 3 4 5 6 7 8 9 10 11 12 13 W1 = [[ 1.63535156, -0.62320365, -0.53718766], [-1.07799357, 0.85639907, -2.29470142]] b1 = [[ 1.74604067], [-0.75184921]] W2 = [[ 0.32171798, -0.25467393, 1.46902454], [-2.05617317, -0.31554548, -0.3756023 ], [ 1.1404819, -1.09976462, -0.1612551 ]] b2 = [[-0.88020257], [ 0.02561572], [ 0.57539477]]
这里有一种变体叫做随机梯度下降(SGD),它等同于小批量梯度下降,但每个小批量仅包含一个样本。我刚刚实现的更新规则并不会改变。改变的是,我将一次只对一个训练样本计算梯度,而不是对整个训练集计算。下面的代码示例展示了随机梯度下降和(批量)梯度下降之间的区别。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 X = data_input Y = labels parameters = initialize_parameters(layers_dims) for i in range (0 , num_iterations): a, caches = forward_propagation(X, parameters) cost = compute_cost(a, Y) grads = backward_propagation(a, caches, parameters) parameters = update_parameters(parameters, grads) X = data_input Y = labels parameters = initialize_parameters(layers_dims) for i in range (0 , num_iterations): for j in range (0 , m): a, caches = forward_propagation(X[:,j], parameters) cost = compute_cost(a, Y[:,j]) grads = backward_propagation(a, caches, parameters) parameters = update_parameters(parameters, grads)
在随机梯度下降中,更新梯度之前只使用一个训练样本。当训练集很大时,随机梯度下降可以更快。但是,参数会向最小值“振荡”,而不是平滑地收敛。这里有一个这种情况的示意图:
在实际应用中,如果既不使用整个训练集,也不仅使用一个训练样本来进行每次更新,通常会得到更快的结果。Mini-Batch Gradient descent使用中间数量的样本来进行每一步的更新。使用Mini-Batch Gradient descent时,训练时会遍历小批量样本而不是单个训练样本。
我们应该额外关注的是:
梯度下降、小批量梯度下降和随机梯度下降之间的区别在于你用来进行一次更新步骤的样本数量。
你需要调整学习率超参数 。
使用一个调整得当的小批量大小,通常它会优于梯度下降或随机梯度下降(特别是当训练集很大时)
3.2Mini-Batch Gradient descent 该算法主要分为两步:
如下所示,创建一个训练集(X, Y)的打乱版本。X和Y的每一列代表一个训练样本。注意,X和Y之间的随机洗牌是同步进行的。这样在洗牌后,X的第 列是与Y中第 标签对应的样本。洗牌步骤确保样本将随机分配到不同的小批量中。
将打乱的(X, Y)分割成大小为mini_batch_size
(这里是64)的小批量。注意,训练样本的数量并不总是能被mini_batch_size
整除。最后一个小批量可能会更小,但这不需要担心。当最终的小批量小于完整的mini_batch_size
时,它将如下所示:
下面我们实现这个算法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 def random_mini_batches (X, Y, mini_batch_size = 64 , seed = 0 ): """ 从 (X, Y) 中创建随机小批量的列表 参数: X -- 输入数据,形状为 (输入大小, 样本数量) Y -- 真实的“标签”向量(蓝点为1 / 红点为0),形状为 (1, 样本数量) mini_batch_size -- 小批量的大小,整数 返回: mini_batches -- 同步的 (mini_batch_X, mini_batch_Y) 列表 """ 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
1 2 3 4 5 6 7 8 9 10 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 ]))
1 2 3 4 5 6 7 shape of the 1st mini_batch_X: (12288, 64) shape of the 2nd mini_batch_X: (12288, 64) shape of the 3rd mini_batch_X: (12288, 20) shape of the 1st mini_batch_Y: (1, 64) shape of the 2nd mini_batch_Y: (1, 64) shape of the 3rd mini_batch_Y: (1, 20) mini batch sanity check: [ 0.90085595 -0.7612069 0.2344157 ]
3.3Momentum 由于Mini-Batch Gradient descent在仅看到示例子集后进行参数更新,因此更新的方向存在一定方差,因此Mini-Batch Gradient descent所采取的路径将“振荡”收敛。 利用Momentum可以减少这些振荡。
Momentum考虑了过去的梯度来平滑更新。 我们将先前梯度的“方向”存储在变量 中。 形式上,这将是先前步骤梯度的指数加权平均值。 您还可以将 视为滚下山的球的“速度”,根据山坡的梯度/坡度方向增加速度(和动量)。
下面我们实现这个算法:
首先是初始化速度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 def initialize_velocity (parameters ): """ 以python字典的形式初始化速度,其中: - 键: "dW1", "db1", ..., "dWL", "dbL" - 值:与相应梯度/参数形状相同的零值numpy数组。 参数: parameters -- 包含你的参数的python字典。 parameters['W' + str(l)] = Wl parameters['b' + str(l)] = bl 返回: v -- 包含当前速度的python字典。 v['dW' + str(l)] = dWl的速度 v['db' + str(l)] = dbl的速度 """ L = len (parameters) // 2 v = {} for i in range (L): v['dW' + str (i + 1 )] = np.zeros(parameters["W" + str (i + 1 )].shape) v['db' + str (i + 1 )] = np.zeros(parameters["b" + str (i + 1 )].shape) return v
1 2 3 4 5 6 7 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" ]))
1 2 3 4 5 6 7 8 9 10 11 12 13 v["dW1"] = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]] v["db1"] = [[0.0], [0.0]] v["dW2"] = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]] v["db2"] = [[0.0], [0.0], [0.0]]
接着是momentum算法实现参数的更新。动量更新规则是,对于 :
其中L是层数, 是动量, 是学习率。所有参数应该存储在parameters
字典中。注意迭代器l
在for
循环中从0开始,而第一个参数是 和 (这里的上标是“一”)。因此,在编码时需要将l
改为l+1
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 def update_parameters_with_momentum (parameters, grads, v, beta, learning_rate ): """ 使用动量方法更新参数 参数: parameters -- 包含你的参数的python字典: parameters['W' + str(l)] = Wl parameters['b' + str(l)] = bl grads -- 包含每个参数梯度的python字典: grads['dW' + str(l)] = dWl grads['db' + str(l)] = dbl v -- 包含当前速度的python字典: v['dW' + str(l)] = ... v['db' + str(l)] = ... beta -- 动量超参数,标量 learning_rate -- 学习率,标量 返回: parameters -- 包含更新后参数的python字典 v -- 包含更新后速度的python字典 """ L = len (parameters) // 2 for i in range (L): v['dW' + str (i + 1 )] = beta * v['dW' + str (i + 1 )] + (1 - beta) * grads['dW' + str (i + 1 )] v['db' + str (i + 1 )] = beta * v['db' + str (i + 1 )] + (1 - beta) * grads['db' + str (i + 1 )] parameters['W' + str (i + 1 )] -= learning_rate * v['dW' + str (i + 1 )] parameters['b' + str (i + 1 )] -= learning_rate * v['db' + str (i + 1 )] return parameters, v
1 2 3 4 5 6 7 8 9 10 11 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" ]))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 W1 = [[ 1.62544598, -0.61290114, -0.52907334], [-1.07347112, 0.86450677, -2.30085497]] b1 = [[ 1.74493465], [-0.76027113]] W2 = [[ 0.31930698, -0.24990073, 1.4627996 ], [-2.05974396, -0.32173003, -0.38320915], [ 1.13444069, -1.0998786, -0.1713109 ]] b2 = [[-0.87809283], [ 0.04055394], [ 0.58207317]] v["dW1"] = [[-0.11006192, 0.11447237, 0.09015907], [ 0.05024943, 0.09008559, -0.06837279]] v["db1"] = [[-0.01228902], [-0.09357694]] v["dW2"] = [[-0.02678881, 0.05303555, -0.06916608], [-0.03967535, -0.06871727, -0.08452056], [-0.06712461, -0.00126646, -0.11173103]] v["db2"] = [[ 0.02344157], [ 0.16598022], [ 0.07420442]]
这个算法需要额外注意的是 :
速度是以0 开始初始化的。因此,算法需要几次迭代来“积累”速度并开始采取更大的步伐。
如果 ,那么这就变成了没有动量的标准梯度下降。
如何选择 ?
动量 越大,更新就越平滑,因为我们考虑了更多过去的梯度。但是,如果 太大,也可能过度平滑更新。
的常见值范围从 0.8 到 0.999。如果你不想调整这个值, 通常是一个合理的默认选择。
为你的模型调整最优的 可能需要尝试多个值,以查看哪个在降低成本函数 的值方面效果最好。
3.4Adam
它计算过去梯度的指数加权平均值,并将其存储在变量 (未进行偏差校正前)和 (进行偏差校正后)中。
它计算过去梯度平方的指数加权平均值,并将其存储在变量 (未进行偏差校正前)和 (进行偏差校正后)中。
它基于结合“1”和“2”的信息来更新参数。
更新规则,对于 :
其中:
t 表示 Adam 执行的步数
L 是层数
和 是控制两个指数加权平均的超参数。
是学习率
是一个非常小的数,用来避免除以零 像往常一样,我们会将所有参数存储在 parameters
字典中。
下面我们实现这个算法:
首先是初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 def initialize_adam (parameters ) : """ 以两个python字典的形式初始化 v 和 s: - 键: "dW1", "db1", ..., "dWL", "dbL" - 值:形状与相应梯度/参数相同的零值numpy数组。 参数: parameters -- 包含你的参数的python字典。 parameters["W" + str(l)] = Wl parameters["b" + str(l)] = bl 返回: v -- 将包含梯度指数加权平均的python字典。 v["dW" + str(l)] = ... v["db" + str(l)] = ... s -- 将包含平方梯度指数加权平均的python字典。 s["dW" + str(l)] = ... s["db" + str(l)] = ... """ L = len (parameters) // 2 v = {} s = {} for i in range (L): v["dW" + str (i + 1 )] = np.zeros(parameters["W" + str (i + 1 )].shape) v["db" + str (i + 1 )] = np.zeros(parameters["b" + str (i + 1 )].shape) s["dW" + str (i + 1 )] = np.zeros(parameters["W" + str (i + 1 )].shape) s["db" + str (i + 1 )] = np.zeros(parameters["b" + str (i + 1 )].shape) return v, s
1 2 3 4 5 6 7 8 9 10 11 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" ]))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 v["dW1"] = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]] v["db1"] = [[0.0], [0.0]] v["dW2"] = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]] v["db2"] = [[0.0], [0.0], [0.0]] s["dW1"] = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]] s["db1"] = [[0.0], [0.0]] s["dW2"] = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]] s["db2"] = [[0.0], [0.0], [0.0]]
接着我们利用写好的初始化函数和上述提到的Adam算法公式来写出对应的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 def update_parameters_with_adam (parameters, grads, v, s, t, learning_rate = 0.01 , beta1 = 0.9 , beta2 = 0.999 , epsilon = 1e-8 ): """ 使用Adam方法更新参数 参数: parameters -- 包含你的参数的python字典: parameters['W' + str(l)] = Wl parameters['b' + str(l)] = bl grads -- 包含每个参数梯度的python字典: grads['dW' + str(l)] = dWl grads['db' + str(l)] = dbl v -- Adam变量,第一梯度的移动平均值,python字典 s -- Adam变量,平方梯度的移动平均值,python字典 learning_rate -- 学习率,标量。 beta1 -- 第一时刻估计的指数衰减超参数 beta2 -- 第二时刻估计的指数衰减超参数 epsilon -- 防止Adam更新中除以零的超参数 返回: parameters -- 包含更新后参数的python字典 v -- Adam变量,第一梯度的移动平均值,python字典 s -- Adam变量,平方梯度的移动平均值,python字典 """ L = len (parameters) // 2 v_corrected = {} s_corrected = {} for i in range (L): v["dW" + str (i + 1 )] = beta1 * v["dW" + str (i + 1 )] + (1 - beta1) * grads["dW" + str (i + 1 )] v["db" + str (i + 1 )] = beta1 * v["db" + str (i + 1 )] + (1 - beta1) * grads["db" + str (i + 1 )] v_corrected["dW" + str (i + 1 )] = v["dW" + str (i + 1 )] / (1 - beta1 ** t) v_corrected["db" + str (i + 1 )] = v["db" + str (i + 1 )] / (1 - beta1 ** t) s["dW" + str (i + 1 )] = beta2 * s["dW" + str (i + 1 )] + (1 - beta2) * np.multiply(grads["dW" + str (i + 1 )], grads["dW" + str (i + 1 )]) s["db" + str (i + 1 )] = beta2 * s["db" + str (i + 1 )] + (1 - beta2) * np.multiply(grads["db" + str (i + 1 )], grads["db" + str (i + 1 )]) s_corrected["dW" + str (i + 1 )] = s["dW" + str (i + 1 )] / (1 - beta2 ** t) s_corrected["db" + str (i + 1 )] = s["db" + str (i + 1 )] / (1 - beta2 ** t) parameters["W" + str (i + 1 )] -= learning_rate * v_corrected["dW" + str (i + 1 )] / (epsilon + np.sqrt(s_corrected["dW" + str (i + 1 )])) parameters["b" + str (i + 1 )] -= learning_rate * v_corrected["db" + str (i + 1 )] / (epsilon + np.sqrt(s_corrected["db" + str (i + 1 )])) return parameters, v, s
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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" ]))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 W1 = [[ 1.63178673, -0.61919778, -0.53561312], [-1.08040999, 0.85796626, -2.29409733]] b1 = [[ 1.75225313], [-0.75376553]] W2 = [[ 0.32648046, -0.25681174, 1.46954931], [-2.05269934, -0.31497584, -0.37661299], [ 1.14121081, -1.09244991, -0.16498684]] b2 = [[-0.88529979], [ 0.03477238], [ 0.57537385]] v["dW1"] = [[-0.11006192, 0.11447237, 0.09015907], [ 0.05024943, 0.09008559, -0.06837279]] v["db1"] = [[-0.01228902], [-0.09357694]] v["dW2"] = [[-0.02678881, 0.05303555, -0.06916608], [-0.03967535, -0.06871727, -0.08452056], [-0.06712461, -0.00126646, -0.11173103]] v["db2"] = [[ 0.02344157], [ 0.16598022], [ 0.07420442]] s["dW1"] = [[0.00121136, 0.00131039, 0.00081287], [0.0002525, 0.00081154, 0.00046748]] s["db1"] = [[1.51020075e-05], [8.75664434e-04]] s["dW2"] = [[7.17640232e-05, 2.81276921e-04, 4.78394595e-04], ... [4.50571368e-04, 1.60392066e-07, 1.24838242e-03]] s["db2"] = [[5.49507194e-05], [2.75494327e-03], [5.50629536e-04]]
4四种算法在模型中的可视化对比 4.1准备工作
1 train_X, train_Y = load_dataset()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def plot_decision_boundary (model, X, y ): x_min, x_max = X[0 , :].min () - 1 , X[0 , :].max () + 1 y_min, y_max = X[1 , :].min () - 1 , X[1 , :].max () + 1 h = 0.01 xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h)) Z = model(np.c_[xx.ravel(), yy.ravel()]) Z = Z.reshape(xx.shape) plt.contourf(xx, yy, Z, cmap=plt.cm.Spectral) plt.ylabel('x2' ) plt.xlabel('x1' ) y = y.reshape(X[0 ,:].shape) plt.scatter(X[0 , :], X[1 , :], c=y, cmap=plt.cm.Spectral) plt.show()
4.2定义模型 我们已经实现了一个三层神经网络。你将用以下方法来训练它:
Mini-batch Gradient Descent:它会调用你的函数:
update_parameters_with_gd()
Mini-batchMomentum:它会调用你的函数:
initialize_velocity()
和 update_parameters_with_momentum()
Mini-batch Adam:它会调用你的函数:
initialize_adam()
和 update_parameters_with_adam()
模型代码 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 def model (X, Y, layers_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 ): """ 可以在不同优化器模式下运行的三层神经网络模型。 参数: X -- 输入数据,形状为 (2, 样本数量) Y -- 真实的“标签”向量(蓝点为1 / 红点为0),形状为 (1, 样本数量) layers_dims -- python列表,包含每层的大小 learning_rate -- 学习率,标量。 mini_batch_size -- 小批量的大小 beta -- 动量超参数 beta1 -- 过去梯度估计的指数衰减超参数 beta2 -- 过去平方梯度估计的指数衰减超参数 epsilon -- 防止Adam更新中除以零的超参数 num_epochs -- 迭代次数 print_cost -- 如果为True,每1000次迭代打印一次成本 返回: parameters -- 包含更新后参数的python字典 """ L = len (layers_dims) costs = [] t = 0 seed = 10 parameters = initialize_parameters(layers_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 ("迭代次数 %i 后的成本: %f" % (i, cost)) if print_cost and i % 100 == 0 : costs.append(cost) plt.plot(costs) plt.ylabel('成本' ) plt.xlabel('迭代次数(每100次)' ) plt.title("学习率 = " + str (learning_rate)) plt.show() return parameters
4.3对比算法效果 4.3.1Mini-batch Gradient descent 1 2 3 4 5 6 7 8 9 10 11 12 13 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)
Accuracy: 0.7966666666666666
4.3.2Mini-batch gradient descent with momentum 1 2 3 4 5 6 7 8 9 10 11 12 13 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)
Accuracy: 0.7966666666666666
4.3.3Mini-batch with Adam mode 1 2 3 4 5 6 7 8 9 10 11 12 13 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)
Accuracy: 0.94
5.总结 Momentum通常有所帮助,但鉴于较小的学习率和简单的数据集,其影响几乎可以忽略不计。此外,你在成本中看到的巨大波动来自于一些小批量对优化算法来说比其他批量更难处理的事实。
另一方面,Adam明显优于mini-batch gradient descent和Momentum。如果在这个简单的数据集上运行更多的迭代,所有三种方法都会导致非常好的结果。然而,你已经看到Adam收敛得更快。
Adam的一些优点包括:
相对较低的内存要求(尽管高于梯度下降和带动量的梯度下降)
通常即使在很少调整超参数的情况下也能表现良好( 除外)