Graphite  0.5.0
GPU-accelerated graph optimization framework
Loading...
Searching...
No Matches
levenberg_marquardt.hpp
Go to the documentation of this file.
1
2#pragma once
3#include <graphite/graph.hpp>
5#include <iomanip>
6
11namespace graphite {
12
17namespace optimizer {
18
19template <typename T, typename S>
20T compute_rho(Graph<T, S> *graph, thrust::device_vector<T> &delta_x,
21 const T chi2, const T new_chi2, const T mu,
22 const bool step_is_good) {
23 // Compute rho
24 // TODO: Don't store these in the graph
25 auto &b = graph->get_b();
26 T num = (chi2 - new_chi2);
27 T denom = 1.0;
28 if (step_is_good) {
29
30 const auto n = delta_x.size();
31 const auto bb = b.data().get();
32 const auto dx = delta_x.data().get();
33
34 denom = thrust::transform_reduce(
35 thrust::device, thrust::make_counting_iterator<std::size_t>(0),
36 thrust::make_counting_iterator<std::size_t>(n),
37 [dx, bb, mu] __host__ __device__(const std::size_t i) {
38 T x = dx[i];
39 return x * (mu * x + bb[i]);
40 },
41 T{0}, thrust::plus<T>{});
42
43 denom += 1.0e-3;
44 }
45
46 return num / (denom);
47}
48
52template <typename T, typename S> class LevenbergMarquardtOptions {
53public:
55 : solver(nullptr), iterations(10), initial_damping(1e-4),
56 optimization_level(0), verbose(false), stop_flag(nullptr),
57 use_identity(false), streams(nullptr) {}
58
60 Solver<T, S> *solver;
62 size_t iterations;
68 bool verbose;
70 bool *stop_flag;
75
80 bool validate() const {
81 if (solver == nullptr) {
82 if (verbose) {
83 std::cerr << "Levenberg-Marquardt options invalid: solver is null"
84 << std::endl;
85 }
86 return false;
87 }
88 if (streams == nullptr) {
89 if (verbose) {
90 std::cerr << "Levenberg-Marquardt options invalid: streams is null"
91 << std::endl;
92 }
93 return false;
94 }
95
96 return true;
97 }
98};
99
109template <typename T, typename S>
110bool levenberg_marquardt(Graph<T, S> *graph,
111 LevenbergMarquardtOptions<T, S> *options) {
112
113 // Initialize something for all iterations
114 auto start = std::chrono::steady_clock::now();
115
116 if (!options->validate()) {
117 if (options->verbose) {
118 std::cerr << "Levenberg-Marquardt options invalid" << std::endl;
119 }
120 return false;
121 }
122
123 T mu = static_cast<T>(options->initial_damping);
124 T nu = 2;
125
126 auto solver = options->solver;
127 auto streams = options->streams;
128
129 if (!graph->initialize_optimization(options->optimization_level)) {
130 return false;
131 }
132
133 if (!graph->build_structure()) {
134 return false;
135 }
136
137 solver->update_structure(graph, *streams);
138
139 graph->linearize(*streams);
140
141 // Initialize solver values
142 solver->update_values(graph, *streams);
143 T chi2 = graph->chi2();
144
145 thrust::device_vector<T> delta_x(graph->get_hessian_dimension());
146
147 bool run = true;
148
149 double time =
150 std::chrono::duration<double>(std::chrono::steady_clock::now() - start)
151 .count();
152 // Print iteration table headers
153 if (options->verbose) {
154 std::cout << std::setprecision(12) << std::setw(18) << "Iteration"
155 << std::setw(24) << "Initial Chi2" << std::setw(24)
156 << "Current Chi2" << std::setw(24) << "Lambda" << std::setw(24)
157 << "Time" << std::setw(24) << "Total Time" << std::endl;
158 std::cout
159 << "---------------------------------------------------------------"
160 "---------------------------------------------------------------"
161 "------------"
162 << std::endl;
163 }
164
165 const auto num_iterations = options->iterations;
166 for (size_t i = 0; i < num_iterations && run; i++) {
167
168 start = std::chrono::steady_clock::now();
169
170 solver->set_damping_factor(graph, static_cast<T>(mu), options->use_identity,
171 *streams);
172 bool solve_ok = solver->solve(graph, delta_x.data().get(), *streams);
173
174 graph->backup_parameters();
175 graph->apply_update(delta_x.data().get(), *streams);
176
177 // Try step
178 graph->compute_error();
179 T new_chi2 = graph->chi2();
180
181 if (!solve_ok) {
182 new_chi2 = std::numeric_limits<T>::max();
183 }
184
185 T rho = compute_rho(graph, delta_x, chi2, new_chi2, mu, solve_ok);
186
187 if (solve_ok && std::isfinite(new_chi2) && rho > 0) {
188 // update hyperparameters
189 double alpha = 1.0 - pow(2.0 * rho - 1.0, 3);
190 alpha = std::max(std::min(alpha, 2.0 / 3.0), 1.0 / 3.0);
191 mu *= static_cast<T>(alpha);
192 nu = 2;
193 // Relinearize since step is accepted
194 graph->linearize(*streams);
195 solver->update_values(graph, *streams);
196 // std::cout << "Good step" << std::endl;
197 // std::cout << "rho: " << rho << std::endl;
198 } else {
199 graph->revert_parameters();
200 graph->compute_error();
201 graph->chi2();
202 // update hyperparameters
203 mu *= nu;
204 nu *= 2;
205 // std::cout << "Bad step" << std::endl;
206 // std::cout << "rho: " << rho << std::endl;
207 // std::cout << "Previous chi2: " << chi2 << std::endl;
208 // std::cout << "Current chi2: " << new_chi2 << std::endl;
209 new_chi2 = chi2;
210 }
211
212 double iteration_time =
213 std::chrono::duration<double>(std::chrono::steady_clock::now() - start)
214 .count();
215 time += iteration_time;
216 if (options->verbose) {
217 std::cout << std::setprecision(12) << std::setw(18) << i << std::setw(24)
218 << chi2 << std::setw(24) << new_chi2 << std::setw(24) << mu
219 << std::setw(24) << iteration_time << std::setw(24) << time
220 << std::endl;
221 }
222 chi2 = new_chi2;
223
224 if (!std::isfinite(mu)) {
225 std::cout << "Damping factor is infinite, terminating optimization"
226 << std::endl;
227 run = false;
228 }
229
230 if (rho == 0) {
231 std::cout << "Rho is zero, terminating optimization" << std::endl;
232 break;
233 }
234
235 if (options->stop_flag && *(options->stop_flag)) {
236 std::cout << "Stopping optimization due to stop flag" << std::endl;
237 break;
238 }
239 }
240
241 return run;
242}
243
255template <typename T, typename S>
256bool levenberg_marquardt2(Graph<T, S> *graph,
257 LevenbergMarquardtOptions<T, S> *options) {
258
259 // Initialize something for all iterations
260 auto start = std::chrono::steady_clock::now();
261
262 if (!options->validate()) {
263 if (options->verbose) {
264 std::cerr << "Levenberg-Marquardt options invalid" << std::endl;
265 }
266 return false;
267 }
268
269 T mu = static_cast<T>(options->initial_damping);
270 T nu = 2;
271 int num_bad = 0;
272
273 auto solver = options->solver;
274 auto streams = options->streams;
275
276 if (!graph->initialize_optimization(options->optimization_level)) {
277 return false;
278 }
279
280 if (!graph->build_structure()) {
281 return false;
282 }
283 solver->update_structure(graph, *streams);
284
285 graph->linearize(*streams);
286 solver->update_values(graph, *streams);
287 T chi2 = graph->chi2();
288
289 thrust::device_vector<T> delta_x(graph->get_hessian_dimension());
290
291 bool run = true;
292
293 double time =
294 std::chrono::duration<double>(std::chrono::steady_clock::now() - start)
295 .count();
296 // Print iteration table headers
297 if (options->verbose) {
298 std::cout << std::setprecision(12) << std::setw(18) << "Iteration"
299 << std::setw(24) << "Initial Chi2" << std::setw(24)
300 << "Current Chi2" << std::setw(24) << "Lambda" << std::setw(24)
301 << "Time" << std::setw(24) << "Total Time" << std::endl;
302 std::cout
303 << "---------------------------------------------------------------"
304 "---------------------------------------------------------------"
305 "------------"
306 << std::endl;
307 }
308
309 const auto num_iterations = options->iterations;
310 for (size_t i = 0; i < num_iterations && run; i++) {
311
312 start = std::chrono::steady_clock::now();
313 T initial_chi2 = chi2;
314 T end_chi2 = initial_chi2;
315
316 solver->set_damping_factor(graph, static_cast<T>(mu), options->use_identity,
317 *streams);
318
319 bool solve_ok = solver->solve(graph, delta_x.data().get(), *streams);
320
321 graph->backup_parameters();
322
323 graph->apply_update(delta_x.data().get(), *streams);
324
325 // Try step
326 graph->compute_error();
327
328 T new_chi2 = graph->chi2();
329
330 if (!solve_ok) {
331 new_chi2 = std::numeric_limits<T>::max();
332 }
333
334 T rho = compute_rho(graph, delta_x, chi2, new_chi2, mu, solve_ok);
335
336 bool step_accepted = false;
337 if (solve_ok && std::isfinite(new_chi2) && rho > 0) {
338 // update hyperparameters
339 double alpha = 1.0 - pow(2.0 * rho - 1.0, 3);
340 alpha = std::max(std::min(alpha, 2.0 / 3.0), 1.0 / 3.0);
341 mu *= static_cast<T>(alpha);
342 nu = 2;
343 // Relinearize since step is accepted
344 graph->linearize(*streams);
345
346 solver->update_values(graph, *streams);
347
348 // H and b are valid
349 // residuals should be valid
350 // chi2 should be valid
351
352 end_chi2 = new_chi2;
353 step_accepted = true;
354 // std::cout << "Good step" << std::endl;
355 // std::cout << "rho: " << rho << std::endl;
356 } else {
357 graph->revert_parameters();
358
359 // At this point, what is valid?
360 // - Linear system (H and b) are still valid
361 // - chi2 and derivatives are invalid (so the implicit Hessian is invalid,
362 // i.e. PCG will be invalid)
363 // - However chi2 depends on the residuals, which are also invalid
364 // So we need to recompute the error, and the chi2
365
366 graph->compute_error();
367 graph->chi2();
368 // hyperparameters
369 mu *= nu;
370 nu *= 2;
371 // std::cout << "Bad step" << std::endl;
372 new_chi2 = initial_chi2; // chi2 computation may be non-deterministic
373 }
374
375 double iteration_time =
376 std::chrono::duration<double>(std::chrono::steady_clock::now() - start)
377 .count();
378 time += iteration_time;
379 if (options->verbose) {
380 std::cout << std::setprecision(4) << std::setw(10) << i << std::setw(16)
381 << chi2 << std::setw(16) << new_chi2 << std::setw(16) << mu
382 << std::setw(16) << iteration_time << std::setw(16) << time
383 << std::endl;
384 }
385 chi2 = new_chi2;
386
387 if (!std::isfinite(mu)) {
388 std::cout << "Damping factor is infinite, terminating optimization"
389 << std::endl;
390 run = false;
391 }
392
393 if (rho == 0) {
394 std::cout << "Rho is zero, terminating optimization" << std::endl;
395 break;
396 }
397
398 if (options->stop_flag && *(options->stop_flag)) {
399 std::cout << "Stopping optimization due to stop flag" << std::endl;
400 break;
401 }
402
403 if (step_accepted) {
404 if (((initial_chi2 - end_chi2) * 1.0e3) < initial_chi2) {
405 num_bad++;
406 } else {
407 num_bad = 0;
408 }
409
410 if (num_bad >= 3) {
411 break;
412 }
413 }
414 }
415
416 return run;
417}
418
419} // namespace optimizer
420} // namespace graphite
Definition stream.hpp:7
Levenberg-Marquardt options.
Definition levenberg_marquardt.hpp:52
bool * stop_flag
Pointer to a stop flag which can be used to terminate the optimization.
Definition levenberg_marquardt.hpp:70
double initial_damping
Initial damping factor (lambda) value.
Definition levenberg_marquardt.hpp:64
bool validate() const
Validate the options.
Definition levenberg_marquardt.hpp:80
StreamPool * streams
Pointer to a pool of streams.
Definition levenberg_marquardt.hpp:74
uint8_t optimization_level
Constraints with level <= optimization_level will be optimized.
Definition levenberg_marquardt.hpp:66
Solver< T, S > * solver
Pointer to the linear solver to use for the optimization.
Definition levenberg_marquardt.hpp:60
bool verbose
If true, print verbose output during optimization.
Definition levenberg_marquardt.hpp:68
size_t iterations
Maximum number of iterations to run the optimization for.
Definition levenberg_marquardt.hpp:62
bool use_identity
If true, use identity damping instead of diagonal-scaled damping.
Definition levenberg_marquardt.hpp:72
bool levenberg_marquardt(Graph< T, S > *graph, LevenbergMarquardtOptions< T, S > *options)
Levenberg-Marquardt optimization algorithm.
Definition levenberg_marquardt.hpp:110
bool levenberg_marquardt2(Graph< T, S > *graph, LevenbergMarquardtOptions< T, S > *options)
Levenberg-Marquardt with similar early termination stopping criteria to ORB-SLAM.
Definition levenberg_marquardt.hpp:256
The top-level namespace for Graphite.
Definition eigen_solver.cpp:4