AutoDiffCostFunction

class AutoDiffCostFunction

定义一个 CostFunction 或者 SizeCostFunction 可能非常繁琐并且计算导数时很容易出错,因此 Ceres 提供了自动求导工具

template <typename CostFunctor,
       int kNumResiduals,  // Number of residuals, or ceres::DYNAMIC.
       int... Ns>          // Size of each parameter block
class AutoDiffCostFunction : public
SizedCostFunction<kNumResiduals, Ns> {
 public:
  // Instantiate CostFunctor using the supplied arguments.
  template<class ...Args>
  explicit AutoDiffCostFunction(Args&& ...args);
  explicit AutoDiffCostFunction(std::unique_ptr<CostFunctor> functor);
  explicit AutoDiffCostFunction(CostFunctor* functor, ownership = TAKE_OWNERSHIP);

  // Ignore the template parameter kNumResiduals and use
  // num_residuals instead.
  AutoDiffCostFunction(CostFunctor* functor,
                       int num_residuals,
                       ownership = TAKE_OWNERSHIP);
  AutoDiffCostFunction(std::unique_ptr<CostFunctor> functor,
                       int num_residuals);
};

为了能够自动计算导数,用户在使用的时候需要自定义一个 class 并且重载 operator() 运算,且要使用模板参数 T。自动求导框架使用 Jet 对象来替换模板参数 T,以便于计算导数,但这些操作对用户来说都是隐藏的,因此用户只需要把这里的 T 当做一个 double 或者 float 类型的数据进行使用即可。括号运算符重载函数必须将残差的计算结果写入到最后一个非 const 参数中去,同时返回 true 预示着计算成功。

例如,考虑一个误差 e=kxye=k-x^{\top}y,其中 x,yx,y 都是两个维度的向量,kk 是一个常数,该误差的形式是最小二乘问题中的常见模式, xyx^{\top}y 值可能是一系列测量结果的模型期望值,其中每次测量都有一个 kk 来构成代价函数实例,也就是说 x,yx,y 使我们的待优化变量,实际中我们会有多个 kk ,每个都会构成一个残差项。可以参考曲线拟合的思路。通过 Ceres 去实现上述建模的问题,我们首先要定义一个对象,并对括号运算符进行重载,计算残差。

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_;
};

注意,在函数 operator() 的声明中,输入参数 x,yx,y 的位置在前面,通过一个 const * 类型的模板参数传入,当然,如果有三个参数的情况下,第三个参数应该在 yy 的后面传入,输出永远是最后一个参数,是一个指向数组的指针,在上面的实现中,误差是一个标量,只有一个维度,因此只有 e[0]e[0]被赋值。定义了该类之后,自动微分的代价函数可以被如下定义使用

auto* cost_function
    = new AutoDiffCostFunction<MyScalarCostFunctor, 1, 2, 2>(1.0);
                                                    ^  ^  ^
                                                    |  |  |
                        Dimension of residual ------+  |  |
                        Dimension of x ----------------+  |
                        Dimension of y -------------------+

在这个例子中,对应每一个 kk 都会有一个代价函数实例被创建。在上面的实例化中,MyScalarCostFunction 后面的模板参数 <1, 2, 2> 分别代表残差的维度,第一个参数的维度,第二个参数的维度。默认情况下,AutoDiffCostFunction 将获得传递给它的代价函数指针的所有权,即在删除 AutoDiffCostFunction 自身时代价函数也会被删除。不过,在某些情况下这可能并不可取,因此也可以在构造函数中指定 DO_NOT_TAKE_OWNERSHIP 作为第二个参数,同时传递一个不需要被 AutoDiffCostFunction 删除的成本函数指针。做法如下:

MyScalarCostFunctor functor(1.0)
auto* cost_function
    = new AutoDiffCostFunction<MyScalarCostFunctor, 1, 2, 2>(
        &functor, DO_NOT_TAKE_OWNERSHIP);

在某些情况下,残差的维度我们甚至一开始是不确定的,需要在程序运行时才能够确定,AutoDiffCostFunction 也支持动态的残差维度。例如

auto functor = std::make_unique<CostFunctorWithDynamicNumResiduals>(1.0);
auto* cost_function
    = new AutoDiffCostFunction<CostFunctorWithDynamicNumResiduals,
                                                    DYNAMIC, 2, 2>(
        std::move(functor),                            ^     ^  ^
        runtime_number_of_residuals); <----+           |     |  |
                                           |           |     |  |
                                           |           |     |  |
          Actual number of residuals ------+           |     |  |
          Indicate dynamic number of residuals --------+     |  |
          Dimension of x ------------------------------------+  |
          Dimension of y ---------------------------------------+

A common beginner’s error when first using AutoDiffCostFunction is to get the sizing wrong. In particular, there is a tendency to set the template parameters to (dimension of residual, number of parameters) instead of passing a dimension parameter for every parameter block. In the example above, that would be <MyScalarCostFunction, 1, 2>, which is missing the 2 as the last template argument.

Last updated