引言

梯度下降的原理相信大家都已经清楚,梯度下降是通过求解目标函数在给定数据点出的梯度,然后调数自变量(或者参数)朝着梯度方向变化,按照这种方式变化的自变量将更加速度得使目标函数达到最大值。

最基本的梯度下降方法称为随机梯度下降(SGD),是通过每次选取一个样本,计算当前梯度并进行更新。由于每次采样一个样本,会使得梯度方向变化剧烈,因此快速收敛,因此在不断的探索过程中,人们对梯度的计算方式进行了各种各样的改进,今天要讲的是Momentum(动量法)和Nesterov-accelerated-gradient(NAG)。

Momentum

动量法,顾名思义,在梯度的计算过程中增加了动量信息,从百度百科可以看到

物理学上讲的动量是与速度方向有关的量。在动量法梯度下降中,同样也是应用了这个原理,它是在当前样本梯度的基础上,以指数加权平均的方式将上一步的梯度累加到当前样本梯度之上,形成最终的梯度向量。其中指数加权平均(也常用在平滑曲线)的公式长这个样子:

在这里,v表示的是最终的梯度,vt-1表示上一步的梯度,θt表示通过当前样本计算的梯度。可以看到最终的梯度是将当前样本梯度以及上一步梯度融合的结果。

这样的好处是,可以使每次更新更加温和,不至于突变,同时在陷入局部极值点或者鞍点时可以及时跳出。论文中给出的更新公式为:

NAG

NAG的思想是在动量法的基础上展开的。动量法是思想是,将当前梯度与过去梯度加权平均,来获取即将更新的梯度。在知道梯度之后,更新自变量到新的位置。也就是说我们在其实在每一步,是知道下一时刻位置的。这是Nesterov就说了:那既然这样的话,我们何不直接采用下一时刻的梯度来和上一时刻梯度进行加权平均呢?

其实这样的NAG是具有提前向前看一步的能力的。一个应用场景是,当在上坡的时候,它可以提前感知到上坡,并提前减速。

代码实践

清楚原理之后便可以开始编写代码,使用python编写,应用闭包技巧实现梯度存储的功能。这里仅展示核心代码,即梯度求解、梯度更新、以及整体代码结构方面的内容。完整代码详见我的github:https://github.com/wangjunhe8127/Momentum-and-Nesterov-accelerated-gradient

梯度下降

# 计算mg梯度
def com_mg_gradient(perior_gradient, beta, x):
    # 以下两行主要算法
    now_gradient = com_gradient1(perior_gradient, x)
    res = beta*perior_gradient+(1-beta)*now_gradient
    return res
# 计算nag梯度
def com_nag_gradient(perior_gradient, alpha, beta, x):
    # 以下两行主要算法
    now_gradient = com_gradient2(perior_gradient, x - alpha*beta*perior_gradient)
    res = beta*perior_gradient+now_gradient
    return res

这里的代码很好理解

梯度更新

def MG(alpha, beta, x, gradient):
    all_x = [x]
    all_y = []
    all_gradient = [gradient]
    all_steps = []
    def update_gradient(t):
        nonlocal gradient, x, all_x, all_y, all_gradient, all_steps
        gradient = com_mg_gradient(gradient, beta, x)
        x = x - alpha*gradient
        y = com_y1(x)
        all_x.append(x.tolist())
        all_y.append(y)
        all_gradient.append(gradient.tolist())
        all_steps.append(t)
        return all_x, all_y, all_gradient, all_steps
    return 

这里使用闭包,闭包就是函数中又有一个函数,当对象传参不多,且不需要全局变量的话,这种方法最好。在这里我们将gradient等设置为包内变量,通过nonlocal关键字设置为包内临时变量。注意这些通过关键字设置的变量需要通过函数传递,或者在包内定义。

为了后面画图方便,将这些梯度、函数值、自变量值、步数等放入数组并return。

整体结构

1、字典传参

这里同样采用了方便的字典参数调试技巧

2、在调用模型的开始阶段,将参数序号传入,即代码里的target

3、主函数

主函数实现了绘图功能,效果如下: