凸優化之無約束優化(一維搜索方法:二分法、牛頓法、割線法)


 

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)擬拋物線插值法

迭代算法跟割線法類似,不同點為使用三個點的函數值近似公式中的兩個一階導數。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM