ceres库主要是用来优化问题,和深度学习思想差不多,迭代优化,逐渐逼近最优解。
回顾一下非线性最小二乘法
1.非线性最小二乘法
方程式AX=B,我们可根据其形式求解析解。如果该问题为线性,我们可对目标函数求导,零导数为零,可求得目标函数极值,并且其为全局最小值,则为目标函数的最优解。
但问题往往为非线性,由于函数复杂,无法写出其导数形式,我们不可能再通过导数找全局最优解,而是通过不断的迭代计算找到函数局部最小解,并且该局部最小解在误差允许的范围内,我们就可认为目标函数取得最优解,以上就是非线性最小二乘法的思想。
这可将求导问题转化为梯度下降问题:
- 给定某个初值
;
- 对于第K次迭代,寻找一个增量
,使得
达到极小值(局部最小)
- 若
足够小则停止迭代
- 否则 另
,返回第二步
基于以上思想,ceres库可通过优化迭代求解非线性最小二乘问题最优解。
2.ceres 优化过程
2.1 ceres优化使用流程
2.1.1构建优化问题
ceres::Problem problem;
2.1.2创建代价函数
cost_function = new ceres::AutoDiffCostFunction<LOSS,16,6>(new LOSS(pose,alpha,a,d));
参数介绍:CostFunctor 自己编写的代价函数,
第一个六 表示要优化的残差参数数组大小 (residual 大小)
第二个六 表示 待优化参数数组大小 (即下文中X大小)
2.1.3添加代价函数、损失函数
problem.AddResidualBlock(new ceres::AutoDiffCostFunction<LOSS,6,6>(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<double> pose,vector<double> alpha,vector<double> a,vector<double> d):_pose(pose),_alpha(alpha),_a(a),_d(d) {}
template<typename T>
bool operator()(const T *theta, T *residual) const {
...
for (int i = 0; i < 6; i++) residual[i] =...
return true;
}
//const Mat TD;
const vector<double> _alpha, _a, _d,_pose;
};
在结构体构造的重载运算符中,如果要使用loss的参数,首先要将其转化为Jet的格式,例如pose,传入的数据类型为vector<double> 那么要将其转化为vector<T> 形式,转化的方法也非常简单。
vector<T> 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.youkuaiyun.com/hltt3838/article/details/109695164