Line Search and Quasi-Newton Methods 线性搜索与拟牛顿法

2019年11月20日 阅读数:140
这篇文章主要向大家介绍Line Search and Quasi-Newton Methods 线性搜索与拟牛顿法,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

Gradient Descent

机器学习中不少模型的参数估计都要用到优化算法,梯度降低是其中最简单也用得最多的优化算法之一。梯度降低(Gradient Descent)[3]也被称之为最快梯度(Steepest Descent),可用于寻找函数的局部最小值。梯度降低的思路为,函数值在梯度反方向降低是最快的,只要沿着函数的梯度反方向移动足够小的距离到一个新的点,那么函数值一定是非递增的,如图1所示。算法

梯度降低思想的数学表述以下:app

b=aαF(a)f(a)f(b)(1)(1)b=a−α∇F(a)⇒f(a)≥f(b)

其中f(x)f(x)为存在下界的可导函数。根据该思路,若是咱们从x0x0为出发点,每次沿着当前函数梯度反方向移动必定距离αkαk,获得序列x0,x1,,xnx0,x1,⋯,xn:机器学习

xk+1=xkαkf(xk),0kn(2)(2)xk+1=xk−αk∇f(xk),0≤k≤n

对应的各点函数值序列之间的关系为:ide

f(x0)f(x1)f(x2)f(xn)(3)(3)f(x0)≥f(x1)≥f(x2)≥⋯≥f(xn)

很显然,当nn达到必定值时,函数f(x)f(x)是会收敛到局部最小值的。算法1简单描述了通常化的梯度优化方法。在算法1中,咱们须要选择一个搜索方向dkdk知足如下关系:函数

f(xk+αdk)<f(xk)α(0,ϵ](4)(4)f(xk+αdk)<f(xk)∀α∈(0,ϵ]

dk=f(x)dk=−∇f(x)时f(x)f(x)降低最快,可是只要知足f(xk)Tdk<0∇f(xk)Tdk<0的dkdk均可以做为搜素方向。通常搜索方向表述为以下形式:学习

dk=Bkf(xk)(5)(5)dk=−Bk∇f(xk)

其中BkBk为正定矩阵。当Bk=IBk=I时对应最快梯度降低算法;当Bk=H(xk)1Bk=H(xk)−1时对应牛顿法,若是H(xk)=2f(xk)H(xk)=∇2f(xk)为正定矩阵。 在迭代过程当中用于更新xkxk的步长αkαk能够是常数也能够是变化的。若是αkαk足够小,收敛是能够获得保证的,但这意味这迭代次数nn要很大时函数才会收敛(图2(a));若是αkαk比较大,更新后的点极可能越过局部最优解(图2(b))。有什么方法能够帮助咱们自动肯定最优步长呢?下面要说的线性搜索就包含一组解决方案。测试

 

Line Search

在给定搜索方向dkdk的前提下,线性搜索要解决的问题以下:优化

α=argminα0h(α)=argminα0f(xk+αdk)(6)(6)α=argminα≥0h(α)=argminα≥0f(xk+αdk)

若是h(α)h(α)是可微的凸函数,咱们能经过解析解直接求得上式最优的步长;但非线性的优化问题须要经过迭代形式求得近似的最优步长。对于上式,局部或全局最优解对应的导数为h(α)=f(xk+αdk)Tdk=0h′(α)=∇f(xk+αdk)Tdk=0。由于dkdk与f(xk)f(xk)在xkxk处的梯度方向夹角大于90度,所以h(0)0h′(0)≤0,若是能找到α^α^使得h(α^)>0h′(α^)>0,那么一定存在α[0,α^)α⋆∈[0,α^)使得h(α)=0h′(α⋆)=0。有多种迭代算法能够求得αα⋆的近似值,下面选择几种典型的介绍。ui

 

Bisection Search

二分线性搜索(Bisection Line Search)[2]可用于求解函数的根,其思想很简单,就是不断将现有区间划分为两半,选择一定含有使h(α)=0h′(α)=0的半个区间做为下次迭代的区间,直到寻得h(α)0h′(α⋆)≈0为止,算法描述见2。二分线性搜素能够确保h(α)h(α)是收敛的,只要h(α)h(α)在区间(0,α^)(0,α^)上是连续的且h(0)h′(0)和h(α^)h(α^)异号。经历nn次迭代后,当前区间[αl,αh][αl,αh]的长度为:atom

L=(12)nα^(7)(7)L=(12)nα^

由迭代的终止条件之一αhαlϵαh−αl≥ϵ知迭代次数的上界为:

Lϵk[log2(α^ϵ)](8)(8)L≤ϵ⇒k≤[log2⁡(α^ϵ)]

下面给出二分搜索的Python代码

 

