本文介绍如何使用ceres优化库搭建一个最小二乘问题,要提前安装好ceres库。
一、问题定义
首先,考虑寻找函数最小值的问题:
1
2
(
10
−
x
)
2
.
\frac{1}{2}(10 -x)^2.
21(10−x)2.
这是一个很简单的问题,其最小值位于
x
=
10
x = 10
x=10,但这是一个很好的说明用Ceres解决问题的函数。第一步是编写一个代价函数CostFunctor,这个代价函数CostFunctor可以用来衡量
1
2
(
10
−
x
)
2
\frac{1}{2}(10 -x)^2
21(10−x)2的大小,我们选用原函数的导数形式
f
(
x
)
=
10
−
x
f(x) = 10 - x
f(x)=10−x作为代价函数,该代价函数的为0时对应原函数的最小值。
struct CostFunctor {
template <typename T>
bool operator()(const T* const x, T* residual) const {
residual[0] = 10.0 - x[0];
return true;
}
};
这里需要注意的重要一点是,operator()
是一种模板化方法,它假设其所有输入和输出都是某种类型的T
。这里使用模板化允许Ceres
调用CostFunctor::operator<T>()
,当只需要残差值时使用T=double
,当需要雅可比时使用特殊类型T=Jet
。
一旦我们有了计算残差函数的方法,现在是时候用它来构造一个非线性最小二乘问题,并让Ceres
解决它了。本文使用自动求导的方式进行求解,关于自动求导的介绍以及其余求导方式的介绍,见博主的其他博客。(链接待补充…)
int main(int argc, char** argv) {
// 定义要优化求解的变量,并赋予初值
double initial_x = 5.0;
double x = initial_x;
// 构建problem
ceres::Problem problem;
// Set up the only cost function (also known as residual). This uses
// auto-differentiation to obtain the derivative (jacobian).
ceres::CostFunction* cost_function =
new ceres::AutoDiffCostFunction<CostFunctor, 1, 1>(new CostFunctor);
problem.AddResidualBlock(cost_function, nullptr, &x);
// 运行优化器
ceres::Solver::Options options;
options.linear_solver_type = ceres::DENSE_QR;
options.minimizer_progress_to_stdout = true;
ceres::Solver::Summary summary;
ceres::Solve(options, &problem, &summary);
std::cout << summary.BriefReport() << "\n";
std::cout << "x : " << initial_x << " -> " << x << "\n";
return 0;
}
AutoDiffCostFunction
将一个CostFunctor
作为输入,自动对其进行微分,并为其提供一个CostFunction
接口。
iter cost cost_change |gradient| |step| tr_ratio tr_radius ls_iter iter_time total_time
0 1.250000e+01 0.00e+00 5.00e+00 0.00e+00 0.00e+00 1.00e+04 0 1.69e-05 3.70e-05
1 1.249750e-07 1.25e+01 5.00e-04 5.00e+00 1.00e+00 3.00e+04 1 2.79e-05 1.12e-04
2 1.388518e-16 1.25e-07 1.67e-08 5.00e-04 1.00e+00 9.00e+04 1 5.01e-06 1.27e-04
Ceres Solver Report: Iterations: 3, Initial cost: 1.250000e+01, Final cost: 1.388518e-16, Termination: CONVERGENCE
x : 5 -> 10
从x=5
开始,求解器在两次迭代中变为10
。细心的读者会注意到这是一个线性问题,一个线性解应该足以获得最佳值。求解器的默认配置是针对非线性问题的,为了简单起见,在本例中我们没有更改它。确实可以使用Ceres
在一次迭代中获得该问题的解决方案。还要注意,在第一次迭代中,解算器确实非常接近最优函数值0
。在讨论Ceres
的收敛性和参数设置时,我们将更详细地讨论这些问题。
二、完整代码
1.CPP代码
#include <iostream>
#include <ceres/ceres.h>
struct CostFunctor {
template <typename T>
bool operator()(const T* const x, T* residual) const {
residual[0] = 10.0 - x[0];
return true;
}
};
int main(int argc, char** argv) {
// 定义要优化求解的变量,并赋予初值
double initial_x = 5.0;
double x = initial_x;
// 构建problem
ceres::Problem problem;
// Set up the only cost function (also known as residual). This uses
// auto-differentiation to obtain the derivative (jacobian).
ceres::CostFunction* cost_function =
new ceres::AutoDiffCostFunction<CostFunctor, 1, 1>(new CostFunctor);
problem.AddResidualBlock(cost_function, nullptr, &x);
// 运行优化器
ceres::Solver::Options options;
options.linear_solver_type = ceres::DENSE_QR;
options.minimizer_progress_to_stdout = true;
ceres::Solver::Summary summary;
ceres::Solve(options, &problem, &summary);
std::cout << summary.BriefReport() << "\n";
std::cout << "x : " << initial_x << " -> " << x << "\n";
return 0;
}
2.CMakeLists.txt配置文件
cmake_minimum_required(VERSION 2.8)
project(ceres_test)
set(CMAKE_CXX_STANDARD 14)
find_package(Ceres REQUIRED )
include_directories( ${CERES_INCLUDE_DIRS})
# include_directories("/usr/include/eigen3")
add_executable(HelloWorld HelloWorld.cpp)
target_link_libraries(HelloWorld ${CERES_LIBRARIES})