Ceres学习笔记006_使用Ceres解决一般无约束优化问题

Ceres除了能够解决非线性最小二乘问题外,还能解决一般无约束优化问题,此时只需要有目标函数和梯度,也不需要提供数据。

与一般优化问题不同的是,非线性最小二乘优化问题的目标函数具有明确的物理意义——残差。

本文将以Rosenbrock函数为例,使用Ceres优化出能够使其最小化的参数值,Rosenbrock函数表达式如下:

f ( x , y ) = ( 1 − x ) 2 + 100 ( y − x 2 ) 2 f(x,y)=(1-x)^2 + 100(y - x^2)^2 f(x,y)=(1x)2+100(yx2)2 (1)

在数学最优化中,Rosenbrock函数是一个用来测试最优化算法性能的非凸函数,也被称为山谷函数或香蕉函数。

目标就是为了获得Rosenbrock函数的最小值,因此不需要再另外构建数学模型。

1 代码实践

与非线性最小二乘问题类似,一般优化问题也需要计算导数,导数计算方法也有自动微分法、数值微分法、解析解法3种。

1.1 自动微分法

非线性最小二乘优化问题中,使用CostFunction类来构建代价函数,而与之相对应的,一般无约束优化问题中,使用的是FirstOrderFunction类,但也要先构造用户自定义“残差”模型,且也是模板仿函数类型,如下:

// 用户自定义"残差"计算模型
// f(x,y) = (1-x)^2 + 100*(y - x^2)^2;
struct Rosenbrock 
{
template <typename T>
bool operator()(const T* parameters, T* cost) const 
{
    const T x = parameters[0];
    const T y = parameters[1];
    cost[0] = (1.0 - x) * (1.0 - x) + 100.0 * (y - x * x) * (y - x * x);
    return true;
}
};

在此基础上,再使用AutoDiffFirstOrderFunction类来构建FirstOrderFunction类型的实例对象,用于按照上述仿函数对象来计算目标函数值以及梯度(如果有需要的话),代码如下:

ceres::FirstOrderFunction* function = new ceres::AutoDiffFirstOrderFunction<Rosenbrock, 2>(new Rosenbrock);

非线性最小二乘优化问题中,使用Problem类来构建问题,使用Solver::Options来配置求解器参数,使用Solver::Summary来输出日志,而与之相对应的,一般无约束优化问题中,使用的是GradientProblemGradientProblemSolver::OptionsGradientProblemSolver::Summary,代码如下:

ceres::GradientProblem problem(function);
ceres::GradientProblemSolver::Options options;
options.minimizer_progress_to_stdout = true;
ceres::GradientProblemSolver::Summary summary;
ceres::Solve(options, problem, parameters, &summary);

完整代码如下:

#include "ceres/ceres.h"
#include "ceres/autodiff_first_order_function.h"  // 从2.0.0开始支持
#include "glog/logging.h"

// 用户自定义"残差"计算模型
// f(x,y) = (1-x)^2 + 100*(y - x^2)^2;
struct Rosenbrock 
{
    template <typename T>
    bool operator()(const T* parameters, T* cost) const 
    {
        const T x = parameters[0];
        const T y = parameters[1];
        cost[0] = (1.0 - x) * (1.0 - x) + 100.0 * (y - x * x) * (y - x * x);
        return true;
    }
};

int main(int argc, char** argv) 
{
    google::InitGoogleLogging(argv[0]);
    double parameters[2] = { -1.2, 1.0 };
    ceres::FirstOrderFunction* function = new ceres::AutoDiffFirstOrderFunction<Rosenbrock, 2>(new Rosenbrock);
    ceres::GradientProblem problem(function);
    ceres::GradientProblemSolver::Options options;
    options.minimizer_progress_to_stdout = true;
    ceres::GradientProblemSolver::Summary summary;
    ceres::Solve(options, problem, parameters, &summary);
    std::cout << summary.FullReport() << std::endl;
    std::cout << "Initial x = " << -1.2 << ", y = " << 1.0 << std::endl;
    std::cout << "Final   x = " << parameters[0] << ", y = " << parameters[1] << std::endl;
    std::system("pause");
    return 0;
}

