LM在最优化中占据着极其重要的地位,将非线性问题转为为线性问题来求解,求出最优解。
最近在搞相机标定,而不得不面对LM,首先的想法百度,(个人不喜欢去看e文,总觉得效率不高,事实上错了)找到了一些相关的文章,好难啊,这是第一想法。不过还是找到了几篇大牛的文章,以及相关的MATLAB实现代码,本文的c实现便是基于MATLAB代码。感谢原作者
http://www.codelast.com/?p=29,这是大牛对之及其通俗的解释,相信看了能明白个大概。
http://www2.imm.dtu.dk/pubdb/views/edoc_download.php/3215/pdf/imm3215.pdf
讲解的非常透彻的一篇文档,以及实现。E文的,应该看懂都没问题。
% 计算函数f的雅克比矩阵,是解析式 syms a b y x real; f=a*exp(-b*x); Jsym=jacobian(f,[a b]) % 拟合用数据。参见《数学试验》,p190,例2 data_1=[0.25 0.5 1 1.5 2 3 4 6 8]; obs_1=[19.21 18.15 15.36 14.10 12.89 9.32 7.45 5.24 3.01]; % 2. LM算法 % 初始猜测s a0=10; b0=0.5; y_init = a0*exp(-b0*data_1); % 数据个数 Ndata=length(obs_1); % 参数维数 Nparams=2; % 迭代最大次数 n_iters=50; % LM算法的阻尼系数初值 lamda=0.01; % step1: 变量赋值 updateJ=1; a_est=a0; b_est=b0; % step2: 迭代 for it=1:n_iters if updateJ==1 % 根据当前估计值,计算雅克比矩阵 J=zeros(Ndata,Nparams); for i=1:length(data_1) J(i,:)=[exp(-b_est*data_1(i)) -a_est*data_1(i)*exp(-b_est*data_1(i))]; end % 根据当前参数,得到函数值 y_est = a_est*exp(-b_est*data_1); % 计算误差 d=obs_1-y_est; % 计算(拟)海塞矩阵 H=J'*J; % 若是第一次迭代,计算误差 if it==1 e=dot(d,d); end end % 根据阻尼系数lamda混合得到H矩阵 H_lm=H+(lamda*eye(Nparams,Nparams)); % 计算步长dp,并根据步长计算新的可能的\参数估计值 dp=inv(H_lm)*(J'*d(:)); g = J'*d(:); a_lm=a_est+dp(1); b_lm=b_est+dp(2); % 计算新的可能估计值对应的y和计算残差e y_est_lm = a_lm*exp(-b_lm*data_1); d_lm=obs_1-y_est_lm; e_lm=dot(d_lm,d_lm); % 根据误差,决定如何更新参数和阻尼系数 if e_lm<e lamda=lamda/10; a_est=a_lm; b_est=b_lm; e=e_lm; disp(e); updateJ=1; else updateJ=0; lamda=lamda*10; end end %显示优化的结果 a_est b_est
以上是MATLAB实现,据说原作者是http://www.shenlejun.cn/my/article/show.asp?id=17&page=1但是好像打不开。
于是我自己想用c语言来实现,写了很久发现还是有点写不出来啊,看来代码实现能力还是不行。最后不得已写了一个 以上MATLAB的c语言版本。
非常感谢那些把经验和知识分享出来的牛人。
#include <stdio.h> #include <math.h> #include <string.h> /* 函数表达式为 f(x)=a*exp(-b*x),a,b为待估参数 */ double data_1[9]={0.25 ,0.5, 1 ,1.5 ,2, 3, 4, 6 ,8}; double obs_1[9]={19.21 ,18.15 ,15.36 ,14.10 ,12.89, 9.32 ,7.45 ,5.24 ,3.01}; /* void cal_jaco(double **J, double a_bst, double b_bst) { int i,j; j=0; for(i=0;i<9;i++) { J[i][j] = exp(-b_bst*data_1[i]); J[i][j+1] = -a_bst*data_1[i]*exp(-b_bst*data_1[i]); } } */ //计算函数的值,目前只计算已知函数,即无法对输入的函数求解 void cal_fun_val(double *val,double a_bst, double b_bst) { int i; for(i=0;i<9;i++) { val[i]=a_bst*exp(-b_bst*data_1[i]); } } //一位向量的减法 void matrix_sub(double *result, double *first_m, double *second_m) { int i; for(i=8;i>=0;i--) { result[i]=first_m[i]-second_m[i]; } } //一维向量的加法 void matrix_add(double *result, const double *first_m, const double *second_m) { int i; for (i = 8; i >= 0; i--) { result[i] = first_m[i] + second_m[i]; } } //二维向量的转置 void tran_matrix(double **tran_mat, double **matrix, int row,int col) { int i,j; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { *((double *)tran_mat + row*j +i) = *((double *)matrix + col*i + j); } } } //打印出n*m的矩阵 void print_matrix(double **matrix, int row, int col) { int i,j; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { printf("%f ", *((double *)matrix + col*i + j)); } printf("\n"); } } //二维矩阵的乘法 /* outa=inb*inc */ void matrix_mut(double ** outa, double** inb, int b_row, int b_col, double** inc, int c_col) { int i, j, k,num; double tempdata=0; num = 0; double *tempa = (double *)outa; double t1, t2; for (i = 0; i < b_row; i++) { for (k = 0; k < c_col; k++) { for (j = 0; j < b_col; j++) { t1 = (*((double *)inb + b_col*i + j)); t2 = (*((double *)inc + c_col*j + k)); tempdata += t1*t2; } tempa[num]=tempdata; num++; tempdata = 0; } } } //矩阵与常数相乘 void cost_mat_mul(double **res_mat, int ratio, double **mat, int row, int col) { int i, j; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { (*((double *)res_mat + col*i + j)) = ratio * (*((double *)mat + col*i + j)); } } } //向量点积 double dot(double * mat1, double * mat2,int nofelem) { double *temp_mat1 = mat1; double *temp_mat2 = mat2; double result = 0; int i; for (i = 0; i<nofelem;i++) result += (*temp_mat1++)*(*temp_mat2++); return result; } #define MAX 2 #define E 0.000000001 /** * 计算矩阵行列式src的模值 */ double calculate_A(double src[][MAX], int n) { int i, j, k, x, y; double tmp[MAX][MAX], t; double result = 0.0; if (n == 1) { return src[0][0]; } for (i = 0; i < n; ++i) { for (j = 0; j < n - 1; ++j) { for (k = 0; k < n - 1; ++k) { x = j + 1; y = k >= i ? k + 1 : k; tmp[j][k] = src[x][y]; } } t = calculate_A(tmp, n - 1); if (i % 2 == 0) { result += src[0][i] * t; } else { result -= src[0][i] * t; } } return result; } /** * 计算伴随矩阵 */ void calculate_A_adjoint(double src[MAX][MAX], double dst[MAX][MAX], int n) { int i, j, k, t, x, y; double tmp[MAX][MAX]; if (n == 1) { dst[0][0] = 1; return; } for (i = 0; i < n; ++i) { for (j = 0; j < n; ++j) { for (k = 0; k < n - 1; ++k) { for (t = 0; t < n - 1; ++t) { x = k >= i ? k + 1 : k; y = t >= j ? t + 1 : t; tmp[k][t] = src[x][y]; } } dst[j][i] = calculate_A(tmp, n - 1); if ((i + j) % 2 == 1) { dst[j][i] = -1 * dst[j][i]; } } } } /** * 得到逆矩阵 */ int inv(const double src[MAX][MAX], double dst[MAX][MAX], int n) { double A = calculate_A(src, n); double tmp[MAX][MAX]; int i, j; if (fabs(A - 0) <= E) { // printf("不可能有逆矩阵!\n"); return 0; } calculate_A_adjoint(src, tmp, n); for (i = 0; i < n; ++i) { for (j = 0; j < n; ++j) { dst[i][j] = (double)(tmp[i][j] / A); } } return 1; } //LM算法 核心代码 int LM() { // 迭代最大次数 int n_iters=400; // LM算法的阻尼系数初值 double lamda=0.01; //初始值 double a0=20; double b0=0.5; // step1: 变量赋值 int updateJ=1; double a_est=a0; double b_est=b0; int it=0; double J[9][2]; double Jt[2][9]; double y_est[9]; double d[9]; double H[2][2] = {0}; double H_1m[2][2]; double e; double tempeye[2][2] = { { 1, 0 }, { 0, 1 } }; double eye[2][2] = { { 1, 0 }, { 0, 1 } }; double g[2] = {0}; int i, j; double invH_1m[2][2] = {0}; double dp[2] = {0}; double a_1m, b_1m; double y_est_lm[9]; double d_1m[9], e_1m; double temp = 0; for ( it =0;it<n_iters; it++) { if(updateJ == 1) { // cal_jaco(J,a_est,b_est); for (i = 0; i<9; i++) {//计算雅克比矩阵 J[i][0] = exp(-b_est*data_1[i]); J[i][1] = -a_est*data_1[i] * exp(-b_est*data_1[i]); } cal_fun_val(y_est, a_est, b_est); matrix_sub(d, obs_1, y_est);//计算当前的误差 tran_matrix(Jt, J, 9, 2); matrix_mut(H, Jt, 2, 9, J, 2);//得到Hessian 矩阵 if (it == 0) { e = dot(d, d, 9);//第一次时计算误差作为误差初值 } } cost_mat_mul(tempeye, lamda, eye, 2, 2); /* for (i = 0; i < 2; i++) { for (j = 0; j < 2; j++) { tempeye[i][j] = eye[i][j] * lamda; } }*/ // matrix_add(H_1m, H, tempeye); for (i = 0; i < 2; i++) { for (j = 0; j < 2; j++) { H_1m[i][j] = H[i][j] + tempeye[i][j]; } } for (i = 0; i < 2; i++) { for (j = 0; j < 9; j++) { temp += Jt[i][j] * d[j]; } g[i] = temp; } inv(H_1m, invH_1m, 2); temp = 0; for (i = 0; i < 2; i++) { for (j = 0; j < 2; j++) { temp += invH_1m[i][j] * g[j]; } dp[i] = temp;//求解步长 } a_1m = a_est + dp[0]; b_1m = b_est + dp[1]; for (i = 0; i < 9; i++) { y_est_lm[i] = a_1m*exp(-b_1m*data_1[i]); d_1m[i] = obs_1[i] - y_est_lm[i]; } e_1m = dot(d_1m, d_1m, 9);//计算新的 a,b时的误差 if (e_1m < e)//如果误差下降了,这是我们想要的 { lamda = lamda / 10; a_est = a_1m; b_est = b_1m; e = e_1m; printf("%f \n", e); updateJ = 1; } else { updateJ = 0; lamda = lamda * 10; } } printf("%f,%f ", a_est, b_est); return 1; } int main() { LM(); return 0; }