已知连续函数
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初始化为xstar
ya=f(a)
b=a+s #c初始化为a+s
yb=f(b)
if yb>ya: #s为上升方向
a,b=b,a #交换a,b
ya,yb=yb,ya
s=-s #调整s为下降方向
c=b+s #c置为b+s
yc=f(c)
while yc<=yb: #c同a、b处于同一下降区间
a,ya=b,yb #a置为b
b,yb=c,yc #b置为c
s*=lamd #扩大步长s
c=b+s #重置c为b+s
yb=f(b)
if a>c: #若a大c小
a,c=c,a #交换a,c
return 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=1
a,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/p2
while c-a>eps:
xstar=x01
a,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/p2
k+=1
bestx=x01
besty=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.316808419788213
nit: 3
x: 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)均一致的结果。