手撕代码:梯度下降及其优化算法

一、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

测试结果与决策边界如下:

在这里插入图片描述
在这里插入图片描述

四、测试:动量梯度下降

动量梯度下降的公式如下图中的左图所示,其中 β = 0.9 \beta=0.9 β=0.9

在这里插入图片描述

代码实现需修改:一初始化参数部分;二更新参数部分。具体如下:

#初始化动量参数
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梯度下降和动量梯度下降较快:

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值