手撕代码:梯度下降及其优化算法
程序员文章站
2022-03-16 19:27:21
...
一、batch梯度下降 VS mini-batch梯度下降
当数据量达百万级别时,即使采取向量化技术进行batch梯度下降,所需时间也比较久。此时可将数据集分成多批,每一个小批量的数据集则称为mini-batch。
两者实现的区别在于:
batch梯度下降对整个数据集进行一次正向、反向传播后,更新参数的过程则完成了一次迭代
mini-batch梯度下降对所有批量(假设有k个)的数据集进行了k次正向、反向传播并更新了参数的过程才算完成了一次迭代(epoch)
注意:mini-batch梯度下降当每个批量的数据量为1时,则为随机梯度下降
batch梯度下降迭代过程成本函数下降得比较平稳;mini-batch梯度下降则有更多的噪声
伪代码如下所示:
#仅做比较,不运行。
#batch梯度下降
X = data_input
Y = labels
parameters = initialize_parameters(layers_dims)
for i in range(0,num_iterations):
#前向传播
A,cache = forward_propagation(X,parameters)
#计算损失
cost = compute_cost(A,Y)
#反向传播
grads = backward_propagation(X,Y,cache)
#更新参数
parameters = update_parameters(parameters,grads)
#mini-batch梯度下降算法:
X = data_input
Y = labels
parameters = initialize_parameters(layers_dims)
for i in (0,num_iterations):
for j in m:
#前向传播
A,cache = forward_propagation(X,parameters)
#计算成本
cost = compute_cost(A,Y)
#后向传播
grads = backward_propagation(X,Y,cache)
#更新参数
parameters = update_parameters(parameters,grads)
二、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 = []
#将X,Y乱序
permutation = list(np.random.permutation(m))
shuffled_X = X[:,permutation]
shuffled_Y = Y[:,permutation]
#分割
num_complete_minibatches = m // mini_batch_size
for i in range(0,num_complete_minibatches):
mini_batch_X = shuffled_X[:,i*mini_batch_size:(i+1)*mini_batch_size]
mini_batch_Y = shuffled_Y[:,i*mini_batch_size:(i+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:]
mini_batch_Y = shuffled_Y[:,num_complete_minibatches*mini_batch_size:]
mini_batch = (mini_batch_X,mini_batch_Y)
mini_batches.append(mini_batch)
return mini_batches
mini-batch梯度下降的应用过程与之前类似,下面代码中选用不同的优化器仅需修改:一参数初始化部分;二更新参数部分。不同优化器的实现后文再给出:
#定义模型
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,is_plot=True):
L = len(layers_dims)
costs = []
t = 0 #每学习完一个minibatch就增加1
seed = 10 #随机种子
#初始化参数
parameters = opt_utils.initialize_parameters(layers_dims)
#选择优化器
if optimizer == "gd":
pass #不使用任何优化器,直接使用梯度下降法
elif optimizer == "momentum":
v = initialize_velocity(parameters) #使用动量
elif optimizer == "adam":
v, s = initialize_adam(parameters)#使用Adam优化
else:
print("optimizer参数错误,程序退出。")
exit(1)
#开始学习
for i in range(num_epochs):
#定义随机 minibatches,我们在每次遍历数据集之后增加种子以重新排列数据集,使每次数据的顺序都不同
seed = seed + 1
minibatches = random_mini_batches(X,Y,mini_batch_size,seed)
for minibatch in minibatches:
#选择一个minibatch
(minibatch_X,minibatch_Y) = minibatch
#前向传播
A3 , cache = opt_utils.forward_propagation(minibatch_X,parameters)
#计算误差
cost = opt_utils.compute_cost(A3 , minibatch_Y)
#反向传播
grads = opt_utils.backward_propagation(minibatch_X,minibatch_Y,cache)
#更新参数
if optimizer == "gd":
parameters = update_parameters_with_gd(parameters,grads,learning_rate)
elif optimizer == "momentum":
parameters, v = update_parameters_with_momentun(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 i % 100 == 0:
costs.append(cost)
#是否打印误差值
if print_cost and i % 1000 == 0:
print("第" + str(i) + "次遍历整个数据集,当前误差值:" + str(cost))
#是否绘制曲线图
if is_plot:
plt.plot(costs)
plt.ylabel('cost')
plt.xlabel('epochs (per 100)')
plt.title("Learning rate = " + str(learning_rate))
plt.show()
return parameters
三、测试:batch梯度下降
更新参数部分代码如下:
def update_parameters_with_gd(param,grads,learning_rate):
L = len(param)//2
for i in range(1,L+1):
param["W"+str(i)] = param["W"+str(i)] - learning_rate * grads["dW"+str(i)]
param["b"+str(i)] = param["b"+str(i)] - learning_rate * grads["db"+str(i)]
return param
测试结果与决策边界如下:
四、测试:动量梯度下降
动量梯度下降的公式如下图中的左图所示,其中
代码实现需修改:一初始化参数部分;二更新参数部分。具体如下:
#初始化动量参数
def initialize_velocity(param):
L = len(param)//2
v = {}
for i in range(1,L+1):
v["dW"+str(i)] = np.zeros_like(param["W"+str(i)])
v["db"+str(i)] = np.zeros_like(param["b"+str(i)])
return v
#更新参数
def update_parameters_with_momentun(param,grads,v,beta,learning_rate):
L = len(param)//2
for i in range(1,L+1):
v["dW"+str(i)] = beta*v["dW"+str(i)] + (1-beta)*grads["dW"+str(i)]
v["db"+str(i)] = beta*v["db"+str(i)] + (1-beta)*grads["db"+str(i)]
param["W"+str(i)] = param["W"+str(i)] - learning_rate*v["dW"+str(i)]
param["b"+str(i)] = param["b"+str(i)] - learning_rate*v["db"+str(i)]
return param,v
由于此处数据集较小,所以采用动量梯度下降的结果与batch梯度下降基本一致,不再重复
五、测试:Adam梯度下降
Adam优化算法结合了动量梯度下降和RMSprop,同时修正了动量梯度下降一开始的误差,公式如下:
代码实现如下:
#初始化参数
def initialize_adam(param):
L = len(param)//2
v = {}
s = {}
for i in range(1,L+1):
v["dW"+str(i)] = np.zeros_like(param["W"+str(i)])
v["db"+str(i)] = np.zeros_like(param["b"+str(i)])
s["dW"+str(i)] = np.zeros_like(param["W"+str(i)])
s["db"+str(i)] = np.zeros_like(param["b"+str(i)])
return (v,s)
#更新参数
def update_parameters_with_adam(param,grads,v,s,t,learning_rate=0.01,
beta1=0.9,beta2=0.999,epsilon=1e-8):
L = len(param)//2
v_corrected = {}
s_corrected = {}
for i in range(1,L+1):
v["dW"+str(i)] = beta1*v["dW"+str(i)] + (1-beta1)*grads["dW"+str(i)]
v["db"+str(i)] = beta1*v["db"+str(i)] + (1-beta1)*grads["db"+str(i)]
v_corrected["dW"+str(i)] = v["dW"+str(i)] / (1-np.power(beta1,t))
v_corrected["db"+str(i)] = v["db"+str(i)] / (1-np.power(beta1,t))
s["dW"+str(i)] = beta2*s["dW"+str(i)] + (1-beta2)*np.power(grads["dW"+str(i)],2)
s["db"+str(i)] = beta2*s["db"+str(i)] + (1-beta2)*np.power(grads["db"+str(i)],2)
s_corrected["dW"+str(i)] = s["dW"+str(i)] / (1-np.power(beta2,t))
s_corrected["db"+str(i)] = s["db"+str(i)] / (1-np.power(beta2,t))
param["W"+str(i)] = param["W"+str(i)] - learning_rate*(v_corrected["dW"+str(i)]/np.sqrt(s_corrected["dW"+str(i)])+epsilon)
param["b"+str(i)] = param["b"+str(i)] - learning_rate*(v_corrected["db"+str(i)]/np.sqrt(s_corrected["db"+str(i)])+epsilon)
return (param,v,s)
学习效果和决策边界如下,可以看出学习速度比batch梯度下降和动量梯度下降较快:
上一篇: 深度学习入门:基于Python的理论与实现——感知机
下一篇: GB28181国标流转成RTSP流
推荐阅读