Ceres Solver (自學)

介紹

Ceres可以用來解受邊界約束的非線性最小二乘問題,如
(1)minx12iρi(fi(xi1,...,xik)2)s.tljxjμj \min_x \frac{1}{2} \sum_i \rho_i(\Vert f_i(x_{i_1}, ...,x_{i_k})\Vert^2) \quad s.t \quad l_j \leq x_j \leq \mu_j \tag{1}
這一章我們將學習如何使用Ceres Solver解(1)問題。表達式ρi(fi(xi1,...,xik)2)\rho_i(\Vert f_i(x_{i_1}, ..., x_{i_k})\Vert^2)ResidualBock,其中fi(.)f_i(.)CostFunction,它依賴參數塊[xi1,...,xxk][x_{i_1},...,x_{x_k}]。在大多數優化問題中,一小組標量出現在一起(例如參數塊[xi1,...,xxk][x_{i_1},...,x_{x_k}])。例如,相機的位姿由3個標量組成的平移向量和4個標量組成的旋轉(用四元數表示旋轉)組成,其中涉及到額一小組標量叫做ParameterBlock,當然一個parameterBlock可以僅僅是一個參數,ljl_jμj\mu_j是參數塊xjx_j的邊界約束。

ρi\rho_i是一個LossFunction,一個LossFunction是一個標量函數,它用來減少非線性最小二乘中outliners的影響。
在特性的情況下,當ρi(x)=x\rho_i(x) =x,也就是identity function, 同時lj=l_j = - \inftyμj=\mu_j = \infty,我們得到更熟悉的非線性最小二乘問題。
(2)12ifi(xi1,..,xik2) \frac{1}{2}\sum_i \Vert f_i(x_{i_1}, .., x_{i_k}\Vert^2) \tag{2}

Hello Word

一開始,我們考慮下面的一個問題,找該函數的最小值
12(10x)2 \frac{1}{2} (10-x)^2
這是一個簡單額問題,它的最小值是當x=10x=10的時候取得最小值,但是這是使用Ceres解這個問題一個好的開始。

第一步寫一個仿函數(functor),它用來估計函數f(x)=10xf(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;
 }
};

注意這裏重要的是operator()是一個模板方法,它假設所有的輸入和輸出是T類型,模板的使用允許Ceres調用CostFunctor::operator<T>(),對於T=double時僅僅需要殘差(residual)值,對於T=Jet時需要雅克比。在導數那一節我們將要討論Ceres中提供的多種導數類型。

一旦我們有了計算參差函數(residual function)的方法,就可以使用它構造一個最小二乘問題,然後使用Ceres 求解。

int main(int argc, char** argv) {
  google::InitGoogleLogging(argv[0]);

  // 1. 設置變量初始值
  double initial_x = 5.0;
  double x = initial_x;

  // 2. 構建一個問題.
  Problem problem;

  // 3. 配置cost function並使用ceres提供的自動求導的方式求導
  CostFunction* cost_function =
      new AutoDiffCostFunction<CostFunctor, 1, 1>(new CostFunctor);
  problem.AddResidualBlock(cost_function, NULL, &x);

  // 4. 啓動求解器
  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;
}

AutoDiffCostFunctionCostFunctor作爲輸入,自動求導它並使用CostFunction作爲接口。
編譯和運行 examples/helloworld.cc將得到

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 : 5 -> 10

x=5x =5開始,求解器在兩次迭代後到達10。仔細的讀者注意到這是一個線性問題,線性求解應該能到到達最佳的值。求解器的默認配置意在非線性問題,爲了簡單性,在這個例子中沒有改變它。的確,使用Ceres可以在一次迭代後獲取它的解。另外我們還注意在一次迭代後求解器就非常接近最佳值0。當我們談到Ceres的收斂和參數配置時將深入探討這些細節。

導數

像大多數優化包一樣,Ceres Solver依賴於能夠在任意參數值下估計目標函數中每個項的值和導數。正確有效地做法對於獲得好結果至關重要。 Ceres Solver提供了許多方法。 你已經看到其中一個 - 自動求導的例子examples/helloworld.cc

