深度学习第二课程笔记-第二周
本文参考何宽所写,点击这里可进入
这部分包括
1. 分割数据集
2. 优化梯度下降算法:
2.1 不使用任何优化算法
2.2 mini-batch梯度下降法
2.3 使用具有动量的梯度下降算法
2.4 使用Adam算法
想象一下成本函数J ,最小化成本就像找到丘陵的最低点,在训练的每一步中,都会按照某个方向更新参数,以尽可能达到最低点。它类似于最快的下山的路,如下图:
数据处理
- 将X,Y数据按照对应数量进行分割,记为x[t],y[t],表示的数量。对应矩阵维数如图所示。
- 这里根据我们切割不同,
- 当我们切割大小
size=m
,即为整个样本时候,为正常梯度下降Gradient Descent
。 - 当我们切割大小
size=1
,即为单个样本时候,叫做随机梯度下降Stochastic Gradient Descent
。 - 当我们切割大小
1<size<m
,既不是选择全部的数据来学习,也不是选择一个样本来学习,而是把所有的数据集分割为一小块一小块的来学习,它会随机选择一小块(mini-batch),块大小一般为2的n次方倍(64,128,256,512)。为小批量梯度下降MiNi-Batch Gradient Descent
。
我们来看下不同梯度下降的比较:
- 在实际中,更好的方法是使用小批量(mini-batch)梯度下降法。一方面,充分利用的GPU的并行性,更一方面,不会让计算时间特别长。
指数加权平均(Exponentially weighted averages)包含动量的梯度下降
由于小批量梯度下降只看到了一个子集的参数更新,更新的方向有一定的差异,所以小批量梯度下降的路径将“振荡地”走向收敛,使用动量可以减少这些振荡,动量考虑了过去的梯度以平滑更新, 我们将把以前梯度的方向
存储在变量v
中,从形式上讲,这将是前面的梯度的指数加权平均值。
可以理解为下一次的方向
是和上一次的方向
有相关性的,受到影响不同,这里影响指的就是权重
。
- 同时注意到
beta=0.9
效果是最好的,vdW
和vdb
初值都为零。
RMSProp算法(Root Mean Square prop)
主要作用,加速梯度下降
Adam算法(Adaptive Moment Estimation )
Adam算法是训练神经网络中最有效的算法之一,它是RMSProp算法与Momentum算法的结合体
可以看到这里,将他们结合了起来,并且在beta选取中,一般选取beta1=0.9(dw)
,beta2=0.999(dw)二次方
,ε=1e-8
参数调试
每个超参数如果设置得不好,都会对训练产生巨大的负面影响,而这些超参数有一个相对重要排名如下图:
学习率alpha
,动量beta
,Adam算法中的beta1,beta2,ε
,层数layers
,隐藏层数节点的数量hidden units
,学习衰退率 learning rate decay
,数据切分大小mini-batch size
等等。- 其中
学习率alpha
是最重要的,其次动量beta
,隐藏层数节点的数量hidden units
,数据切分大小mini-batch size
,再者层数layers
,学习衰退率 learning rate decay
。至于Adam算法中的beta1,beta2,ε
我们基本保持不变
代码测试
在我们之前使用的都是最简单的梯度下降,Gradient Descent
,核心公式为具体含义不再解释,也不再进行测试。
- 我们直接来看 mini-batch梯度下降法
使用mini-batch要经过两个步骤:
1. 把训练集打乱,但是X和Y依旧是一一对应的,之后,X的第i列是与Y中的第i个标签对应的样本。乱序步骤确保将样本被随机分成不同的小批次。如下图,X和Y的每一列代表一个样本
- 切分,我们把训练集打乱之后,我们就可以对它进行切分了。这里切分的大小是64,如下图:
代码如下
def random_mini_batches(X,Y,mini_batch_size=64,seed=0):
"""
从(X,Y)中创建一个随机的mini-batch列表
参数:
X - 输入数据,维度为(输入节点数量,样本的数量)
Y - 对应的是X的标签,【1 | 0】(蓝|红),维度为(1,样本的数量)
mini_batch_size - 每个mini-batch的样本数量
返回:
mini-bacthes - 一个同步列表,维度为(mini_batch_X,mini_batch_Y)
"""
np.random.seed(seed) #指定随机种子
m = X.shape[1]
mini_batches = []
#第一步:打乱顺序
permutation = list(np.random.permutation(m)) #它会返回一个长度为m的随机数组,且里面的数是0到m-1
shuffled_X = X[:,permutation] #将每一列的数据按permutation的顺序来重新排列。
shuffled_Y = Y[:,permutation].reshape((1,m))
"""
#如果不好理解的话,看一下下面的伪代码,看看X和Y是如何根据permutation来打乱顺序的。
x = np.array([[1,2,3,4,5,6,7,8,9],
[9,8,7,6,5,4,3,2,1]])
y = np.array([[1,0,1,0,1,0,1,0,1]])
random_mini_batches(x,y)
permutation= [7, 2, 1, 4, 8, 6, 3, 0, 5]
shuffled_X= [[8 3 2 5 9 7 4 1 6]
[2 7 8 5 1 3 6 9 4]]
shuffled_Y= [[0 1 0 1 1 1 0 1 0]]
"""
#第二步,分割
num_complete_minibatches = math.floor(m / mini_batch_size) #把训练集分割成多少份,如果值是99.99,那么返回值是99,剩下的0.99会被舍弃
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]
"""
#如果不好理解的话单独执行下面的代码,它可以帮你理解一些。
a = np.array([[1,2,3,4,5,6,7,8,9],
[9,8,7,6,5,4,3,2,1],
[1,2,3,4,5,6,7,8,9]])
k=1
mini_batch_size=3
print(a[:,1*3:(1+1)*3]) #从第4列到第6列
'''
[[4 5 6]
[6 5 4]
[4 5 6]]
'''
k=2
print(a[:,2*3:(2+1)*3]) #从第7列到第9列
'''
[[7 8 9]
[3 2 1]
[7 8 9]]
'''
#看一下每一列的数据可能就会好理解一些
"""
mini_batch = (mini_batch_X,mini_batch_Y)
mini_batches.append(mini_batch)
#如果训练集的大小刚好是mini_batch_size的整数倍,那么这里已经处理完了
#如果训练集的大小不是mini_batch_size的整数倍,那么最后肯定会剩下一些,我们要把它处理了
if m % mini_batch_size != 0:
#获取最后剩余的部分
mini_batch_X = shuffled_X[:,mini_batch_size * num_complete_minibatches:]
mini_batch_Y = shuffled_Y[:,mini_batch_size * num_complete_minibatches:]
mini_batch = (mini_batch_X,mini_batch_Y)
mini_batches.append(mini_batch)
return mini_batches
包含动量的梯度下降
先进行初始化
def initialize_velocity(parameters):
"""
初始化速度,velocity是一个字典:
- keys: "dW1", "db1", ..., "dWL", "dbL"
- values:与相应的梯度/参数维度相同的值为零的矩阵。
参数:
parameters - 一个字典,包含了以下参数:
parameters["W" + str(l)] = Wl
parameters["b" + str(l)] = bl
返回:
v - 一个字典变量,包含了以下参数:
v["dW" + str(l)] = dWl的速度
v["db" + str(l)] = dbl的速度
"""
L = len(parameters) // 2 #神经网络的层数
v = {}
for l in range(L):
v["dW" + str(l + 1)] = np.zeros_like(parameters["W" + str(l + 1)])
v["db" + str(l + 1)] = np.zeros_like(parameters["b" + str(l + 1)])
return v
初始化完成了,就开始影响梯度的方向,需要使用以下公式进行更新
def update_parameters_with_momentun(parameters,grads,v,beta,learning_rate):
"""
使用动量更新参数
参数:
parameters - 一个字典类型的变量,包含了以下字段:
parameters["W" + str(l)] = Wl
parameters["b" + str(l)] = bl
grads - 一个包含梯度值的字典变量,具有以下字段:
grads["dW" + str(l)] = dWl
grads["db" + str(l)] = dbl
v - 包含当前速度的字典变量,具有以下字段:
v["dW" + str(l)] = ...
v["db" + str(l)] = ...
beta - 超参数,动量,实数
learning_rate - 学习率,实数
返回:
parameters - 更新后的参数字典
v - 包含了更新后的速度变量
"""
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
需要注意的是速度v vv是用0来初始化的,因此,该算法需要经过几次迭代才能把速度提升上来并开始跨越更大步伐。当beta=0时,该算法相当于是没有使用momentum算法的标准的梯度下降算法。当beta越大的时候,说明平滑的作用越明显。通常0.9是比较合适的值。
Adam算法
Adam算法是训练神经网络中最有效的算法之一,它是RMSProp算法与Momentum算法的结合体。
1. 计算以前的梯度的指数加权平均值
,并将其存储在变量v (偏差校正前)和V(corrected)(偏差校正后)中。
2. 计算以前梯度的平方的指数加权平均值
,并将其存储在变量s (偏差校正前)和S(corrected)(偏差校正后)中。
3. 根据1和2更新参数。
一般选取beta1=0.9(dw)
,beta2=0.999(dw)二次方
,ε=1e-8
初始化参数:
def initialize_adam(parameters):
"""
初始化v和s,它们都是字典类型的变量,都包含了以下字段:
- keys: "dW1", "db1", ..., "dWL", "dbL"
- values:与对应的梯度/参数相同维度的值为零的numpy矩阵
参数:
parameters - 包含了以下参数的字典变量:
parameters["W" + str(l)] = Wl
parameters["b" + str(l)] = bl
返回:
v - 包含梯度的指数加权平均值,字段如下:
v["dW" + str(l)] = ...
v["db" + str(l)] = ...
s - 包含平方梯度的指数加权平均值,字段如下:
s["dW" + str(l)] = ...
s["db" + str(l)] = ...
"""
L = len(parameters) // 2
v = {}
s = {}
for l in range(L):
v["dW" + str(l + 1)] = np.zeros_like(parameters["W" + str(l + 1)])
v["db" + str(l + 1)] = np.zeros_like(parameters["b" + str(l + 1)])
s["dW" + str(l + 1)] = np.zeros_like(parameters["W" + str(l + 1)])
s["db" + str(l + 1)] = np.zeros_like(parameters["b" + str(l + 1)])
return (v,s)
更新参数
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 - 包含了以下字段的字典:
parameters['W' + str(l)] = Wl
parameters['b' + str(l)] = bl
grads - 包含了梯度值的字典,有以下key值:
grads['dW' + str(l)] = dWl
grads['db' + str(l)] = dbl
v - Adam的变量,第一个梯度的移动平均值,是一个字典类型的变量
s - Adam的变量,平方梯度的移动平均值,是一个字典类型的变量
t - 当前迭代的次数
learning_rate - 学习率
beta1 - 动量,超参数,用于第一阶段,使得曲线的Y值不从0开始(参见天气数据的那个图)
beta2 - RMSprop的一个参数,超参数
epsilon - 防止除零操作(分母为0)
返回:
parameters - 更新后的参数
v - 第一个梯度的移动平均值,是一个字典类型的变量
s - 平方梯度的移动平均值,是一个字典类型的变量
"""
L = len(parameters) // 2
v_corrected = {} #偏差修正后的值
s_corrected = {} #偏差修正后的值
for l in range(L):
#梯度的移动平均值,输入:"v , grads , beta1",输出:" v "
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 , beta1 , t" , 输出:"v_corrected"
v_corrected["dW" + str(l + 1)] = v["dW" + str(l + 1)] / (1 - np.power(beta1,t))
v_corrected["db" + str(l + 1)] = v["db" + str(l + 1)] / (1 - np.power(beta1,t))
#计算平方梯度的移动平均值,输入:"s, grads , beta2",输出:"s"
s["dW" + str(l + 1)] = beta2 * s["dW" + str(l + 1)] + (1 - beta2) * np.square(grads["dW" + str(l + 1)])
s["db" + str(l + 1)] = beta2 * s["db" + str(l + 1)] + (1 - beta2) * np.square(grads["db" + str(l + 1)])
#计算第二阶段的偏差修正后的估计值,输入:"s , beta2 , t",输出:"s_corrected"
s_corrected["dW" + str(l + 1)] = s["dW" + str(l + 1)] / (1 - np.power(beta2,t))
s_corrected["db" + str(l + 1)] = s["db" + str(l + 1)] / (1 - np.power(beta2,t))
#更新参数,输入: "parameters, learning_rate, v_corrected, s_corrected, epsilon". 输出: "parameters".
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)
下面的测试就不放代码了,看下结果:
- 这是我们的原始数据
- 采用梯度下降
#使用普通的梯度下降
layers_dims = [train_X.shape[0],5,2,1]
parameters = model(train_X, train_Y, layers_dims, optimizer="gd",is_plot=True)
第0次遍历整个数据集,当前误差值:0.690735512291
第1000次遍历整个数据集,当前误差值:0.685272532846
第2000次遍历整个数据集,当前误差值:0.647072224072
第3000次遍历整个数据集,当前误差值:0.619524554997
第4000次遍历整个数据集,当前误差值:0.576584435595
第5000次遍历整个数据集,当前误差值:0.607242639597
第6000次遍历整个数据集,当前误差值:0.529403331768
第7000次遍历整个数据集,当前误差值:0.460768239859
第8000次遍历整个数据集,当前误差值:0.465586082399
第9000次遍历整个数据集,当前误差值:0.464517972217
Accuracy: 0.796666666667
- 使用动量的梯度下降
第0次遍历整个数据集,当前误差值:0.690741298835
第1000次遍历整个数据集,当前误差值:0.685340526127
第2000次遍历整个数据集,当前误差值:0.64714483701
第3000次遍历整个数据集,当前误差值:0.619594303208
第4000次遍历整个数据集,当前误差值:0.576665034407
第5000次遍历整个数据集,当前误差值:0.607323821901
第6000次遍历整个数据集,当前误差值:0.529476175879
第7000次遍历整个数据集,当前误差值:0.460936190049
第8000次遍历整个数据集,当前误差值:0.465780093701
第9000次遍历整个数据集,当前误差值:0.464739596792
因为这个例子比较简单,使用动量效果很小
- Adam优化后的梯度下降
第0次遍历整个数据集,当前误差值:0.690552244611
第1000次遍历整个数据集,当前误差值:0.185501364386
第2000次遍历整个数据集,当前误差值:0.150830465753
第3000次遍历整个数据集,当前误差值:0.07445438571
第4000次遍历整个数据集,当前误差值:0.125959156513
第5000次遍历整个数据集,当前误差值:0.104344435342
第6000次遍历整个数据集,当前误差值:0.100676375041
第7000次遍历整个数据集,当前误差值:0.0316520301351
第8000次遍历整个数据集,当前误差值:0.111972731312
第9000次遍历整个数据集,当前误差值:0.197940071525
Accuracy: 0.94
- 具有动量的梯度下降通常可以有很好的效果,但由于小的学习速率和简单的数据集所以它的影响几乎是轻微的。另一方面,Adam明显优于小批量梯度下降和具有动量的梯度下降。