复制代码
 1 def bisection(dfun,theta,args,d,low,high,maxiter=1e4):
 2     """
 3     #Functionality:find the root of the function(fun) in the interval [low,high]
 4     #@Parameters
 5     #dfun:compute the graident of function f(x)
 6     #theta:Parameters of the model
 7     #args:other variables needed to compute the value of dfun
 8     #[low,high]:the interval which contains the root
 9     #maxiter:the max number of iterations
10     """
11     eps=1e-6
12     val_low=np.sum(dfun(theta+low*d,args)*d.T)
13     val_high=np.sum(dfun(theta+high*d,args)*d.T)
14     if val_low*val_high>0:
15         raise Exception('Invalid interval!')
16     iter_num=1
17     while iter_num<maxiter:
18         mid=(low+high)/2
19         val_mid=np.sum(dfun(theta+mid*d,args)*d.T)
20         if abs(val_mid)<eps or abs(high-low)<eps:
21             return mid
22         elif val_mid*val_low>0:
23             low=mid
24         else:
25             high=mid
26         iter_num+=1
复制代码

Backtracking

回溯线性搜索(Backing Line Search)[1]基于Armijo准则计算搜素方向上的最大步长,其基本思想是沿着搜索方向移动一个较大的步长估计值,而后以迭代形式不断缩减步长,直到该步长使得函数值f(xk+αdk)f(xk+αdk)相对与当前函数值f(xk)f(xk)的减少程度大于指望值(知足Armijo准则)为止。Armijo准则(见图3)的数学描述以下:

f(xk+αdk)f(xk)+c1αf(xk)Tdk(9)(9)f(xk+αdk)≤f(xk)+c1αf′(xk)Tdk

其中f:RnRf:Rn→R,c1(0,1)c1∈(0,1),αα为步长,dkRndk∈Rn为知足f(xk)Tdk<0f′(xk)Tdk<0的搜索方向。可是仅凭Armijo准则不足以求得较好的步长,根据前面的梯度降低的知识可知,只要αα足够小就能知足Armijo准则。所以经常使用的策略就是从较大的步长开始,而后以τ(0,1)τ∈(0,1)的速度缩短步长,直到知足Armijo准则为止,这样选出来的步长不至于过小,对应的算法描述见3。前面介绍的二分线性搜索的目标是求得知足h(α)0h′(α)≈0的最优步长近似值,而回溯线性搜索放松了对步长的约束,只要步长能使函数值有足够大的变化便可。前者能够少计算几回搜索方向,但在计算最优步长上花费了很多代价;后者退而求其次,找到一个差很少的步长便可,那么代价就是要多计算几回搜索方向。接下来,咱们要证实回溯线性搜索在Armijo准则下的收敛性问题[6]。由于h(0)=f(xk)Tdk<0h′(0)=f′(xk)Tdk<0,且0<c1<10<c1<1,则有

h(0)<c1h(0)<0(10)(10)h′(0)<c1h′(0)<0

根据导数的基本定义,结合上式,有以下关系:

h(0)=limα0h(α)h(0)α=limα0f(xk+αdk)f(xk)α<ch(0)(11)(11)h′(0)=limα→0h(α)−h(0)α=limα→0f(xk+αdk)−f(xk)α<ch′(0)

所以,存在一个步长α^>0α^>0,对任意的α(0,α^)α∈(0,α^),下式均成立

f(xk+αdk)f(xk)α<cf(xk)Tdk(12)(12)f(xk+αdk)−f(xk)α<cf′(xk)Tdk

α(0,α^),f(xk+αdk)<f(xk)+cαf(xk)Tdk∀α∈(0,α^),f(xk+αdk)<f(xk)+cαf′(xk)Tdk。 下面给出基于Armijo准则的线性搜索Python代码:

 

复制代码
 1 def ArmijoBacktrack(fun,dfun,theta,args,d,stepsize=1,tau=0.5,c1=1e-3):
 2     """
 3     #Functionality:find an acceptable stepsize via backtrack under Armijo rule
 4     #@Parameters
 5     #fun:compute the value of objective function
 6     #dfun:compute the gradient of objective function
 7     #theta:a vector of parameters of the model
 8     #stepsize:initial step size
 9     #c1:sufficient decrease Parameters
10     #tau:rate of shrink of stepsize
11     """
12     slope=np.sum(dfun(theta,args)*d.T)
13     obj_old=costFunction(theta,args)
14     theta_new=theta+stepsize*d
15     obj_new=costFunction(theta_new,args)
16     while obj_new>obj_old+c1*stepsize*slope:
17         stepsize*=tau
18         theta_new=theta+stepsize*d
19         obj_new=costFunction(theta_new,args)
20     return stepsize
复制代码

Interpolation

