Ceres Solver Document学习笔记
Posted Leo-Peng
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Ceres Solver Document学习笔记相关的知识,希望对你有一定的参考价值。
Ceres Solver Document学习笔记
Ceres Solver Document学习笔记
之前在学习Vins-Mono时,在Vins-Mono后端就是使用Ceres实现的滑窗优化,当时对Ceres的了解也仅仅是在简单使用的层面上,最近项目中有再次用到Ceres相关的内容,因此我把Ceres Solver的Document扫了一遍,这篇博客是相关的笔记。我个人觉得Ceres比较重要的优势主要有如下几点:
- Ceres中提供了自动求导的功能,在优化算法推到的过程中,最麻烦的也就是求雅克比的过程,而Ceres Solver提供的自动求导功能直接为用户避免了这个麻烦,因此在工程中使用Ceres后除非需要优化系统性能才会采用手动求导雅克比;
- Ceres中提供了局部参数化的功能接口,也就是LocalParameteriztion对象,这对于求解旋转变换相关问题有极大的帮助;
其他的诸如计算速度,求解器丰富等就不多提及了,下面开始展开细节
1. 基本概念
我看大多数博客都会用如下例子:
min
x
1
2
∑
i
ρ
i
(
∥
f
i
(
x
i
1
,
…
,
x
i
k
)
∥
2
)
\\min _\\mathbfx \\frac12 \\sum_i \\rho_i\\left(\\left\\|f_i\\left(x_i_1, \\ldots, x_i_k\\right)\\right\\|^2\\right)
xmin21i∑ρi(∥fi(xi1,…,xik)∥2)
s.t.
l
j
≤
x
j
≤
u
j
\\text s.t. \\quad l_j \\leq x_j \\leq u_j
s.t. lj≤xj≤uj其中,公式中的各个部分对应的概念是:
ρ
i
(
∥
f
i
(
x
i
1
,
…
,
x
i
k
)
∥
2
)
\\rho_i\\left(\\left\\|f_i\\left(x_i_1, \\ldots, x_i_k\\right)\\right\\|^2\\right)
ρi(∥fi(xi1,…,xik)∥2)被称为ResidualBlock,也就是残差;
x
i
1
,
…
,
x
i
k
\\left\\x_i_1, \\ldots, x_i_k\\right\\
xi1,…,xik被成为ParameterBlock,也就是输入参数;
f
i
(
⋅
)
f_i(\\cdot)
fi(⋅)被称为CostFunction,也就是代价函数,是根据输入参数直接计算残差的模块;
ρ
i
(
⋅
)
\\rho_i(\\cdot)
ρi(⋅)被称为LossFunction,也就是损失函数或者鲁棒核函数,通常用来减少输入参数中外点影响的模块;
以上都是Ceres中的定义,在其他不同的工具或者任务中定义可能不相同,Ceres所做的也就是根据输入参数计算损失,并通过凸优化的方法将损失降到局部最小,关于凸优化中的基本原理就不在此补充,下面主要就Ceres各个模块的用法进行介绍。
2. 基本方法
2.1 CostFunction
CostFunction负责计算残差和雅克比矩阵,CostFuntion类代码如下:
class CostFunction
public:
virtual bool Evaluate(double const* const* parameters,
double* residuals,
double** jacobians) = 0;
const vector<int32>& parameter_block_sizes();
int num_residuals() const;
protected:
vector<int32>* mutable_parameter_block_sizes();
void set_num_residuals(int num_residuals);
;
Evaluate成员函数负责根据输入的paramters计算residuals和jacobians,如果jacobians为nullptr,通常我们只需要在Evaluate函数中实现residuals的计算,jacobians后面可以通过Ceres提供的自动求导(数值求导)替代,否则,我们还需要在Evaluate函数中实现jacobians的计算,jacobians是一个大小为num_residuals * parameter_block_sizes_的行主数组,具体计算公式为: jacobians [ i ] [ r ∗ parameter block sizes [ i ] + c ] = ∂ residual [ r ] ∂ parameters [ i ] [ c ] \\textjacobians[i] [r * \\text parameter block sizes[i] + c] =\\frac\\partial \\text residual[r]\\partial \\text parameters[i][c] jacobians[i][r∗ parameter block sizes[i]+c]=∂parameters[i][c]∂residual[r]在CostFunction中用成员变量CostFunction::parameter_block_sizes_和CostFunction::num_residuals_分别用于记录输入parameters和residuals的尺寸,这两个信息可以通过继承该类后使用访问其设置,或者通过Problem::AddResidualBlock()函数添加。
此外,如果paramters大小和residuals大小在编译时是已知的,我们就可以使用SizeCostFunction,该函数可以将paramters大小和residuals大小设置为模板参数,用户只需要在使用的时候给模板参数赋值就可以,如下:
template<int kNumResiduals, int... Ns>
class SizedCostFunction : public CostFunction
public:
virtual bool Evaluate(double const* const* parameters,
double* residuals,
double** jacobians) const = 0;
;
2.2 AutoDiffCostFunction
该方法就是Ceres中提供的自动求导的方法,如果需要使用自动微分函数,必须在CostFunction中定义模板符号函数operator(),该函数根据paramters计算residuals,如下所示:
class MyScalarCostFunctor
MyScalarCostFunctor(double k): k_(k)
template <typename T>
bool operator()(const T* const x , const T* const y, T* e) const
e[0] = k_ - x[0] * y[0] - x[1] * y[1];
return true;
private:
double k_;
;
然后带自动微分功能的CostFunction构造函数如下:
CostFunction* cost_function
= new AutoDiffCostFunction<MyScalarCostFunctor, 1, 2, 2>(
new MyScalarCostFunctor(1.0)); ^ ^ ^
| | |
Dimension of residual ------+ | |
Dimension of x ----------------+ |
Dimension of y -------------------+
默认情况下,当AutoDiffCostFunction销毁时,其对应的CostFunction也会销毁,如果不希望存在这种从属关系,可以在实例化AutoDiffCostFunction时,传入参数DO_NOT_TAKE_OWNERSHIP,如下代码所示:
MyScalarCostFunctor functor(1.0)
CostFunction* cost_function
= new AutoDiffCostFunction<MyScalarCostFunctor, 1, 2, 2>(
&functor, DO_NOT_TAKE_OWNERSHIP);
AutoDiffCostFunction同时支持在运行时确定残差维度,此时需要在实例化时传入参数DYNAMIC,代码如下所示:
CostFunction* cost_function
= new AutoDiffCostFunction<MyScalarCostFunctor, DYNAMIC, 2, 2>(
new CostFunctorWithDynamicNumResiduals(1.0), ^ ^ ^
runtime_number_of_residuals); <----+ | | |
| | | |
| | | |
Actual number of residuals ------+ | | |
Indicate dynamic number of residuals --------+ | |
Dimension of x ------------------------------------+ |
Dimension of y ---------------------------------------+
AutoDIffCostFunction在编译时就需要确定参数维度,但是在一些使用场景中需要在运行时才确定,那么这时就需要使用DynamicAutoDiffCostFunction,具体使用方法如下:
template <typename CostFunctor, int Stride = 4>
class DynamicAutoDiffCostFunction : public CostFunction
;
struct MyCostFunctor
template<typename T>
bool operator()(T const* const* parameters, T* residuals) const
DynamicAutoDiffCostFunction<MyCostFunctor, 4>* cost_function =
new DynamicAutoDiffCostFunction<MyCostFunctor, 4>(
new MyCostFunctor());
cost_function->AddParameterBlock(5);
cost_function->AddParameterBlock(10);
cost_function->SetNumResiduals(21);
2.3 NumericDiffCostFuntion
NumericDiffCostFunction作用和AutoDiffCostFunction作用是相同的,二者唯一的区别是NumericDiffCostFunction中不再使用模板类型,而是使用double类型代替模板类型。谷歌推荐类型为AutoDiffCostFunction,C++模板的使用使得自动求导效率更高,而数值的差花费更多,容易出现数字错误,导致收敛速度变慢。
2.4 LossFunction
LossFunction就是上文提到的代价计算公式中的鲁棒核函数
ρ
i
(
⋅
)
\\rho_i(\\cdot)
ρi(⋅)部分,用于减少输入异常值对结果的影响,通常鲁棒核函数
ρ
i
(
⋅
)
\\rho_i(\\cdot)
ρi(⋅)要求满足
ρ
(
0
)
=
0
\\rho(0)=0
ρ(0)=0
ρ
′
(
0
)
=
1
\\rho^\\prime(0)=1
ρ′(0)=1
ρ
′
(
s
)
<
1
\\rho^\\prime(s)<1
ρ′(s)<1
ρ
′
′
(
s
)
<
0
\\rho^\\prime \\prime(s)<0
ρ′′(s)<0具体地,在Ceres中提供了如下核函数:
TrivialLoss
ρ
(
s
)
=
s
\\rho(s)=s
ρ(s)=sHuberLoss
ρ
(
s
)
=
s
s
≤
1
2
s
−
1
s
>
1
\\rho(s)=\\left\\\\beginarrayll s & s \\leq 1 \\\\ 2 \\sqrts-1 & s>1 \\endarray\\right.
ρ(s)=s2s以上是关于Ceres Solver Document学习笔记的主要内容,如果未能解决你的问题,请参考以下文章
[ceres-solver] From QuaternionParameterization to LocalParameterization