数值算法:无约束优化之一维搜索方法之二分法牛顿法割线法

Posted 宏斌PKUCIS

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数值算法:无约束优化之一维搜索方法之二分法牛顿法割线法相关的知识,希望对你有一定的参考价值。

 

1、二分法(一阶导)

二分法是利用目标函数的一阶导数来连续压缩区间的方法,因此这里除了要求 f 在 [a0,b0] 为单峰函数外,还要去 f(x) 连续可微。

(1)确定初始区间的中点 x(0)=(a0+b0)/2 。然后计算 f(x) 在 x(0) 处的一阶导数 f\'(x(0)), 如果 f\'(x(0)) >0 , 说明极小点位于 x(0) 的左侧,也就是所,极小点所在的区间压缩为[a0,x(0)];反之,如果 f\'(x(0)) <0,说明极小点位于x(0)的右侧,极小点所在的区间压缩为[x(0),b0];如果f\'(x(0)) = 0,说明就是函数 f(x) 的极小点。

(2)根据新的区间构造x(1),以此来推,直到f\'(x(k)) = 0,停止。

可见经过N步迭代之后,整个区间的总压缩比为(1/2)N,这比黄金分割法和斐波那契数列法的总压缩比要小。

1 #ifndef _BINARYSECTION_H_
2 #define _BINARYSECTION_H_
3 
4 typedef float (* PtrOneVarFunc)(float x);
5 void BinarySectionMethod(float a, float b, PtrOneVarFunc fi, float epsilon);
6 
7 #endif
 1 #include<iostream>
 2 #include<cmath>
 3 #include "BinarySection.h"
 4 
 5 using namespace std;
 6 
 7 void BinarySectionMethod(float a, float b, PtrOneVarFunc tangent, float epsilon)
 8 {
 9     float a0,b0,middle;
10     int k;
11     k = 1;
12     a0 = a;
13     b0 = b;
14     middle = ( a0 + b0 )/2;
15     
16     while( abs(tangent(middle)) - epsilon > 0 )
17     {
18 #ifdef _DEBUG
19         cout<<k++<<"th iteration:x="<<middle<<",f\'("<<middle<<")="<<tangent(middle)<<endl;
20 #endif
21  
22         if( tangent(middle) > 0)
23         {
24             b0 = middle;
25         }
26         else
27         {
28             a0 = middle;
29         }
30         middle =( a0+b0)/2;
31     }
32     
33     cout<<k<<"th iteration:x="<<middle<<",f\'("<<middle<<")="<<tangent(middle)<<endl;
34 }
 1 #include<iostream>
 2 #include "BinarySection.h"
 3 
 4 
 5 float TangentFunctionofOneVariable(float x)
 6 {
 7     return 14*x-5;//7*x*x-5*x+2;
 8 }
 9 
10 int main()
11 {
12     BinarySectionMethod(-50, 50, TangentFunctionofOneVariable, 0.001);
13     return 0;
14 }
1th iteration:x=0,f\'(0)=-5
2th iteration:x=25,f\'(25)=345
3th iteration:x=12.5,f\'(12.5)=170
4th iteration:x=6.25,f\'(6.25)=82.5
5th iteration:x=3.125,f\'(3.125)=38.75
6th iteration:x=1.5625,f\'(1.5625)=16.875
7th iteration:x=0.78125,f\'(0.78125)=5.9375
8th iteration:x=0.390625,f\'(0.390625)=0.46875
9th iteration:x=0.195312,f\'(0.195312)=-2.26562
10th iteration:x=0.292969,f\'(0.292969)=-0.898438
11th iteration:x=0.341797,f\'(0.341797)=-0.214844
12th iteration:x=0.366211,f\'(0.366211)=0.126953
13th iteration:x=0.354004,f\'(0.354004)=-0.0439453
14th iteration:x=0.360107,f\'(0.360107)=0.0415039
15th iteration:x=0.357056,f\'(0.357056)=-0.0012207
16th iteration:x=0.358582,f\'(0.358582)=0.0201416
17th iteration:x=0.357819,f\'(0.357819)=0.00946045
18th iteration:x=0.357437,f\'(0.357437)=0.00411987
19th iteration:x=0.357246,f\'(0.357246)=0.00144958
20th iteration:x=0.357151,f\'(0.357151)=0.000114441

 