我們將要考慮其他的兩種:分析和數值導數(Analytic and numeric derivatives)。

數值導數(Numeric Derivatives)

在一些情況下,不可能定義一個模板cost functor,例如,當殘差的估計涉及到調用一個你不能控制的庫函數時。在那種情況下可以使用數值求導。用戶定義一個仿函數來計算殘差值,然後構造一個NumericDiffCostFunction使用它。例如,對於f(x)=10xf(x) = 10 -x對應的仿函數是

struct NumericDiffCostFunctor {
  bool operator()(const double* const x, double* residual) const {
    residual[0] = 10.0 - x[0];
    return true;
  }
};

它被添加到Problemzhong

CostFunction* cost_function =
  new NumericDiffCostFunction<NumericDiffCostFunctor, ceres::CENTRAL, 1, 1>(
      new NumericDiffCostFunctor);
problem.AddResidualBlock(cost_function, NULL, &x);

注意到當我們使用自動求導時

CostFunction* cost_function =
    new AutoDiffCostFunction<CostFunctor, 1, 1>(new CostFunctor);
problem.AddResidualBlock(cost_function, NULL, &x);

對比可知道,除了額外的模板參數指示用於計算數值導數的有限求導方案的類型外,該構造看起來幾乎與用於自動微分的構造相同[3]。 有關更多詳細信息,請參閱NumericDiffCostFunction的文檔。

一般來說,我們推薦自動求導而不是數值求導。 C ++模板的使得自動求導更加有效,而數字求導很昂貴,容易出現數值錯誤,並導致收斂速度變慢。

分析導數(Analytic Derivatives)

在一些情況下,使用自動求導是不可行的。例如,在某些情況下計算導數的閉合解比靠自動求導的鏈式規則更加有效。

在那種情況,提供你自己的殘差和牙可以計算的代碼是可能的。這樣做需要定義CostFucntion的子類或者如果你在編譯時間知道參數和殘差的大小,可以定義SizedCostFunction的子類。這裏是一個實現f(x)=10xf(x) = 10- x的一個SimpleConstFunction的例子

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 != NULL && jacobians[0] != NULL) {
      jacobians[0][0] = -1;
    }
    return true;
  }
};

SimpleCostFunction :: Evaluate提供了parameters的輸入數組,殘差的輸出數組residuals和雅可比的輸出數組jacobiansjacobians數組是可選的,Evaluate應該檢查它何時爲非null,如果是非null,則用殘差函數的導數值填充它。 在這裏,由於殘差函數是線性的,雅可比矩陣是常數。

從上面的代碼片段可以看出,實現CostFunction對象有點單調乏味。 我們建議除非您有充分的理由自己管理雅可比計算,否則使用AutoDiffCostFunctionNumericDiffCostFunction來構造殘差塊。

關於更多導數(More About Derivatives)

計算導數是迄今爲止使用Ceres最複雜的部分,並且根據環境,用戶可能需要更復雜的計算導數的方法。本節僅涉及Ceres如何求導的表面。 一旦您熟悉使用NumericDiffCostFunctionAutoDiffCostFunction,我們建議您查看DynamicAutoDiffCostFunctionCostFunctionToFunctorNumericDiffFunctorConditionedCostFunction,以獲得構建和計算成本函數的更高級方法。

Powell’s Function

現在考慮一個稍微複雜的例子 - Powell函數的最小化。讓x=[x1,x2,x3,x4]x = [x_1, x_2, x_3, x_4]
f1(x)=x1+10x2f2(x)=5(x3x4)f3(x)=(x22x3)2f4(x)=10(x1x4)2F(x)=[fx(x),f2(x),f3(x),f4(x)] f_1(x) = x_1 + 10x_2 \\ f_2(x) = \sqrt 5 (x_3-x_4) \\ f_3(x) = (x_2-2x_3)^2 \\ f_4(x) = \sqrt 10 (x_1-x_4)^2 \\ F(x) = [f_x(x), f_2(x), f_3(x), f_4(x)]
F(x)F(x)是4個參數的函數,有4個殘差,我們想找到一個xx使得12F(x)2\frac{1}{2}\Vert F(x)\Vert^2最小。

再一次,第一步要定義目標函數每一項的估計的仿函數,這裏是估計f4(x1,x4)f_4(x_1, x_4)的代碼:

struct F4 {
  template <typename T>
  bool operator()(const T* const x1, const T* const x4, T* residual) const {
    residual[0] = T(sqrt(10.0)) * (x1[0] - x4[0]) * (x1[0] - x4[0]);
    return true;
  }
};

類似的,我們可以定義F1,F2,F3F_1, F_2,F_3分別估計f1(x1,x2),f2(x3,x4),f3(x2,x3)f_1(x_1, x_2), f_2(x_3, x_4), f_3(x_2, x_3)。使用這些仿函數,Problem構造如下:

double x1 =  3.0; double x2 = -1.0; double x3 =  0.0; double x4 = 1.0;

Problem problem;

// Add residual terms to the problem using the using the autodiff
// wrapper to get the derivatives automatically.
problem.AddResidualBlock(
  new AutoDiffCostFunction<F1, 1, 1, 1>(new F1), NULL, &x1, &x2);
problem.AddResidualBlock(
  new AutoDiffCostFunction<F2, 1, 1, 1>(new F2), NULL, &x3, &x4);
problem.AddResidualBlock(
  new AutoDiffCostFunction<F3, 1, 1, 1>(new F3), NULL, &x2, &x3)
problem.AddResidualBlock(
  new AutoDiffCostFunction<F4, 1, 1, 1>(new F4), NULL, &x1, &x4);

請注意,每個ResidualBlock僅取決於相應殘差對象所依賴的兩個參數,而不取決於所有四個參數。編譯和運行**examples/powell.cc **將得到

Initial x1 = 3, x2 = -1, x3 = 0, x4 = 1
iter      cost      cost_change  |gradient|   |step|    tr_ratio  tr_radius  ls_iter  iter_time  total_time
   0  1.075000e+02    0.00e+00    1.55e+02   0.00e+00   0.00e+00  1.00e+04       0    4.95e-04    2.30e-03
   1  5.036190e+00    1.02e+02    2.00e+01   2.16e+00   9.53e-01  3.00e+04       1    4.39e-05    2.40e-03
   2  3.148168e-01    4.72e+00    2.50e+00   6.23e-01   9.37e-01  9.00e+04       1    9.06e-06    2.43e-03
   3  1.967760e-02    2.95e-01    3.13e-01   3.08e-01   9.37e-01  2.70e+05       1    8.11e-06    2.45e-03
   4  1.229900e-03    1.84e-02    3.91e-02   1.54e-01   9.37e-01  8.10e+05       1    6.91e-06    2.48e-03
   5  7.687123e-05    1.15e-03    4.89e-03   7.69e-02   9.37e-01  2.43e+06       1    7.87e-06    2.50e-03
   6  4.804625e-06    7.21e-05    6.11e-04   3.85e-02   9.37e-01  7.29e+06       1    5.96e-06    2.52e-03
   7  3.003028e-07    4.50e-06    7.64e-05   1.92e-02   9.37e-01  2.19e+07       1    5.96e-06    2.55e-03
   8  1.877006e-08    2.82e-07    9.54e-06   9.62e-03   9.37e-01  6.56e+07       1    5.96e-06    2.57e-03
   9  1.173223e-09    1.76e-08    1.19e-06   4.81e-03   9.37e-01  1.97e+08       1    7.87e-06    2.60e-03
  10  7.333425e-11    1.10e-09    1.49e-07   2.40e-03   9.37e-01  5.90e+08       1    6.20e-06    2.63e-03
  11  4.584044e-12    6.88e-11    1.86e-08   1.20e-03   9.37e-01  1.77e+09       1    6.91e-06    2.65e-03
  12  2.865573e-13    4.30e-12    2.33e-09   6.02e-04   9.37e-01  5.31e+09       1    5.96e-06    2.67e-03
  13  1.791438e-14    2.69e-13    2.91e-10   3.01e-04   9.37e-01  1.59e+10       1    7.15e-06    2.69e-03

