Ceres Solver Document学习笔记
Posted Jichao_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 _{\\mathbf{x}} \\frac{1}{2} \\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 ] \\text{jacobians}[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\\{\\begin{array}{ll} s & s \\leq 1 \\\\ 2 \\sqrt{s}-1 & s>1 \\end{array}\\right.
ρ(s)={s2s
[ceres-solver] From QuaternionParameterization to LocalParameterization