优化算法学习(LM算法)

it2022-05-05  148

文章目录

推荐书籍理论理解程序实现ceres安装代码:

推荐书籍

建议学习,METHODS FOR NON-LINEAR LEAST SQUARES PROBLEMS: http://www2.imm.dtu.dk/pubdb/views/edoc_download.php/3215/pdf/imm3215.pdf 篇幅不长,容易理解 学习的时候可以参考另一篇,UNCONSTRAINED OPTIMIZATION:http://www2.imm.dtu.dk/pubdb/views/edoc_download.php/3217/pdf/imm3217.pdf

Numerical Optimization 2nd --Jorge Nocedal Stephen J. Wright: http://www.bioinfo.org.cn/~wangchao/maa/Numerical_Optimization.pdf 《视觉SLAM十四讲》第六讲 https://github.com/gaoxiang12/slambook

理论理解

知乎上看到一个回答非常好:

LM算法可以理解为Gauss-Newton算法与最速下降法的结合,如果理解了如何用上述算法求解目标函数最小值的问题,自然也能理解LM。

其实算法的本质就是 a. 站在当前位置( x k x_k xk ),我们需要一个预言(oracle)告诉我们往哪走能找到目的地(最优解可能的方向,比如梯度方向);b. 我们沿着该方向走了一段距离之后(stepsize),更新当前位置信息( x k + 1 x_{k+1} xk+1 ),再问预言家我们下一步往哪走,以此反复。

所以,梯度下降法,给的 oracle 就是当前位置的梯度信息(损失方程关于变量的一阶导数):

x k + 1 = x k − α g k x_{k+1}=x_k-\alpha g_k xk+1=xkαgk

如果是牛顿法,给的 oracle 就是Hessian matrix(损失方程关于变量的二阶导数):

x k + 1 = x k − H k − 1 g k x_{k+1}=x_k-H_k^{-1}g_k xk+1=xkHk1gk(1)

为什么是一阶导数和二阶导数?因为我们知道,对于任意(处处可导的)方程,在其任意一点,我们都可以用泰勒展开式对其拟合,阶数越高,精度越高。但是,考虑到高阶导数的计算复杂度,以及三阶以上函数的非凸性,也不会使用高阶导数。

好了,那么LM算法的优势是什么?牛顿法虽然收敛速度快,但是需要计算 Hessian matrix,对于高维的问题,计算二阶导数会很复杂。因此我们有了Gauss-Newton算法。Gauss-Newton算法不直接计算Hessian matrix,而是通过 Jacobian matrix 对 Hessian matrix 进行拟合:

H ≈ J T J H\approx J^TJ HJTJ

但是,用 Jacobian matrix 拟合Hessian matrix,所计算出来的结果不一定可逆。所以在此基础上,我们引入了一个identity matrix:

H ≈ J T J + μ I H\approx J^TJ+\mu I HJTJ+μI

这也就得到了LM算法。如果我们把上述式子带入之前的公式(1),可以得到

x k + 1 = x k − ( J k T J k + μ I ) − 1 g k x_{k+1}=x_k-(J_k^TJ_k+\mu I)^{-1}g_k xk+1=xk(JkTJk+μI)1gk

所以我们发现,当 μ \mu μ接近于0时,这个算法近似于Gauss-Newton算法;当 μ \mu μ很大时,这个算法近似于最速下降法。因此,这也是为什么LM算法称为Gauss-Newton算法与最速下降法的结合。最后,上一张图表示几种算法之间的关系: 参考文献:Wilamowski, B. M., & Yu, H. (2010). Improved computation for Levenberg–Marquardt training. IEEE transactions on neural networks, 21(6), 930-937.

一个回答:Matlab 的话现成的代码也是很多的;比如,Solve nonlinear least-squares (nonlinear data-fitting) problems,或者 Levenberg-Marquardt-Fletcher algorithm for nonlinear least squares problems。你可以在网站里面搜搜有没有适合你的。

作者:Sixiang 链接:https://www.zhihu.com/question/269579938/answer/349205519 来源:知乎

这是最上面推荐的书,英文不难:

gradient matrix, hessian matrix, jacobian matrix: https://www.value-at-risk.net/functions/

csdn 有个不错的博客:数值优化(Numerical Optimization)学习系列-目录:https://blog.csdn.net/fangqingan_java/article/details/48951191

程序实现

视觉SLAM十四讲里推荐了**Ceres库**,Ceres solver 是谷歌开发的一款用于非线性优化的库,在谷歌的开源激光雷达slam项目cartographer中被大量使用。 安装和使用参考: https://zhaoxuhui.top/blog/2018/04/04/ceres&ls.html 下面把关键操作贴出来:

ceres安装

下载源码 git clone https://github.com/ceres-solver/ceres-solver.git安装依赖: sudo apt-get install cmake # google-glog + gflags sudo apt-get install libgoogle-glog-dev libgtest-dev libgflags-dev # BLAS & LAPACK sudo apt-get install libatlas-base-dev liblapack-dev # Eigen3 sudo apt-get install libeigen3-dev # SuiteSparse and CXSparse (optional) sudo apt-get install libsuitesparse-dev libcxsparse3.1.4

