这篇文章翻译自官方教程Automatic Derivatives并且参考了少年的此间的博客文章Ceres-Solver学习笔记(5)
现在我们将讨论自动微分算法。它是一种可以快速计算精确导数的算法,同时用户只要做与数值微分法类似的工作。下面的代码片段实现了对Rat43(见前两节)的CostFunction
。
struct Rat43CostFunctor {
Rat43CostFunctor(const double x, const double y) : x_(x), y_(y) {}
template <typename T>
bool operator()(const T* parameters, T* residuals) const {
//变化1
const T b1 = parameters[0];
const T b2 = parameters[1];
const T b3 = parameters[2];
const T b4 = parameters[3];
residuals[0] = b1 * pow(1.0 + exp(b2 - b3 * x_), -1.0 / b4) - y_;
return true;
}
private:
const double x_;
const double y_;
};
CostFunction* cost_function =
new AutoDiffCostFunction<Rat43CostFunctor, 1, 4>( //变化2
new Rat43CostFunctor(x, y));
我把对应的数值微分法代码贴在这里以供对比。
struct Rat43CostFunctor {
Rat43CostFunctor(const double x, const double y) : x_(x), y_(y) {}
bool operator()(const double* parameters, double* residuals) const {
const double b1 = parameters[0];
const double b2 = parameters[1];
const double b3 = parameters[2];
const double b4 = parameters[3];
residuals[0] = b1 * pow(1.0 + exp(b2 - b3 * x_), -1.0 / b4) - y_;
return true;
}
const double x_;
const double y_;
}
CostFunction* cost_function =
new NumericDiffCostFunction<Rat43CostFunctor, FORWARD, 1, 4>(
new Rat43CostFunctor(x, y));
注意,与数值微分法相比,在定义自动微分的Functor时,唯一的区别是对操作符operator()
的设置。
在数值微差的情况下
//数值微分法
bool operator()(const double* parameters, double* residuals) const;
//自动微分法
template <typename T> bool operator()(const T* parameters, T* residuals) const;
这个变化有什么影响呢?下表比较了使用各种方法对Rat43进行计算残差和雅可比矩阵的时间。
CostFunction | Time (ns) |
---|---|
Rat43Analytic | 255 |
Rat43AnalyticOptimized | 92 |
Rat43NumericDiffForward | 262 |
Rat43NumericDiffCentral | 517 |
Rat43NumericDiffRidders | 3760 |
Rat43AutomaticDiff | 129 |
我们可以使用自动微分(Rat43AutomaticDiff)来得到精确的微分。而这与编写数字微分的代码量相差不多,但比优化后的解析微分法只慢 40% 40 % 。 为了研究它的工作原理,必须要学习二元数(Dual number)和射流(Jet)
二元数(Dual number)和射流(Jet)
阅读这一小节和下一节关于实现Jets的内容,与在Ceres求解器中使用自动微分没有直接关系。但是,在调试和推理自动微分的性能时,了解Jets的工作原理是非常有用的。
二元数是实数的一个延伸,类似于复数。复数则通过引入虚数来增加实数,比如 i i ,二元数引入了一个极小(infinitesimal)二元数单位,比如
,且 ϵ2=0 ϵ 2 = 0 (平方后太小可以忽略)。一个二元数 a+vϵ a + v ϵ 包含两个分量,实分量 a a 和极小分量的
。令人惊喜的是,这个简单的变化带来了一种方便的计算精确导数的方法,而不需要复杂的符号表达式。
例如,考虑函数
然后
观察 ϵ ϵ 的系数&#