line_search_minimizer.cc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. // Ceres Solver - A fast non-linear least squares minimizer
  2. // Copyright 2023 Google Inc. All rights reserved.
  3. // http://ceres-solver.org/
  4. //
  5. // Redistribution and use in source and binary forms, with or without
  6. // modification, are permitted provided that the following conditions are met:
  7. //
  8. // * Redistributions of source code must retain the above copyright notice,
  9. // this list of conditions and the following disclaimer.
  10. // * Redistributions in binary form must reproduce the above copyright notice,
  11. // this list of conditions and the following disclaimer in the documentation
  12. // and/or other materials provided with the distribution.
  13. // * Neither the name of Google Inc. nor the names of its contributors may be
  14. // used to endorse or promote products derived from this software without
  15. // specific prior written permission.
  16. //
  17. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  18. // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  19. // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  20. // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  21. // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  22. // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  23. // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  24. // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  25. // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  26. // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  27. // POSSIBILITY OF SUCH DAMAGE.
  28. //
  29. // Author: sameeragarwal@google.com (Sameer Agarwal)
  30. //
  31. // Generic loop for line search based optimization algorithms.
  32. //
  33. // This is primarily inspired by the minFunc packaged written by Mark
  34. // Schmidt.
  35. //
  36. // http://www.di.ens.fr/~mschmidt/Software/minFunc.html
  37. //
  38. // For details on the theory and implementation see "Numerical
  39. // Optimization" by Nocedal & Wright.
  40. #include "ceres/line_search_minimizer.h"
  41. #include <algorithm>
  42. #include <cmath>
  43. #include <cstdlib>
  44. #include <memory>
  45. #include <string>
  46. #include <vector>
  47. #include "Eigen/Dense"
  48. #include "ceres/array_utils.h"
  49. #include "ceres/evaluator.h"
  50. #include "ceres/internal/eigen.h"
  51. #include "ceres/internal/export.h"
  52. #include "ceres/line_search.h"
  53. #include "ceres/line_search_direction.h"
  54. #include "ceres/stringprintf.h"
  55. #include "ceres/types.h"
  56. #include "ceres/wall_time.h"
  57. #include "glog/logging.h"
  58. namespace ceres::internal {
  59. namespace {
  60. bool EvaluateGradientNorms(Evaluator* evaluator,
  61. const Vector& x,
  62. LineSearchMinimizer::State* state,
  63. std::string* message) {
  64. Vector negative_gradient = -state->gradient;
  65. Vector projected_gradient_step(x.size());
  66. if (!evaluator->Plus(
  67. x.data(), negative_gradient.data(), projected_gradient_step.data())) {
  68. *message = "projected_gradient_step = Plus(x, -gradient) failed.";
  69. return false;
  70. }
  71. state->gradient_squared_norm = (x - projected_gradient_step).squaredNorm();
  72. state->gradient_max_norm =
  73. (x - projected_gradient_step).lpNorm<Eigen::Infinity>();
  74. return true;
  75. }
  76. } // namespace
  77. void LineSearchMinimizer::Minimize(const Minimizer::Options& options,
  78. double* parameters,
  79. Solver::Summary* summary) {
  80. const bool is_not_silent = !options.is_silent;
  81. double start_time = WallTimeInSeconds();
  82. double iteration_start_time = start_time;
  83. CHECK(options.evaluator != nullptr);
  84. Evaluator* evaluator = options.evaluator.get();
  85. const int num_parameters = evaluator->NumParameters();
  86. const int num_effective_parameters = evaluator->NumEffectiveParameters();
  87. summary->termination_type = NO_CONVERGENCE;
  88. summary->num_successful_steps = 0;
  89. summary->num_unsuccessful_steps = 0;
  90. VectorRef x(parameters, num_parameters);
  91. State current_state(num_parameters, num_effective_parameters);
  92. State previous_state(num_parameters, num_effective_parameters);
  93. IterationSummary iteration_summary;
  94. iteration_summary.iteration = 0;
  95. iteration_summary.step_is_valid = false;
  96. iteration_summary.step_is_successful = false;
  97. iteration_summary.cost_change = 0.0;
  98. iteration_summary.gradient_max_norm = 0.0;
  99. iteration_summary.gradient_norm = 0.0;
  100. iteration_summary.step_norm = 0.0;
  101. iteration_summary.linear_solver_iterations = 0;
  102. iteration_summary.step_solver_time_in_seconds = 0;
  103. // Do initial cost and gradient evaluation.
  104. if (!evaluator->Evaluate(x.data(),
  105. &(current_state.cost),
  106. nullptr,
  107. current_state.gradient.data(),
  108. nullptr)) {
  109. summary->termination_type = FAILURE;
  110. summary->message = "Initial cost and jacobian evaluation failed.";
  111. if (is_not_silent) {
  112. LOG(WARNING) << "Terminating: " << summary->message;
  113. }
  114. return;
  115. }
  116. if (!EvaluateGradientNorms(evaluator, x, &current_state, &summary->message)) {
  117. summary->termination_type = FAILURE;
  118. summary->message =
  119. "Initial cost and jacobian evaluation failed. More details: " +
  120. summary->message;
  121. if (is_not_silent) {
  122. LOG(WARNING) << "Terminating: " << summary->message;
  123. }
  124. return;
  125. }
  126. summary->initial_cost = current_state.cost + summary->fixed_cost;
  127. iteration_summary.cost = current_state.cost + summary->fixed_cost;
  128. iteration_summary.gradient_norm = sqrt(current_state.gradient_squared_norm);
  129. iteration_summary.gradient_max_norm = current_state.gradient_max_norm;
  130. if (iteration_summary.gradient_max_norm <= options.gradient_tolerance) {
  131. summary->message =
  132. StringPrintf("Gradient tolerance reached. Gradient max norm: %e <= %e",
  133. iteration_summary.gradient_max_norm,
  134. options.gradient_tolerance);
  135. summary->termination_type = CONVERGENCE;
  136. if (is_not_silent) {
  137. VLOG(1) << "Terminating: " << summary->message;
  138. }
  139. return;
  140. }
  141. iteration_summary.iteration_time_in_seconds =
  142. WallTimeInSeconds() - iteration_start_time;
  143. iteration_summary.cumulative_time_in_seconds =
  144. WallTimeInSeconds() - start_time + summary->preprocessor_time_in_seconds;
  145. summary->iterations.push_back(iteration_summary);
  146. LineSearchDirection::Options line_search_direction_options;
  147. line_search_direction_options.num_parameters = num_effective_parameters;
  148. line_search_direction_options.type = options.line_search_direction_type;
  149. line_search_direction_options.nonlinear_conjugate_gradient_type =
  150. options.nonlinear_conjugate_gradient_type;
  151. line_search_direction_options.max_lbfgs_rank = options.max_lbfgs_rank;
  152. line_search_direction_options.use_approximate_eigenvalue_bfgs_scaling =
  153. options.use_approximate_eigenvalue_bfgs_scaling;
  154. std::unique_ptr<LineSearchDirection> line_search_direction =
  155. LineSearchDirection::Create(line_search_direction_options);
  156. LineSearchFunction line_search_function(evaluator);
  157. LineSearch::Options line_search_options;
  158. line_search_options.interpolation_type =
  159. options.line_search_interpolation_type;
  160. line_search_options.min_step_size = options.min_line_search_step_size;
  161. line_search_options.sufficient_decrease =
  162. options.line_search_sufficient_function_decrease;
  163. line_search_options.max_step_contraction =
  164. options.max_line_search_step_contraction;
  165. line_search_options.min_step_contraction =
  166. options.min_line_search_step_contraction;
  167. line_search_options.max_num_iterations =
  168. options.max_num_line_search_step_size_iterations;
  169. line_search_options.sufficient_curvature_decrease =
  170. options.line_search_sufficient_curvature_decrease;
  171. line_search_options.max_step_expansion =
  172. options.max_line_search_step_expansion;
  173. line_search_options.is_silent = options.is_silent;
  174. line_search_options.function = &line_search_function;
  175. std::unique_ptr<LineSearch> line_search(LineSearch::Create(
  176. options.line_search_type, line_search_options, &summary->message));
  177. if (line_search.get() == nullptr) {
  178. summary->termination_type = FAILURE;
  179. if (is_not_silent) {
  180. LOG(ERROR) << "Terminating: " << summary->message;
  181. }
  182. return;
  183. }
  184. LineSearch::Summary line_search_summary;
  185. int num_line_search_direction_restarts = 0;
  186. while (true) {
  187. if (!RunCallbacks(options, iteration_summary, summary)) {
  188. break;
  189. }
  190. iteration_start_time = WallTimeInSeconds();
  191. if (iteration_summary.iteration >= options.max_num_iterations) {
  192. summary->message = "Maximum number of iterations reached.";
  193. summary->termination_type = NO_CONVERGENCE;
  194. if (is_not_silent) {
  195. VLOG(1) << "Terminating: " << summary->message;
  196. }
  197. break;
  198. }
  199. const double total_solver_time = iteration_start_time - start_time +
  200. summary->preprocessor_time_in_seconds;
  201. if (total_solver_time >= options.max_solver_time_in_seconds) {
  202. summary->message = "Maximum solver time reached.";
  203. summary->termination_type = NO_CONVERGENCE;
  204. if (is_not_silent) {
  205. VLOG(1) << "Terminating: " << summary->message;
  206. }
  207. break;
  208. }
  209. iteration_summary = IterationSummary();
  210. iteration_summary.iteration = summary->iterations.back().iteration + 1;
  211. iteration_summary.step_is_valid = false;
  212. iteration_summary.step_is_successful = false;
  213. bool line_search_status = true;
  214. if (iteration_summary.iteration == 1) {
  215. current_state.search_direction = -current_state.gradient;
  216. } else {
  217. line_search_status = line_search_direction->NextDirection(
  218. previous_state, current_state, &current_state.search_direction);
  219. }
  220. if (!line_search_status &&
  221. num_line_search_direction_restarts >=
  222. options.max_num_line_search_direction_restarts) {
  223. // Line search direction failed to generate a new direction, and we
  224. // have already reached our specified maximum number of restarts,
  225. // terminate optimization.
  226. summary->message = StringPrintf(
  227. "Line search direction failure: specified "
  228. "max_num_line_search_direction_restarts: %d reached.",
  229. options.max_num_line_search_direction_restarts);
  230. summary->termination_type = FAILURE;
  231. if (is_not_silent) {
  232. LOG(WARNING) << "Terminating: " << summary->message;
  233. }
  234. break;
  235. } else if (!line_search_status) {
  236. // Restart line search direction with gradient descent on first iteration
  237. // as we have not yet reached our maximum number of restarts.
  238. CHECK_LT(num_line_search_direction_restarts,
  239. options.max_num_line_search_direction_restarts);
  240. ++num_line_search_direction_restarts;
  241. if (is_not_silent) {
  242. LOG(WARNING) << "Line search direction algorithm: "
  243. << LineSearchDirectionTypeToString(
  244. options.line_search_direction_type)
  245. << ", failed to produce a valid new direction at "
  246. << "iteration: " << iteration_summary.iteration
  247. << ". Restarting, number of restarts: "
  248. << num_line_search_direction_restarts << " / "
  249. << options.max_num_line_search_direction_restarts
  250. << " [max].";
  251. }
  252. line_search_direction =
  253. LineSearchDirection::Create(line_search_direction_options);
  254. current_state.search_direction = -current_state.gradient;
  255. }
  256. line_search_function.Init(x, current_state.search_direction);
  257. current_state.directional_derivative =
  258. current_state.gradient.dot(current_state.search_direction);
  259. // TODO(sameeragarwal): Refactor this into its own object and add
  260. // explanations for the various choices.
  261. //
  262. // Note that we use !line_search_status to ensure that we treat cases when
  263. // we restarted the line search direction equivalently to the first
  264. // iteration.
  265. const double initial_step_size =
  266. (iteration_summary.iteration == 1 || !line_search_status)
  267. ? std::min(1.0, 1.0 / current_state.gradient_max_norm)
  268. : std::min(1.0,
  269. 2.0 * (current_state.cost - previous_state.cost) /
  270. current_state.directional_derivative);
  271. // By definition, we should only ever go forwards along the specified search
  272. // direction in a line search, most likely cause for this being violated
  273. // would be a numerical failure in the line search direction calculation.
  274. if (initial_step_size < 0.0) {
  275. summary->message = StringPrintf(
  276. "Numerical failure in line search, initial_step_size is "
  277. "negative: %.5e, directional_derivative: %.5e, "
  278. "(current_cost - previous_cost): %.5e",
  279. initial_step_size,
  280. current_state.directional_derivative,
  281. (current_state.cost - previous_state.cost));
  282. summary->termination_type = FAILURE;
  283. if (is_not_silent) {
  284. LOG(WARNING) << "Terminating: " << summary->message;
  285. }
  286. break;
  287. }
  288. line_search->Search(initial_step_size,
  289. current_state.cost,
  290. current_state.directional_derivative,
  291. &line_search_summary);
  292. if (!line_search_summary.success) {
  293. summary->message = StringPrintf(
  294. "Numerical failure in line search, failed to find "
  295. "a valid step size, (did not run out of iterations) "
  296. "using initial_step_size: %.5e, initial_cost: %.5e, "
  297. "initial_gradient: %.5e.",
  298. initial_step_size,
  299. current_state.cost,
  300. current_state.directional_derivative);
  301. if (is_not_silent) {
  302. LOG(WARNING) << "Terminating: " << summary->message;
  303. }
  304. summary->termination_type = FAILURE;
  305. break;
  306. }
  307. const FunctionSample& optimal_point = line_search_summary.optimal_point;
  308. CHECK(optimal_point.vector_x_is_valid)
  309. << "Congratulations, you found a bug in Ceres. Please report it.";
  310. current_state.step_size = optimal_point.x;
  311. previous_state = current_state;
  312. iteration_summary.step_solver_time_in_seconds =
  313. WallTimeInSeconds() - iteration_start_time;
  314. if (optimal_point.vector_gradient_is_valid) {
  315. current_state.cost = optimal_point.value;
  316. current_state.gradient = optimal_point.vector_gradient;
  317. } else {
  318. Evaluator::EvaluateOptions evaluate_options;
  319. evaluate_options.new_evaluation_point = false;
  320. if (!evaluator->Evaluate(evaluate_options,
  321. optimal_point.vector_x.data(),
  322. &(current_state.cost),
  323. nullptr,
  324. current_state.gradient.data(),
  325. nullptr)) {
  326. summary->termination_type = FAILURE;
  327. summary->message = "Cost and jacobian evaluation failed.";
  328. if (is_not_silent) {
  329. LOG(WARNING) << "Terminating: " << summary->message;
  330. }
  331. return;
  332. }
  333. }
  334. if (!EvaluateGradientNorms(evaluator,
  335. optimal_point.vector_x,
  336. &current_state,
  337. &summary->message)) {
  338. summary->termination_type = FAILURE;
  339. summary->message =
  340. "Step failed to evaluate. This should not happen as the step was "
  341. "valid when it was selected by the line search. More details: " +
  342. summary->message;
  343. if (is_not_silent) {
  344. LOG(WARNING) << "Terminating: " << summary->message;
  345. }
  346. break;
  347. }
  348. // Compute the norm of the step in the ambient space.
  349. iteration_summary.step_norm = (optimal_point.vector_x - x).norm();
  350. const double x_norm = x.norm();
  351. x = optimal_point.vector_x;
  352. iteration_summary.gradient_max_norm = current_state.gradient_max_norm;
  353. iteration_summary.gradient_norm = sqrt(current_state.gradient_squared_norm);
  354. iteration_summary.cost_change = previous_state.cost - current_state.cost;
  355. iteration_summary.cost = current_state.cost + summary->fixed_cost;
  356. iteration_summary.step_is_valid = true;
  357. iteration_summary.step_is_successful = true;
  358. iteration_summary.step_size = current_state.step_size;
  359. iteration_summary.line_search_function_evaluations =
  360. line_search_summary.num_function_evaluations;
  361. iteration_summary.line_search_gradient_evaluations =
  362. line_search_summary.num_gradient_evaluations;
  363. iteration_summary.line_search_iterations =
  364. line_search_summary.num_iterations;
  365. iteration_summary.iteration_time_in_seconds =
  366. WallTimeInSeconds() - iteration_start_time;
  367. iteration_summary.cumulative_time_in_seconds =
  368. WallTimeInSeconds() - start_time +
  369. summary->preprocessor_time_in_seconds;
  370. summary->iterations.push_back(iteration_summary);
  371. // Iterations inside the line search algorithm are considered
  372. // 'steps' in the broader context, to distinguish these inner
  373. // iterations from from the outer iterations of the line search
  374. // minimizer. The number of line search steps is the total number
  375. // of inner line search iterations (or steps) across the entire
  376. // minimization.
  377. summary->num_line_search_steps += line_search_summary.num_iterations;
  378. summary->line_search_cost_evaluation_time_in_seconds +=
  379. line_search_summary.cost_evaluation_time_in_seconds;
  380. summary->line_search_gradient_evaluation_time_in_seconds +=
  381. line_search_summary.gradient_evaluation_time_in_seconds;
  382. summary->line_search_polynomial_minimization_time_in_seconds +=
  383. line_search_summary.polynomial_minimization_time_in_seconds;
  384. summary->line_search_total_time_in_seconds +=
  385. line_search_summary.total_time_in_seconds;
  386. ++summary->num_successful_steps;
  387. const double step_size_tolerance =
  388. options.parameter_tolerance * (x_norm + options.parameter_tolerance);
  389. if (iteration_summary.step_norm <= step_size_tolerance) {
  390. summary->message = StringPrintf(
  391. "Parameter tolerance reached. "
  392. "Relative step_norm: %e <= %e.",
  393. (iteration_summary.step_norm /
  394. (x_norm + options.parameter_tolerance)),
  395. options.parameter_tolerance);
  396. summary->termination_type = CONVERGENCE;
  397. if (is_not_silent) {
  398. VLOG(1) << "Terminating: " << summary->message;
  399. }
  400. return;
  401. }
  402. if (iteration_summary.gradient_max_norm <= options.gradient_tolerance) {
  403. summary->message = StringPrintf(
  404. "Gradient tolerance reached. "
  405. "Gradient max norm: %e <= %e",
  406. iteration_summary.gradient_max_norm,
  407. options.gradient_tolerance);
  408. summary->termination_type = CONVERGENCE;
  409. if (is_not_silent) {
  410. VLOG(1) << "Terminating: " << summary->message;
  411. }
  412. break;
  413. }
  414. const double absolute_function_tolerance =
  415. options.function_tolerance * std::abs(previous_state.cost);
  416. if (std::abs(iteration_summary.cost_change) <=
  417. absolute_function_tolerance) {
  418. summary->message = StringPrintf(
  419. "Function tolerance reached. "
  420. "|cost_change|/cost: %e <= %e",
  421. std::abs(iteration_summary.cost_change) / previous_state.cost,
  422. options.function_tolerance);
  423. summary->termination_type = CONVERGENCE;
  424. if (is_not_silent) {
  425. VLOG(1) << "Terminating: " << summary->message;
  426. }
  427. break;
  428. }
  429. }
  430. }
  431. } // namespace ceres::internal