美文网首页程序员
梯度优化算法及python代码实例

梯度优化算法及python代码实例

作者: Leo蓝色 | 来源:发表于2018-12-26 15:33 被阅读81次

    1 梯度下降简单说明

    想必大家对梯度下降或多或少有所了解,这里对他的原理就不再多说(其他教程说的比我好多了),只给出下列公式(按照吴恩达的深度学习课程中的公式):W:=W-\alpha*dW b:=b-\alpha*db
    上述公式中dW与db为权重W与偏置b的梯度方向,\alpha为步长/学习速率,即一个超参。我们的任务就是通过大量数据的学习,来逼近真实的W和b。

    1.1 随机梯度下降

    该方法简写做SGD。通常,在数据集较少的情况下,我们一般使用批梯度下降,也就是用所有的数据集来进行求解。当数据量较多时,批梯度下降就不再适合,因为速度超级慢。所以我们在每次迭代时随机选取一个样本点来进行权重W、偏置b的更新。这样能保证权重随着迭代不断地逼近真实值。但要警惕,每次只是依靠当前一个数据集,容易陷入局部极小。

    1.2 Mini-Batch梯度下降

    此种方法可以看做批梯度下降与SGD的折中方法:在SGD中每次迭代时,不是选取1个样本点,而是选取M个样本点来进行梯度下降。此种方法的好处在于:若数据集中有一些噪音数据,可以有效的平均噪音数据的影响,一定程度上避免陷入局部极小值。据吴恩达深度学习课程介绍,M的选取也是有经验借鉴的。若数据集数量N<=2000,则M=1即可。若N很大,那么M的取值可以取2的K次方,即M={32,64,128,256,512}。

    1.3 Momentum梯度下降

    在说明此种方法之前,首先需要了解什么是指数加权平均。
    假设有数据d_{1},d_{2},...,d_{100},他们的平均数为:
    a_{100}=\frac{d_{1}+d_{2}+...+d_{100}}{100} \ (1.1)
    若我们按照公式:
    v_{100}=\beta*v_{99}+(1-\beta)*d_{100}
    v_{99}=\beta*v_{98}+(1-\beta)*d_{99}
    ...
    v_{1}=\beta*v_{0}+(1-\beta)*d_{1}
    对第一式化简可得v_{100}=(1-\beta)*d_{100}+(1-\beta)*\beta*d_{99}+(1-\beta)*\beta^2*d_{98}+...+(1-\beta)*\beta^{99}*d_{1} \ (1.2)
    通过(1.1)(1.2)之间的比较可以看到,(1.1)中每一项的权重都是一样的,也就是说每一项对平均值的贡献同样重要。而(1.2)中,每一项都不同,尤其是各数值的加权影响力随时间呈指数式递减,时间越靠近当前时刻的数据加权影响力越大。一般我们取\beta为0.9(详细的介绍请看下面参考文献)
    把该思想应用到梯度下降中,就发明了动量梯度下降(Momentum),在每次迭代时需要先计算一下当前的动量梯度,然后用此动量梯度来进行更新权重。==注意==:我们需要设定初始的动量梯度v_{0}为0,此为冷启动问题,有时为了消除0的影响需要进行偏差修正:每次得到的v除以1-\beta^t,t为当前的迭代次数,当t较小时能赋予d_{t}较大权重,当t大时,该修正方法趋近于1,对动量结果影响微乎其微。下面是动量梯度更新的方法
    V_{dW}=\beta*V_{dW}+(1-\beta)*dW
    V_{db}=\beta*V_{db}+(1-\beta)*db
    W:=W-\alpha*V_{dW}
    b:=b-\alpha*V_{db}
    吴恩达在这里说此处不需要设置偏差修正
    至于为什么Momentum比传统的梯度下降快,可以参考吴恩达的深度学习教程。我个人理解是 当前的梯度下降方向参考了此前已经走过的梯度方向,受到了他们的影响,所以会比传统的梯度下降更快地逼近正确的方向。

    1.4 RMSProp

    全称为root mean square prop,均方根。我个人理解,此方法的目的是在参数值波动较大的方向上减少学习速率,在参数值波动较小的方向上加快学习速率,这样能更快地逼近极值点。此方法构造了一些式子来完成这个目标。
    S_{dW}=\beta*S_{dW}+(1-\beta)*(dW)^2
    S_{db}=\beta*S_{db}+(1-\beta)*(db)^2
    W:=W-\alpha*\frac{dW}{\sqrt{S_{dW}}+e}
    b:=b-\alpha*\frac{db}{\sqrt{S_{db}}+e}
    当dW小时,S_{dW}同样会小,此时\frac{\alpha}{\sqrt{S_{dW}}+e}变大,变相的增大了\alpha,所以就会加速。反之,会减速。e通常取10e^{-8},此为一个防止除法无意义的常量。

    1.5 Adam优化算法

    这个方法结合了Moment和RMSProp,Adam(Adaptive Moment Estimation)本质上是带有动量项的RMSprop,它利用梯度的一阶矩估计和二阶矩估计动态调整每个参数的学习率。Adam的优点主要在于经过偏置校正后,每一次迭代学习率都有个确定范围,使得参数比较平稳(参考文献[2])
    废话不多说直接上公式
    V_{dW}=\beta_{1}*V_{dW}+(1-\beta_{1})*dW \ V_{db}=\beta_{1}*V_{db}+(1-\beta_{1})*db
    S_{dW}=\beta_{2}*S_{dW}+(1-\beta_{2})*(dW)^2 \ S_{db}=\beta_{1}*S_{db}+(1-\beta_{1})*(db)^2
    V^{correct}_{dW}=\frac{V_{dW}}{1-\beta_{1}^t} \ V^{correct}_{db}=\frac{V_{db}}{1-\beta_{1}^t}
    S^{correct}_{dW}=\frac{S_{dW}}{1-\beta_{2}^t} \ S^{correct}_{db}=\frac{S_{db}}{1-\beta_{2}^t}
    W:=W-\alpha*\frac{V^{correct}_{dW}}{\sqrt{S^{correct}_{dW}}+e}
    b:=b-\alpha*\frac{V^{correct}_{db}}{\sqrt{S^{correct}_{db}}+e}
    据吴恩达所述,一般\beta_1=0.9 \beta_2=0.999 且不常修改。e与RMSProp一致。

    2 Python代码及结果

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    """
    Python 3.6
    Author LiHao
    Time 2018/10/12 10:28
    """
    import numpy as np
    import random
    from math import sqrt
    """
    验证梯度下降程序
    """
    def random_num():
        #读取随机数据 50个
        data = []
        func = lambda x:x**2+2*x+1+random.uniform(-0.3,0.3)
        for i in np.arange(-5,5,0.2):
            data.append([i,func(i)])
        return data
    
    class Gradient(object):
        """
        利用数据求解 求解一元二次方程 x**2 + 2*x + 1的系数
        分别利用
        梯度下降法/Mini-Batch梯度下降法
        动量梯度下降法
        RMSProp
        Adam
        """
        def __init__(self,alpha = 0.001, error = 1e-03,A=0.1,B=0.1,C=0.1,data_num=100,max_iter=30000):
            """
            :param alpha: 步长
            :param error: 误差
            :param maxcount: 最大迭代次数
            """
            self._alpha = alpha
            self._error = error
            #读入数据并初始化各个系数
            self.data_num = data_num
            self._data = self.inputs()
            #这里固定了为一元二次方程 x**2+2*x+1  _A _B _C 为真实系数
            self.A = A
            self._A = 1
            self.B = B
            self._B = 2
            self.C = C
            self._C = 1
            self._max_iter = max_iter
    
        def inputs(self):
            datas = []
            for i in range(self.data_num):
                datas.extend(random_num())
            #print("LH -*- 得到的数据个数:",len(datas))
            return datas
    
        def get_y_hat(self,mini_data):
            """
            获取预测值 y^
            :param mini_data:
            :return:
            """
            y_hat = self.A * np.power(mini_data[:,0],2) + self.B * mini_data[:, 0] + self.C
            return y_hat
    
        def gradient(self,mini_data,y_hat,m):
            self.A = self.A - self._alpha * np.sum((y_hat-mini_data[:,1])*np.power(mini_data[:,0],2))/m
            self.B = self.B - self._alpha * np.sum((y_hat-mini_data[:,1])*mini_data[:,0])/m
            self.C = self.C - self._alpha * np.sum(y_hat-mini_data[:,1])/m
    
        def minibatch_gradient_train(self,m=2,error=1e-02):
            """
            minibatch-梯度下降
            :param m:
            :param error:
            :return:
            """
            self._error = error
            all_lens = len(self._data)
            if all_lens % m ==0:
                epoch = int(all_lens/m)
            else:
                epoch = int(all_lens/m) + 1
            Error = 1.0
            count = 1
            while(Error>self._error and count < self._max_iter):
                #分批次求解 随机在数据集中选取一个epoch,里面含有m个数据,不满m的取剩余的数据
                ie = random.randint(0,epoch-1)
                mini_data = np.array(self._data[ie*m:(ie+1)*m],dtype=np.float32)
                current_m = mini_data.shape[0]
                y_hat = self.get_y_hat(mini_data)
                Error = (abs(self.A - self._A) + abs(self.B - self._B) + abs(self.C - self._C)) / 3
                #print("LH -*- epoch: ",ie,"\tloss : ",Error," A,B,C:",self.A,self.B,self.C)
                self.gradient(mini_data,y_hat,current_m)
                count += 1
            print("LH -*- Minibatch -*-Final A,B,C,iter:",self.A,self.B,self.C,count," error:",Error)
    
        def momentum_gradient(self,mini_data,y_hat,m,pre_va,pre_vb,pre_vc,beta):
            “”“
            动量梯度求解具体过程
            ”“”
            da =  np.sum((y_hat - mini_data[:, 1]) * np.power(mini_data[:, 0], 2)) / m
            db =  np.sum((y_hat - mini_data[:, 1]) * mini_data[:, 0]) / m
            dc =  np.sum(y_hat - mini_data[:, 1]) / m
            va = da * (1 - beta) + beta * pre_va
            vb = db * (1 - beta) + beta * pre_vb
            vc = dc * (1 - beta) + beta * pre_vc
            self.A = self.A - self._alpha * va
            self.B = self.B - self._alpha * vb
            self.C = self.C - self._alpha * vc
            return va,vb,vc
    
        def momentum_gradient_train(self,m=2,beta=0.9,error=1e-02):
            """
            动量梯度下降
            较之梯度下降更加快速
            当前步的梯度与历史的梯度方向有关
            :param m:
            :param beta:
            :param error:
            :return:
            """
            self._error = error
            all_lens = len(self._data)
            if all_lens % m == 0:
                epoch = int(all_lens / m)
            else:
                epoch = int(all_lens / m) + 1
            Error = 1.0
            count = 1
            pre_va = 0.0
            pre_vb = 0.0
            pre_vc = 0.0
            while (Error >= self._error and count < self._max_iter):
                ie = random.randint(0,epoch-1)
                mini_data = np.array(self._data[ie * m:(ie + 1) * m], dtype=np.float32)
                current_m = mini_data.shape[0]
                y_hat = self.get_y_hat(mini_data)
                Error =(abs(self.A-self._A)+abs(self.B-self._B)+abs(self.C-self._C))/3
                #print("LH -*- epoch: ", ie, "\terror : ", Error, " A,B,C:", self.A, self.B, self.C)
                pre_va,pre_vb,pre_vc = self.momentum_gradient(mini_data, y_hat, current_m,pre_va,pre_vb,pre_vc,beta)
                count += 1
            print("LH -*- Momentum -*-Final A,B,C,iter:", self.A, self.B, self.C, count, " error:", Error)
    
    
        def rmsprop_gradient(self,mini_data,y_hat,m,pre_sa,pre_sb,pre_sc,beta,eps=10e-8):
            da = np.sum((y_hat - mini_data[:, 1]) * np.power(mini_data[:, 0], 2)) / m
            db = np.sum((y_hat - mini_data[:, 1]) * mini_data[:, 0]) / m
            dc = np.sum(y_hat - mini_data[:, 1]) / m
            sa = da**2 * (1 - beta) + beta * pre_sa
            sb = db**2 * (1 - beta) + beta * pre_sb
            sc = dc**2 * (1 - beta) + beta * pre_sc
            self.A = self.A - self._alpha * da / (sqrt(sa) + eps)
            self.B = self.B - self._alpha * db / (sqrt(sb) + eps)
            self.C = self.C - self._alpha * dc / (sqrt(sc) + eps)
            return sa, sb, sc
    
        def rmsprop_gradient_train(self,m=2,beta=0.9,error=1e-02):
            """
            RMSProp梯度下降
            自适应更新学习速率
            :param m:minibatch
            :param beta:超参数
            :param error:误差
            :return:
            """
            self._error = error
            all_lens = len(self._data)
            if all_lens % m == 0:
                epoch = int(all_lens / m)
            else:
                epoch = int(all_lens / m) + 1
            # 进行分批次求解
            Error = 1.0
            count = 1
            pre_sa = 0.0
            pre_sb = 0.0
            pre_sc = 0.0
            while (Error >= self._error and count < self._max_iter):
                ie = random.randint(0,epoch-1)
                mini_data = np.array(self._data[ie * m:(ie + 1) * m], dtype=np.float32)
                current_m = mini_data.shape[0]
                y_hat = self.get_y_hat(mini_data)
                Error = (abs(self.A - self._A) + abs(self.B - self._B) + abs(self.C - self._C)) / 3
                # print("LH -*- epoch: ", ie, "\terror : ", Error, " A,B,C:", self.A, self.B, self.C)
                pre_sa, pre_sb, pre_sc = self.rmsprop_gradient(mini_data, y_hat, current_m, pre_sa, pre_sb, pre_sc,
                                                                    beta)
                count += 1
            print("LH -*- RMSProp -*- Final A,B,C,iter:", self.A, self.B, self.C, count, " error:", Error)
    
    
        def adam_gradient(self,mini_data,y_hat,m,pre_sa,pre_sb,pre_sc,pre_va,pre_vb,pre_vc,beta_1,beta_2,count,eps=10e-8):
            #correct值归一化分母
            norm_value_1 = 1-pow(beta_1,count)
            norm_value_2 = 1-pow(beta_2,count)
            #求da db dc
            da = np.sum((y_hat - mini_data[:, 1]) * np.power(mini_data[:, 0], 2)) / m
            db = np.sum((y_hat - mini_data[:, 1]) * mini_data[:, 0]) / m
            dc = np.sum(y_hat - mini_data[:, 1]) / m
            #求va vb vc
            va = da * (1 - beta_1) + beta_1 * pre_va
            vb = db * (1 - beta_1) + beta_1 * pre_vb
            vc = dc * (1 - beta_1) + beta_1 * pre_vc
            va_correct = va / norm_value_1
            vb_correct = vb / norm_value_1
            vc_correct = vc / norm_value_1
            #求sa sb sc
            sa = da ** 2 * (1 - beta_2) + beta_2 * pre_sa
            sb = db ** 2 * (1 - beta_2) + beta_2 * pre_sb
            sc = dc ** 2 * (1 - beta_2) + beta_2 * pre_sc
            sa_correct = sa / norm_value_2
            sb_correct = sb / norm_value_2
            sc_correct = sc / norm_value_2
            self.A = self.A - self._alpha * va_correct / (sqrt(sa_correct) + eps)
            self.B = self.B - self._alpha * vb_correct / (sqrt(sb_correct) + eps)
            self.C = self.C - self._alpha * vc_correct / (sqrt(sc_correct) + eps)
            return va,vb,vc,sa,sb,sc
    
        def adam_gradient_train(self,m=2,beta_1=0.9,beta_2=0.999,error=1e-02):
            """
            adam优化算法
            结合动量及RMSProp
            :param m:minibatch
            :param error:误差
            :return:
            """
            self._error = error
            all_lens = len(self._data)
            if all_lens % m == 0:
                epoch = int(all_lens / m)
            else:
                epoch = int(all_lens / m) + 1
            Error = 1.0
            count = 1
            pre_sa = 0.0
            pre_sb = 0.0
            pre_sc = 0.0
            pre_va = 0.0
            pre_vb = 0.0
            pre_vc = 0.0
            while (Error >= self._error and count < self._max_iter):
                ie = random.randint(0, epoch-1)
                mini_data = np.array(self._data[ie * m:(ie + 1) * m], dtype=np.float32)
                current_m = mini_data.shape[0]
                y_hat = self.get_y_hat(mini_data)
                Error = (abs(self.A - self._A) + abs(self.B - self._B) + abs(self.C - self._C)) / 3
                # print("LH -*- epoch: ", ie, "\terror : ", Error, " A,B,C:", self.A, self.B, self.C)
                pre_va,pre_vb,pre_vc,pre_sa, pre_sb, pre_sc = self.adam_gradient(mini_data, y_hat,current_m, pre_sa, pre_sb,pre_sc,pre_va,pre_vb,pre_vc,beta_1,beta_2,count)
                count += 1
            print("LH -*- Adam -*- Final A,B,C,iter:", self.A, self.B, self.C, count, " error:", Error)
    
    if __name__ == '__main__':
        g = Gradient()
        #分别测试不同的方法观察结果
        #g.minibatch_gradient_train(m=64,error=0.002)
        #g.momentum_gradient_train(m=64,error=0.002,beta=0.9)
        g.rmsprop_gradient_train(m=64,error=0.002,beta=0.9)
        #g.adam_gradient_train(m=64,error=0.002)
    
    LH -*- Minibatch -*-Final A,B,C,iter: 0.9998391374256507 2.000062286798814 0.994375273081942 15357  error: 0.0019482761020684913
    LH -*- Momentum -*-Final A,B,C,iter: 1.000219900542389 2.000187069910188 0.9944682440600149 10864  error: 0.0019906410380585346
    LH -*- RMSProp -*- Final A,B,C,iter: 1.000128957762776 2.000499604490694 1.0035405980946732 2109  error: 0.0017750440141715007
    LH -*- Adam -*- Final A,B,C,iter: 1.000215617975376 2.0007904489544006 1.004995565781187 5911  error: 0.0019990631734925213
    

    观察结果发现RMSProp最快,其次是Adam,然后是Momentum,最后是一般的梯度下降。
    据吴恩达所说,目前最广泛使用的优化算法Adam,也是最稳定的一种。
    下面从参考文献[3]中截取了两个动图,形象的说明了各个算法的速度


    优化算法示例图1
    优化算法示例2

    参考文献:

    1.吴恩达.深度学习课程笔记v5.54.pdf(黄海广整理)
    2.https://blog.csdn.net/u014595019/article/details/52989301
    3.http://cs231n.github.io/neural-networks-3/

    相关文章

      网友评论

        本文标题:梯度优化算法及python代码实例

        本文链接:https://www.haomeiwen.com/subject/xubhlqtx.html