这里libcxsparse可能存在版本问题(出现找不到对应版本),解决办法:

sudo apt-get install bash-completion sudo gedit /etc/bash.bashrc

将这一部分取消注释,并保存,即可自动补全: sudo apt-get install libsuitesparse-dev libcxsparse(按tab)

cd ceres-solver/ mkdir build cd build cmake .. make -j3 sudo make install

代码:

利用Ceres简单实现最小二乘曲线拟合。首先需要生成数据,这里采用OpenCV的随机数生成器生成误差。

#include <iostream> #include <opencv2/core/core.hpp> #include <ceres/ceres.h> using namespace std; using namespace cv; using namespace ceres; //vector,用于存放x、y的观测数据 //待估计函数为y=3.5x^3+1.6x^2+0.3x+7.8 vector<double> xs; vector<double> ys; //定义CostFunctor结构体用于描述代价函数 struct CostFunctor{ double x_guan,y_guan; //构造函数,用已知的x、y数据对其赋值 CostFunctor(double x,double y) { x_guan = x; y_guan = y; } //重载括号运算符,两个参数分别是估计的参数和由该参数计算得到的残差 //注意这里的const,一个都不能省略,否则就会报错 template <typename T> bool operator()(const T* const params,T* residual)const { residual[0]=y_guan-(params[0]*x_guan*x_guan*x_guan+params[1]*x_guan*x_guan+params[2]*x_guan+params[3]); return true; } }; //生成实验数据 void generateData() { RNG rng; //RNG::gaussian( σ) 返回一个均值为0,标准差为σ的随机数。 double w_sigma = 1.0; for(int i=0;i<100;i++) { double x = i; double y = 3.5*x*x*x+1.6*x*x+0.3*x+7.8; xs.push_back(x); ys.push_back(y+rng.gaussian(w_sigma)); } for(int i=0;i<xs.size();i++) { cout<<"x:"<<xs[i]<<" y:"<<ys[i]<<endl; } } //简单描述我们优化的目的就是为了使我们估计参数算出的y'和实际观测的y的差值之和最小 //所以代价函数(CostFunction)就是y'-y,其对应每一组观测值与估计值的残差。 //由于我们优化的是残差之和,因此需要把代价函数全部加起来,使这个函数最小,而不是单独的使某一个残差最小 //默认情况下,我们认为各组的残差是等权的,也就是核函数系数为1。 //但有时可能会出现粗差等情况,有可能不等权,但这里不考虑。 //这个求和以后的函数便是我们优化的目标函数 //通过不断调整我们的参数值,使这个目标函数最终达到最小,即认为优化完成 int main(int argc, char **argv) { generateData(); //创建一个长度为4的double数组用于存放参数 double params[4]={1.0}; //第一步,创建Problem对象,并对每一组观测数据添加ResidualBlock //由于每一组观测点都会得到一个残差,而我们的目的是最小化所有残差的和 //所以采用for循环依次把每个残差都添加进来 Problem problem; for(int i=0;i<xs.size();i++) { //利用我们之前写的结构体、仿函数,创建代价函数对象,注意初始化的方式 //尖括号中的参数分别为误差类型,输出维度(因变量个数),输入维度(待估计参数的个数) CostFunction* cost_function = new AutoDiffCostFunction<CostFunctor,1,4>(new CostFunctor(xs[i],ys[i])); //三个参数分别为代价函数、核函数和待估参数 problem.AddResidualBlock(cost_function,NULL,params); } //第二步,配置Solver Solver::Options options; //配置增量方程的解法 options.linear_solver_type=ceres::DENSE_QR; //是否输出到cout options.minimizer_progress_to_stdout=true; //第三步,创建Summary对象用于输出迭代结果 Solver::Summary summary; //第四步,执行求解 Solve(options,&problem,&summary); //第五步,输出求解结果 cout<<summary.BriefReport()<<endl; cout<<"p0:"<<params[0]<<endl; cout<<"p1:"<<params[1]<<endl; cout<<"p2:"<<params[2]<<endl; cout<<"p3:"<<params[3]<<endl; return 0; }

CMakeLists.txt:

cmake_minimum_required(VERSION 2.6) project(ceres_test) set( CMAKE_CXX_FLAGS "-std=c++11 -O3" ) # 添加cmake模块以使用ceres库 list( APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake_modules ) # 寻找Ceres库并添加它的头文件 find_package( Ceres REQUIRED ) include_directories( ${CERES_INCLUDE_DIRS} ) # OpenCV find_package( OpenCV REQUIRED ) include_directories( ${OpenCV_DIRS} ) add_executable(ceres_test main.cpp) # 与Ceres和OpenCV链接 target_link_libraries( ceres_test ${CERES_LIBRARIES} ${OpenCV_LIBS} ) install(TARGETS ceres_test RUNTIME DESTINATION bin)

另外,有个levmar的C/C++的库:(这个还不会用) levmar : Levenberg-Marquardt nonlinear least squares algorithms in C/C++ http://users.ics.forth.gr/~lourakis/levmar/index.html#download http://users.ics.forth.gr/~lourakis/sparseLM/


最新回复(0)