• 【Ceres】Ceres学习笔记1


    1. 简介

    Ceres 可以解决以下形式的边界约束鲁棒化非线性最小二乘问题:
    min ⁡ x 1 2 ∑ i ρ i ( ∥ f i ( x i 1 , . . . , x i k ) ∥ 2 ) s.t. l j ≤ x j ≤ u j

    minx12iρi(fi(xi1,...,xik)2)s.t.ljxjuj" role="presentation">minx12iρi(fi(xi1,...,xik)2)s.t.ljxjuj
    xmins.t.21iρi(fi(xi1,...,xik)2)ljxjuj
    ​ 其中 ρ i ( ∥ f i ( x i 1 , . . . , x i k ) ∥ 2 ) \rho_i\left(\left\|f_i\left(x_{i_1}, ... ,x_{i_k}\right)\right\|^2\right) ρi(fi(xi1,...,xik)2) 被称为残差块(Residual Block), f i f_i fi (.)称为代价函数(Cost Function), ρ i \rho_i ρi 称为 Loss Function,十四讲中为核函数。Loss Function 是一个标量函数,用于减少异常值对非线性最小二乘问题求解的影响。

    ​ 作为一种特殊情况,当 ρ i ( x ) = x ρ_i(x)=x ρi(x)=x,即恒等函数,并且 l j = − ∞ l_j=−∞ lj= u j = ∞ u_j=∞ uj= 时,便得到更熟悉的非线性最小二乘问题:
    1 2 ∑ i ∥ f i ( x i 1 , . . . , x i k ) ∥ 2 \frac{1}{2}\sum_{i} \left\|f_i\left(x_{i_1}, ... ,x_{i_k}\right)\right\|^2 21ifi(xi1,...,xik)2

    2. Hello World

    ​ 首先,考虑寻找函数 f ( x ) = 1 2 ( 10 − x ) 2 f(x) = \frac{1}{2} (10 - x)^2 f(x)=21(10x)2 的最小值的问题。

    (1)写一个仿函数用来求函数 f ( x ) = 10 − x f(x) = 10 - x f(x)=10x 的值。

    struct CostFunctor {
       template <typename T>
       bool operator()(const T* const x, T* residual) const {
         residual[0] = 10.0 - x[0];
         return true;
       }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    ​ 需要注意的重要一点是 operator() 是一个模板化方法,它假定它的所有输入和输出都是T类型。这里使用模板使得Ceres 可以调用 CostFunctor::operator(),当残差需要时T可以为double类型。

    (2)一旦有了计算残差函数的方法,那么使用它构造一个非线性最小二乘问题 Ceres 解决便可以自动求解了。

    int main(int argc, char** argv) {
        google::InitGoogleLogging(argv[0]);
    
        // 要求解的变量及其初始值。
        double initial_x = 5.0;
        double x = initial_x;
    
        // 构建最小二乘问题
        ceres::Problem problem;
    
        // 设置唯一的代价函数(也称为残差)
        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;	//输出到 cout
        
        ceres::Solver::Summary summary;
        Solve(options, &problem, &summary);
    
        std::cout << summary.BriefReport() << "\n";
        std::cout << "x : " << initial_x << " -> " << x << "\n";
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    AutoDiffCostFunctionCostFunctor 作为输入,自动区分它并给它一个 CostFunction 接口。

    3.导数

    (1)数值导数

    struct NumericDiffCostFunctor {
      bool operator()(const double* const x, double* residual) const {
        residual[0] = 10.0 - x[0];
        return true;
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    数值微分:

    CostFunction* cost_function =
      new NumericDiffCostFunction<NumericDiffCostFunctor, ceres::CENTRAL, 1, 1>(
          new NumericDiffCostFunctor);
    problem.AddResidualBlock(cost_function, nullptr, &x);
    
    • 1
    • 2
    • 3
    • 4

    自动微分:

    CostFunction* cost_function =
        new AutoDiffCostFunction<CostFunctor, 1, 1>(new CostFunctor);
    problem.AddResidualBlock(cost_function, nullptr, &x);
    
    • 1
    • 2
    • 3

    ​ 一般来说,建议使用自动微分而不是数值微分。 使用 C++ 模板可以提高自动微分效率,而数值微分成本高,容易出现数值错误,并导致收敛速度较慢。

    (2)解析导数

    ​ 应该用不到吧。。。。

    class QuadraticCostFunction : public ceres::SizedCostFunction<1, 1> {
     public:
      virtual ~QuadraticCostFunction() {}
      virtual bool Evaluate(double const* const* parameters,
                            double* residuals,
                            double** jacobians) const {
        const double x = parameters[0][0];
        residuals[0] = 10 - x;
    
        // Compute the Jacobian if asked for.
        if (jacobians != nullptr && jacobians[0] != nullptr) {
          jacobians[0][0] = -1;
        }
        return true;
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    ​ 主要掌握 NumericDiffCostFunctionAutoDiffCostFunction,了解构建与计算代价函数的方法。此外,还有DynamicAutoDiffCostFunctionCostFunctionToFunctorNumericDiffFunctorConditionedCostFunction等方法,用到时查询帮助文档。

    4.鲍威尔函数

    ​ 现在考虑一个稍微复杂一点的例子:鲍威尔函数的最小化。

    ​ 令 x = [ x 1 , x 2 , x 3 , x 4 ] x = [x_1, x_2, x_3, x_4] x=[x1,x2,x3,x4], 且
    f 1 ( x ) = x 1 + 10 x 2 f 2 ( x ) = 5 ( x 3 − x 4 ) f 3 ( x ) = ( x 2 − 2 x 3 ) 2 f 4 ( x ) = 10 ( x 1 − x 4 ) 2 F ( x ) = [ f 1 ( x ) ,   f 2 ( x ) ,   f 3 ( x ) ,   f 4 ( x ) ]

    f1(x)=x1+10x2f2(x)=5(x3x4)f3(x)=(x22x3)2f4(x)=10(x1x4)2F(x)=[f1(x), f2(x), f3(x), f4(x)]" role="presentation">f1(x)=x1+10x2f2(x)=5(x3x4)f3(x)=(x22x3)2f4(x)=10(x1x4)2F(x)=[f1(x), f2(x), f3(x), f4(x)]
    f1(x)f2(x)f3(x)f4(x)F(x)=x1+10x2=5 (x3x4)=(x22x3)2=10 (x1x4)2=[f1(x), f2(x), f3(x), f4(x)]
    F ( x ) F(x) F(x) 是四个参数的函数,有四个残差,希望找到 x x x 使得 1 2 ∥ F ( x ) ∥ 2 \frac{1}{2}\|F(x)\|^2 21F(x)2 最小。

    (1)定义仿函数

    ​ 以下为评估 f 4 ( x 1 , x 4 ) f_4(x_1, x_4) f4(x1,x4) 的代码

    struct F4 {
      template <typename T>
      bool operator()(const T* const x1, const T* const x4, T* residual) const {
        residual[0] = sqrt(10.0) * (x1[0] - x4[0]) * (x1[0] - x4[0]);
        return true;
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    (2)

    int main(int argc, char** argv) {
        google::InitGoogleLogging(argv[0]);
    
        double x1 =  3.0; double x2 = -1.0; double x3 =  0.0; double x4 = 1.0;
    
        ceres::Problem problem;
    
    
        problem.AddResidualBlock(
            new ceres::AutoDiffCostFunction<F1, 1, 1, 1>(new F1), nullptr, &x1, &x2);
        problem.AddResidualBlock(
            new ceres::AutoDiffCostFunction<F2, 1, 1, 1>(new F2), nullptr, &x3, &x4);
        problem.AddResidualBlock(
            new ceres::AutoDiffCostFunction<F3, 1, 1, 1>(new F3), nullptr, &x2, &x3);
        problem.AddResidualBlock(
            new ceres::AutoDiffCostFunction<F4, 1, 1, 1>(new F4), nullptr, &x1, &x4);
    
        ceres::Solver::Options options;
        options.max_num_iterations = 100;
        options.linear_solver_type = ceres::DENSE_QR;
        options.minimizer_progress_to_stdout = true;
    
        std::cout << "Initial x1 = " << x1
                  << ", x2 = " << x2
                  << ", x3 = " << x3
                  << ", x4 = " << x4
                  << "\n";
    
        ceres::Solver::Summary summary;
        Solve(options, &problem, &summary);
        std::cout << summary.FullReport() << "\n";
        // clang-format off
        std::cout << "Final x1 = " << x1
                  << ", x2 = " << x2
                  << ", x3 = " << x3
                  << ", x4 = " << x4
                  << "\n";
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    5.曲线拟合

    ​ 到目前为止,我们看到的示例都是没有数据的简单优化问题。 最小二乘和非线性最小二乘分析的最初目的是拟合数据曲线。 现给出 y = e 0.3 x + 0.1 y = e^{0.3x + 0.1} y=e0.3x+0.1示例。它包含通过对曲线进行采样并添加标准偏差 σ=0.2 的高斯噪声生成的数据。
    y = e m x + c y = e^{mx + c} y=emx+c
    ​ 首先定义一个模板化对象来评估残差。每次观察都会有一个残差。

    struct ExponentialResidual {
      ExponentialResidual(double x, double y)
          : x_(x), y_(y) {}
    
      template <typename T>
      bool operator()(const T* const m, const T* const c, T* residual) const {
        residual[0] = y_ - exp(m[0] * x_ + c[0]);
        return true;
      }
    
     private:
      // Observations for a sample.
      const double x_;
      const double y_;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    ​ 假设观察在一个 2n 大小的数组中,称为数据,那么问题的构造就是为每个观察创建一个 CostFunction 的简单问题。

    double m = 0.0;
    double c = 0.0;
    
    Problem problem;
    for (int i = 0; i < kNumObservations; ++i) {
      CostFunction* cost_function =
           new AutoDiffCostFunction<ExponentialResidual, 1, 1, 1>(
               new ExponentialResidual(data[2 * i], data[2 * i + 1]));
      problem.AddResidualBlock(cost_function, nullptr, &m, &c);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    #include 
    #include "ceres/ceres.h"
    #include "glog/logging.h"
    
    const int kNumObservations = 67;    //数据点的个数
    // clang-format off
    const double data[] = {
        0.000000e+00, 1.133898e+00,
        7.500000e-02, 1.334902e+00,
        1.500000e-01, 1.213546e+00,
        2.250000e-01, 1.252016e+00,
        3.000000e-01, 1.392265e+00,
        3.750000e-01, 1.314458e+00,
        4.500000e-01, 1.472541e+00,
        5.250000e-01, 1.536218e+00,
        6.000000e-01, 1.355679e+00,
        6.750000e-01, 1.463566e+00,
        7.500000e-01, 1.490201e+00,
        8.250000e-01, 1.658699e+00,
        9.000000e-01, 1.067574e+00,
        9.750000e-01, 1.464629e+00,
        1.050000e+00, 1.402653e+00,
        1.125000e+00, 1.713141e+00,
        1.200000e+00, 1.527021e+00,
        1.275000e+00, 1.702632e+00,
        1.350000e+00, 1.423899e+00,
        1.425000e+00, 1.543078e+00,
        1.500000e+00, 1.664015e+00,
        1.575000e+00, 1.732484e+00,
        1.650000e+00, 1.543296e+00,
        1.725000e+00, 1.959523e+00,
        1.800000e+00, 1.685132e+00,
        1.875000e+00, 1.951791e+00,
        1.950000e+00, 2.095346e+00,
        2.025000e+00, 2.361460e+00,
        2.100000e+00, 2.169119e+00,
        2.175000e+00, 2.061745e+00,
        2.250000e+00, 2.178641e+00,
        2.325000e+00, 2.104346e+00,
        2.400000e+00, 2.584470e+00,
        2.475000e+00, 1.914158e+00,
        2.550000e+00, 2.368375e+00,
        2.625000e+00, 2.686125e+00,
        2.700000e+00, 2.712395e+00,
        2.775000e+00, 2.499511e+00,
        2.850000e+00, 2.558897e+00,
        2.925000e+00, 2.309154e+00,
        3.000000e+00, 2.869503e+00,
        3.075000e+00, 3.116645e+00,
        3.150000e+00, 3.094907e+00,
        3.225000e+00, 2.471759e+00,
        3.300000e+00, 3.017131e+00,
        3.375000e+00, 3.232381e+00,
        3.450000e+00, 2.944596e+00,
        3.525000e+00, 3.385343e+00,
        3.600000e+00, 3.199826e+00,
        3.675000e+00, 3.423039e+00,
        3.750000e+00, 3.621552e+00,
        3.825000e+00, 3.559255e+00,
        3.900000e+00, 3.530713e+00,
        3.975000e+00, 3.561766e+00,
        4.050000e+00, 3.544574e+00,
        4.125000e+00, 3.867945e+00,
        4.200000e+00, 4.049776e+00,
        4.275000e+00, 3.885601e+00,
        4.350000e+00, 4.110505e+00,
        4.425000e+00, 4.345320e+00,
        4.500000e+00, 4.161241e+00,
        4.575000e+00, 4.363407e+00,
        4.650000e+00, 4.161576e+00,
        4.725000e+00, 4.619728e+00,
        4.800000e+00, 4.737410e+00,
        4.875000e+00, 4.727863e+00,
        4.950000e+00, 4.669206e+00,
    };
    
    struct ExponentialResidual {
        ExponentialResidual(double x, double y) : x_(x), y_(y) {}
        template <typename T>
        bool operator()(const T* const m, const T* const c, T* residual) const {
            residual[0] = y_ - exp(m[0] * x_ + c[0]);
            return true;
        }
        private:
        const double x_;
        const double y_;
    };
    int main(int argc, char** argv) {
        google::InitGoogleLogging(argv[0]);
        double m = 0.0;
        double c = 0.0;
        ceres::Problem problem;
        for (int i = 0; i < kNumObservations; ++i) {
            problem.AddResidualBlock(
                new ceres::AutoDiffCostFunction<ExponentialResidual, 1, 1, 1>(
                    new ExponentialResidual(data[2 * i], data[2 * i + 1])),
                nullptr,
                &m,
                &c);
        }
        ceres::Solver::Options options;
        options.max_num_iterations = 25;
        options.linear_solver_type = ceres::DENSE_QR;
        options.minimizer_progress_to_stdout = true;
        
        ceres::Solver::Summary summary;
        Solve(options, &problem, &summary);
        std::cout << summary.BriefReport() << "\n";
        std::cout << "Initial m: " << 0.0 << " c: " << 0.0 << "\n";
        std::cout << "Final   m: " << m << " c: " << c << "\n";
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112

    ​ 与Hello World不同的是,曲线拟合是给出数据点求解常数,与其正好相反。

    6.具有鲁棒性的曲线拟合

    ​ 现在假设得到的数据有一些异常值,即有一些不服从噪声模型的点。 如果使用上面的代码来拟合这些数据, 拟合曲线会偏离事实曲线。

    ​ 为了处理异常值,一种标准技术是使用 LossFunction。 损失函数减少了具有高残差的残差块的影响,通常是对应于异常值的块。 为了将损失函数与残差块相关联,更改 problem.AddResidualBlock(cost_function, nullptr , &m, &c);problem.AddResidualBlock(cost_function, new CauchyLoss(0.5) , &m, &c);

    CauchyLoss 是 Ceres Solver 附带的损失函数之一。参数 0.5 指定损失函数的规模。

  • 相关阅读:
    StreamBuilder 用法示例
    用scikit-learn学习谱聚类
    Tomcat调优【精简版】
    LeetCode 10. 正则表达式匹配
    cad文件如何转换成pdf?=
    Verilog task使用说明
    Flutter自定义可拖动组件
    Python爬虫入门
    sql 注入, 报错型注入
    MyBatis数据脱敏
  • 原文地址:https://blog.csdn.net/ASUNAchan/article/details/127408299