Ceres Solver v1.12.0 Solve Report
----------------------------------
                                     Original                  Reduced
Parameter blocks                            4                        4
Parameters                                  4                        4
Residual blocks                             4                        4
Residual                                    4                        4

Minimizer                        TRUST_REGION

Dense linear algebra library            EIGEN
Trust region strategy     LEVENBERG_MARQUARDT

                                        Given                     Used
Linear solver                        DENSE_QR                 DENSE_QR
Threads                                     1                        1
Linear solver threads                       1                        1

Cost:
Initial                          1.075000e+02
Final                            1.791438e-14
Change                           1.075000e+02

Minimizer iterations                       14
Successful steps                           14
Unsuccessful steps                          0

Time (in seconds):
Preprocessor                            0.002

  Residual evaluation                   0.000
  Jacobian evaluation                   0.000
  Linear solver                         0.000
Minimizer                               0.001

Postprocessor                           0.000
Total                                   0.005

Termination:                      CONVERGENCE (Gradient tolerance reached. Gradient max norm: 3.642190e-11 <= 1.000000e-10)

Final x1 = 0.000292189, x2 = -2.92189e-05, x3 = 4.79511e-05, x4 = 4.79511e-05

很容易看出這個問題的最優解是在x1=0,x2=0,x3=0,x4=0x1 = 0,x2 = 0,x3 = 0, x4 = 0時目標函數值爲0。在10次迭代中,Ceres找到一個具有目標函數值的解 4×10124 \times 10^{-12}

曲線擬合

直到現在我們看到的例子都是簡單的沒有數據的優化問題。最小二乘與非線性最小二乘分析的最初目的是對數據擬合。現在讓我們考慮這樣的一個例子,它是樣本曲線y=e0.3x+0.1y=e^{0.3x+0.1}加上標準差爲σ=0.2\sigma=0.2高斯噪聲生成的數據。讓我們擬合該數據曲線
y=emx+c y = e^{mx+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] = T(y_) - exp(m[0] * T(x_) + c[0]);
    return true;
  }

 private:
  // Observations for a sample.
  const double x_;
  const double y_;
};

假設觀察數據的大小是2n數組,叫做data,對於每一次觀察,創建一個CostFunction增加到Problem

//設置m,c的初值
double m = 0.0;
double c = 0.0;
//kNumObservations = 67
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, NULL, &m, &c);
}

計算和編譯**examples/curve_fitting.cc **

