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比较重要的优势主要有如下几点:

  1. Ceres中提供了自动求导的功能,在优化算法推到的过程中,最麻烦的也就是求雅克比的过程,而Ceres Solver提供的自动求导功能直接为用户避免了这个麻烦,因此在工程中使用Ceres后除非需要优化系统性能才会采用手动求导雅克比;
  2. 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. ljxjuj其中,公式中的各个部分对应的概念是:
ρ 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

linux ceres-solver怎么编译

Ceres Solver Document学习笔记

Ceres Solver Document学习笔记

Ceres-solver安装(win10+vs2015)

ceres-solver无脑搞定协方差小技巧