注:Ceres2.0.0开始才支持AutoDiffFirstOrderFunction
优化过程及结果如下:

   0: f: 2.420000e+01 d: 0.00e+00 g: 2.16e+02 h: 0.00e+00 s: 0.00e+00 e:  0 it: 2.60e-04 tt: 2.60e-04
   1: f: 4.280493e+00 d: 1.99e+01 g: 1.52e+01 h: 2.01e-01 s: 8.62e-04 e:  2 it: 1.02e-03 tt: 2.52e-03
   2: f: 3.571154e+00 d: 7.09e-01 g: 1.35e+01 h: 3.78e-01 s: 1.34e-01 e:  3 it: 7.86e-04 tt: 3.40e-03
   3: f: 3.440869e+00 d: 1.30e-01 g: 1.73e+01 h: 1.36e-01 s: 1.00e+00 e:  1 it: 1.86e-04 tt: 3.68e-03
   4: f: 3.213597e+00 d: 2.27e-01 g: 1.55e+01 h: 1.06e-01 s: 4.59e-01 e:  1 it: 1.74e-04 tt: 3.94e-03
   5: f: 2.839723e+00 d: 3.74e-01 g: 1.05e+01 h: 1.34e-01 s: 5.24e-01 e:  1 it: 1.86e-04 tt: 4.20e-03
   6: f: 2.448490e+00 d: 3.91e-01 g: 1.29e+01 h: 3.04e-01 s: 1.00e+00 e:  1 it: 2.33e-04 tt: 4.50e-03
   7: f: 1.943019e+00 d: 5.05e-01 g: 4.00e+00 h: 8.81e-02 s: 7.43e-01 e:  1 it: 2.11e-04 tt: 4.79e-03
   8: f: 1.731469e+00 d: 2.12e-01 g: 7.36e+00 h: 1.71e-01 s: 4.60e-01 e:  2 it: 3.98e-04 tt: 5.25e-03
   9: f: 1.503267e+00 d: 2.28e-01 g: 6.47e+00 h: 8.66e-02 s: 1.00e+00 e:  1 it: 2.02e-04 tt: 5.52e-03
  10: f: 1.228331e+00 d: 2.75e-01 g: 2.00e+00 h: 7.70e-02 s: 7.90e-01 e:  1 it: 2.17e-04 tt: 5.79e-03
  11: f: 1.016523e+00 d: 2.12e-01 g: 5.15e+00 h: 1.39e-01 s: 3.76e-01 e:  2 it: 3.91e-04 tt: 6.24e-03
  12: f: 9.145773e-01 d: 1.02e-01 g: 6.74e+00 h: 7.98e-02 s: 1.00e+00 e:  1 it: 2.14e-04 tt: 6.50e-03
  13: f: 7.508302e-01 d: 1.64e-01 g: 3.88e+00 h: 5.76e-02 s: 4.93e-01 e:  1 it: 2.30e-04 tt: 6.79e-03
  14: f: 5.832378e-01 d: 1.68e-01 g: 5.56e+00 h: 1.42e-01 s: 1.00e+00 e:  1 it: 2.20e-04 tt: 7.06e-03
  15: f: 3.969581e-01 d: 1.86e-01 g: 1.64e+00 h: 1.17e-01 s: 1.00e+00 e:  1 it: 2.27e-04 tt: 7.34e-03
  16: f: 3.171557e-01 d: 7.98e-02 g: 3.84e+00 h: 1.18e-01 s: 3.97e-01 e:  2 it: 4.17e-04 tt: 7.80e-03
  17: f: 2.641257e-01 d: 5.30e-02 g: 3.27e+00 h: 6.14e-02 s: 1.00e+00 e:  1 it: 2.32e-04 tt: 8.08e-03
  18: f: 1.909730e-01 d: 7.32e-02 g: 5.29e-01 h: 8.55e-02 s: 6.82e-01 e:  1 it: 2.38e-04 tt: 8.37e-03
  19: f: 1.472012e-01 d: 4.38e-02 g: 3.11e+00 h: 1.20e-01 s: 3.47e-01 e:  2 it: 4.22e-04 tt: 8.84e-03
  20: f: 1.093558e-01 d: 3.78e-02 g: 2.97e+00 h: 8.43e-02 s: 1.00e+00 e:  1 it: 2.45e-04 tt: 9.14e-03
  21: f: 6.710346e-02 d: 4.23e-02 g: 1.42e+00 h: 9.64e-02 s: 8.85e-01 e:  1 it: 2.51e-04 tt: 9.44e-03
  22: f: 3.993377e-02 d: 2.72e-02 g: 2.30e+00 h: 1.29e-01 s: 4.63e-01 e:  2 it: 4.21e-04 tt: 9.91e-03
  23: f: 2.911794e-02 d: 1.08e-02 g: 2.55e+00 h: 6.55e-02 s: 1.00e+00 e:  1 it: 2.53e-04 tt: 1.02e-02
  24: f: 1.457683e-02 d: 1.45e-02 g: 2.77e-01 h: 6.37e-02 s: 6.14e-01 e:  1 it: 2.51e-04 tt: 1.05e-02
  25: f: 8.577515e-03 d: 6.00e-03 g: 2.86e+00 h: 1.40e-01 s: 1.00e+00 e:  1 it: 2.51e-04 tt: 1.08e-02
  26: f: 3.486574e-03 d: 5.09e-03 g: 1.76e-01 h: 1.23e-02 s: 1.00e+00 e:  1 it: 2.53e-04 tt: 1.11e-02
  27: f: 1.257570e-03 d: 2.23e-03 g: 1.39e-01 h: 5.08e-02 s: 1.00e+00 e:  1 it: 2.51e-04 tt: 1.14e-02
  28: f: 2.783568e-04 d: 9.79e-04 g: 6.20e-01 h: 6.47e-02 s: 1.00e+00 e:  1 it: 2.51e-04 tt: 1.17e-02
  29: f: 2.533399e-05 d: 2.53e-04 g: 1.68e-02 h: 1.98e-03 s: 1.00e+00 e:  1 it: 2.51e-04 tt: 1.20e-02
  30: f: 7.591572e-07 d: 2.46e-05 g: 5.40e-03 h: 9.27e-03 s: 1.00e+00 e:  1 it: 2.58e-04 tt: 1.23e-02
  31: f: 1.902460e-09 d: 7.57e-07 g: 1.62e-03 h: 1.89e-03 s: 1.00e+00 e:  1 it: 2.54e-04 tt: 1.27e-02
  32: f: 1.003030e-12 d: 1.90e-09 g: 3.50e-05 h: 3.52e-05 s: 1.00e+00 e:  1 it: 2.54e-04 tt: 1.30e-02
  33: f: 4.835994e-17 d: 1.00e-12 g: 1.05e-07 h: 1.13e-06 s: 1.00e+00 e:  1 it: 3.56e-04 tt: 1.36e-02
  34: f: 1.885250e-22 d: 4.84e-17 g: 2.69e-10 h: 1.45e-08 s: 1.00e+00 e:  1 it: 2.65e-04 tt: 1.39e-02

