欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 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

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

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

四、测试:动量梯度下降

动量梯度下降的公式如下图中的左图所示,其中β=0.9\beta=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梯度下降和动量梯度下降较快:

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

相关标签: 深度学习