天天看點

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

關于最優化求解,吳軍有篇blog講的很不錯,證明和解釋都很清楚,具體可以參考http://www.cnblogs.com/joneswood/archive/2012/03/11/2390529.html。

這裡根據那篇blog的内容,主要講解運用最廣泛的LBFGS的算法思想和LBFGS源碼的求解實際的最優化問題。

理論部分

一般優化算法中,比較簡單的是梯度下降法,其主要思想為:

給定目标函數f(x),給定初始值x,沿着目标函數梯度方向下降,尋找最優解。

通過将目标函數,在初始值位置按照梯度下降方向,進行泰勒展開:

BFGS優化算法的了解以及LBFGS源碼求解最優化問題
BFGS優化算法的了解以及LBFGS源碼求解最優化問題

:代表第k個點的自變量(向量);

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

:機關方向(向量),即|d|=1;

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

:步長(實數);

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

:目标函數在Xk這一點的梯度(導數向量);

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

:α的高階無窮小。

式[1]中的高階無窮小可以忽略,是以,要使[1]式取得最小值,應使

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

取到最小,由此可得,

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

時,目标函數下降得最快,這就是負梯度方向作為“最速下降”方向的由來。

由于梯度下降法隻用到了目标函數的一階導數資訊,由此想到将二階導數資訊運用到優化求解中,這就是牛頓法的思想。在

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

點處對目标函數進行泰勒展開,并隻取二階導數及其之前的幾項(忽略更高階的導數項),可得:

BFGS優化算法的了解以及LBFGS源碼求解最優化問題
BFGS優化算法的了解以及LBFGS源碼求解最優化問題

:目标函數在

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

這一點的梯度;

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

:目标函數在

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

這一點的Hessian矩陣(二階導數矩陣)。

由于極小值點必然是駐點,而駐點是一階導數為0的點,是以,對 r(X) 這個函數來說,要取到極小值,應該分析其一階導數。對X求一階導數,并令其等于0:

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

是以,下一點的計算方法:

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

在方向d上按步長1(1×d = d)移動到點X,方向d的計算方法:

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

牛頓法的基本步驟:每一步疊代過程中,通過解線性方程組得到搜尋方向,然後将自變量移動到下一個點,然後再計算是否符合收斂條件,不符合的話就一直按這個政策(解方程組→得到搜尋方向→移動點→檢驗收斂條件)繼續下去。由于牛頓法利用了目标函數的二階導數資訊,其收斂速度為二階收斂,是以它比最速下降法要快,但是其對一般問題不是整體收斂的,隻有當初始點充分接近極小點時,才有很好的收斂性。

牛頓法中,在每一次要得到新的搜尋方向的時候,都需要計算Hessian矩陣(二階導數矩陣)。在自變量維數非常大的時候,這個計算工作是非常耗時的,是以,拟牛頓法的誕生的意義就是:它采用了一定的方法來構造與Hessian矩陣相似的正定矩陣,而這個構造方法計算量比牛頓法小。

a. DFP算法

假設目标函數可以用二次函數進行近似(實際上很多函數可以用二次函數很好地近似):

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

忽略高階無窮小部分,隻看前面3項,其中A為目标函數的Hessian矩陣。此式等号兩邊對X求導,可得:

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

于是,當 X=Xi 時,将[2]式兩邊均左乘Ai+1-1,有:

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

上式左右兩邊近似相等,但如果我們把它換成等号,并且用另一個矩陣H來代替上式中的A-1,則得到:

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

方程[4]就是拟牛頓方程,其中的矩陣H,就是Hessian矩陣的逆矩陣的一個近似矩陣.在疊代過程中生成的矩陣序列H0,H1,H2,……中,每一個矩陣Hi+1,都是由前一個矩陣Hi修正得到的。DFP算法的修正方法如下,設:

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

再設:

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

其中,m和n均為實數,v和w均為N維向量。将[6]代入[5]式,再将[5]式代入[4]式,可得:

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

得到的m,n,v,w值如下:

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

将[8]~[11]代入[6]式,然後再将[6]代入[5]式,就得到了Hessian矩陣的逆矩陣的近似陣H的計算方法:

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

通過上述描述,DFP算法的流程大緻如下:

