Add line search to the trust region minimizer.
If enabled, the TrustRegionMinimizer can now further improve the
quality of the step with a Armijo line search.
This is the first step towards adding support for bounds on
variables.
Change-Id: I453b42853cfa6ca4f75812900c13d286a473c2df
diff --git a/internal/ceres/minimizer.h b/internal/ceres/minimizer.h
index ee77726..f9f2b51 100644
--- a/internal/ceres/minimizer.h
+++ b/internal/ceres/minimizer.h
@@ -114,6 +114,7 @@
callbacks = options.callbacks;
inner_iteration_minimizer = NULL;
inner_iteration_tolerance = options.inner_iteration_tolerance;
+ is_constrained = false;
}
int max_num_iterations;
@@ -180,6 +181,9 @@
Minimizer* inner_iteration_minimizer;
double inner_iteration_tolerance;
+
+ // Use a bounds constrained optimization algorithm.
+ bool is_constrained;
};
static bool RunCallbacks(const vector<IterationCallback*> callbacks,
diff --git a/internal/ceres/trust_region_minimizer.cc b/internal/ceres/trust_region_minimizer.cc
index f9f14e0..72e89b9 100644
--- a/internal/ceres/trust_region_minimizer.cc
+++ b/internal/ceres/trust_region_minimizer.cc
@@ -44,6 +44,7 @@
#include "ceres/file.h"
#include "ceres/internal/eigen.h"
#include "ceres/internal/scoped_ptr.h"
+#include "ceres/line_search.h"
#include "ceres/linear_least_squares_problems.h"
#include "ceres/sparse_matrix.h"
#include "ceres/stringprintf.h"
@@ -57,6 +58,45 @@
namespace {
// Small constant for various floating point issues.
const double kEpsilon = 1e-12;
+
+LineSearch::Summary DoLineSearch(const Minimizer::Options& options,
+ const Vector& x,
+ const Vector& gradient,
+ const double cost,
+ const Vector& delta,
+ Evaluator* evaluator) {
+ LineSearchFunction line_search_function(evaluator);
+ line_search_function.Init(x, delta);
+
+ LineSearch::Options line_search_options;
+ line_search_options.interpolation_type =
+ options.line_search_interpolation_type;
+ line_search_options.min_step_size = options.min_line_search_step_size;
+ line_search_options.sufficient_decrease =
+ options.line_search_sufficient_function_decrease;
+ line_search_options.max_step_contraction =
+ options.max_line_search_step_contraction;
+ line_search_options.min_step_contraction =
+ options.min_line_search_step_contraction;
+ line_search_options.max_num_iterations =
+ options.max_num_line_search_step_size_iterations;
+ line_search_options.sufficient_curvature_decrease =
+ options.line_search_sufficient_curvature_decrease;
+ line_search_options.max_step_expansion =
+ options.max_line_search_step_expansion;
+ line_search_options.function = &line_search_function;
+
+ string message;
+ scoped_ptr<LineSearch>
+ line_search(CHECK_NOTNULL(
+ LineSearch::Create(ceres::ARMIJO,
+ line_search_options,
+ &message)));
+ LineSearch::Summary summary;
+ line_search->Search(1.0, cost, gradient.dot(delta), &summary);
+ return summary;
+}
+
} // namespace
// Compute a scaling vector that is used to improve the conditioning
@@ -81,24 +121,30 @@
double start_time = WallTimeInSeconds();
double iteration_start_time = start_time;
Init(options);
- const bool is_not_silent = !options.is_silent;
-
- summary->termination_type = NO_CONVERGENCE;
- summary->num_successful_steps = 0;
- summary->num_unsuccessful_steps = 0;
Evaluator* evaluator = CHECK_NOTNULL(options_.evaluator);
SparseMatrix* jacobian = CHECK_NOTNULL(options_.jacobian);
TrustRegionStrategy* strategy = CHECK_NOTNULL(options_.trust_region_strategy);
+ const bool is_not_silent = !options.is_silent;
+
+ // If the problem is bounds constrained, then enable the use of a
+ // line search after the trust region step has been computed. This
+ // line search will automatically use a projected the test point
+ // onto the feasible set, there by guaranteeing the feasibility of
+ // the final output.
+ //
+ // TODO(sameeragarwal): Make line search available more generally.
+ const bool use_line_search = options.is_constrained;
+
+ summary->termination_type = NO_CONVERGENCE;
+ summary->num_successful_steps = 0;
+ summary->num_unsuccessful_steps = 0;
+
const int num_parameters = evaluator->NumParameters();
const int num_effective_parameters = evaluator->NumEffectiveParameters();
const int num_residuals = evaluator->NumResiduals();
- VectorRef x_min(parameters, num_parameters);
- Vector x = x_min;
- double x_norm = x.norm();
-
Vector residuals(num_residuals);
Vector trust_region_step(num_effective_parameters);
Vector delta(num_effective_parameters);
@@ -121,6 +167,23 @@
iteration_summary.linear_solver_iterations = 0;
iteration_summary.step_solver_time_in_seconds = 0;
+ VectorRef x_min(parameters, num_parameters);
+ Vector x = x_min;
+ // Project onto the feasible set.
+ if (options.is_constrained) {
+ delta.setZero();
+ if (!evaluator->Plus(x.data(), delta.data(), x_plus_delta.data())) {
+ summary->message = "Unable to project initial point onto the feasible set.";
+ summary->termination_type = FAILURE;
+ LOG_IF(WARNING, is_not_silent) << "Terminating: " << summary->message;
+ return;
+ }
+ x_min = x_plus_delta;
+ x = x_plus_delta;
+ }
+
+ double x_norm = x.norm();
+
// Do initial cost and Jacobian evaluation.
double cost = 0.0;
if (!evaluator->Evaluate(x.data(),
@@ -128,9 +191,9 @@
residuals.data(),
gradient.data(),
jacobian)) {
- summary->message = "Terminating: Residual and Jacobian evaluation failed.";
+ summary->message = "Residual and Jacobian evaluation failed.";
summary->termination_type = FAILURE;
- LOG_IF(WARNING, is_not_silent) << summary->message;
+ LOG_IF(WARNING, is_not_silent) << "Terminating: " << summary->message;
return;
}
@@ -305,19 +368,36 @@
// Undo the Jacobian column scaling.
delta = (trust_region_step.array() * scale.array()).matrix();
- double new_cost = numeric_limits<double>::max();
- if (!evaluator->Plus(x.data(), delta.data(), x_plus_delta.data())) {
+ // Try improving the step further by using an ARMIJO line
+ // search.
+ //
+ // TODO(sameeragarwal): What happens to trust region sizing as
+ // it interacts with the line search ?
+ if (use_line_search) {
+ const LineSearch::Summary line_search_summary =
+ DoLineSearch(options, x, gradient, cost, delta, evaluator);
+ if (line_search_summary.success) {
+ delta *= line_search_summary.optimal_step_size;
+ }
+ }
+
+ double new_cost = std::numeric_limits<double>::max();
+ if (evaluator->Plus(x.data(), delta.data(), x_plus_delta.data())) {
+ if (!evaluator->Evaluate(x_plus_delta.data(),
+ &new_cost,
+ NULL,
+ NULL,
+ NULL)) {
+ LOG(WARNING) << "Step failed to evaluate. "
+ << "Treating it as a step with infinite cost";
+ new_cost = numeric_limits<double>::max();
+ }
+ } else {
LOG(WARNING) << "x_plus_delta = Plus(x, delta) failed. "
<< "Treating it as a step with infinite cost";
- } else if (!evaluator->Evaluate(x_plus_delta.data(),
- &new_cost,
- NULL,
- NULL,
- NULL)) {
- LOG(WARNING) << "Step failed to evaluate. "
- << "Treating it as a step with infinite cost";
- new_cost = numeric_limits<double>::max();
- } else {
+ }
+
+ if (new_cost < std::numeric_limits<double>::max()) {
// Check if performing an inner iteration will make it better.
if (inner_iterations_are_enabled) {
++summary->num_inner_iteration_steps;