ceres solver 03 三种求导方式
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ceres solver 03 三种求导方式相关的知识,希望对你有一定的参考价值。
参考技术A 非线性优化涉及到对目标函数进行求导,从而迭代优化。Ceres Solver 提供了三种求导方式:自动求导、数值求导和解析求导。
自动求导是通过定义一个仿函数,然后传给 AutoDiffCostFunction ,就可以让 Ceres 自己去求导。
所谓仿函数,其实是一个类,只不过这个类的作用像函数,所以叫仿函数。原理就是类实现了 operator() 函数。
自动求导仿函数实现的 operator() 函数必须是模板函数,因为 Ceres 内部求导要用到。
可直接理解 T 为 double 。
Ceres 构造非线性最小二乘问题要先定义代价函数,即上面的 CostFunction ,然后通过 problem.AddResidualBlock(cost_function, NULL, &x); 去构造问题进行求解。
AutoDiffCostFunction 的模板参数:
各个参数说明如下:
有时,无法定义自动求导的模板仿函数,比如参数的估计调用了无法控制的库函数或外部函数。
这种情况无法使用自动求导了,数值求导便可以派上用场了。
数值求导用法类似,先定义仿函数,然后传递给 NumericDiffCostFunction ,然后去构造问题求解。
与自动求导的反函数不同的是,数值求导的 operator() 函数不是模板函数,而是直接使用了 double
这里注意 NumericDiffCostFunction 的模板参数:
各个参数说明如下:
有些情况,自己写求导解析式,计算效率会更高一些。
如果使用解析求导的方式,就要自行计算残差和雅克比。
代价函数以 $ f(x)=10-x $ 为例.
自定义的代价函数类要继承自 CostFunction 或者 SizedCostFunction 。其实 SizedCostFunction 是继承自 CostFunction 的,只是确定了尺寸(各个参数块的数量)。
Evaluate() 函数中计算了残差和雅克比
按照官方说明,以下情况可以使用解析求导
综上所述,建议优先使用自动求导和数值求导的方式,对雅克比计算擅长者和极致性能追求者可考虑使用解析求导的方式。
http://ceres-solver.org/nnls_tutorial.html
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 03 三种求导方式的主要内容,如果未能解决你的问题,请参考以下文章
[ceres-solver] From QuaternionParameterization to LocalParameterization