2、牛顿法(二阶导)

前提:f 在 [a0,b0] 为单峰函数, 且[a0,b0] 在极小点附近,不能离的太远否则可能无法收敛。

这里进一步要求f(x)连续二阶可微。对于函数 f(x) 上一点 x(k),我们可以使用泰勒公式构造一个多项式函数 q(x)=f(x(k))+f\'(x(k))(x-x(k))+1/2 * f\'\'(x(k))(x-x(k))2,对 f(x)在 x(k) 附近进行局部二次拟合,q(x)可以看为f(x)的(在x(k)附近的局部)近似(当f(x)为二次函数时,q(x)=f(x)),因此求f(x)的极小点可以转化为求q(x)的极小点。

0=q\'(x)=f\'(x(k))+f\'\'(x(k))(x-x(k))  =>   x = x(k)  -  f\'(x(k))/f\'\'(x(k))  因此可以选择 x(k+1) = x(k)  -  f\'(x(k))/f\'\'(x(k))

牛顿法能够不断地迫使目标函数f(x)的一阶导数趋于0。x(k+1)由g(x(k))的切线产生:

x(k+1)为g(x(k))的切线与x轴交点。随着x(k) -> x*,  g(x)->0。(g(x)为f(x)的近似)

 

注意1:牛顿法不需要计算函数值,但需要计算一阶导数和二阶导数值,且要求 f\'\'(x)>0,如果f\'\'(x)<0,则可能存在从 x(k) 到 x(k+1)反向调整,收敛到极大点,即越来越偏离极小点。

f\'\'(x(k))>0, x(k)->x*

f\'\'(x(k))<0, x(k)远离x*

注意2:如果初始x(k)的调整幅度太大可能会导致x的调整序列在极小点左右波动。

初始点g\'(x(0))/g\'\'(x(0))太大,造成算法失效

 1 #ifndef _NEWTON_H_
 2 #define _NEWTON_H_
 3 
 4 #define NEWTON_ZERO  0.0001
 5 
 6 typedef float (* PtrOneVarFunc)(float x);
 7 
 8 
 9 
10 void NewtonMethod(float x0, PtrOneVarFunc oneorderderivative, PtrOneVarFunc twoorderderivative, float epsilon);
11 
12 
13 #endif
 1 #include<iostream>
 2 #include<cmath>
 3 #include "Newton.h"
 4 
 5 using namespace std;
 6 
 7 void NewtonMethod(float x0, PtrOneVarFunc oneorderderivative, PtrOneVarFunc twoorderderivative, float epsilon)
 8 {
 9     float xk,newtonstep;
10     int k;
11     k = 0;
12     xk = x0;
13 
14     while( abs(oneorderderivative(xk)) - epsilon > 0)
15     {
16 
17 #ifdef _DEBUG
18         cout<<k<<"th iteration xk="<<xk<<",f\'("<<xk<<")="<<oneorderderivative(xk)<<endl;
19 #endif
20         k++;
21 
22         newtonstep = twoorderderivative(xk);
23         if(abs(newtonstep) - NEWTON_ZERO < 0)
24         {
25             cout<<"f\'\'("<<xk<<")=0, algorithms terminate!"<<endl;
26             return ;
27         }
28 
29         newtonstep = oneorderderivative(xk) / newtonstep;
30         xk = xk - newtonstep;
31     }
32 
33     cout<<k<<"th iteration xk="<<xk<<",f\'("<<xk<<")="<<oneorderderivative(xk)<<endl;
34 }
#include<iostream>
#include "Newton.h"


float OneOrderDerivative(float x)
{
    return (14*x-5);//f(x)=7*x*x-5*x+2;
}


float TwoOrderDerivative(float x)
{
    return 14;//f\'\'(x)=14;
}

int main()
{
    NewtonMethod(-109.23, OneOrderDerivative, TwoOrderDerivative, 0.001);
    return 0;
}
0th iteration xk=-109.23,f\'(-109.23)=-1534.22
1th iteration xk=0.35714,f\'(0.35714)=-4.57764e-05

(3)割线法

