本文主要基于Ceres Solver 官方Tutourial选取要点进行翻译,并适当补充外部内容对某些概念进行简要注释。主要用于个人学习备忘之作用。
Ceres可以解决边界约束鲁棒非线性最小二乘法优化问题。这个拗口的概念可以用以下表达式表示:
- minxf(x) min x f ( x ) 表示使 f(x) f ( x ) 最小的 x x 的取值。
- “非线性”是指代价函数(见下文)为非线性函数。
ρi(∥fi(xi1,...,xik)∥2) ρ i ( ‖ f i ( x i 1 , . . . , x i k ) ‖ 2 ) 这一部分被成为残差块(ResidualBlock
),其中的 fi(⋅) f i ( ⋅ ) 叫做代价函数(CostFunction
)。代价函数依赖于一系列参数 [xi1,...,xik] [ x i 1 , . . . , x i k ] ,这一系列参数(均为标量)称为参数块(ParameterBlock
)。当然参数块中也可以只含有一个变量。 lj l j 和 uj u j 分别是变量块 xj x j 的上下边界。
ρi ρ i 是损失函数(LossFunction
) 。按照损失函数的是一个标量函数,其作用是减少异常值(Outliers
)对优化结果的影响。其效果类似于对函数的过滤。
我感觉这里的损失函数和机器学习中的损失函数的含义可能有所区别。机器学习中的损失函数的概念好像和代价函数相似。对于这一系列概念的辨析有待深入。
一个特殊情况是, ρi(f(x))=f(x) ρ i ( f ( x ) ) = f ( x ) ,也就是没有对函数进行任何过滤,损失函数的输出等于输入。若同时令 lj=−∞ l j = − ∞ 和 uj=+∞ u j = + ∞ ,即参数块的取值没有限制,那么此时问题变成了非线性最小二乘法问题。表达式如下:
每个程序最简单的示例常被称为Hello World。本节将简单描述Ceres的Hello world例程,以便让读者对库的使用步骤快速建立认识。
在Hello World这个例子中,待优化的函数是 f(x)=10−x f ( x ) = 10 − x 。重载()操作符如下:
struct CostFunctor {
template <typename T>
bool operator()(const T* const x, T* residual) const {
residual[0] = T(10.0) - x[0];
return true;
}
};
需要注意的是,这里假设所有的输入和输出都是同一类型T
。在后面的代码中,Ceres通过调用CostFunctor::operator
来使用这一重载操作符。在这个例子中可以令 T = double
,然后仅仅以double类型输出残差值。也可以令 T = Jet
然后输出雅可比矩阵。这一部分在后续教程还有更详细的介绍。
雅可比矩阵实际上就是对一个含有多个参数的函数 f(x) f ( x ) 求一系列一阶偏微分 详情见雅可比矩阵的维基百科页面。
一旦残差方程建立,我们就可以用Ceres来实现非线性最小二乘法的优化算法。代码如下:
int main(int argc, char** argv) {
google::InitGoogleLogging(argv[0]);
// The variable to solve for with its initial value.
double initial_x = 5.0;
double x = initial_x;
// Build the problem.
Problem problem;
// Set up the only cost function (also known as residual). This uses
// auto-differentiation to obtain the derivative (jacobian).
CostFunction* cost_function =
new AutoDiffCostFunction1, 1>(new CostFunctor);
problem.AddResidualBlock(cost_function, NULL, &x);
AutoDiffCostFunction
将刚刚建立的CostFunctor
结构的一个实例作为输入,自动生成其微分并且赋予其一个CostFunction
类型的接口。
// Run the solver!
Solver::Options options;
options.linear_solver_type = ceres::DENSE_QR;
options.minimizer_progress_to_stdout = true;
Solver::Summary summary;
Solve(options, &problem, &summary);
std::cout << summary.BriefReport() << "\n";
std::cout << "x : " << initial_x
<< " -> " << x << "\n";
return 0;
}
这一部分完成对解算器的构建、配置和启动。
编译完成,运行结果如下:
iter cost cost_change |gradient| |step| tr_ratio tr_radius ls_iter iter_time total_time
0 4.512500e+01 0.00e+00 9.50e+00 0.00e+00 0.00e+00 1.00e+04 0 5.33e-04 3.46e-03
1 4.511598e-07 4.51e+01 9.50e-04 9.50e+00 1.00e+00 3.00e+04 1 5.00e-04 4.05e-03
2 5.012552e-16 4.51e-07 3.17e-08 9.50e-04 1.00e+00 9.00e+04 1 1.60e-05 4.09e-03
Ceres Solver Report: Iterations: 2, Initial cost: 4.512500e+01, Final cost: 5.012552e-16, Termination: CONVERGENCE
x : 0.5 -> 10
初始值为5(由代码给出),最终通过两次循环之后到达最优解10。其实本例是一个线性问题,因为
f(x)=10−x f ( x ) = 10 − x 是一个线性函数。但是Ceres仍然可以应用。对于Ceres解非线性应用的流程和具体配置将在后续的教程中给出。