作业笔记:基于二次插值的Wolfe-Powell非精确线搜索算法及Python代码实现

写在前面的说明:

  • 查阅了很多资料,发现资料里对于非精确线搜索的Wolfe-Powell准则求步长都讲得很粗糙,中英文的教材里(黄红选.数学规划 4.2.1节、Jorge Nocedal.Numerical Optimization 3.1节等)只介绍了Wolfe-Powell准则(Wolfe Conditions)的原理,没有给出算法步骤;而能查到的W-P代码实现,都只给了代码,也没有给算法步骤。
  • 关于Wolfe Conditions的数学原理可以看Numerical Optimization一书,不再具体介绍。这篇笔记主要介绍了怎样把原理转化为算法步骤,再转化为Python代码。
  • 关于二次插值法,大多数教材里面只讲了三点二次插值法(陈宝林.最优化理论与算法 9.3.3节),这篇笔记里二点二次插值的内插和外插公式是自己推的,如果有问题也可以指出~

1 准备知识:二次插值法

1.1 概述

二次插值法(抛物线法)基本思路:在极小点附近,用二次三项式 φ ( x ) \varphi(x) φ(x)逼近目标函数 f ( x ) f(x) f(x)

分为三点二次插值法和二点二次插值法:

  • 三点二次插值法:已知三点的函数值,求极小点
  • 二点二次插值法:
    • 已知两点的函数值,和其中一点的导数值,求极小点(内插)
    • 已知两点的导数值,求极小点(外插)

1.2 二点二次插值法

(1)内插:找两点之间的极小点

令二次三项式 φ ( x ) = a + b ( x − x 1 ) + c ( x − x 1 ) 2 \varphi(x)=a+b(x-x_1)+c(x-x_1)^2 φ(x)=a+b(xx1)+c(xx1)2

已知 φ ( x 1 ) = f ( x 1 ) \varphi(x_1)=f(x_1) φ(x1)=f(x1) φ ′ ( x 1 ) = f ′ ( x 1 ) \varphi'(x_1)=f'(x_1) φ(x1)=f(x1) φ ( x 2 ) = f ( x 2 ) \varphi(x_2)=f(x_2) φ(x2)=f(x2),其中 f ′ ( x 1 ) < 0 f'(x_1)<0 f(x1)<0

为保证二次插值函数 φ ( x ) \varphi(x) φ(x)有极小点,要求 f ( x 2 ) > f ( x 1 ) + f ′ ( x 1 ) ( x 2 − x 1 ) f(x_2)>f(x_1)+f'(x_1)(x_2-x_1) f(x2)>f(x1)+f(x1)(x2x1)或者 f ′ ( x 2 ) > 0 f'(x_2)>0 f(x2)>0

得到以下方程组:
{ φ ( x 1 ) = a = f ( x 1 ) φ ′ ( x 1 ) = b = f ′ ( x 1 ) φ ( x 2 ) = a + b ( x 2 − x 1 ) + c ( x 2 − x 1 ) 2 = f ( x 2 ) \left\{\begin{matrix} \varphi(x_1)=a=f(x_1) \\ \varphi'(x_1)=b=f'(x_1) \\ \varphi(x_2)=a+b(x_2-x_1)+c(x_2-x_1)^2=f(x_2) \end{matrix}\right. φ(x1)=a=f(x1)φ(x1)=b=f(x1)φ(x2)=a+b(x2x1)+c(x2x1)2=f(x2)
解方程组,得 a = f ( x 1 ) a=f(x_1) a=f(x1) b = f ′ ( x 1 ) b=f'(x_1) b=f(x1) c = f ( x 2 ) − f ( x 1 ) − f ′ ( x 1 ) ( x 2 − x 1 ) ( x 2 − x 1 ) 2 c=\frac{f(x_2)-f(x_1)-f'(x_1)(x_2-x_1)}{(x_2-x_1)^2} c=(x2x1)2f(x2)f(x1)f(x1)(x2x1)

为求 φ ( x ) \varphi(x) φ(x)的极小点,则令 φ ′ ( x ) = b + 2 c ( x − x 1 ) = 0 \varphi'(x)=b+2c(x-x_1)=0 φ(x)=b+2c(xx1)=0 ⇒ x = x 1 − b 2 c \Rightarrow x=x_1-\frac{b}{2c} x=x12cb,将 b , c b,c b,c代入得极小点
x ˉ = x 1 − f ′ ( x 1 ) ( x 2 − x 1 ) 2 2 [ f ( x 2 ) − f ( x 1 ) − f ′ ( x 1 ) ( x 2 − x 1 ) ] \bar x=x_1-\frac{f'(x_1)(x_2-x_1)^2}{2\left [f(x_2)-f(x_1)-f'(x_1)(x_2-x_1)\right ]} xˉ=x12[f(x2)f(x1)f(x1)(x2x1)]f(x1)(x2x1)2