iter      cost      cost_change  |gradient|   |step|    tr_ratio  tr_radius  ls_iter  iter_time  total_time
   0  1.211734e+02    0.00e+00    3.61e+02   0.00e+00   0.00e+00  1.00e+04        0    4.26e-05    8.59e-05
   1  1.211734e+02   -2.21e+03    0.00e+00   7.52e-01  -1.87e+01  5.00e+03        1    3.89e-05    1.74e-04
   2  1.211734e+02   -2.21e+03    0.00e+00   7.51e-01  -1.86e+01  1.25e+03        1    1.36e-05    1.97e-04
   3  1.211734e+02   -2.19e+03    0.00e+00   7.48e-01  -1.85e+01  1.56e+02        1    1.18e-05    2.15e-04
   4  1.211734e+02   -2.02e+03    0.00e+00   7.22e-01  -1.70e+01  9.77e+00        1    1.11e-05    2.31e-04
   5  1.211734e+02   -7.34e+02    0.00e+00   5.78e-01  -6.32e+00  3.05e-01        1    1.14e-05    2.48e-04
   6  3.306595e+01    8.81e+01    4.10e+02   3.18e-01   1.37e+00  9.16e-01        1    2.95e-05    2.83e-04
   7  6.426770e+00    2.66e+01    1.81e+02   1.29e-01   1.10e+00  2.75e+00        1    2.48e-05    3.14e-04
   8  3.344546e+00    3.08e+00    5.51e+01   3.05e-02   1.03e+00  8.24e+00        1    2.45e-05    3.44e-04
   9  1.987485e+00    1.36e+00    2.33e+01   8.87e-02   9.94e-01  2.47e+01        1    2.69e-05    3.76e-04
  10  1.211585e+00    7.76e-01    8.22e+00   1.05e-01   9.89e-01  7.42e+01        1    2.46e-05    4.06e-04
  11  1.063265e+00    1.48e-01    1.44e+00   6.06e-02   9.97e-01  2.22e+02        1    2.40e-05    4.34e-04
  12  1.056795e+00    6.47e-03    1.18e-01   1.47e-02   1.00e+00  6.67e+02        1    2.41e-05    4.64e-04
  13  1.056751e+00    4.39e-05    3.79e-03   1.28e-03   1.00e+00  2.00e+03        1    2.43e-05    4.93e-04

Solver Summary (v 1.11.0-eigen-(3.2.10)-lapack-suitesparse-(4.4.6)-openmp)

                                    Original                  Reduced
Parameter blocks                            2                        2
Parameters                                  2                        2
Residual blocks                            67                       67
Residual                                   67                       67
Minimizer                        TRUST_REGION

Dense linear algebra library            EIGEN
Trust region strategy     LEVENBERG_MARQUARDT

                                        Given                     Used
Linear solver                        DENSE_QR                 DENSE_QR
Threads                                     1                        1
Linear solver threads                       1                        1

Cost:
Initial                          1.211734e+02
Final                            1.056751e+00
Change                           1.201167e+02

Minimizer iterations                       13
Successful steps                            8
Unsuccessful steps                          5

Time (in seconds):
Preprocessor                           0.0000

  Residual evaluation                  0.0001
  Jacobian evaluation                  0.0001
  Linear solver                        0.0000
Minimizer                              0.0005

Postprocessor                          0.0000
Total                                  0.0005

Termination:                      CONVERGENCE (Function tolerance reached. |cost_change|/cost: 3.541695e-08 <= 1.000000e-06)

Initial m: 0 c: 0
Final   m: 0.291861 c: 0.131439

m=0,c=0m=0, c=0作爲開始的初始目標函數值爲1.211734e+02,Ceres找到一個解m=0.291861,c=0.131439m=0.291861, c=0.131439使得目標函數的值爲1.05675,這些值與原始的參數值m=0.3,c=0.1m=0.3,c=0.1有些不同,但是是希望的,因爲我們構造的曲線來自帶有噪聲的數據,我們希望有些偏差。實際上,如果要估計m = 0.3,c = 0.1的目標函數,則擬合值會更差,目標函數值爲1.082425。 下圖說明了適合度。
擬合曲線

魯棒性曲線擬合

現在假設我們給定的數據有一些outliers,也就是有一些點不符合噪聲模型。我們仍然使用上面的代碼擬合這樣的數據,得到的擬合曲線如下圖,注意看擬合的曲線如何偏離真實的曲線
具有外點的曲線擬合

爲了解決outliers問題,一個標準的技術是使用LossFunction。損失函數能夠減少帶有很高殘差數據的影響,這些數據通常是outliers,應該減少其影響。爲了在殘差塊中使用損失函數,我們改變

problem.AddResidualBlock(cost_function, NULL , &m, &c);

problem.AddResidualBlock(cost_function, new CauchyLoss(0.5) , &m, &c);

CauchyLoss是Ceres Solver附帶的損失函數之一。 參數0.5指定損失函數的比例。 結果,我們得到了下面的擬合。 注意擬合曲線如何向後靠近實際曲線移動。
魯棒性曲線擬合

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章