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 预示着计算成功。
例如,考虑一个误差 ,其中 都是两个维度的向量, 是一个常数,该误差的形式是最小二乘问题中的常见模式, 值可能是一系列测量结果的模型期望值,其中每次测量都有一个 来构成代价函数实例,也就是说 使我们的待优化变量,实际中我们会有多个 ,每个都会构成一个残差项。可以参考曲线拟合的思路。通过 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() 的声明中,输入参数 的位置在前面,通过一个 const * 类型的模板参数传入,当然,如果有三个参数的情况下,第三个参数应该在 的后面传入,输出永远是最后一个参数,是一个指向数组的指针,在上面的实现中,误差是一个标量,只有一个维度,因此只有 被赋值。定义了该类之后,自动微分的代价函数可以被如下定义使用
auto* cost_function
= new AutoDiffCostFunction<MyScalarCostFunctor, 1, 2, 2>(1.0);
^ ^ ^
| | |
Dimension of residual ------+ | |
Dimension of x ----------------+ |
Dimension of y -------------------+
在这个例子中,对应每一个 都会有一个代价函数实例被创建。在上面的实例化中,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