SGD、Momentum、 AdaGrad、Adam

目录

1.SGD

1.1 SGD的缺点

2. Momentum

3. AdaGrad

4. Adam

5 使用哪种更新方法呢


神经网络的学习的目的是找到使损失函数的值尽可能小的参数。这是寻找最优参数的问题,解决这个问题的过程称为最优化(optimization)。遗憾的是,神经网络的最优化问题非常难。
为了找到最优参数,我们将参数的梯度(导数)作为了线索。使用参数的梯度,沿梯度方向更新参数,并重复这个步骤多次,从而逐渐靠近最优参数,这个过程称为随机梯度下降法(stochastic gradient descent)

1.SGD

SGD、Momentum、 AdaGrad、Adam_第1张图片

这里把需要更新的权重参数记为W,把损失函数关于W的梯度记为 。η表示学习率,实际上会取0.01或0.001这些事先决定好的值。

class SGD:

    """随机梯度下降法(Stochastic Gradient Descent)"""

    def __init__(self, lr=0.01):
        self.lr = lr
        
    def update(self, params, grads):
        for key in params.keys():
            params[key] -= self.lr * grads[key]

进行初始化时的参数 lr表示learning rate(学习率)。这个学习率会保存为实例变量。此外,代码段中还定义了 update(params, grads)方法,这个方法在SGD中会被反复调用。参数 params和 grads(与之前的神经网络的实现一样)是字典型变量,按 params['W1']、 grads['W1']的形式,分别保存了权重参数和它们的梯度。

使用这个 SGD类,可以按如下方式进行神经网络的参数的更新(下面的代码是不能实际运行的伪代码)。
SGD、Momentum、 AdaGrad、Adam_第2张图片

1.1 SGD的缺点

SGD的缺点是,如果函数的形状非均向(anisotropic),比如呈延伸状,搜索的路径就会非常低效。

SGD低效的根本原因是,梯度的方向并没有指向最小值的方向。

如下图所示:

SGD、Momentum、 AdaGrad、Adam_第3张图片

从(x, y) = (-7.0, 2.0)处(初始值)开始搜索,SGD呈“之”字形移动。这是一个相当低效的路径
基于SGD的最优化的更新路径:呈“之”字形朝最小值(0, 0)移动,效率低

SGD、Momentum、 AdaGrad、Adam_第4张图片

为了改正SGD的缺点,下面我们将介绍Momentum、 AdaGrad、 Adam这3种方法来取代SGD。我们会简单介绍各个方法,并用数学式和Python进行实现。
 

2. Momentum

Momentum是“动量”的意思,和物理有关。用数学式表示Momentum方法,如下所示。
SGD、Momentum、 AdaGrad、Adam_第5张图片

和前面的SGD一样, W表示要更新的权重参数, 表示损失函数关于W的梯度, η表示学习率。这里新出现了一个变量v,对应物理上的速度。表示了物体在梯度方向上受力,在这个力的作用下,物体的速度增加这一物理法则。Momentum方法给人的感觉就像是小球在地面上滚动。

αv这一项。在物体不受任何力时,该项承担使物体逐渐减速的任务(α设定为0.9之类的值),对应物理上的地面摩擦或空气阻力。代码如下

class Momentum:

    """Momentum SGD"""

    def __init__(self, lr=0.01, momentum=0.9):
        self.lr = lr
        self.momentum = momentum
        self.v = None
        
    def update(self, params, grads):
        if self.v is None:
            self.v = {}
            for key, val in params.items():                                
                self.v[key] = np.zeros_like(val)
                
        for key in params.keys():
            self.v[key] = self.momentum*self.v[key] - self.lr*grads[key] 
            params[key] += self.v[key]

实例变量 v会保存物体的速度。初始化时, v中什么都不保存,但当第一次调用update()时, v会以字典型变量的形式保存与参数结构相同的数据。基于Momentum的最优化的更新路径如下图所示:

SGD、Momentum、 AdaGrad、Adam_第6张图片

和SGD相比,我们发现“之”字形的“程度”减轻了。这是因为虽然x轴方向上受到的力非常小,但是一直在同一方向上受力,所以朝同一个方向会有一定的加速。反过来,虽然y轴方向上受到的力很大,但是因为交互地受到正方向和反方向的力,它们会互相抵消,所以y轴方向上的速度不稳定。因此,和SGD时的情形相比,可以更快地朝x轴方向靠近,减弱“之”字形的变动程度
 

3. AdaGrad

在神经网络的学习中,学习率(数学式中记为η)的值很重要。学习率过小,会导致学习花费过多时间;反过来,学习率过大,则会导致学习发散而不能正确进行。
在关于学习率的有效技巧中,有一种被称为学习率衰减(learning rate decay)的方法,即随着学习的进行,使学习率逐渐减小。实际上,一开始“多”学,然后逐渐“少”学的方法,在神经网络的学习中经常被使用。
逐渐减小学习率的想法,相当于将“全体”参数的学习率值一起降低。而AdaGrad 进一步发展了这个想法,针对“一个一个”的参数,赋予其“定制”的值
AdaGrad会为参数的每个元素适当地调整学习率,与此同时进行学习AdaGrad的Ada来自英文单词Adaptive,即“适当的”的意思)。
SGD、Momentum、 AdaGrad、Adam_第7张图片

和前面的SGD一样, W表示要更新的权重参数, 表示损失函数关于W的梯度, η表示学习率。这里新出现了变量h,如式(6.5)所示,它保存了以前的所有梯度值的平方和(式(6.5)中的表示对应矩阵元素的乘法)。然后,在更新参数时,通过乘以 ,就可以调整学习的尺度。这意味着,参数的元素中变动较大(被大幅更新)的元素的学习率将变小。也就是说,可以按参数的元素进行学习率衰减,使变动大的参数的学习率逐渐减小。

AdaGrad 的 实 现 过 程 如 下 所 示
 

class AdaGrad:

    """AdaGrad"""

    def __init__(self, lr=0.01):
        self.lr = lr
        self.h = None
        
    def update(self, params, grads):
        if self.h is None:
            self.h = {}
            for key, val in params.items():
                self.h[key] = np.zeros_like(val)
            
        for key in params.keys():
            self.h[key] += grads[key] * grads[key]
            params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)

使用AdaGrad解决上面的最优化问题,
 

SGD、Momentum、 AdaGrad、Adam_第8张图片

函数的取值高效地向着最小值移动。由于y轴方向上的梯度较大,因此刚开始变动较大,但是后面会根据这个较大的变动按比例进行调整,减小更新的步伐。因此, y轴方向上的更新程度被减弱,“之”字形的变动程度有所衰减。
 

4. Adam

Adam是2015年提出的新方法。它的理论有些复杂,直观地讲,就是融合了Momentum和AdaGrad的方法。通过组合前面两个方法的优点,有望实现参数空间的高效搜索。此外,进行超参数的“偏置校正”也是Adam的特征。详细推导过程可以看论文:http://arxiv.org/abs/1412.6980v8

class Adam:

    """Adam (http://arxiv.org/abs/1412.6980v8)"""

    def __init__(self, lr=0.001, beta1=0.9, beta2=0.999):
        self.lr = lr
        self.beta1 = beta1
        self.beta2 = beta2
        self.iter = 0
        self.m = None
        self.v = None
        
    def update(self, params, grads):
        if self.m is None:
            self.m, self.v = {}, {}
            for key, val in params.items():
                self.m[key] = np.zeros_like(val)
                self.v[key] = np.zeros_like(val)
        
        self.iter += 1
        lr_t  = self.lr * np.sqrt(1.0 - self.beta2**self.iter) / (1.0 - self.beta1**self.iter)         
        
        for key in params.keys():
            #self.m[key] = self.beta1*self.m[key] + (1-self.beta1)*grads[key]
            #self.v[key] = self.beta2*self.v[key] + (1-self.beta2)*(grads[key]**2)
            self.m[key] += (1 - self.beta1) * (grads[key] - self.m[key])
            self.v[key] += (1 - self.beta2) * (grads[key]**2 - self.v[key])
            
            params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)
            
            #unbias_m += (1 - self.beta1) * (grads[key] - self.m[key]) # correct bias
            #unbisa_b += (1 - self.beta2) * (grads[key]*grads[key] - self.v[key]) # correct bias
            #params[key] += self.lr * unbias_m / (np.sqrt(unbisa_b) + 1e-7)

使用Adam解决式的最优化问题:
SGD、Momentum、 AdaGrad、Adam_第9张图片

基于Adam的更新过程就像小球在碗中滚动一样。虽然Momentun也有类似的移动,但是相比之下, Adam的小球左右摇晃的程度有所减轻。这得益于学习的更新程度被适当地调整了。
Adam会设置3个超参数。一个是学习率(论文中以α出现),另外两个是一次momentum系数β 1和二次momentum系数β 2。根据论文,标准的设定值是β 1为0.9, β 2 为0.999。设置了这些值后,大多数情
况下都能顺利运行。

 

5 使用哪种更新方法呢

SGD、Momentum、 AdaGrad、Adam_第10张图片

根据使用的方法不同,参数更新的路径也不同。只看这个图的话, AdaGrad似乎是最好的,不过也要注意,结果会根据要解决的问题而变。并且,很显然,超参数(学习率等)的设定值不同,结果也会发生变化。
上面我们介绍了SGD、 Momentum、 AdaGrad、 Adam这4种方法,那么用哪种方法好呢?非常遗憾,(目前)并不存在能在所有问题中都表现良好的方法。这4种方法各有各的特点,都有各自擅长解决的问题和不擅长解决的问题。很多研究中至今仍在使用SGD。 Momentum和AdaGrad也是值得一试的方法。最近,很多研究人员和技术人员都喜欢用Adam。
 

你可能感兴趣的:(机器学习与深度学习算法,神经网络,python,机器学习,sgd,adam算法)