基于Armijo准则的回溯线性搜索的收敛速度没法获得保证,特别是要回退不少次后才能落入知足Armijo准则的区间。若是咱们根据已有的函数值和导数信息,采用多项式插值法(Interpolation)[12,6,5,9]拟合函数,而后根据该多项式函数估计函数的极值点,这样选择合适步长的效率会高不少。 假设咱们只有xkxk处的函数值f(xk)f(xk)及其倒数f(xk)f′(xk),且第一次尝试的步长为α0α0。若是α0α0不知足条件,那么咱们根据这些信息能够构造一个二次近似函数hq(α)hq(α)

hq(α)=(h(α0)h(0)α0h(0)α20)α2+h(0)α+h(0)(13)(13)hq(α)=(h(α0)−h(0)−α0h′(0)α02)α2+h′(0)α+h(0)

注意,该二次函数知足hq(0)=h(0)hq(0)=h(0),hq(0)=h(0)hq′(0)=h′(0)和hq(α0)=h(α0)hq(α0)=h(α0),如图4(a)所示。接下来,根据hq(α)hq(α)的最小值估计下一个步长:

α1=h(0)α202[h(0)+h(0)α0h(α0)](14)(14)α1=h′(0)α022[h(0)+h′(0)α0−h(α0)]

若是α1α1仍然不知足条件,咱们能够继续重复上述过程,直到获得的步长知足条件为止。假设咱们在整个线性搜索过程当中都用二次插值函数,那么最好有c1(0,0.5]c1∈(0,0.5],为何呢?简单证实一下:若是α0α0不知足Armijo准则,那么一定存在比α0α0小的步长知足该准则,因此利用二次插值函数估算的步长α1<α0α1<α0才合理。结合α0α0不知足Armijo准则和α1<α0α1<α0,可知c10.5c1≤0.5。 若是咱们已经尝试了多个步长,却每次只用上一次步长的相关信息构造二次函数,未免是对计算资源的浪费,其实咱们能够利用多个步长的信息构造信息量更大更准确的插值函数的。在计算导数的代价大于计算函数值的代价时,应尽可能避免计算h(α)h′(α),下面给出一个三次插值函数hc(α)hc(α),如图4(b)所示

hc(α)=aα3+bα2+h(0)α+h(0)(15)(15)hc(α)=aα3+bα2+h′(0)α+h(0)

其中

[ab]=1α2i1α2i(αiαi1)[α2i1α3i1α2iα3i][h(αi)h(0)h(0)αih(αi1)h(0)h(0)αi1](16)(16)[ab]=1αi−12αi2(αi−αi−1)[αi−12−αi2−αi−13αi3][h(αi)−h(0)−h′(0)αih(αi−1)−h(0)−h′(0)αi−1]

hc(α)hc(α)求导,可得极值点αi+1[0,αi]αi+1∈[0,αi]的形式以下:

αi+1=b+b23ah(0)−−−−−−−−−−√3a(17)(17)αi+1=−b+b2−3ah′(0)3a

利用以上的三次插值函数求解下一个步长的过程不断重复,直到步长知足条件为止。若是出现a=0a=0的状况,三次插值函数退化为二次插值函数,在实现该算法时须要注意这点。在此过程当中,若是αiαi过小或αi1αi−1与αiαi太接近,须要重置αi=αi1/2αi=αi−1/2,该保护措施(safeguards)保证下一次的步长不至于过小[6,5]。为何会有这个做用呢?1)由于αi+1[0,αi]αi+1∈[0,αi],因此当αiαi很小时αi+1αi+1也很小;2)当αi1αi−1与αiαi太靠近时有aba≈b≈∞,根据αi+1αi+1的表达式可知αi+10αi+1≈0。 可是,在不少状况下,计算函数值后只需付出较小的代价就能顺带计算出导数值或其近似值,这使得咱们能够用更精确的三次Hermite多项式[6]进行插值,如图4(c)所示

H3(α)=[1+2αiααiαi1][ααi1αiαi1]2h(αi)+[1+2ααi1αiαi1][αi+1ααiαi1]2h(αi1)+(ααi)[ααi1αiαi1]2h(αi)+(ααi1)[αiααiαi1]2h(αi1)(18)(18)H3(α)=[1+2αi−ααi−αi−1][α−αi−1αi−αi−1]2h(αi)+[1+2α−αi−1αi−αi−1][αi+1−ααi−αi−1]2h(αi−1)+(α−αi)[α−αi−1αi−αi−1]2h′(αi)+(α−αi−1)[αi−ααi−αi−1]2h′(αi−1)

其中,三次Hermite多项式知足H3(αi1)=h(αi1)H3(αi−1)=h(αi−1),H3(αi)=h(αi)H3(αi)=h(αi),H3(αi1)=h(αi1)H3′(αi−1)=h′(αi−1)和H3(αi)=h(αi)H3′(αi)=h′(αi)。H(α)H(α)的最小值只可能在两侧的端点或者极值点处,令H3(α)=0H3′(α)=0可得下一个步长:

αi+1=αi(αiαi1)[h(αi)+d2d1h(αi)h(αi1)+2d2](19)(19)αi+1=αi−(αi−αi−1)[h′(αi)+d2−d1h′(αi)−h′(αi−1)+2d2]

其中

d1=h(αi)+h(αi1)3[h(αi)h(αi1)αiαi1](20)(20)d1=h′(αi)+h′(αi−1)−3[h(αi)−h(αi−1)αi−αi−1]
d2=sign(αiαi1)d21h(αi1)h(αi)−−−−−−−−−−−−−−−√(21)(21)d2=sign(αi−αi−1)d12−h′(αi−1)h′(αi)

下面给出二次插值及三次插值的Python代码:

 

复制代码
 1 def quadraticInterpolation(a,h,h0,g0):
 2     """
 3     #Functionality:Approximate h(a) with a quadratic function and return its stationary point
 4     #@Parameters
 5     #a:current stepsize
 6     #h:a function value about stepsize,h(a)=f(x_k+a*d)
 7     #h:h(0)=f(x_k)
 8     #g0:h'(0)=f'(0)
 9     """
10     numerator=g0*a**2
11     denominator=2*(g0*a+h0-h)
12     if abs(denominator)<1e-12:#indicates that a is almost 0
13         return a
14     return numerator/denominator
复制代码
复制代码
def cubicInterpolation(a0,h0,a1,h1,h,g):
    """
    #Functionality:Approximate h(x) with a cubic function and return its stationary point
    #This version of cubic interpolation computes h'(x) as few as possible,suitable for the case in which computing derivative is more expensive than computing function values
    #@Parameters
    #a0 and a1 are stepsize it previous two iterations
    #h0:h(a0)
    #h1:h(a1)
    #h:h(0)=f(x)
    #g:h'(0)
    """
    mat=matlib.matrix([[a0**2,-a1**2],[-a0**3,a1**3]])
    vec=matlib.matrix([[h1-h-g*a1],[h0-h-g*a0]])
    ab=mat*vec/(a0**2*a1**2*(a1-a0))
    a=ab[0,0]
    b=ab[1,0]
    if abs(a)<1e-12:#a=0 and cubic function is a quadratic one
        return -g/(2*b)
    return (-b+np.sqrt(b**2-3*a*g))/(3*a)
复制代码
复制代码
def cubicInterpolationHermite(a0,h0,g0,a1,h1,g1):
    """
    #Functionality:Approximate h(a) with a cubic Hermite polynomial function and return its stationary point
    #This version of cubic interpolation computes h(a) as few as possible,suitable for the case in which computing derivative is easier than computing function values
    #@Parameters
    #a0 and a1 are stepsize it previous two iterations
    #h0:h(a0)
    #g0:h'(a0)
    #h1:h(a1)
    #g1:h'(a1)
    """
    d1=g0+g1-3*(h1-h0)/(a1-a0)
    d2=np.sign(a1-a0)*np.sqrt(d1**2-g0*g1)
    res=a1-(a1-a0)*(g1+d2-d2)/(g1-g0+2*d2)
    return res
复制代码

基于Armijo准则的线性搜索的算法描述以下[4]对应的Armijo线性搜索的Python代码以下:

复制代码
 1 def ArmijoLineSearch(fun,dfun,theta,args,d,a0=1,c1=1e-3,a_min=1e-7,max_iter=1e5):
 2     """
 3     #Functionality:Line search under Armijo condition with quadratic and cubic interpolation
 4     #@Parameters
 5     #fun:objective Function
 6     #dfun:compute the gradient of fun
 7     #theta:a vector of parameters of the model
 8     #args:other variables needed for fun and func
 9     #d:search direction
10     #a0:initial stepsize
11     #c1:constant used in Armijo condition
12     #a_min:minimun of stepsize
13     #max_iter:maximum of the number of iterations
14     """
15     eps=1e-6
16     c1=min(c1,0.5)#c1 should<=0.5
17     a_pre=h_pre=g_pre=0
18     a_cur=a0
19     f_val=fun(theta,args) #h(0)=f(0)
20     g_val=np.sum(dfun(theta,args)*d.T) #h'(0)=f'(x)^Td
21     h_cur=g_cur=0
22     k=0
23     while a_cur>a_min and k<max_iter:
24         h_cur=fun(theta+a_cur*d,args)
25         g_cur=np.sum(dfun(theta+a_cur*d,args)*d.T)
26         if h_cur<=f_val+c1*a_cur*g_val: #meet Armijo condition
27             return a_cur
28         if not k: #k=0,use quadratic interpolation
29             a_new=quadraticInterpolation(a_cur,h_cur,f_val,g_val)
30         else: #k>0,use cubic Hermite interpolation
31             a_new=cubicInterpolationHermite(a_pre,h_pre,g_pre,a_cur,h_cur,g_cur)
32         if abs(a_new-a_cur)<eps or abs(a_new)<eps: #safeguard procedure
33             a_new=a_cur/2
34         a_pre=a_cur
35         a_cur=a_new
36         h_pre=h_cur
37         g_pre=g_cur
38         k+=1
39     return a_min #failed search
复制代码