前提:f 在 [a0,b0] 为单峰函数, 且[a0,b0] 在极小点附近,不能离的太远否则可能无法收敛。

 牛顿法需要f(x)的二阶导数,如果二阶导数不存在,可以采用不同点的一阶导数对其近似,如 f\'\'(x(k))=(f\'(x(k))-f\'(x(k-1)))/(x(k)-x(k-1)),将其带入牛顿迭代公式,可以得到新的迭代公式:

 x(k+1) = x(k)  -  f\'(x(k))  *  (x(k)-x(k-1)) /(f\'(x(k))-f\'(x(k-1)))   <=>   x(k+1) = ( f\'(x(k))x(k-1)  - f\'(x(k-1)) x(k)  )    /   (    f\'(x(k))  -  f\'(x(k-1))  )

这个方法需要两个初始点 x(-1)和x(0),可以看出割线法不需要计算函数值f(x(k)),二阶导数值f\'\'(x(k)),它使用x(k)和x(k-1)之间的割线产生x(k+1)

x(k+1)由过x(k)和x(k-1)的割线产生

 1 #ifndef _SECANT_H_
 2 #define _SECANT_H_
 3 
 4 #define SECANT_ZERO  0.1
 5 
 6 typedef float (* PtrOneVarFunc)(float x);
 7 
 8 
 9 
10 void SecantMethod(float x0, float x_1, PtrOneVarFunc oneorderderivative, float epsilon);
11 
12 
13 #endif
 1 #include<iostream>
 2 #include<cmath>
 3 #include "Secant.h"
 4 
 5 using namespace std;
 6 
 7 void SecantMethod(float x_1, float x0, PtrOneVarFunc oneorderderivative, float epsilon)
 8 {
 9     float xk,xk_1,t1,t_1,tk;
10     int k;
11     k = 0;
12     xk = x0;
13     xk_1 = x_1;
14 
15     if(xk - xk_1 < SECANT_ZERO)
16     {
17         cout<<"x0 == x_1,algorithm terminate."<<endl;
18         return;
19     }
20 
21     while( abs(oneorderderivative(xk)) - epsilon > 0)
22     {
23 #ifdef _DEBUG
24         cout<<k<<"th iteration xk_1="<<xk_1<<",f\'("<<xk_1<<")="<<oneorderderivative(xk_1)<<",xk="<<xk<<",f\'("<<xk<<")="<<oneorderderivative(xk)<<endl;
25 #endif
26 
27         k++;
28 
29         t1 = oneorderderivative(xk);
30         t_1 = oneorderderivative(xk_1);
31 
32         tk = xk;
33         xk = (t1*xk_1 - t_1*xk)/(t1-t_1);
34         xk_1 = tk;
35     }
36 
37         cout<<k<<"th iteration xk_1="<<xk_1<<",f\'("<<xk_1<<")="<<oneorderderivative(xk_1)<<",xk="<<xk<<",f\'("<<xk<<")="<<oneorderderivative(xk)<<endl;
38 
39 }
 1 #include<iostream>
 2 #include "Secant.h"
 3 
 4 
 5 float OneOrderDerivative(float x)
 6 {
 7     return (14*x-5);//f(x)=7*x*x-5*x+2;
 8 }
 9 
10 
11 int main()
12 {
13     SecantMethod(-109.23, -20, OneOrderDerivative, 0.001);
14     return 0;
15 }
0th iteration xk_1=-109.23,f\'(-109.23)=-1534.22,xk=-20,f\'(-20)=-285
1th iteration xk_1=-20,f\'(-20)=-285,xk=0.357142,f\'(0.357142)=-1.03116e-05

(4)拟抛物线插值法

迭代算法跟割线法类似,不同点为使用三个点的函数值近似公式中的两个一阶导数。

 

以上是关于数值算法:无约束优化之一维搜索方法之二分法牛顿法割线法的主要内容,如果未能解决你的问题,请参考以下文章

数值算法:无约束优化之一维搜索方法之黄金分割法斐波那契数列法

牛顿法、黄金分割法、二次插值法实验(最优化1)

数值算法:无约束优化之一维搜索方法之多维优化问题中每步迭代的最优学习率设定问题

牛顿法求解无约束最优化问题的方法

数值算法:无约束优化之多维优化之共轭方向法

运筹学(最优化理论)学习笔记 | 共轭梯度法