已知连续函数 f ( x ) f(x) f(x)在 x ∗ x^* x∗近旁存在最优解 x 0 x_0 x0。对博文《最优化方法Python计算:连续函数的单峰区间计算》讨论的 f ( x ) f(x) f(x)单峰区间的包围算法稍加修改,可算得 f ( x ) f(x) f(x)包含 x 0 x_0 x0的单峰区间 [ a , c ] [a,c] [a,c]及含于 ( a , c ) (a,c) (a,c)内的满足 f ( a ) > f ( b ) < f ( c ) f(a)>f(b)<f(c) f(a)>f(b)<f(c)的点 b b b,如下图所示。
相应地修改实现该算法的myBracket函数如下:
def myBracket(f,xstar,s=1e-4,lamd=2):a=xstar #a初始化为xstarya=f(a)b=a+s #c初始化为a+syb=f(b)if yb>ya: #s为上升方向a,b=b,a #交换a,bya,yb=yb,yas=-s #调整s为下降方向c=b+s #c置为b+syc=f(c)while yc<=yb: #c同a、b处于同一下降区间a,ya=b,yb #a置为bb,yb=c,yc #b置为cs*=lamd #扩大步长sc=b+s #重置c为b+syb=f(b)if a>c: #若a大c小a,c=c,a #交换a,creturn a,b,c
令 y a = f ( a ) y_a=f(a) ya=f(a), y b = f ( b ) y_b=f(b) yb=f(b)及 y c = f ( c ) y_c=f(c) yc=f(c)。构造通过点 ( a , y a ) (a,y_a) (a,ya), ( b , y b ) (b,y_b) (b,yb)及 ( c , y c ) (c,y_c) (c,yc)的二次函数 q ( x ) = p 0 + p 1 x + p 2 x 2 q(x)=p_0+p_1x+p_2x^2 q(x)=p0+p1x+p2x2,即
{ y a = p 0 + p 1 a + p 2 a 2 y b = p 0 + p 1 b + p 2 b 2 y c = p 0 + p 1 c + p 2 c 2 . \begin{cases} y_a=p_0+p_1a+p_2a^2\\ y_b=p_0+p_1b+p_2b^2\\ y_c=p_0+p_1c+p_2c^2 \end{cases}. ⎩ ⎨ ⎧ya=p0+p1a+p2a2yb=p0+p1b+p2b2yc=p0+p1c+p2c2.
解出 p 0 p_0 p0, p 1 p_1 p1和 p 2 p_2 p2:
{ p 0 = b c y a ( b − a ) ( c − a ) − a c y b ( b − a ) ( c − b ) + a b y c ( c − a ) ( c − b ) p 1 = − ( b + c ) y a ( b − a ) ( c − a ) + ( a + c ) y c ( b − a ) ( c − b ) − ( a + b ) y c ( c − a ) ( c − b ) p 2 = y a ( b − a ) ( c − a ) − y b ( b − a ) ( c − b ) + y c ( c − a ) ( c − b ) . \begin{cases} p_0=\frac{bcy_a}{(b-a)(c-a)}-\frac{acy_b}{(b-a)(c-b)}+\frac{aby_c}{(c-a)(c-b)}\\ p_1=-\frac{(b+c)y_a}{(b-a)(c-a)}+\frac{(a+c)y_c}{(b-a)(c-b)}-\frac{(a+b)y_c}{(c-a)(c-b)}\\ p_2=\frac{y_a}{(b-a)(c-a)}-\frac{y_b}{(b-a)(c-b)}+\frac{y_c}{(c-a)(c-b)}\end{cases}. ⎩ ⎨ ⎧p0=(b−a)(c−a)bcya−(b−a)(c−b)acyb+(c−a)(c−b)abycp1=−(b−a)(c−a)(b+c)ya+(b−a)(c−b)(a+c)yc−(c−a)(c−b)(a+b)ycp2=(b−a)(c−a)ya−(b−a)(c−b)yb+(c−a)(c−b)yc.
二次式
q ( x ) = p 0 + p 1 x + p 2 x 2 q(x)=p_0+p_1x+p_2x^2 q(x)=p0+p1x+p2x2
称为 f ( x ) f(x) f(x)在 a , b , c a,b,c a,b,c处的二次插值多项式。由于 q ( a ) = f ( a ) q(a)=f(a) q(a)=f(a),, q ( b ) = f ( b ) q(b)=f(b) q(b)=f(b), q ( c ) = f ( c ) q(c)=f(c) q(c)=f(c),故 q ( a ) > q ( b ) < q ( c ) q(a)>q(b)<q(c) q(a)>q(b)<q(c)。即 ( a , b ) (a,b) (a,b)为 q ( x ) q(x) q(x)的一个单峰区间。而二次函数仅有一个最小值,故 q ( x ) q(x) q(x)的最优解 x 0 ′ ∈ ( a , c ) x_0^{'}\in(a,c) x0′∈(a,c),且为其驻点。即
x 0 ′ = − 1 2 ⋅ p 1 p 2 = 1 2 [ ( b + c ) y a ( b − a ) ( c − a ) − ( a + c ) y c ( b − a ) ( c − b ) + ( a + b ) y c ( c − a ) ( c − b ) y a ( b − a ) ( c − a ) − y b ( b − a ) ( c − b ) + y c ( c − a ) ( c − b ) ] . x_0^{'}=-\frac{1}{2}\cdot\frac{p_1}{p_2}=\frac{1}{2}\left[\frac{\frac{(b+c)y_a}{(b-a)(c-a)}-\frac{(a+c)y_c}{(b-a)(c-b)}+\frac{(a+b)y_c}{(c-a)(c-b)}}{\frac{y_a}{(b-a)(c-a)}-\frac{y_b}{(b-a)(c-b)}+\frac{y_c}{(c-a)(c-b)}}\right]. x0′=−21⋅p2p1=21 (b−a)(c−a)ya−(b−a)(c−b)yb+(c−a)(c−b)yc(b−a)(c−a)(b+c)ya−(b−a)(c−b)(a+c)yc+(c−a)(c−b)(a+b)yc .
直观地看,从 f ( x ) f(x) f(x)的极小值点 x 0 x_0 x0的近旁 x ∗ x^* x∗出发,运用包围算法计算 f ( x ) f(x) f(x)的单峰区间 ( a , c ) (a,c) (a,c),及 b ∈ ( a , c ) b\in(a,c) b∈(a,c),满足 f ( x ) > f ( b ) < f ( c ) f(x)>f(b)<f(c) f(x)>f(b)<f(c),过三点 ( a , f ( a ) ) (a,f(a)) (a,f(a)), ( b , f ( b ) ) (b,f(b)) (b,f(b))和 ( c , f ( c ) ) (c,f(c)) (c,f(c))的插值二次函数 q ( x ) q(x) q(x)的极小值点 x 0 ′ ∈ ( a , c ) x_0^{'}\in(a,c) x0′∈(a,c)与出发点 x ∗ x^* x∗相比,离 x 0 x_0 x0更近,如下图所示。
于是,对给定的容错误差 ε \varepsilon ε,从邻近 f ( x ) f(x) f(x)的极小值点 x 0 x_0 x0的点 x ∗ x^* x∗出发,用包围算法算得含点 x 0 x_0 x0的 f ( x ) f(x) f(x)的单峰区间 ( a , c ) (a,c) (a,c)及 b ∈ ( a , c ) b\in(a,c) b∈(a,c),若其长度 ∣ c − a ∣ < ε |c-a|<\varepsilon ∣c−a∣<ε,则即以二次插函数 q ( x ) q(x) q(x)的极小值点 x 0 ′ x_0^{'} x0′作为 x 0 x_0 x0的近似值。否则,以 x 0 ′ x_0^{'} x0′作为包围算法新的出发点 x ∗ x^* x∗计算 f ( x ) f(x) f(x)的单峰区间 ( a , c ) (a,c) (a,c)及含于其内的点 b b b,重复上述计算二次插值函数 q ( x ) q(x) q(x)的最小值点 x 0 ′ x_0^{'} x0′。循环往复,直至 ∣ c − a ∣ < ε |c-a|<\varepsilon ∣c−a∣<ε。此时, x 0 ′ x_0^{'} x0′即为 x 0 x_0 x0的近似值。这一方法称为二次插值法。实现为如下的Python函数
from scipy.optimize import OptimizeResult
def interpolation(fun,xstar,eps=0.0002,**options):k=1a,b,c=myBracket(fun,xstar)ya,yb,yc=fun(a),fun(b),fun(c)p1=-ya*(b+c)/((b-a)*(c-a))+yb*(a+c)/((b-a)*(c-b))-yc*(a+b)/((c-a)*(c-b))p2=ya/((b-a)*(c-a))-yb/((b-a)*(c-b))+yc/((c-a)*(c-b))x01=-0.5*p1/p2while c-a>eps:xstar=x01a,b,c=myBracket(fun,xstar)ya,yb,yc=fun(a),fun(b),fun(c)p1=-ya*(b+c)/((b-a)*(c-a))+yb*(a+c)/((b-a)*(c-b))-yc*(a+b)/((c-a)*(c-b))p2=ya/((b-a)*(c-a))-yb/((b-a)*(c-b))+yc/((c-a)*(c-b))x01=-0.5*p1/p2k+=1bestx=x01besty=fun(bestx)return OptimizeResult(fun=besty, x=bestx, nit=k)
程序的第2~19行定义函数interpolation,实现二次插值算法。参数fun、xstar和eps分别表示目标函数 f ( x ) f(x) f(x),起点 x ∗ x^* x∗和容错误差 ε \varepsilon ε。options用来实现minimize_scalar向interpolation传递xstar和eps等实际参数的机制。
第3行将迭代次数k初始化为1。
第4~8行执行第一次迭代:第4行调用myBracket函数,从 x ∗ x^* x∗起计算 f ( x ) f(x) f(x)的单峰区间 ( a , c ) (a,c) (a,c)及 b ∈ ( a , c ) b\in(a,c) b∈(a,c),满足 f ( a ) > f ( b ) < f ( c ) f(a)>f(b)<f(c) f(a)>f(b)<f(c)。第5行计算 f ( x ) f(x) f(x)在 a , b , c a,b,c a,b,c处的函数值为ya,yb,yc。第6、7行分别计算二次插值函数 q ( x ) q(x) q(x)的一次项系数和二次项系数p1,p2。第8行计算 q ( x ) q(x) q(x)的驻点 x 0 ′ = − 1 2 p 1 p 2 x_0^{'}=-\frac{1}{2}\frac{p_1}{p_2} x0′=−21p2p1赋予x01。
第9~16行的while循环执行余下的迭代操作:第10行将x01赋予xstar,以更新起点 x ∗ x^* x∗。第11~15行执行与第4~8行相同的操作。第16行将迭代次数k自增1。循环往复,直至区间 ( a , c ) (a,c) (a,c)的长度 c − a c-a c−a不超过 ε \varepsilon ε。
需要注意的是,之所以要对表示容错误差 ε \varepsilon ε的参数eps设置缺省值0.0002,是因为myBracket中我们将步长s的缺省值设置为0.0001,使得所计算的单峰区间 ( a , b ) (a,b) (a,b)的长度最小为0.0002。这样,才不至于使得此处的{\bf{while}}语句陷入死循环。读者在使用interpolation时需注意eps参数值不要小于myBracket的s参数的初始值。
例1 用上述程序定义的interpolation函数,计算函数 f ( x ) = x 2 + 4 cos x f(x)=x^2+4\cos{x} f(x)=x2+4cosx在 x 1 = 1.5 x_1=1.5 x1=1.5近旁的极小值点。
解:下列代码完成计算。
from scipy.optimize import minimize_scalar
f=lambda x:x**2+4*np.cos(x)
xstar=1.5
res=minimize_scalar(f, method=interpolation, options={'xstar':1.5,'eps':0.001})
print(res)
程序的第2行设置目标函数 f ( x ) = x 2 + 4 cos x f(x)=x^2+4\cos{x} f(x)=x2+4cosx赋予f,第3行设置起始点 x ∗ x^{*} x∗赋予xstar,第4行调用minimize_scalar函数(第1行导入),传递f给参数fun、interpolation给method并通过options向interpolation传递参数1.5给xstar,0.001给eps。运行程序,输出
fun: 2.316808419788213nit: 3x: 1.895494265404134
与博文《最优化方法Python计算:一元函数搜索算法——牛顿法》中例1的计算结果相比较,对同一函数 f ( x ) f(x) f(x),相同起点 x ∗ = 1.5 x^*=1.5 x∗=1.5,牛顿方法在容错误差 ε = 1.4 ⋅ 1 0 − 8 \varepsilon=1.4\cdot10^{-8} ε=1.4⋅10−8下迭代7次的结果( x 0 x_0 x0的近似值为1.8954942711282465),用二次插值法在容错误差 ε = 0.001 \varepsilon=0.001 ε=0.001下仅迭代3次就可算得与前者直至千万分位( x 0 x_0 x0的近似值为1.895494265404134)均一致的结果。