Get rid of redundant function evaluations in LineSearchMinimizer

1. Replace LineSearch::Summary::optimal_step_size with
   LineSearch:Summary::optimal_point which is a FunctionSample.
2. Add the actual vector position and vector gradient of the
   point in the FunctionSample
3. Use the above two to get rid of an extraneous function evalation
   in LineSearchMinimizer.

Runtime performance is almost 2x improved as a result.

Thanks to @svenpilz for reporting this.

https://github.com/ceres-solver/ceres-solver/issues/296

Change-Id: Iebf2db7acecb2c95c9b1683b73cdc5faab78b02e
This commit is contained in:
Sameer Agarwal
2017-06-28 21:16:39 -07:00
parent 621b79b972
commit c8202e6926
6 changed files with 161 additions and 112 deletions
+38 -49
View File
@@ -54,30 +54,8 @@ using std::vector;
namespace {
// Precision used for floating point values in error message output.
const int kErrorMessageNumericPrecision = 8;
FunctionSample ValueSample(const double x, const double value) {
FunctionSample sample;
sample.x = x;
sample.value = value;
sample.value_is_valid = true;
return sample;
}
FunctionSample ValueAndGradientSample(const double x,
const double value,
const double gradient) {
FunctionSample sample;
sample.x = x;
sample.value = value;
sample.gradient = gradient;
sample.value_is_valid = true;
sample.gradient_is_valid = true;
return sample;
}
} // namespace
ostream& operator<<(ostream &os, const FunctionSample& sample);
// Convenience stream operator for pushing FunctionSamples into log messages.
@@ -113,9 +91,7 @@ LineSearchFunction::LineSearchFunction(Evaluator* evaluator)
: evaluator_(evaluator),
position_(evaluator->NumParameters()),
direction_(evaluator->NumEffectiveParameters()),
evaluation_point_(evaluator->NumParameters()),
scaled_direction_(evaluator->NumEffectiveParameters()),
gradient_(evaluator->NumEffectiveParameters()),
initial_evaluator_residual_time_in_seconds(0.0),
initial_evaluator_jacobian_time_in_seconds(0.0) {}
@@ -129,33 +105,44 @@ void LineSearchFunction::Evaluate(const double x,
const bool evaluate_gradient,
FunctionSample* output) {
output->x = x;
output->vector_x_is_valid = false;
output->value_is_valid = false;
output->gradient_is_valid = false;
output->vector_gradient_is_valid = false;
scaled_direction_ = output->x * direction_;
output->vector_x.resize(position_.rows(), 1);
if (!evaluator_->Plus(position_.data(),
scaled_direction_.data(),
evaluation_point_.data())) {
output->vector_x.data())) {
return;
}
output->vector_x_is_valid = true;
const bool eval_status =
evaluator_->Evaluate(evaluation_point_.data(),
&(output->value),
NULL,
evaluate_gradient ? gradient_.data() : NULL,
NULL);
double* gradient = NULL;
if (evaluate_gradient) {
output->vector_gradient.resize(direction_.rows(), 1);
gradient = output->vector_gradient.data();
}
const bool eval_status = evaluator_->Evaluate(
output->vector_x.data(), &(output->value), NULL, gradient, NULL);
if (!eval_status || !IsFinite(output->value)) {
return;
}
output->value_is_valid = true;
if (evaluate_gradient) {
output->gradient = direction_.dot(gradient_);
if (!evaluate_gradient) {
return;
}
output->gradient_is_valid = IsFinite(output->gradient);
return;
output->gradient = direction_.dot(output->vector_gradient);
if (!IsFinite(output->gradient)) {
return;
}
output->gradient_is_valid = true;
output->vector_gradient_is_valid = true;
}
double LineSearchFunction::DirectionInfinityNorm() const {
@@ -200,7 +187,6 @@ void LineSearch::Search(double step_size_estimate,
summary->cost_evaluation_time_in_seconds = 0.0;
summary->gradient_evaluation_time_in_seconds = 0.0;
summary->polynomial_minimization_time_in_seconds = 0.0;
options().function->ResetTimeStatistics();
this->DoSearch(step_size_estimate, initial_cost, initial_gradient, summary);
options().function->
@@ -254,12 +240,12 @@ double LineSearch::InterpolatingPolynomialMinimizingStepSize(
if (interpolation_type == QUADRATIC) {
// Two point interpolation using function values and the
// gradient at the lower bound.
samples.push_back(ValueSample(current.x, current.value));
samples.push_back(FunctionSample(current.x, current.value));
if (previous.value_is_valid) {
// Three point interpolation, using function values and the
// gradient at the lower bound.
samples.push_back(ValueSample(previous.x, previous.value));
samples.push_back(FunctionSample(previous.x, previous.value));
}
} else if (interpolation_type == CUBIC) {
// Two point interpolation using the function values and the gradients.
@@ -297,8 +283,9 @@ void ArmijoLineSearch::DoSearch(const double step_size_estimate,
// Note initial_cost & initial_gradient are evaluated at step_size = 0,
// not step_size_estimate, which is our starting guess.
const FunctionSample initial_position =
ValueAndGradientSample(0.0, initial_cost, initial_gradient);
FunctionSample initial_position(0.0, initial_cost, initial_gradient);
initial_position.vector_x = function->position();
initial_position.vector_x_is_valid = true;
const double descent_direction_max_norm = function->DirectionInfinityNorm();
FunctionSample previous;
@@ -365,7 +352,7 @@ void ArmijoLineSearch::DoSearch(const double step_size_estimate,
function->Evaluate(step_size, kEvaluateGradient, &current);
}
summary->optimal_step_size = current.x;
summary->optimal_point = current;
summary->success = true;
}
@@ -387,9 +374,9 @@ void WolfeLineSearch::DoSearch(const double step_size_estimate,
// Note initial_cost & initial_gradient are evaluated at step_size = 0,
// not step_size_estimate, which is our starting guess.
const FunctionSample initial_position =
ValueAndGradientSample(0.0, initial_cost, initial_gradient);
FunctionSample initial_position(0.0, initial_cost, initial_gradient);
initial_position.vector_x = options().function->position();
initial_position.vector_x_is_valid = true;
bool do_zoom_search = false;
// Important: The high/low in bracket_high & bracket_low refer to their
// _function_ values, not their step sizes i.e. it is _not_ required that
@@ -431,7 +418,7 @@ void WolfeLineSearch::DoSearch(const double step_size_estimate,
// produce a valid point when ArmijoLineSearch would succeed, we return the
// point with the lowest cost found thus far which satsifies the Armijo
// condition (but not the Wolfe conditions).
summary->optimal_step_size = bracket_low.x;
summary->optimal_point = bracket_low;
summary->success = true;
return;
}
@@ -477,11 +464,13 @@ void WolfeLineSearch::DoSearch(const double step_size_estimate,
// satisfies the strong Wolfe curvature condition, that we return the point
// amongst those found thus far, which minimizes f() and satisfies the Armijo
// condition.
solution =
solution.value_is_valid && solution.value <= bracket_low.value
? solution : bracket_low;
summary->optimal_step_size = solution.x;
if (!solution.value_is_valid || solution.value > bracket_low.value) {
summary->optimal_point = bracket_low;
} else {
summary->optimal_point = solution;
}
summary->success = true;
}