关于最优化求解,吴军有篇blog讲的很不错,证明和解释都很清楚,具体可以参考http://www.cnblogs.com/joneswood/archive/2012/03/11/2390529.html。
这里根据那篇blog的内容,主要讲解运用最广泛的LBFGS的算法思想和LBFGS源码的求解实际的最优化问题。
理论部分
一般优化算法中,比较简单的是梯度下降法,其主要思想为:
【优化算法|BFGS优化算法的理解以及LBFGS源码求解最优化问题】给定目标函数f(x),给定初始值x,沿着目标函数梯度方向下降,寻找最优解。
通过将目标函数,在初始值位置按照梯度下降方向,进行泰勒展开:
文章图片
文章图片
:代表第k个点的自变量(向量);
文章图片
:单位方向(向量),即|d|=1;
文章图片
:步长(实数);
文章图片
:目标函数在Xk这一点的梯度(导数向量);
文章图片
:α的高阶无穷小。
式[1]中的高阶无穷小可以忽略,因此,要使[1]式取得最小值,应使
文章图片
取到最小,由此可得,
文章图片
取
文章图片
时,目标函数下降得最快,这就是负梯度方向作为“最速下降”方向的由来。
由于梯度下降法只用到了目标函数的一阶导数信息,由此想到将二阶导数信息运用到优化求解中,这就是牛顿法的思想。在
文章图片
点处对目标函数进行泰勒展开,并只取二阶导数及其之前的几项(忽略更高阶的导数项),可得:
文章图片
文章图片
:目标函数在
文章图片
这一点的梯度;
文章图片
:目标函数在
文章图片
这一点的Hessian矩阵(二阶导数矩阵)。
由于极小值点必然是驻点,而驻点是一阶导数为0的点,所以,对 r(X) 这个函数来说,要取到极小值,应该分析其一阶导数。对X求一阶导数,并令其等于0:
文章图片
因此,下一点的计算方法:
文章图片
在方向d上按步长1(1×d = d)移动到点X,方向d的计算方法:
文章图片
牛顿法的基本步骤:每一步迭代过程中,通过解线性方程组得到搜索方向,然后将自变量移动到下一个点,然后再计算是否符合收敛条件,不符合的话就一直按这个策略(解方程组→得到搜索方向→移动点→检验收敛条件)继续下去。由于牛顿法利用了目标函数的二阶导数信息,其收敛速度为二阶收敛,因此它比最速下降法要快,但是其对一般问题不是整体收敛的,只有当初始点充分接近极小点时,才有很好的收敛性。
牛顿法中,在每一次要得到新的搜索方向的时候,都需要计算Hessian矩阵(二阶导数矩阵)。在自变量维数非常大的时候,这个计算工作是非常耗时的,因此,拟牛顿法的诞生的意义就是:它采用了一定的方法来构造与Hessian矩阵相似的正定矩阵,而这个构造方法计算量比牛顿法小。
a. DFP算法
假设目标函数可以用二次函数进行近似(实际上很多函数可以用二次函数很好地近似):
文章图片
忽略高阶无穷小部分,只看前面3项,其中A为目标函数的Hessian矩阵。此式等号两边对X求导,可得:
文章图片
于是,当 X=Xi 时,将[2]式两边均左乘Ai+1-1,有:
文章图片
上式左右两边近似相等,但如果我们把它换成等号,并且用另一个矩阵H来代替上式中的A-1,则得到:
文章图片
方程[4]就是拟牛顿方程,其中的矩阵H,就是Hessian矩阵的逆矩阵的一个近似矩阵.在迭代过程中生成的矩阵序列H0,H1,H2,……中,每一个矩阵Hi+1,都是由前一个矩阵Hi修正得到的。DFP算法的修正方法如下,设:
文章图片
再设:
文章图片
其中,m和n均为实数,v和w均为N维向量。将[6]代入[5]式,再将[5]式代入[4]式,可得:
文章图片
得到的m,n,v,w值如下:
文章图片
将[8]~[11]代入[6]式,然后再将[6]代入[5]式,就得到了Hessian矩阵的逆矩阵的近似阵H的计算方法:
文章图片
通过上述描述,DFP算法的流程大致如下:
已知初始正定矩阵H0,从一个初始点开始(迭代),用式子
文章图片
来计算出下一个搜索方向,并在该方向上求出可使目标函数极小化的步长α,然后用这个步长,将当前点挪到下一个点上,并检测是否达到了程序终止的条件,如果没有达到,则用上面所说的[13]式的方法计算出下一个修正矩阵H,并计算下一个搜索方向……周而复始,直到达到程序终止条件。
值得注意的是,矩阵H正定是使目标函数值下降的条件,所以,它保持正定性很重要。可以证明,矩阵H保持正定的充分必要条件是:
文章图片
在迭代过程中,这个条件也是容易满足的。
b. BFGS算法
BFGS算法和DFP算法有些相似,但是形式上更加复杂一些。BFGS算法目前仍然被认为是最好的拟牛顿算法。BFGS算法中矩阵H的计算公式如下所示:
文章图片
在[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
#include
//优化回调函数,每次迭代运行,在这里设置目标函数和梯度方程
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 N2int 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的求解结果一致。