ceres使用

ceres库主要是用来优化问题,和深度学习思想差不多,迭代优化,逐渐逼近最优解。

回顾一下非线性最小二乘法

1.非线性最小二乘法

方程式AX=B,我们可根据其形式求解析解。如果该问题为线性,我们可对目标函数求导,零导数为零,可求得目标函数极值,并且其为全局最小值,则为目标函数的最优解。

但问题往往为非线性,由于函数复杂,无法写出其导数形式,我们不可能再通过导数找全局最优解,而是通过不断的迭代计算找到函数局部最小解,并且该局部最小解在误差允许的范围内,我们就可认为目标函数取得最优解,以上就是非线性最小二乘法的思想。

这可将求导问题转化为梯度下降问题:

  1. 给定某个初值x_{0}
  2. 对于第K次迭代,寻找一个增量\Delta x,使得\left \| f(x_{k}+\Delta x_{k}) \right \|_{2}^{2}达到极小值(局部最小)
  3. \Delta x足够小则停止迭代
  4. 否则 另x_{k+1} = x_{k}+\Delta x_{k},返回第二步

 基于以上思想,ceres库可通过优化迭代求解非线性最小二乘问题最优解。

2.ceres 优化过程

2.1 ceres优化使用流程

2.1.1构建优化问题

ceres::Problem problem;

2.1.2创建代价函数


cost_function = new ceres::AutoDiffCostFunction(new LOSS(pose,alpha,a,d));

参数介绍:CostFunctor 自己编写的代价函数,

                  第一个六  表示要优化的残差参数数组大小 (residual 大小)

                  第二个六 表示 待优化参数数组大小 (即下文中X大小)

2.1.3添加代价函数、损失函数

problem.AddResidualBlock(new ceres::AutoDiffCostFunction(new LOSS(pose,alpha,a,d)),nullptr,x.data());

(这里代价函数直接添加到添加代价函数中)

new LOSS(pose,alpha,a,d)   需要传入代价函数的参数值

2.1.4 配置求解器

ceres::Solver::Options options;
options.linear_solver_type = ceres::ITERATIVE_SCHUR;// 信赖域策略
options.max_num_iterations = 10000;  //迭代次数
options.gradient_tolerance = 1e-3; //梯度阈值
options.function_tolerance = 1e-4;  //相邻两次迭代之间目标函数之差
options.trust_region_strategy_type = ceres::LEVENBERG_MARQUARDT;  //优化策略
options.update_state_every_iteration = true; //是否向终端输出优化过程
  • linear_solver_type:信赖域方法中求解线性方程组所使用的求解器类型,默认为DENSE_QR,其他可选项如下:

    • DENSE_QR:QR分解,用于小规模最小二乘问题求解;
    • DENSE_NORMAL_CHOLESKY&SPARSE_NORMAL_CHOLESKY:Cholesky分解,用于具有稀疏性的大规模非线性最小二乘问题求解;
    • CGNR:使用共轭梯度法求解稀疏方程;
    • DENSE_SCHUR&SPARSE_SCHUR:SCHUR分解,用于BA问题求解;
    •   ITERATIVE_SCHUR:使用共轭梯度SCHUR求解BA问题;
  • min_linear_solver_iteration/max_linear_solver_iteration:线性求解器的最小/最大迭代次数

  • max_num_iterations:求解器的最大迭代次数;

  • minimizer_progress_to_stdout:是否向终端输出优化过程信息

 这里仅仅列了比较常用的求解器参数,博主还是小白,还需继续学习。

2.1.5 运行求解器

ceres::Solver::Summary summary;               // 优化信息
ceres::Solve(options, &problem, &summary);    // 开始优化
cout << summary.BriefReport() << endl;   //输出迭代信息

2.2 求导函数

求导函数有三种:解析求导、数值求导和自动求导

这里只介绍自动求导,其他两个还未接触

2.2.1 自动求导

这是ceres库一个神奇的功能,可以通过比较基础的表达式求解出导数形式,我的理解是通过Jets实现的,采用模板类形式

struct LOSS {
	LOSS(vector pose,vector alpha,vector a,vector d):_pose(pose),_alpha(alpha),_a(a),_d(d) {}
	template
	bool operator()(const T *theta, T *residual) const {

		...
		for (int i = 0; i < 6; i++) residual[i] =...
		return true;
	}
	//const Mat TD;
	const vector _alpha, _a, _d,_pose;
};

 在结构体构造的重载运算符中,如果要使用loss的参数,首先要将其转化为Jet的格式,例如pose,传入的数据类型为vector  那么要将其转化为vector 形式,转化的方法也非常简单。

vector Pose(6);
for(int i=0;i<6;i++) Pose[i]=T(_pose);

 2.3 为优化参数添加上下边界

使用SetParameterUpperBound 与SetParamterLowerBound函数定义边界

problem.SetParameterLowerBound(x.data(), index, lower_bound);
problem.SetParameterUpperBound(x.data(), index, upper_bound);

 lower表示下界,upper表示上界 

第一个参数x 表示优化参数数组,index表示数组中第index个元素,第三个参数表示边界

未完待续。。。。。

推荐 【官网】

参考 https://blog.csdn.net/hltt3838/article/details/109695164

你可能感兴趣的:(ceres,算法,c++,线性代数)