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