已知初始正定矩陣H0,從一個初始點開始(疊代),用式子 

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

 來計算出下一個搜尋方向,并在該方向上求出可使目标函數極小化的步長α,然後用這個步長,将目前點挪到下一個點上,并檢測是否達到了程式終止的條件,如果沒有達到,則用上面所說的[13]式的方法計算出下一個修正矩陣H,并計算下一個搜尋方向……周而複始,直到達到程式終止條件。

值得注意的是,矩陣H正定是使目标函數值下降的條件,是以,它保持正定性很重要。可以證明,矩陣H保持正定的充分必要條件是:

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

在疊代過程中,這個條件也是容易滿足的。

b. BFGS算法

BFGS算法和DFP算法有些相似,但是形式上更加複雜一些。BFGS算法目前仍然被認為是最好的拟牛頓算法。BFGS算法中矩陣H的計算公式如下所示:

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

在[14]式中的最後一項(藍色部分)就是BFGS比DFP多出來的部分,其中w是一個n×1的向量。

在目标函數為二次型時,無論是DFP還是BFGS——也就是說,無論[14]式中有沒有最後一項——它們均可以使矩陣H在n步之内收斂于A-1。

代碼實驗:

網絡上面有很多BFGS的實作算法,用的比較多的是c++實作的源碼庫libbfgs,http://www.chokkan.org/software/liblbfgs/.

網上編譯即可以使用。

 本文以求解 f(x1,x2) = 100(x2-x1^2)^2 + (1-x1)^2為例,對比matlab和libbfgs的結果。

在matlab中求解該優化問題,可得如下結果:

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

運用libbfgs庫,程式設計實作優化問題:

#include <stdio.h>
#include <lbfgs.h>

           
//優化回調函數,每次疊代運作,在這裡設定目标函數和梯度方程
static lbfgsfloatval_t evaluate(
    void *instance,
    const lbfgsfloatval_t *x,
    lbfgsfloatval_t *g,
    const int n,
    const lbfgsfloatval_t step
    )
{
    lbfgsfloatval_t fx = 0.0;

    //計算目标函數
    fx = 100 * (x[1] - x[0] * x[0]) * (x[1] - x[0] * x[0]) + (1 - x[0]) * (1 - x[0]);

    //計算梯度方程
    g[0] = -(400 * x[0] * (x[1] - x[0] * x[0]) + 2 * (1 - x[0]));
    g[1] = 200 * (x[1] - x[0] * x[0]);
    return fx;
}

           
//疊代回調函數,每次疊代可以用來輸出疊代求解獲得的值
static int progress(
    void *instance,
    const lbfgsfloatval_t *x,
    const lbfgsfloatval_t *g,
    const lbfgsfloatval_t fx,
    const lbfgsfloatval_t xnorm,
    const lbfgsfloatval_t gnorm,
    const lbfgsfloatval_t step,
    int n,
    int k,
    int ls
    )
{
           
//輸出目前疊代次數,目标值,x1,x2的值以及目标函數和梯度的誤差二範數等
    printf("Iteration %d:\n", k);
    printf("  fx = %f, x[0] = %f, x[1] = %f\n", fx, x[0], x[1]);
    printf("  xnorm = %f, gnorm = %f, step = %f\n", xnorm, gnorm, step);
    printf("\n");
    return 0;
}

           
//定義目前參數的個數
#define N   2

int main(int argc, char *argv[])
{
    lbfgsfloatval_t fx; //目标函數
    lbfgsfloatval_t *x = lbfgs_malloc(N); //配置設定變量空間
    lbfgs_parameter_t param;  //參數值

    if (x == NULL) {
        printf("ERROR: Failed to allocate a memory block for variables.\n");
        return 1;
    }

    /* 初始化參數. */
   x[0] = -1.2;
   x[1] = 2;

    /* 初始化 L-BFGS 優化算法的設定參數. */
    lbfgs_parameter_init(¶m);
    

    /*
        優化求解
     */
    ret = lbfgs(N, x, &fx, evaluate, progress, NULL, ¶m);

    /* 輸出最終的優化結果. */
    printf("L-BFGS optimization terminated with status code = %d\n", ret);
    printf("  fx = %f, x[0] = %f, x[1] = %f\n", fx, x[0], x[1]);

    lbfgs_free(x); //釋放變量空間
    return 0;
}
           

運作程式:

BFGS優化算法的了解以及LBFGS源碼求解最優化問題

對比可得,L-BFGS程式經過36次疊代即可求得最優解,且跟matlab的求解結果一緻。

繼續閱讀