Solver Summary (v 2.0.0-eigen-(3.3.8)-no_lapack-eigensparse-no_openmp)

Parameters                                  2
Line search direction              LBFGS (20)
Line search type                  CUBIC WOLFE


Cost:
Initial                          2.420000e+01
Final                            1.955192e-27
Change                           2.420000e+01

Minimizer iterations                       36

Time (in seconds):

  Cost evaluation                    0.000000 (0)
  Gradient & cost evaluation         0.001903 (44)
  Polynomial minimization            0.001521
Total                                0.014516

Termination:                      CONVERGENCE (Parameter tolerance reached. Relative step_norm: 1.890726e-11 <= 1.000000e-08.)

Initial x = -1.2, y = 1
Final   x = 1, y = 1

1.2 数值微分法

如果由于一些原因,不能使用自动微分计算导数,比如需要调用第三方库,那么可以使用数值微分法,完整代码如下:

#include "ceres/ceres.h"
#include "numeric_diff_first_order_function.h"  // 从2.1.0开始支持
#include "glog/logging.h"

// 用户自定义"残差"计算模型
// f(x,y) = (1-x)^2 + 100*(y - x^2)^2;
struct Rosenbrock
{
    bool operator()(const double* parameters, double* cost) const 
    {
        const double x = parameters[0];
        const double y = parameters[1];
        cost[0] = (1.0 - x) * (1.0 - x) + 100.0 * (y - x * x) * (y - x * x);
        return true;
    }
};

int main(int argc, char** argv)
{
    google::InitGoogleLogging(argv[0]);
    double parameters[2] = { -1.2, 1.0 };
    ceres::FirstOrderFunction* function = new ceres::NumericDiffFirstOrderFunction<Rosenbrock, ceres::CENTRAL, 2>(new Rosenbrock);
    ceres::GradientProblem problem(function);
    ceres::GradientProblemSolver::Options options;
    options.minimizer_progress_to_stdout = true;
    ceres::GradientProblemSolver::Summary summary;
    ceres::Solve(options, problem, parameters, &summary);
    std::cout << summary.FullReport() << std::endl;
    std::cout << "Initial x = " << -1.2 << ", y = " << 1.0 << std::endl;
    std::cout << "Final   x = " << parameters[0] << ", y = " << parameters[1] << std::endl;
    std::system("pause");
    return 0;
}

注:Ceres2.1.0开始才支持NumericDiffFirstOrderFunction

1.3 解析解法

也可以使用解析解法,手动给出导数计算公式,完整代码如下:

// 用户自定义"残差"计算模型
// f(x,y) = (1-x)^2 + 100*(y - x^2)^2;
class Rosenbrock final : public ceres::FirstOrderFunction 
{
public:
    ~Rosenbrock() override {}

    // parameters相当于是CostFunction中的参数块
    // gradient相当于是CostFunction中的雅克比矩阵
    // 由于只有一个参数块,parameters和gradient都是一维数组
    bool Evaluate(const double* parameters, double* cost, double* gradient) const override 
    {
        const double x = parameters[0];
        const double y = parameters[1];
        cost[0] = (1.0 - x) * (1.0 - x) + 100.0 * (y - x * x) * (y - x * x);
        // 与CostFunction中求雅克比矩阵过程类似
        if (gradient) {
            gradient[0] = -2.0 * (1.0 - x) - 200.0 * (y - x * x) * 2.0 * x;
            gradient[1] = 200.0 * (y - x * x);
        }
        return true;
    }

    int NumParameters() const override { return 2; }
};

int main(int argc, char** argv)
{
    google::InitGoogleLogging(argv[0]);
    double parameters[2] = { -1.2, 1.0 };
    ceres::FirstOrderFunction* function = new Rosenbrock;
    ceres::GradientProblem problem(function);
    ceres::GradientProblemSolver::Options options;
    options.minimizer_progress_to_stdout = true;
    ceres::GradientProblemSolver::Summary summary;
    ceres::Solve(options, problem, parameters, &summary);
    std::cout << summary.FullReport() << std::endl;
    std::cout << "Initial x = " << -1.2 << ", y = " << 1.0 << std::endl;
    std::cout << "Final   x = " << parameters[0] << ", y = " << parameters[1] << std::endl;
    std::system("pause");
    return 0;
}

注:Ceres2.0.0开始才支持FirstOrderFunction
FirstOrderFunctionCostFunction在参数块和雅克比矩阵上面也有差异,CostFunction支持输入多个参数块,因此输入参数类型是双指针(可以简单理解成二维数组),对应的,输出雅克比矩阵也是双指针类型,而FirstOrderFunction只需要一个参数块,因此输入参数类型是指针(可以简单理解成一维数组),对应的,输出雅克比矩阵也是指针类型,这些差异都体现在Evaluate()接口函数的参数列表。
另外,Ceres源码中对FirstOrderFunction类的声明如下:

class CERES_EXPORT FirstOrderFunction 
{
 public:
  virtual ~FirstOrderFunction() {}

  // cost is never null. gradient may be null. The return value
  // indicates whether the evaluation was successful or not.
  virtual bool Evaluate(const double* const parameters, double* cost, double* gradient) const = 0;
  virtual int NumParameters() const = 0;
};

由此可见,FirstOrderFunction是一个纯虚类,包含有Evaluate()NumParameters()这两个纯虚函数,因此继承于FirstOrderFunction的用户自定义“残差”计算模型类必须要重写这两个函数。
Evaluate()函数用于给出雅克比矩阵计算方法;
NumParameters()函数用于指定待优化参数数量。

2 遗留问题

笔者尝试使用CostFunction,按照之前求解Powell方程的思路(参见Ceres学习笔记002_使用Ceres求解Powell方程),来试图解决上述问题,最终无法优化出最终结果,原因未知。

你可能感兴趣的:(#,Ceres,学习)