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

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


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


理論部分

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

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

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

clip_image002

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

clip_image006:單位方向(向量),即|d|=1;

clip_image008:步長(實數);

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

clip_image012:α的高階無窮小。

式[1]中的高階無窮小可以忽略,因此,要使[1]式取得最小值,應使clip_image014取到最小,由此可得,clip_image006[1]clip_image016時,目標函數下降得最快,這就是負梯度方向作爲“最速下降”方向的由來。


由於梯度下降法只用到了目標函數的一階導數信息,由此想到將二階導數信息運用到優化求解中,這就是牛頓法的思想。clip_image004[1]點處對目標函數進行泰勒展開,並只取二階導數及其之前的幾項(忽略更高階的導數項),可得:

clip_image018

clip_image010[1]:目標函數在clip_image004[2]這一點的梯度;

clip_image020:目標函數在clip_image004[3]這一點的Hessian矩陣(二階導數矩陣)。

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

clip_image022

因此,下一點的計算方法:clip_image004[4]在方向d上按步長1(1×d = d)移動到點X,方向d的計算方法:

clip_image024

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


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


a. DFP算法

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

clip_image066

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

clip_image068

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

clip_image070

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

clip_image072

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

clip_image074

再設:

clip_image076

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

clip_image078

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

clip_image080

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

clip_image082

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

已知初始正定矩陣H0,從一個初始點開始(迭代),用式子 clip_image084 來計算出下一個搜索方向,並在該方向上求出可使目標函數極小化的步長α,然後用這個步長,將當前點挪到下一個點上,並檢測是否達到了程序終止的條件,如果沒有達到,則用上面所說的[13]式的方法計算出下一個修正矩陣H,並計算下一個搜索方向……周而復始,直到達到程序終止條件。

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

clip_image086

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


b. BFGS算法

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

clip_image088

在[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中求解該優化問題,可得如下結果:




運用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;
}


運行程序:



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



發佈了22 篇原創文章 · 獲贊 5 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章