vSLAMNet(四)-最优化-高斯牛顿法

1. 概述

  • 高斯牛顿是基于牛顿法发展来的
  • 回想牛顿法求解非线性优化问题需要求解海塞矩阵,有时该矩阵是很难求解的或者当维度较大时占用较大内存也会导致求解失败
  • 高丝牛顿法舍弃了海塞矩阵的求解,采用如下公式代替

$H_{jk} = 2\sum_{i=1}^m J_{ij}J_{ik}$.

2. 算法原理

对于一个非线性最小二乘问题:

$x = \arg \min_x \frac{1}{2}||f(x)||^2$.

高斯牛顿的思想是把$f(x)$利用泰勒展开,取一阶线性项近似,即

$f(x + \Delta x) = f(x) + f’(x)\Delta x = f(x) + J(x)\Delta x$.

再将上述公式代入非线性最小二乘公式,得:

$\frac{1}{2}||f(x + \Delta x)||^2 = \frac{1}{2}[f(x)^Tf(x) + 2f(x)^TJ(x)\Delta x + \Delta x^TJ(x)^TJ(x)\Delta x]$.

上式对$\Delta x$求导,令导数为0,得:

$J(x)^TJ(x)\Delta x = -J(x)^Tf(x)$.

另$H = J^TJ$,$B = -J^Tf$,那么,得到:

$H\Delta x = B$.

通过求解上述公式即可解出增量$\Delta x$,要求H正定,但实际情况并不一定满足这个条件,因此可能发散,另外步长$\Delta x$可能太大,也会导致发散。

综上所述,高斯牛顿法的计算步骤如下:

  • 给定初值$x_0$
  • 对于第k次迭代,计算雅克比矩阵J,矩阵H和矩阵B
  • 求解增量$\Delta x$
  • 如果$\Delta x$足够小,停止迭代;否则,更新$x_{k+1} = x_k + \Delta x$
  • 如果迭代次数达到最大次数,也停止迭代

3. 实例

求解非线性方程$y = e^{ax^2 + bx + c}$,给定n组观测数据$(x, y)$,求解系数$X = [a, b, c]^T$。

解:由题可得:

令$f(X) = y - e^{ax^2 + bx + c}$,N组数据可以组成一个大的非线性方程组,通过构建最小二乘问题来进行系数求解,即

$x = \arg\min_x \frac{1}{2}||f(X)||^2$.

要求解这个问题,根据推导部分可知,需要求解雅克比,

实现代码如下:

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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#include <iostream>
#include <eigen3/Eigen/Core>
#include <vector>
#include <opencv2/opencv.hpp>
#include <eigen3/Eigen/Cholesky>
#include <eigen3/Eigen/QR>
#include <eigen3/Eigen/SVD>
#include <chrono>

/* 计时类 */
class Runtimer{
public:
inline void start()
{
t_s_ = std::chrono::steady_clock::now();
}

inline void stop()
{
t_e_ = std::chrono::steady_clock::now();
}

inline double duration()
{
return std::chrono::duration_cast<std::chrono::duration<double>>(t_e_ - t_s_).count() * 1000.0;
}

private:
std::chrono::steady_clock::time_point t_s_; //start time ponit
std::chrono::steady_clock::time_point t_e_; //stop time point
};

/* 优化方程 */
class CostFunction{
public:
CostFunction(double* a, double* b, double* c, int max_iter, double min_step, bool is_out):
a_(a), b_(b), c_(c), max_iter_(max_iter), min_step_(min_step), is_out_(is_out)
{}

void addObservation(double x, double y)
{
std::vector<double> ob;
ob.push_back(x);
ob.push_back(y);
obs_.push_back(ob);
}

void calcJ_fx()
{
J_ .resize(obs_.size(), 3);
fx_.resize(obs_.size(), 1);

for ( size_t i = 0; i < obs_.size(); i ++)
{
std::vector<double>& ob = obs_.at(i);
double& x = ob.at(0);
double& y = ob.at(1);
double j1 = -x*x*exp(*a_ * x*x + *b_*x + *c_);
double j2 = -x*exp(*a_ * x*x + *b_*x + *c_);
double j3 = -exp(*a_ * x*x + *b_*x + *c_);
J_(i, 0 ) = j1;
J_(i, 1) = j2;
J_(i, 2) = j3;
fx_(i, 0) = y - exp( *a_ *x*x + *b_*x +*c_);
}
}

void calcH_b()
{
H_ = J_.transpose() * J_;
B_ = -J_.transpose() * fx_;
}

void calcDeltax()
{
deltax_ = H_.ldlt().solve(B_);
}

void updateX()
{
*a_ += deltax_(0);
*b_ += deltax_(1);
*c_ += deltax_(2);
}

double getCost()
{
Eigen::MatrixXd cost= fx_.transpose() * fx_;
return cost(0,0);
}

void solveByGaussNewton()
{
double sumt =0;
bool is_conv = false;
for( size_t i = 0; i < max_iter_; i ++)
{
Runtimer t;
t.start();
calcJ_fx();
calcH_b();
calcDeltax();
double delta = deltax_.transpose() * deltax_;
t.stop();
if( is_out_ )
{
std::cout << "Iter: " << std::left <<std::setw(3) << i << " Result: "<< std::left <<std::setw(10) << *a_ << " " << std::left <<std::setw(10) << *b_ << " " << std::left <<std::setw(10) << *c_ <<
" step: " << std::left <<std::setw(14) << delta << " cost: "<< std::left <<std::setw(14) << getCost() << " time: " << std::left <<std::setw(14) << t.duration() <<
" total_time: "<< std::left <<std::setw(14) << (sumt += t.duration()) << std::endl;
}
if( delta < min_step_)
{
is_conv = true;
break;
}
updateX();
}

if( is_conv == true)
std::cout << "\nConverged\n";
else
std::cout << "\nDiverged\n\n";
}

Eigen::MatrixXd fx_;
Eigen::MatrixXd J_; // 雅克比矩阵
Eigen::Matrix3d H_; // H矩阵
Eigen::Vector3d B_;
Eigen::Vector3d deltax_;
std::vector< std::vector<double> > obs_; // 观测
double* a_, *b_, *c_;

int max_iter_;
double min_step_;
bool is_out_;
};//class CostFunction




int main(int argc, char **argv) {

const double aa = 0.1, bb = 0.5, cc = 2; // 实际方程的参数
double a =0.0, b=0.0, c=0.0; // 初值

/* 构造问题 */
CostFunction cost_func(&a, &b, &c, 50, 1e-10, true);

/* 制造数据 */
const size_t N = 100; //数据个数
cv::RNG rng(cv::getTickCount());
for( size_t i = 0; i < N; i ++)
{
/* 生产带有高斯噪声的数据 */
double x = rng.uniform(0.0, 1.0) ;
double y = exp(aa*x*x + bb*x + cc) + rng.gaussian(0.05);

/* 添加到观测中 */
cost_func.addObservation(x, y);
}
/* 用高斯牛顿法求解 */
cost_func.solveByGaussNewton();
return 0;
}

(以上代码参考:https://zhuanlan.zhihu.com/p/42383070)

4. 参考