当前位置: 首页 > 工具软件 > Ceres Solver > 使用案例 >

ceres solver之三种求导方式

卜阳
2023-12-01

非线性优化涉及到对目标函数进行求导,从而迭代优化。
Ceres Solver提供了三种求导方式:自动求导、数值求导和解析求导。

1. 自动求导

自动求导是通过定义一个仿函数,然后传给AutoDiffCostFunction,就可以让Ceres自己去求导。

1.1 定义仿函数

所谓仿函数,其实是一个类,只不过这个类的作用像函数,所以叫仿函数。原理就是类实现了operator()函数。

struct CostFunctor {
   template <typename T>
   bool operator()(const T* const x, T* residual) const {
     residual[0] = T(10.0) - x[0];
     return true;
   }
};

自动求导仿函数实现的operator()函数必须是模板函数,因为Ceres内部求导要用到。
可直接理解Tdouble

1.2 构造CostFunction

CostFunction* cost_function = new AutoDiffCostFunction<CostFunctor, 1, 1>(new CostFunctor);

Ceres构造非线性最小二乘问题要先定义代价函数,即上面的CostFunction,然后通过problem.AddResidualBlock(cost_function, NULL, &x);去构造问题进行求解。

AutoDiffCostFunction的模板参数:

  1. 第1个参数是仿函数
  2. 第2个参数是残差块中残差的数量
  3. 第3个参数是第一个参数块中参数的数量
  4. 如果有多个参数块,依次写出各个参数块中参数的数量

各个参数说明如下:

CostFunction* cost_function
    = new AutoDiffCostFunction<CostFunctor, 1, 1>(new CostFunctor(1.0));              
                                            ^  ^
                                            |  |
                Dimension of residual ------+  |  
                Dimension of x ----------------+  

2. 数值求导

有时,无法定义自动求导的模板仿函数,比如参数的估计调用了无法控制的库函数或外部函数。
这种情况无法使用自动求导了,数值求导便可以派上用场了。

数值求导用法类似,先定义仿函数,然后传递给NumericDiffCostFunction,然后去构造问题求解。

2.1 定义仿函数

struct NumericDiffCostFunctor {
  bool operator()(const double* const x, double* residual) const {
    residual[0] = 10.0 - x[0];
    return true;
  }
};

与自动求导的反函数不同的是,数值求导的operator()函数不是模板函数,而是直接使用了double

2.2 构造CostFunction

CostFunction* cost_function =
  new NumericDiffCostFunction<NumericDiffCostFunctor, ceres::CENTRAL, 1, 1>(
      new NumericDiffCostFunctor);

这里注意NumericDiffCostFunction的模板参数:

  1. 第1个参数是仿函数
  2. 第2个参数是数值求到的方式。这里选用了CENTRAL,还有FORWARDRIDDERS
  3. 第3个参数是残差块中残差的数量
  4. 第4个参数是第一个参数块中参数的数量
  5. 如果有多个参数块,依次写出各个参数块中参数的数量

各个参数说明如下:

CostFunction* cost_function
    = new NumericDiffCostFunction<NumericDiffCostFunctor, CENTRAL, 1, 1>(
        new NumericDiffCostFunctor;                          ^     ^  ^  
                                                             |     |  |  
                                 Finite Differencing Scheme -+     |  |  
                                 Dimension of residual ------------+  | 
                                 Dimension of x ----------------------+  

3. 解析求导

有些情况,自己写求导解析式,计算效率会更高一些。

如果使用解析求导的方式,就要自行计算残差和雅克比。

3.1 定义代价函数类

代价函数以$ f(x)=10-x $为例.

class QuadraticCostFunction : public ceres::SizedCostFunction<1, 1> {
 public:
  virtual ~QuadraticCostFunction() {}
  virtual bool Evaluate(double const* const* parameters,
                        double* residuals,
                        double** jacobians) const {
    const double x = parameters[0][0];
    residuals[0] = 10 - x;
<span class="token comment">// Compute the Jacobian if asked for.</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>jacobians <span class="token operator">!=</span> <span class="token constant">NULL</span> <span class="token operator">&amp;&amp;</span> jacobians<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">!=</span> <span class="token constant">NULL</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  jacobians<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>

}
};

自定义的代价函数类要继承自CostFunction或者SizedCostFunction。其实SizedCostFunction是继承自CostFunction的,只是确定了尺寸(各个参数块的数量)。

Evaluate()函数中计算了残差和雅克比

3.2 构造CostFunction

CostFunction* cost_function = new QuadraticCostFunction;

3.3 什么情况下使用解析求导

按照官方说明,以下情况可以使用解析求导

  1. 函数式简单,便于求出导数解析式
  2. 能使用Matlab Maple Mathmatic SymPy等数学软件求出了导数的解析式
  3. 性能极致要求
  4. 没有其他好的方法去求导
  5. 喜欢手算导数

4. 总结

综上所述,建议优先使用自动求导和数值求导的方式,对雅克比计算擅长者和极致性能追求者可考虑使用解析求导的方式。

5. 参考

http://ceres-solver.org/nnls_tutorial.html

 类似资料: