From cbae8561930f5e608ef82ff8ed5747bb8dff8e88 Mon Sep 17 00:00:00 2001 From: Sameer Agarwal Date: Sun, 2 Sep 2012 13:50:43 -0700 Subject: [PATCH] Various cleanups to nist.cc. More flexible testing. Read and parse the certified cost value from the data file. Remove the ugly hack for computing the certified cost. Refactored the flags parsing logic Change-Id: I8f2e6be183b758b2453302fcdc6696bfa0db5eb8 --- examples/bundle_adjuster.cc | 132 +++++++----------------------------- examples/nist.cc | 116 +++++++++++++------------------ include/ceres/types.h | 25 +++++-- internal/ceres/types.cc | 105 ++++++++++++++++++++++++---- 4 files changed, 185 insertions(+), 193 deletions(-) diff --git a/examples/bundle_adjuster.cc b/examples/bundle_adjuster.cc index f5c1ac105..e5f9100a6 100644 --- a/examples/bundle_adjuster.cc +++ b/examples/bundle_adjuster.cc @@ -66,27 +66,30 @@ #include "snavely_reprojection_error.h" DEFINE_string(input, "", "Input File name"); +DEFINE_string(trust_region_strategy, "levenberg_marquardt", + "Options are: levenberg_marquardt, dogleg"); +DEFINE_string(linear_solver, "sparse_schur", "Options are: " + "sparse_schur, dense_schur, iterative_schur, sparse_normal_cholesky, " + "dense_qr, dense_normal_cholesky and cgnr"); +DEFINE_string(preconditioner, "jacobi", "Options are: " + "identity, jacobi, schur_jacobi, cluster_jacobi, " + "cluster_tridiagonal"); +DEFINE_string(sparse_linear_algebra_library, "suite_sparse", + "Options are: suite_sparse and cx_sparse"); +DEFINE_string(ordering, "schur", "Options are: schur, user, natural"); +DEFINE_string(dogleg, "traditional_dogleg", "Options are: traditional_dogleg," + "subspace_dogleg"); + DEFINE_bool(use_quaternions, false, "If true, uses quaternions to represent " "rotations. If false, angle axis is used"); DEFINE_bool(use_local_parameterization, false, "For quaternions, use a local " "parameterization."); DEFINE_bool(robustify, false, "Use a robust loss function"); -DEFINE_string(trust_region_strategy, "lm", "Options are: lm, dogleg"); DEFINE_double(eta, 1e-2, "Default value for eta. Eta determines the " "accuracy of each linear solve of the truncated newton step. " "Changing this parameter can affect solve performance "); -DEFINE_string(solver_type, "sparse_schur", "Options are: " - "sparse_schur, dense_schur, iterative_schur, sparse_cholesky, " - "dense_qr, dense_cholesky and conjugate_gradients"); -DEFINE_string(preconditioner_type, "jacobi", "Options are: " - "identity, jacobi, schur_jacobi, cluster_jacobi, " - "cluster_tridiagonal"); -DEFINE_string(sparse_linear_algebra_library, "suitesparse", - "Options are: suitesparse and cxsparse"); -DEFINE_string(ordering_type, "schur", "Options are: schur, user, natural"); -DEFINE_string(dogleg_type, "traditional", "Options are: traditional, subspace"); DEFINE_bool(use_block_amd, true, "Use a block oriented fill reducing " "ordering."); @@ -111,88 +114,19 @@ namespace ceres { namespace examples { void SetLinearSolver(Solver::Options* options) { - if (FLAGS_solver_type == "sparse_schur") { - options->linear_solver_type = ceres::SPARSE_SCHUR; - } else if (FLAGS_solver_type == "dense_schur") { - options->linear_solver_type = ceres::DENSE_SCHUR; - } else if (FLAGS_solver_type == "iterative_schur") { - options->linear_solver_type = ceres::ITERATIVE_SCHUR; - } else if (FLAGS_solver_type == "sparse_cholesky") { - options->linear_solver_type = ceres::SPARSE_NORMAL_CHOLESKY; - } else if (FLAGS_solver_type == "cgnr") { - options->linear_solver_type = ceres::CGNR; - } else if (FLAGS_solver_type == "dense_qr") { - // DENSE_QR is included here for completeness, but actually using - // this option is a bad idea due to the amount of memory needed - // to store even the smallest of the bundle adjustment jacobian - // arrays - options->linear_solver_type = ceres::DENSE_QR; - } else if (FLAGS_solver_type == "dense_cholesky") { - // DENSE_NORMAL_CHOLESKY is included here for completeness, but - // actually using this option is a bad idea due to the amount of - // memory needed to store even the smallest of the bundle - // adjustment jacobian arrays - options->linear_solver_type = ceres::DENSE_NORMAL_CHOLESKY; - } else { - LOG(FATAL) << "Unknown ceres solver type: " - << FLAGS_solver_type; - } - - if (options->linear_solver_type == ceres::CGNR) { - options->linear_solver_min_num_iterations = 5; - if (FLAGS_preconditioner_type == "identity") { - options->preconditioner_type = ceres::IDENTITY; - } else if (FLAGS_preconditioner_type == "jacobi") { - options->preconditioner_type = ceres::JACOBI; - } else { - LOG(FATAL) << "For CGNR, only identity and jacobian " - << "preconditioners are supported. Got: " - << FLAGS_preconditioner_type; - } - } - - if (options->linear_solver_type == ceres::ITERATIVE_SCHUR) { - options->linear_solver_min_num_iterations = 5; - if (FLAGS_preconditioner_type == "identity") { - options->preconditioner_type = ceres::IDENTITY; - } else if (FLAGS_preconditioner_type == "jacobi") { - options->preconditioner_type = ceres::JACOBI; - } else if (FLAGS_preconditioner_type == "schur_jacobi") { - options->preconditioner_type = ceres::SCHUR_JACOBI; - } else if (FLAGS_preconditioner_type == "cluster_jacobi") { - options->preconditioner_type = ceres::CLUSTER_JACOBI; - } else if (FLAGS_preconditioner_type == "cluster_tridiagonal") { - options->preconditioner_type = ceres::CLUSTER_TRIDIAGONAL; - } else { - LOG(FATAL) << "Unknown ceres preconditioner type: " - << FLAGS_preconditioner_type; - } - } - - if (FLAGS_sparse_linear_algebra_library == "suitesparse") { - options->sparse_linear_algebra_library = SUITE_SPARSE; - } else if (FLAGS_sparse_linear_algebra_library == "cxsparse") { - options->sparse_linear_algebra_library = CX_SPARSE; - } else { - LOG(FATAL) << "Unknown sparse linear algebra library type."; - } - + CHECK(StringToLinearSolverType(FLAGS_linear_solver, + &options->linear_solver_type)); + CHECK(StringToPreconditionerType(FLAGS_preconditioner, + &options->preconditioner_type)); + CHECK(StringToSparseLinearAlgebraLibraryType( + FLAGS_sparse_linear_algebra_library, + &options->sparse_linear_algebra_library)); options->num_linear_solver_threads = FLAGS_num_threads; } void SetOrdering(BALProblem* bal_problem, Solver::Options* options) { options->use_block_amd = FLAGS_use_block_amd; - - // Only non-Schur solvers support the natural ordering for this - // problem. - if (FLAGS_ordering_type == "natural") { - if (options->linear_solver_type == SPARSE_SCHUR || - options->linear_solver_type == DENSE_SCHUR || - options->linear_solver_type == ITERATIVE_SCHUR) { - LOG(FATAL) << "Natural ordering with Schur type solver does not work."; - } - return; - } + CHECK(StringToOrderingType(FLAGS_ordering, &options->ordering_type)); // Bundle adjustment problems have a sparsity structure that makes // them amenable to more specialized and much more efficient @@ -208,8 +142,7 @@ void SetOrdering(BALProblem* bal_problem, Solver::Options* options) { // the right ParameterBlock ordering, or by manually specifying a // suitable ordering vector and defining // Options::num_eliminate_blocks. - if (FLAGS_ordering_type == "schur") { - options->ordering_type = ceres::SCHUR; + if (options->ordering_type == ceres::SCHUR) { return; } @@ -248,22 +181,9 @@ void SetMinimizerOptions(Solver::Options* options) { options->eta = FLAGS_eta; options->max_solver_time_in_seconds = FLAGS_max_solver_time; options->use_nonmonotonic_steps = FLAGS_nonmonotonic_steps; - if (FLAGS_trust_region_strategy == "lm") { - options->trust_region_strategy_type = LEVENBERG_MARQUARDT; - } else if (FLAGS_trust_region_strategy == "dogleg") { - options->trust_region_strategy_type = DOGLEG; - } else { - LOG(FATAL) << "Unknown trust region strategy: " - << FLAGS_trust_region_strategy; - } - if (FLAGS_dogleg_type == "traditional") { - options->dogleg_type = TRADITIONAL_DOGLEG; - } else if (FLAGS_dogleg_type == "subspace") { - options->dogleg_type = SUBSPACE_DOGLEG; - } else { - LOG(FATAL) << "Unknown dogleg type: " - << FLAGS_dogleg_type; - } + CHECK(StringToTrustRegionStrategyType(FLAGS_trust_region_strategy, + &options->trust_region_strategy_type)); + CHECK(StringToDoglegType(FLAGS_dogleg, &options->dogleg_type)); } void SetSolverOptionsFromFlags(BALProblem* bal_problem, diff --git a/examples/nist.cc b/examples/nist.cc index 2c1a4c5ea..0f5951f2b 100644 --- a/examples/nist.cc +++ b/examples/nist.cc @@ -51,6 +51,18 @@ DEFINE_string(nist_data_dir, "", "Directory containing the NIST non-linear" "regression examples"); +DEFINE_string(trust_region_strategy, "levenberg_marquardt", + "Options are: levenberg_marquardt, dogleg"); +DEFINE_string(dogleg, "traditional_dogleg", + "Options are: traditional_dogleg, subspace_dogleg"); +DEFINE_string(linear_solver, "dense_qr", "Options are: " + "sparse_cholesky, dense_qr, dense_normal_cholesky and" + "cgnr"); +DEFINE_string(preconditioner, "jacobi", "Options are: " + "identity, jacobi"); +DEFINE_int32(num_iterations, 10000, "Number of iterations"); +DEFINE_bool(nonmonotonic_steps, false, "Trust region algorithm can use" + " nonmonotic steps"); using Eigen::Dynamic; using Eigen::RowMajor; @@ -120,8 +132,13 @@ class NISTProblem { final_parameters_(0, parameter_id) = std::atof(pieces[2 + kNumTries].c_str()); } + // Certfied cost + SkipLines(ifs, 1); + GetAndSplitLine(ifs, &pieces); + certified_cost_ = std::atof(pieces[4].c_str()) / 2.0; + // Read the observations. - SkipLines(ifs, 20 - kNumParameters); + SkipLines(ifs, 18 - kNumParameters); for (int i = 0; i < kNumObservations; ++i) { GetAndSplitLine(ifs, &pieces); // Response. @@ -145,12 +162,14 @@ class NISTProblem { int response_size() const { return response_.cols(); } int num_parameters() const { return initial_parameters_.cols(); } int num_starts() const { return initial_parameters_.rows(); } + double certified_cost() const { return certified_cost_; } private: Matrix predictor_; Matrix response_; Matrix initial_parameters_; Matrix final_parameters_; + double certified_cost_; }; #define NIST_BEGIN(CostFunctionName) \ @@ -340,27 +359,7 @@ int RegressionDriver(const std::string& filename, Solve(options, &problem, &summaries[start]); } - // Ugly hack to get the objective function value at the certified - // optimal parameter values. So we build the problem and call Ceres - // with zero iterations to get the initial_cost. - { - Matrix initial_parameters = nist_problem.final_parameters(); - ceres::Problem problem; - for (int i = 0; i < nist_problem.num_observations(); ++i) { - problem.AddResidualBlock( - new ceres::AutoDiffCostFunction( - new Model(predictor.data() + nist_problem.predictor_size() * i, - response.data() + nist_problem.response_size() * i)), - NULL, - initial_parameters.data()); - } - - ceres::Solver::Options options; - options.max_num_iterations = 0; - Solve(options, &problem, &summaries[nist_problem.num_starts()]); - } - - double certified_cost = summaries[nist_problem.num_starts()].initial_cost; + const double certified_cost = nist_problem.certified_cost(); int num_success = 0; const int kMinNumMatchingDigits = 4; @@ -380,6 +379,7 @@ int RegressionDriver(const std::string& filename, std::cerr << "FAILURE"; std::cerr << " summary: " << summary.BriefReport() + << " Certified cost: " << certified_cost << std::endl; } else { ++num_success; @@ -389,7 +389,31 @@ int RegressionDriver(const std::string& filename, return num_success; } -void SolveNISTProblems(const ceres::Solver::Options& options) { +void SetMinimizerOptions(ceres::Solver::Options* options) { + CHECK(ceres::StringToLinearSolverType(FLAGS_linear_solver, + &options->linear_solver_type)); + CHECK(ceres::StringToPreconditionerType(FLAGS_preconditioner, + &options->preconditioner_type)); + CHECK(ceres::StringToTrustRegionStrategyType( + FLAGS_trust_region_strategy, + &options->trust_region_strategy_type)); + CHECK(ceres::StringToDoglegType(FLAGS_dogleg, &options->dogleg_type)); + + options->max_num_iterations = FLAGS_num_iterations; + options->use_nonmonotonic_steps = FLAGS_nonmonotonic_steps; + options->function_tolerance = 1e-18; + options->gradient_tolerance = 1e-18; + options->parameter_tolerance = 1e-18; +} + +void SolveNISTProblems() { + if (FLAGS_nist_data_dir.empty()) { + LOG(FATAL) << "Must specify the directory containing the NIST problems"; + } + + ceres::Solver::Options options; + SetMinimizerOptions(&options); + std::cerr << "Lower Difficulty\n"; int easy_success = 0; easy_success += RegressionDriver("Misra1a.dat", options); @@ -437,50 +461,6 @@ void SolveNISTProblems(const ceres::Solver::Options& options) { int main(int argc, char** argv) { google::ParseCommandLineFlags(&argc, &argv, true); google::InitGoogleLogging(argv[0]); - - // TODO(sameeragarwal): Test more combinations of non-linear and - // linear solvers. - ceres::Solver::Options options; - options.max_num_iterations = 10000; - options.function_tolerance *= 1e-10; - options.gradient_tolerance *= 1e-10; - options.parameter_tolerance *= 1e-10; - - options.linear_solver_type = ceres::DENSE_QR; - options.trust_region_strategy_type = ceres::LEVENBERG_MARQUARDT; - std::cerr << "Levenberg-Marquardt - DENSE_QR\n"; - SolveNISTProblems(options); - - options.trust_region_strategy_type = ceres::DOGLEG; - options.dogleg_type = ceres::TRADITIONAL_DOGLEG; - std::cerr << "\n\nTraditional Dogleg - DENSE_QR\n\n"; - SolveNISTProblems(options); - - options.trust_region_strategy_type = ceres::DOGLEG; - options.dogleg_type = ceres::SUBSPACE_DOGLEG; - std::cerr << "\n\nSubspace Dogleg - DENSE_QR\n\n"; - SolveNISTProblems(options); - - options.linear_solver_type = ceres::DENSE_NORMAL_CHOLESKY; - options.trust_region_strategy_type = ceres::LEVENBERG_MARQUARDT; - std::cerr << "Levenberg-Marquardt - DENSE_NORMAL_CHOLESKY\n"; - SolveNISTProblems(options); - - options.trust_region_strategy_type = ceres::DOGLEG; - options.dogleg_type = ceres::TRADITIONAL_DOGLEG; - std::cerr << "\n\nTraditional Dogleg - DENSE_NORMAL_CHOLESKY\n\n"; - SolveNISTProblems(options); - - options.trust_region_strategy_type = ceres::DOGLEG; - options.dogleg_type = ceres::SUBSPACE_DOGLEG; - std::cerr << "\n\nSubspace Dogleg - DENSE_NORMAL_CHOLESKY\n\n"; - SolveNISTProblems(options); - - options.linear_solver_type = ceres::CGNR; - options.preconditioner_type = ceres::JACOBI; - options.trust_region_strategy_type = ceres::LEVENBERG_MARQUARDT; - std::cerr << "Levenberg-Marquardt - CGNR + JACOBI\n"; - SolveNISTProblems(options); - + SolveNISTProblems(); return 0; }; diff --git a/include/ceres/types.h b/include/ceres/types.h index 3980885b5..2c0b7bddf 100644 --- a/include/ceres/types.h +++ b/include/ceres/types.h @@ -37,6 +37,8 @@ #ifndef CERES_PUBLIC_TYPES_H_ #define CERES_PUBLIC_TYPES_H_ +#include "ceres/internal/port.h" + namespace ceres { // Basic integer types. These typedefs are in the Ceres namespace to avoid @@ -297,16 +299,31 @@ enum DimensionType { }; const char* LinearSolverTypeToString(LinearSolverType type); +bool StringToLinearSolverType(string value, LinearSolverType* type); + const char* PreconditionerTypeToString(PreconditionerType type); +bool StringToPreconditionerType(string value, PreconditionerType* type); + const char* SparseLinearAlgebraLibraryTypeToString( SparseLinearAlgebraLibraryType type); +bool StringToSparseLinearAlgebraLibraryType( + string value, + SparseLinearAlgebraLibraryType* type); + +const char* OrderingTypeToString(OrderingType type); +bool StringToOrderingType(string value, OrderingType* type); + +const char* TrustRegionStrategyTypeToString(TrustRegionStrategyType type); +bool StringToTrustRegionStrategyType(string value, + TrustRegionStrategyType* type); + +const char* DoglegTypeToString(DoglegType type); +bool StringToDoglegType(string value, DoglegType* type); + const char* LinearSolverTerminationTypeToString( LinearSolverTerminationType type); -const char* OrderingTypeToString(OrderingType type); + const char* SolverTerminationTypeToString(SolverTerminationType type); -const char* SparseLinearAlgebraLibraryTypeToString( - SparseLinearAlgebraLibraryType type); -const char* TrustRegionStrategyTypeToString( TrustRegionStrategyType type); bool IsSchurType(LinearSolverType type); } // namespace ceres diff --git a/internal/ceres/types.cc b/internal/ceres/types.cc index 2e950c51d..9339bc4f5 100644 --- a/internal/ceres/types.cc +++ b/internal/ceres/types.cc @@ -28,13 +28,21 @@ // // Author: sameeragarwal@google.com (Sameer Agarwal) +#include #include #include "ceres/types.h" +#include "glog/logging.h" namespace ceres { #define CASESTR(x) case x: return #x +#define STRENUM(x) if (value == #x) { *type = x; return true;} + +void UpperCase(string* input) { + std::transform(input->begin(), input->end(), input->begin(), ::toupper); +} + const char* LinearSolverTypeToString(LinearSolverType solver_type) { switch (solver_type) { CASESTR(DENSE_NORMAL_CHOLESKY); @@ -49,6 +57,18 @@ const char* LinearSolverTypeToString(LinearSolverType solver_type) { } } +bool StringToLinearSolverType(string value, LinearSolverType* type) { + UpperCase(&value); + STRENUM(DENSE_NORMAL_CHOLESKY); + STRENUM(DENSE_QR); + STRENUM(SPARSE_NORMAL_CHOLESKY); + STRENUM(DENSE_SCHUR); + STRENUM(SPARSE_SCHUR); + STRENUM(ITERATIVE_SCHUR); + STRENUM(CGNR); + return false; +} + const char* PreconditionerTypeToString( PreconditionerType preconditioner_type) { switch (preconditioner_type) { @@ -62,6 +82,16 @@ const char* PreconditionerTypeToString( } } +bool StringToPreconditionerType(string value, PreconditionerType* type) { + UpperCase(&value); + STRENUM(IDENTITY); + STRENUM(JACOBI); + STRENUM(SCHUR_JACOBI); + STRENUM(CLUSTER_JACOBI); + STRENUM(CLUSTER_TRIDIAGONAL); + return false; +} + const char* SparseLinearAlgebraLibraryTypeToString( SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type) { switch (sparse_linear_algebra_library_type) { @@ -72,6 +102,16 @@ const char* SparseLinearAlgebraLibraryTypeToString( } } + +bool StringToSparseLinearAlgebraLibraryType( + string value, + SparseLinearAlgebraLibraryType* type) { + UpperCase(&value); + STRENUM(SUITE_SPARSE); + STRENUM(CX_SPARSE); + return false; +} + const char* OrderingTypeToString(OrderingType ordering_type) { switch (ordering_type) { CASESTR(NATURAL); @@ -82,6 +122,48 @@ const char* OrderingTypeToString(OrderingType ordering_type) { } } +bool StringToOrderingType(string value, OrderingType* type) { + UpperCase(&value); + STRENUM(NATURAL); + STRENUM(USER); + STRENUM(SCHUR); + return false; +} + +const char* TrustRegionStrategyTypeToString( + TrustRegionStrategyType trust_region_strategy_type) { + switch (trust_region_strategy_type) { + CASESTR(LEVENBERG_MARQUARDT); + CASESTR(DOGLEG); + default: + return "UNKNOWN"; + } +} + +bool StringToTrustRegionStrategyType(string value, + TrustRegionStrategyType* type) { + UpperCase(&value); + STRENUM(LEVENBERG_MARQUARDT); + STRENUM(DOGLEG); + return false; +} + +const char* DoglegTypeToString(DoglegType dogleg_type) { + switch (dogleg_type) { + CASESTR(TRADITIONAL_DOGLEG); + CASESTR(SUBSPACE_DOGLEG); + default: + return "UNKNOWN"; + } +} + +bool StringToDoglegType(string value, DoglegType* type) { + UpperCase(&value); + STRENUM(TRADITIONAL_DOGLEG); + STRENUM(SUBSPACE_DOGLEG); + return false; +} + const char* SolverTerminationTypeToString( SolverTerminationType termination_type) { switch (termination_type) { @@ -98,27 +180,20 @@ const char* SolverTerminationTypeToString( } } -const char* SparseLinearAlgebraTypeToString( - SparseLinearAlgebraLibraryType sparse_linear_algebra_library_type) { - switch (sparse_linear_algebra_library_type) { - CASESTR(CX_SPARSE); - CASESTR(SUITE_SPARSE); - default: - return "UNKNOWN"; - } -} - -const char* TrustRegionStrategyTypeToString( - TrustRegionStrategyType trust_region_strategy_type) { - switch (trust_region_strategy_type) { - CASESTR(LEVENBERG_MARQUARDT); - CASESTR(DOGLEG); +const char* LinearSolverTerminationTypeToString( + LinearSolverTerminationType termination_type) { + switch (termination_type) { + CASESTR(TOLERANCE); + CASESTR(MAX_ITERATIONS); + CASESTR(STAGNATION); + CASESTR(FAILURE); default: return "UNKNOWN"; } } #undef CASESTR +#undef STRENUM bool IsSchurType(LinearSolverType type) { return ((type == SPARSE_SCHUR) ||