(2)外插:找两点之外的极小点

令二次三项式 φ ( x ) = a + b ( x − x 2 ) + c ( x − x 2 ) 2 \varphi(x)=a+b(x-x_2)+c(x-x_2)^2 φ(x)=a+b(xx2)+c(xx2)2

已知 φ ′ ( x 1 ) = f ′ ( x 1 ) \varphi'(x_1)=f'(x_1) φ(x1)=f(x1) φ ′ ( x 2 ) = f ′ ( x 2 ) \varphi'(x_2)=f'(x_2) φ(x2)=f(x2)

为保证二次插值函数 φ ( x ) \varphi(x) φ(x)有极小点,要求 f ′ ( x 1 ) < f ′ ( x 2 ) < 0 f'(x_1)<f'(x_2)<0 f(x1)<f(x2)<0

得到以下方程组:
{ φ ′ ( x 1 ) = b + 2 c ( x 1 − x 2 ) = f ′ ( x 1 ) φ ′ ( x 2 ) = b + 2 c ( x 2 − x 2 ) = f ′ ( x 2 ) \left\{\begin{matrix} \varphi'(x_1)=b+2c(x_1-x_2)=f'(x_1) \\ \varphi'(x_2)=b+2c(x_2-x_2)=f'(x_2) \end{matrix}\right. {φ(x1)=b+2c(x1x2)=f(x1)φ(x2)=b+2c(x2x2)=f(x2)
解方程组,得 b = f ′ ( x 2 ) b=f'(x_2) b=f(x2) c = f ′ ( x 1 ) − f ′ ( x 2 ) 2 ( x 2 − x 1 ) c=\frac{f'(x_1)-f'(x_2)}{2(x_2-x_1)} c=2(x2x1)f(x1)f(x2)

为求 φ ( x ) \varphi(x) φ(x)的极小点,则令 φ ′ ( x ) = b + 2 c ( x − x 2 ) = 0 \varphi'(x)=b+2c(x-x_2)=0 φ(x)=b+2c(xx2)=0 ⇒ x = x 2 − b 2 c \Rightarrow x=x_2-\frac{b}{2c} x=x22cb,将 b , c b,c b,c代入得极小点
x ˉ = x 2 + f ′ ( x 2 ) ( x 2 − x 1 ) f ′ ( x 1 ) − f ′ ( x 2 ) \bar x=x_2+\frac{f'(x_2)(x_2-x_1)}{f'(x_1)-f'(x_2)} xˉ=x2+f(x1)f(x2)f(x2)(x2x1)

2 准备知识:Wolfe Conditions

f ( x k + α k d k ) ≤ f ( x k ) + ρ α k g ( x k ) T d k       c o n d i t i o n ( 1 ) g ( x k + 1 ) T d k ≥ σ g ( x k ) T d k                             c o n d i t i o n ( 2 ) f(x_k+\alpha_k d_k)\leq f(x_k)+\rho \alpha_k g(x_k)^T d_k \ \ \ \ \ condition(1) \\ g(x_{k+1})^T d_k \geq \sigma g(x_k)^T d_k \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ condition(2) f(xk+αkdk)f(xk)+ραkg(xk)Tdk     condition(1)g(xk+1)Tdkσg(xk)Tdk                           condition(2)

其中 ρ ∈ ( 0 , 1 / 2 ) , σ ∈ ( ρ , 1 ) \rho \in(0,1/2),\sigma \in (\rho,1) ρ(0,1/2),σ(ρ,1)


3 基于二点二次插值的Wolfe-Powell法

3.1 基本思路

  • 满足 c o n d i t i o n ( 1 ) condition(1) condition(1),且满足 c o n d i t i o n ( 2 ) condition(2) condition(2),直接输出步长 α \alpha α
  • 满足 c o n d i t i o n ( 1 ) condition(1) condition(1),但不满足 c o n d i t i o n ( 2 ) condition(2) condition(2),用外插公式增大步长 α \alpha α(见Step 3)
  • 不满足 c o n d i t i o n ( 1 ) condition(1) condition(1),用内插公式缩小步长 α \alpha α(见Step 2)

3.2 计算步骤

Step 1.

选取初始数据,给定初始搜索区间 [ 0 , α max ⁡ ] [0,\alpha_{\max}] [0,αmax],给出参数 ρ ∈ ( 0 , 1 / 2 ) , σ ∈ ( ρ , 1 ) \rho \in(0,1/2),\sigma \in (\rho,1) ρ(0,1/2),σ(ρ,1),令 α 1 = 0 , α 2 = α max ⁡ \alpha_1=0,\alpha_2=\alpha_{\max} α1=0,α2=αmax,取 α ∈ ( α 1 , α 2 ) \alpha \in(\alpha_1,\alpha_2) α(α1,α2),计算 ϕ 1 = f ( x k ) \phi_1=f(x_k) ϕ1=f(xk) ϕ 1 ′ = g ( x k ) T d k \phi_1'=g(x_k)^T d_k ϕ1=g(xk)Tdk

def WolfePowell(f,d,x,alpha_,rho,sigma):
    a1 = 0
    a2 = alpha_
    alpha = (a1+a2)/2

输入的初始数据有,f:目标函数,d:初始方向,x:初始点,alpha_ α max ⁡ \alpha_{\max} αmaxrho ρ \rho ρsigma σ \sigma σ。另外,a1 α 1 \alpha_1 α1a2 α 2 \alpha_2 α2alpha α \alpha α,算法中取 α = ( α 1 + α 2 ) / 2 \alpha=(\alpha_1+\alpha_2)/2 α=(α1+α2)/2

phi1 = fun(x) 
phi1_ = np.dot(grad,d)

phi1 ϕ 1 \phi_1 ϕ1phi1_ ϕ 1 ′ \phi_1' ϕ1,求梯度的方法会在后面说明。

Step 2.

计算 ϕ = ϕ ( α ) = f ( x k + α d k ) \phi=\phi(\alpha)=f(x_k+\alpha d_k) ϕ=ϕ(α)=f(xk+αdk)

ϕ ≤ ϕ 1 + ρ α ϕ 1 ′ \phi\leq\phi_1+\rho\alpha\phi_1' ϕϕ1+ραϕ1条件1)成立,转到Step 3;否则,由二点二次插值法的内插公式计算 α ˉ \bar \alpha αˉ
α ˉ = α 1 − ϕ 1 ′ ( α − α 1 ) 2 2 [ ϕ − ϕ 1 − ϕ 1 ′ ( α − α 1 ) ] \bar \alpha=\alpha_1-\frac{\phi_1'(\alpha-\alpha_1)^2}{2[\phi-\phi_1-\phi_1'(\alpha-\alpha_1)]} αˉ=α12[ϕϕ1ϕ1(αα1)]ϕ1(αα1)2
α 2 = α \alpha_2=\alpha α2=α α = α ˉ \alpha=\bar \alpha α=αˉ,转到Step 2

if phi <= phi1 + rho * alpha * phi1_:
    ……
else:
    alpha_new = a1 - 0.5*(alpha-a1)**2*phi1_/((phi-phi1)-(alpha-a1)*phi1_)
    a2 = alpha
    alpha = alpha_new

Step 3.

计算 ϕ ′ = ϕ ′ ( α ) = g ( x k + α d k ) T d k \phi'=\phi'(\alpha)=g(x_k+\alpha d_k)^T d_k ϕ=ϕ(α)=g(xk+αdk)Tdk

ϕ ′ ≥ σ ϕ 1 ′ \phi'\geq\sigma\phi_1' ϕσϕ1条件2)成立,则令 α k = α \alpha_k=\alpha αk=α,输出 α k \alpha_k αk,停止;否则,由二点二次插值法的外插公式计算 α ˉ \bar \alpha αˉ
α ˉ = α + ϕ ′ ( α − α 1 ) ϕ 1 ′ − ϕ ′ \bar \alpha=\alpha+\frac{\phi'(\alpha-\alpha_1)}{\phi_1'-\phi'} αˉ=α+ϕ1ϕϕ(αα1)

if phi <= phi1 + rho * alpha * phi1_:
    ……
    if phi_ >= sigma*phi0_:
        break
    else:
        alpha_new = alpha + (alpha-a1) *phi_ / (phi1_-phi_)
        a1 = alpha
        alpha = alpha_new
        phi1 = phi
        phi1_ = phi_
else:
    ……

4 对Wolfe-Powell法的改进

4.1 计算步长方法的改进

  • 网上大多数的W-P程序中,采用如下的方法计算步长α
    W-P法计算步骤

本文的算法中,二点二次插值法的内插和外插,分别对应上面第(3)步和第(4)步。但不同之处在于对步长放缩的方式:

  • 转到第(3)步时,不满足 c o n d i t i o n ( 1 ) condition(1) condition(1),所以要缩小步长 λ \lambda λ。此时步长 λ \lambda λ太大,取 λ \lambda λ和步长下界 a a a的中值为新步长,就能使步长缩小。因为 a ≥ 0 a\geq0 a0,所以步长最多缩小到原来的 1 / 2 1/2 1/2。但是,这种方法得到的新步长,并不一定能使目标函数最小。采用二次插值法的内插法,可以找到 [ α 1 , α ] [\alpha_1,\alpha] [α1,α]之间的极小点,能使目标函数近似最小,提高了算法效率。

  • 转到第(4)步时,满足 c o n d i t i o n ( 1 ) condition(1) condition(1),但不满足 c o n d i t i o n ( 2 ) condition(2) condition(2),所以要增大步长 λ \lambda λ。此时步长 λ \lambda λ太小,取 λ \lambda λ和步长上界 b b b的中值为新步长,就能使步长放大。因为 λ = min ⁡ ( 2 λ , ( λ + b ) / 2 ) \lambda=\min{(2\lambda,(\lambda+b)/2)} λ=min(2λ,(λ+b)/2),步长最多放大到原来的 2 2 2倍。但是,这种方法得到的新步长,并不一定能使目标函数最小。采用二次插值法的外插法,可以找到 [ α 1 , α ] [\alpha_1,\alpha] [α1,α]之外(大于 α \alpha α一侧)的极小点,能使目标函数近似最小,提高了算法效率。

4.2 求梯度方法的改进

  • 在有些程序中,需要手动计算梯度并以数组形式输入

    def fun(x):
        return x[0]**2+2*x[1]**2-2*x[0]*x[1]+2*x[1]+2
    def gfun(x):
        return np.array([2*x[0]-2*x[1], 4*x[1]-2*x[0]+2])
    
  • 在另一些程序中,采用了数值法求梯度,数值法在参数比较少的情况下,效果较好;但参数极多的情况下,计算量非常大、运行效率极差;常用于检验所写梯度的正确性。

def num_gradient(f, x, delta = DELTA):
    """ 计算数值梯度 """
    # 中心差分
    try:
        params = x.tolist()
        gradient_vector = np.array([(f(*(params[:i] + [params[i] + delta] + params[i + 1:]))
                                    -f(*(params[:i] + [params[i] - delta] + params[i + 1:])))
                                    /(2 * delta) for i in range(len(params))])
        return gradient_vector
    # 若中心差分报错(例如根号x在0处求梯度,定义域不可行),改为前向差分或后向差分
    except:
        try:
            params = x.tolist()
            gradient_vector = np.array([(f(*(params[:i] + [params[i] + delta] + params[i + 1:]))
                                        -f(*(params[:])))
                                        /delta for i in range(len(params))])
            return gradient_vector
        except:
            params = x.tolist()
            gradient_vector = np.array([(-f(*(params[:i] + [params[i] - delta] + params[i + 1:]))
                                        +f(*(params[:])))
                                        /delta for i in range(len(params))])
            return gradient_vector

因此,我们对求梯度方法进行了改进,采用了解析法,也就是先计算出偏导表达式,再形成梯度向量。用解析法求得的梯度更为精确,效果更好。

dx = []
grad = []
for a in range(dimensions):
    dx.append(diff(f, symbols('x'+str(a),real=True)))  #偏导表达式,梯度
    item={}
    for i in range(dimensions):
        item[x_[i]] = x[i]
    grad.append(dx[a].subs(item))  #梯度值

若输入为:

f = 100*(x_[1]-x_[0]**2)**2+(1-x_[0])**2
x = [2,2]
d = [-1,-1]
print(dx)
print(grad)

输出为:

[-400*x0*(-x0**2 + x1) + 2*x0 - 2, -200*x0**2 + 200*x1] #偏导表达式,梯度
[1602,-400]  #梯度值

4.3 统计维度方法的改进

  • 在有些程序中,需要手动输入目标函数的维度:
n = int(input("please input the dimensions n="))

我们利用正则表达式来统计目标函数的维度:

import re
dimension_set = []
dimension_set = re.findall(r'x[0-9]\d*',str(f))  
  #统计x0,x1,...总数,'x[0-9]\d*'表示非负整数,\d表示单个数字字符,*表示前面的字符0次或多次
dimensions = len(set(dimension_set))  # 用set()去重

5 Python代码实现

import numpy as np
from sympy import *
import re

'''Wolfe-Powell非精确线性搜索,返回函数f在x处,方向d时的步长alpha'''
def WolfePowell(f,d,x,alpha_,rho,sigma):
    maxk = 1000  #迭代上限
    k = 0
    phi0 = fun(x) 
    dimensions = dim(f_)

    dx = []
    grad = []
    for a in range(dimensions):
        dx.append(diff(f, symbols('x'+str(a),real=True))) #求偏导
        item={}
        for i in range(dimensions):
            item[x_[i]] = x[i]
        grad.append(dx[a].subs(item)) #求梯度
        
    phi0_ = np.dot(grad,d)
    print(dx)
    print(grad)
    
    a1 = 0
    a2 = alpha_
    alpha = (a1+a2)/2
    phi1 = phi0
    phi1_ = phi0_

    k = 0;
    for k in range(maxk):    #限制迭代上限,避免时间太长
        phi = fun(x + alpha * d)
        if phi <= phi1 + rho * alpha * phi1_:
            newx = x + alpha * d
            newdx = []
            newgrad = []
            for a in range(dimensions):
                newdx.append(diff(f, symbols('x'+str(a),real=True)))  # 求偏导
                newitem={}
                for i in range(dimensions):
                    newitem[x_[i]] = newx[i]
                newgrad.append(newdx[a].subs(newitem)) #求梯度

            phi_ = np.dot(newgrad,d)
            
            if phi_ >= sigma*phi0_:
                break
            else:
                alpha_new = alpha + (alpha-a1) *phi_ / (phi1_-phi_)
                a1 = alpha
                alpha = alpha_new
                phi1 = phi
                phi1_ = phi_
        else:
            alpha_new = a1 + 0.5*(a1-alpha)**2*phi1_/((phi1-phi)-(a1-alpha)*phi1_)
            a2 = alpha
            alpha = alpha_new
    k = k + 1
    return alpha

'''利用正则表达式统计目标函数维度'''
def dim(f_):
    dimension_set = []
    dimension_set = re.findall(r'x[0-9]\d*',str(f_))
    dimensions = len(set(dimension_set))
    return dimensions
'''测试'''
x_ = []
for a in range(100):
    x_.append(symbols('x'+str(a),real=True))  #设置符号变量
   
def fun(x_):
    return 100*(x_[1]-x_[0]**2)**2+(1-x_[0])**2  #目标函数
f_ = fun(x_)   #用于W-P

alpha_ = 1  #alpha_max
rho = 0.25  # rho∈(0,1/2) 
sigma = 0.5  # sigma∈(rho,1)
x = np.random.rand(dim(f_))   #随机的初始点
d = np.array([-1,-1])     #初始方向

alpha=WolfePowell(f_,d,x,alpha_,rho,sigma)
print(alpha)
  • 21
    点赞
  • 73
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值