Wolfe Search

前面说到单凭Armijo准则(不考虑回溯策略)选出的步长可能过小,为了排除这些微小的步长,咱们加上曲率的约束条件(如图5所示)

h(α)=f(xk+αdk)Tdkc2f(xk)Tdk(22)(22)h′(α)=f′(xk+αdk)Tdk≥c2f′(xk)Tdk

其中c2(c1,1)c2∈(c1,1),c1c1为Armijo准则中的常量。h(α)h′(α)为很小的负数甚至为正数时,说明从xkxk沿着dkdk移动αα后的函数梯度方向与搜索方向的夹角接近90度,继续向前移动已经不能很明显减少函数值了,此时能够中止沿着dkdk继续搜索;反之,说明继续减少函数值的空间仍是很大的,能够继续向前搜索。Armijo准则与曲率约束二者合起来称为Wolfe准则[5]:

{f(xk+αdk)f(xk+αdk)Tdkf(xk)+c1αf(xk)Tdkc2f(xk)Tdk(23)(23){f(xk+αdk)≤f(xk)+c1αf′(xk)Tdkf′(xk+αdk)Tdk≥c2f′(xk)Tdk

其中0<c1<c2<10<c1<c2<1。如图6所示,知足Wolfe准则的步长也许离h(α)h(α)的极值点较远。咱们能够修改曲率约束条件使得步长落到h(α)h(α)的极值点的一个较宽的领域中,强Wolfe准则对步长αα的约束以下:

{f(xk+αdk)|f(xk+αdk)Tdk|f(xk)+c1αf(xk)Tdkc2|f(xk)Tdk|(24)(24){f(xk+αdk)≤f(xk)+c1αf′(xk)Tdk|f′(xk+αdk)Tdk|≥c2|f′(xk)Tdk|

强Wolfe准则不容许h(α)h′(α)为太大的正数,能够排除远离极值点的区间。那么究竟是否存在知足强Wolfe准则的步长呢?假设h(α)=f(xk+αdk)h(α)=f(xk+αdk)连续可微,在整个α>0α>0的定义域上存在下界。由于0<c1<10<c1<1,因此l(α)=f(xk)+αc1f(xk)Tdkl(α)=f(xk)+αc1f′(xk)Tdk必然与h(α)h(α)至少有一个交点。假设αα′为最小的交点对应的步长,则有

f(xk+αdk)=f(xk)+αc1f(xk)Tdk(25)(25)f(xk+α′dk)=f(xk)+α′c1f′(xk)Tdk

那么对于知足α(0,α)α∈(0,α′)的步长必然都知足Armijo准则。根据零值定理,存在α′′(0,α)α″∈(0,α′)知足

f(xk+αdk)f(xk)=αf(xk+α′′dk)Tdk(26)(26)f(xk+α′dk)−f(xk)=α′f′(xk+α″dk)Tdk

结合上面两个关系式,由0<c1<c20<c1<c2和f(xk)Tdk<0f′(xk)Tdk<0,可得

f(xk+α′′dk)Tdk=c1f(xk)Tdk>c2f(xk)Tdk(27)(27)f′(xk+α″dk)Tdk=c1f′(xk)Tdk>c2f′(xk)Tdk

由此可知,α′′α″知足强Wolfe准则。若是h(α)h(α)是一个较为平滑的函数,那么包含α′′α″的较小领域都会知足强Wolfe准则。 若是在线性搜索过程当中利用强Wolfe准则,能够更精确得找到更靠近极值点的步长,在目前线性搜索中用得不少。基于强Wolfe准则的线性搜索包含两个阶段:第一个阶段从初始步长开始,不断增大步长,直到找到一个知足强Wolfe准则的步长或包含该步长的区间为止;第二个阶段是在已知包含知足强Wolfe准则步长区间的基础上,不断缩减区间,直到找到知足强Wolfe准则的步长为止。基于强Wolfe准则的线性搜索算法描述以下5

 

在算法5中,αnewαnew的更新是由于在区间(αpre,αcur)(αpre,αcur)内没有知足Wolfe准则的步长,因此要选取一个大于αcurαcur的步长αnewαnew。在算法中,咱们是用二次插值函数计算αnewαnew的,因此要求0<c1<0.50<c1<0.5。固然,也能够用其余方法,好比让αcurαcur乘以一个大于1的常数,只要能较快找到一个包含知足Wolfe的区间便可。因此,该算法每次尝试的步长αcurαcur的逐渐递增的;一旦找到了包含知足Wolfe准则的步长的区间,当即调用zoomzoom函数不断缩短区间,并返回知足Wolfe的步长。根据算法逻辑,咱们能够推断出αpreαpre知足Armijo准则,但违背曲率约束,并且导数为负数。由上述三个条件,可知αpreαpre一定位于知足Wolfe准则的区间的左侧的呈降低趋势的曲线上,只要αcurαcur位于该区间的右侧便可。那么怎样判断区间(αpre,αcur)(αpre,αcur)是否包含知足Wolfe准则的步长呢?下面给出三种αcurαcur位于该区间的右侧的充分条件:

  1. αcurαcur不知足Armijo准则;
  2. h(αcur)h(αpre)h(αcur)≥h(αpre);
  3. h(αcur)0h′(αcur)≥0

这一点结合图7就很容易理解了,我在图中分别用红色和绿色点标注了αpreαpre和αcurαcur可能的位置,蓝色带数字的圆圈注明了αcurαcur知足哪些条件。基于Wolfe准则的线性搜索Python代码以下:

复制代码
 1 def WolfeLineSearch(fun,dfun,theta,args,d,a0=1,c1=1e-4,c2=0.9,a_min=1e-7,max_iter=1e5):
 2     """
 3     #Functionality:find a stepsize meeting Wolfe condition
 4     #@Parameters
 5     #fun:objective Function
 6     #dfun:compute the gradient of fun
 7     #theta:a vector of parameters of the model
 8     #args:other variables needed for fun and func
 9     #d:search direction
10     #a0:intial stepsize
11     #c1:constant used in Armijo condition
12     #c2:constant used in curvature condition
13     #a_min:minimun of stepsize
14     #max_iter:maximum of the number of iterations
15     """
16     eps=1e-16
17     c1=min(c1,0.5)
18     a_pre=0
19     a_cur=a0
20     f_val=fun(theta,args) #h(0)=f(x)
21     g_val=np.sum(dfun(theta,args)*d.T)
22     h_pre=f_val #h'(0)=f'(x)^Td
23     k=0
24     while k<max_iter and abs(a_cur-a_pre)>=eps:
25         h_cur=fun(theta+a_cur*d,args) #f(x+ad)
26         if h_cur>f_val+c1*a_cur*g_val or h_cur>=h_pre and k>0:
27             return zoom(fun,dfun,theta,args,d,a_pre,a_cur,c1,c2)
28         g_cur=np.sum(dfun(theta+a_cur*d,args)*d.T)
29         if abs(g_cur)<=-c2*g_val:#satisfy Wolfe condition
30             return a_cur
31         if g_cur>=0:
32             return zoom(fun,dfun,theta,args,d,a_pre,a_cur,c1,c2)
33         a_new=quadraticInterpolation(a_cur,h_cur,f_val,g_val)
34         a_pre=a_cur
35         a_cur=a_new
36         h_pre=h_cur
37         k+=1
38     return a_min
复制代码

zoom函数的算法描述见6。zoom函数中须要传入搜寻区间[αlow,αhigh][αlow,αhigh],其中αlow<αhighαlow<αhigh。本文中的zoom函数与文献[5]中的内容略有差别,可是本文的zoom函数思路更简单和清晰。由算法5中分析获得的调用zoom函数的条件,知道αlowαlow必须知足Armijo准则,且位于全部知足Wolfe准则的步长的左侧。咱们先取[αlow,αhigh][αlow,αhigh]区间的中值做为下一个测试的步长αnewαnew,若是刚好知足Wolfe准则,则直接返回。若是αnewαnew违反Armijo准则或大于h(αlow)h(αlow),显然区间[αlow,αnew][αlow,αnew]包含知足Wolfe准则的步长,所以用αnewαnew替换αhighαhigh以缩短区间长度;不然,αnewαnew必然也知足Armijo准则,若是h(αnew)>0h′(αnew)>0,则αnewαnew与αhighαhigh都在知足Wolfe准则的区间右侧,用αnewαnew替代αhighαhigh,反之则用αnewαnew替代αlowαlow。上述的迭代过程不断缩短步长,知道求得知足Wolfe准则的步长为止。若是在有限迭代次数内搜索失败,则返回必然知足Armijo准则的步长αlowαlow。

zoom函数对应的Python代码以下:

复制代码
 1 def zoom(fun,dfun,theta,args,d,a_low,a_high,c1=1e-3,c2=0.9,max_iter=1e4):
 2     """
 3     #Functionality:enlarge the interval to find a stepsize meeting Wolfe condition
 4     #@Parameters
 5     #fun:objective Function
 6     #dfun:compute the gradient of fun
 7     #theta:a vector of parameters of the model
 8     #args:other variables needed for fun and func
 9     #d:search direction
10     #[a_low,a_high]:interval containing a stepsize satisfying Wolfe condition
11     #c1:constant used in Armijo condition
12     #c2:constant used in curvature condition
13     #max_iter:maximum of the number of iterations
14     """
15     if a_low>a_high:
16         print('low:%f,high:%f'%(a_low,a_high))
17         raise Exception('Invalid interval of stepsize in zoom procedure')
18     eps=1e-16
19     h=fun(theta,args) #h(0)=f(x)
20     g=np.sum(dfun(theta,args)*d.T) #h'(0)=f'(x)^Td
21     k=0
22     h_low=fun(theta+a_low*d,args)
23     h_high=fun(theta+a_high*d,args)
24     if h_low>h+c1*a_low*g:
25         raise Exception('Left endpoint violates Armijo condition in zoom procedure')
26     while k<max_iter and abs(a_high-a_low)>=eps:
27         a_new=(a_low+a_high)/2
28         h_new=fun(theta+a_new*d,args)
29         if h_new>h+c1*a_new*g or h_new>h_low:
30             a_high=a_new
31             h_high=h_new
32         else:
33             g_new=np.sum(dfun(theta+a_new*d,args)*d.T)
34             if abs(g_new)<=-c2*g: #satisfy Wolfe condition
35                 return a_new 
36             if g_new*(a_high-a_low)>=0:
37                 a_high=a_new
38                 h_high=h_new
39             else:
40                 a_low=a_new
41                 h_low=h_new
42         k+=1
43     return a_low #a_low definitely satisfy Armijo condition
复制代码

Newton's Method

牛顿法(Newton's method)[8]以迭代方式求解函数的根,其基本思想是从一个初始点出发,不断在当前点xkxk处用切线近似函数f(x)f(x),并求得该切线与xx轴的交点做为下一次的迭代初始点xk+1xk+1,直到找到f(x)=0f(x)=0的近似解为止。Newton法可用于二次可微函数f(x)f(x)的最优化问题。 在xkxk处用二阶泰勒展开来对f(xk)f(xk)其进行逼近。

f(xk+x)f(xk)+f(xk)x+12xTBkx(28)(28)f(xk+△x)≈f(xk)+f′(xk)△x+12△xTBk△x

如今,咱们的目标是在xkxk附近求得使f(x)f(x)取得极小值的x△x。将上式对x△x求导可得函数f(x)f′(x)在xk+1=xk+xxk+1=xk+△x处的线性近似以下:

f(xk+1)=f(xk)+Bk(xk+1xk)(29)(29)f′(xk+1)=f′(xk)+Bk(xk+1−xk)

其中Bk=2f(xk)Bk=∇2f(xk)为f(x)f(x)在xkxk处对应的Hessian矩阵。因为函数的极值点通常都对应f(x)=0f′(x)=0,令f(xk+1)=0f′(xk+1)=0并化简可得迭代公式为:

xk+1=xkB1kf(xk)(30)(30)xk+1=xk−Bk−1f′(xk)

牛顿迭代法收敛速度很快,对于二次函数能够一次性找到最优解。但用于求解优化问题时,须要付出很大的代价求得函数的一阶导数、二阶导数及其逆矩阵。此外,有的函数还存在不可导、Hessian矩阵不可逆、迭代点之间存在循环(即xk+t=xkxk+t=xk)等情形,这些都成为了牛顿法的应用障碍。牛顿迭代法用于求解极值点f(x)=0f′(x)=0的步骤见算法7。固然,也可用牛顿法求最优步长,只需将算法7中的函数f(x)f(x)替换为关于步长的函数h(α)h(α)便可。

 

Quasi-Newton Method

拟牛顿(Quasi-Newton)[11]算法可用于求解函数的局部最优解,也就是那些导数为0的驻点。牛顿法用于解决优化问题时,事先假设原函数可用二次函数近似,而后用一阶和二阶导数寻找局部最优解。而在拟牛顿算法中,不须要准确计算Hessian矩阵,取而代之的是运用下面的拟牛顿条件分析连续两个梯度向量获得的近似值矩阵BkBk:

f(xk+1)f(xk)Bk+1(xk+1xk)(31)(31)f′(xk+1)−f′(xk)≈Bk+1(xk+1−xk)

拟牛顿算法的算法流程见8,其基本思想是利用矩阵BkBk计算牛顿方向的近似值dkdk。各类拟牛顿算法的主要差别在于近似Hessian矩阵的更新策略,表1列出了部分主流的拟牛顿算法的迭代更新规则,其中sk=xk+1xk=αkB1kf(xk)sk=xk+1−xk=−αkBk−1f′(xk),yk=f(xk+1)f(xk)yk=f′(xk+1)−f′(xk)。拟牛顿算法中最经常使用的是BFGS,其针对有限内存的机器的算法变种L-BFGS[4]在机器学习领域又备受青睐。BFGS须要存储n×nn×n的矩阵HkHk用于近似Hessian矩阵的逆矩阵;而L-BFGS仅须要存储过去mm(mm通常小于10)对nn维的更新数据(x,f(xi))(x,f′(xi))便可。L-BFGS的空间复杂度为线性,特别适用于变量很是多的优化问题。BFGS的算法描述很容易写出来,以下:

 

复制代码
 1 def BFGS(fun,dfun,theta,args,H=None,mode=0,eps=1e-12,max_iter=1e4):
 2     """
 3     #Functionality:find the minimum of objective function f(x)
 4     #@Parameters
 5     #fun:objective function f(x)
 6     #dfun:compute the gradient of f(x)
 7     #args:parameters needed by fun and dfun
 8     #theta:start vector of parameters of the model
 9     #H:initial inverse Hessian approximation
10     #mode:index of line search algorithm
11     """
12     x_pre=x_cur=theta
13     g=dfun(x_cur,args)
14     I=matlib.eye(theta.size)
15     if not H:#initialize H as an identity matrix
16         H=I
17     k=0
18     while k<max_iter and np.sum(np.abs(g))>eps:
19         d=-g*H
20         step=LineSearch(fun,dfun,x_pre,args,d,1,mode)
21         x_cur=x_pre+step*d
22         s=step*d
23         y=dfun(x_cur,args)-dfun(x_pre,args)
24         ys=np.sum(y*s.T)
25         if abs(ys)<eps:
26             return x_cur
27         change=(ys+np.sum(y*H*y.T))*(s.T*s)/(ys**2)-(H*y.T*s+s.T*y*H)/ys
28         H+=change
29         g=dfun(x_cur,args)
30         x_pre=x_cur
31         k+=1
32     return x_cur
复制代码

下面咱们分析如何构造下L-BFGS的算法[10,13]。假设咱们如今处于优化过程的第k(km)k(k≥m)次迭代,参数为xkxk,梯度gk=f(xk)gk=f′(xk),已经保存的mm条更新数据为sk=xk+1xksk=xk+1−xk及yk=gk+1gkyk=gk+1−gk。咱们最终须要计算的是搜索方向dk=Hkgkdk=−Hkgk,因而令Vk=(IρkyksTk)Vk=(I−ρkykskT),ρk=1/(yTksk)ρk=1/(ykTsk),将表1中BFGS的HkHk的更新规则展开,咱们能够获得下式:

===HkgkVTk1Hk1Vk1gk+sk1ρk1sTk1gkVTk1VTk2Hk2Vk2Vk1gk+VTk1sk2ρk2sTk2Vk1gk+sk1ρk1sTk1gk(VTk1VTk2VTkm)Hkm(VkmVk2Vk1)gk+(VTk1VTk2VTkm+1)skmρkmsTkm(Vkm+1Vk1Vk)gk+(VTk1VTk2VTkm+2)skm+1ρkm+1sTkm+1(Vkm+2Vk2Vk1)gk++VTk1sk2ρk2sTk2Vk1gk+sk1ρk1sTk1gk(32)(32)Hkgk=Vk−1THk−1Vk−1gk+sk−1ρk−1sk−1Tgk=Vk−1TVk−2THk−2Vk−2Vk−1gk+Vk−1Tsk−2ρk−2sk−2TVk−1gk+sk−1ρk−1sk−1Tgk=(Vk−1TVk−2T⋯Vk−mT)Hk−m(Vk−m⋯Vk−2Vk−1)gk+(Vk−1TVk−2T⋯Vk−m+1T)sk−mρk−msk−mT(Vk−m+1⋯Vk−1Vk)gk+(Vk−1TVk−2T⋯Vk−m+2T)sk−m+1ρk−m+1sk−m+1T(Vk−m+2⋯Vk−2Vk−1)gk+⋯+Vk−1Tsk−2ρk−2sk−2TVk−1gk+sk−1ρk−1sk−1Tgk

上式很是有规律,这就为迭代求解奠基了很好的基础。咱们令q0=gkq0=gk,则当1im1≤i≤m时有

qi=(VkiVk2Vk1)gk(33)(33)qi=(Vk−i⋯Vk−2Vk−1)gk
ai=ρkisTkiqi1(34)(34)ai=ρk−isk−iTqi−1