Update nist.cc to better evaluate results.
Ceres beats Minpack and HBN handily.
Change-Id: I7df8a47b753202ed0b53ab128ce48466bf9f8083
diff --git a/examples/nist.cc b/examples/nist.cc
index 440ab5c..7504e51 100644
--- a/examples/nist.cc
+++ b/examples/nist.cc
@@ -28,18 +28,48 @@
//
// Author: sameeragarwal@google.com (Sameer Agarwal)
//
-// NIST non-linear regression problems solved using Ceres.
+// The National Institute of Standards and Technology has released a
+// set of problems to test non-linear least squares solvers.
//
-// The data was obtained from
-// http://www.itl.nist.gov/div898/strd/nls/nls_main.shtml, where more
-// background on these problems can also be found.
+// More information about the background on these problems and
+// suggested evaluation methodology can be found at:
//
-// Currently not all problems are solved successfully. Some of the
-// failures are due to convergence to a local minimum, and some fail
-// because of numerical issues.
+// http://www.itl.nist.gov/div898/strd/nls/nls_info.shtml
//
-// TODO(sameeragarwal): Fix numerical issues so that all the problems
-// converge and then look at convergence to the wrong solution issues.
+// The problem data themselves can be found at
+//
+// http://www.itl.nist.gov/div898/strd/nls/nls_main.shtml
+//
+// The problems are divided into three levels of difficulty, Easy,
+// Medium and Hard. For each problem there are two starting guesses,
+// the first one far away from the global minimum and the second
+// closer to it.
+//
+// A problem is considered successfully solved, if every components of
+// the solution matches the globally optimal solution in at least 4
+// digits or more.
+//
+// This dataset was used for an evaluation of Non-linear least squares
+// solvers:
+//
+// P. F. Mondragon & B. Borchers, A Comparison of Nonlinear Regression
+// Codes, Journal of Modern Applied Statistical Methods, 4(1):343-351,
+// 2005.
+//
+// The results from Mondragon & Borchers can be summarized as
+// Excel Gnuplot GaussFit HBN MinPack
+// Average LRE 2.3 4.3 4.0 6.8 4.4
+// Winner 1 5 12 29 12
+//
+// Where the row Winner counts, the number of problems for which the
+// solver had the highest LRE.
+
+// In this file, we implement the same evaluation methodology using
+// Ceres. Currently using Levenberg-Marquard with DENSE_QR, we get
+//
+// Excel Gnuplot GaussFit HBN MinPack Ceres
+// Average LRE 2.3 4.3 4.0 6.8 4.4 9.4
+// Winner 0 0 5 11 2 41
#include <iostream>
#include <fstream>
@@ -347,11 +377,12 @@
Matrix predictor = nist_problem.predictor();
Matrix response = nist_problem.response();
Matrix final_parameters = nist_problem.final_parameters();
- std::vector<ceres::Solver::Summary> summaries(nist_problem.num_starts() + 1);
- std::cerr << filename << std::endl;
+
+ printf("%s\n", filename.c_str());
// Each NIST problem comes with multiple starting points, so we
// construct the problem from scratch for each case and solve it.
+ int num_success = 0;
for (int start = 0; start < nist_problem.num_starts(); ++start) {
Matrix initial_parameters = nist_problem.initial_parameters(start);
@@ -365,39 +396,41 @@
initial_parameters.data());
}
- Solve(options, &problem, &summaries[start]);
- }
+ ceres::Solver::Summary summary;
+ Solve(options, &problem, &summary);
- const double certified_cost = nist_problem.certified_cost();
-
- int num_success = 0;
- const int kMinNumMatchingDigits = 4;
- for (int start = 0; start < nist_problem.num_starts(); ++start) {
- const ceres::Solver::Summary& summary = summaries[start];
-
- int num_matching_digits = 0;
- if (IsSuccessfulTermination(summary.termination_type)
- && summary.final_cost < certified_cost) {
- num_matching_digits = kMinNumMatchingDigits + 1;
- } else {
- num_matching_digits =
- -std::log10(fabs(summary.final_cost - certified_cost) / certified_cost);
+ // Compute the LRE by comparing each component of the solution
+ // with the ground truth, and taking the minimum.
+ Matrix final_parameters = nist_problem.final_parameters();
+ const double kMaxNumSignificantDigits = 11;
+ double log_relative_error = kMaxNumSignificantDigits + 1;
+ for (int i = 0; i < num_parameters; ++i) {
+ const double tmp_lre =
+ -std::log10(std::fabs(final_parameters(i) - initial_parameters(i)) /
+ std::fabs(final_parameters(i)));
+ // The maximum LRE is capped at 11 - the precision at which the
+ // ground truth is known.
+ //
+ // The minimum LRE is capped at 0 - no digits match between the
+ // computed solution and the ground truth.
+ log_relative_error =
+ std::min(log_relative_error,
+ std::max(0.0, std::min(kMaxNumSignificantDigits, tmp_lre)));
}
- std::cerr << "start " << start + 1 << " " ;
- if (num_matching_digits <= kMinNumMatchingDigits) {
- std::cerr << "FAILURE";
- } else {
- std::cerr << "SUCCESS";
+ const int kMinNumMatchingDigits = 4;
+ if (log_relative_error >= kMinNumMatchingDigits) {
++num_success;
}
- std::cerr << " summary: "
- << summary.BriefReport()
- << " Certified cost: " << certified_cost
- << std::endl;
+ printf("start: %d status: %s lre: %4.1f initial cost: %e final cost:%e certified cost: %e\n",
+ start + 1,
+ log_relative_error < kMinNumMatchingDigits ? "FAILURE" : "SUCCESS",
+ log_relative_error,
+ summary.initial_cost,
+ summary.final_cost,
+ nist_problem.certified_cost());
}
-
return num_success;
}
@@ -427,7 +460,7 @@
ceres::Solver::Options options;
SetMinimizerOptions(&options);
- std::cerr << "Lower Difficulty\n";
+ std::cout << "Lower Difficulty\n";
int easy_success = 0;
easy_success += RegressionDriver<Misra1a, 1, 2>("Misra1a.dat", options);
easy_success += RegressionDriver<Chwirut, 1, 3>("Chwirut1.dat", options);
@@ -438,7 +471,7 @@
easy_success += RegressionDriver<DanWood, 1, 2>("DanWood.dat", options);
easy_success += RegressionDriver<Misra1b, 1, 2>("Misra1b.dat", options);
- std::cerr << "\nMedium Difficulty\n";
+ std::cout << "\nMedium Difficulty\n";
int medium_success = 0;
medium_success += RegressionDriver<Kirby2, 1, 5>("Kirby2.dat", options);
medium_success += RegressionDriver<Hahn1, 1, 7>("Hahn1.dat", options);
@@ -452,7 +485,7 @@
medium_success += RegressionDriver<Roszman1, 1, 4>("Roszman1.dat", options);
medium_success += RegressionDriver<ENSO, 1, 9>("ENSO.dat", options);
- std::cerr << "\nHigher Difficulty\n";
+ std::cout << "\nHigher Difficulty\n";
int hard_success = 0;
hard_success += RegressionDriver<MGH09, 1, 4>("MGH09.dat", options);
hard_success += RegressionDriver<Thurber, 1, 7>("Thurber.dat", options);
@@ -464,11 +497,11 @@
hard_success += RegressionDriver<Rat43, 1, 4>("Rat43.dat", options);
hard_success += RegressionDriver<Bennet5, 1, 3>("Bennett5.dat", options);
- std::cerr << "\n";
- std::cerr << "Easy : " << easy_success << "/16\n";
- std::cerr << "Medium : " << medium_success << "/22\n";
- std::cerr << "Hard : " << hard_success << "/16\n";
- std::cerr << "Total : " << easy_success + medium_success + hard_success << "/54\n";
+ std::cout << "\n";
+ std::cout << "Easy : " << easy_success << "/16\n";
+ std::cout << "Medium : " << medium_success << "/22\n";
+ std::cout << "Hard : " << hard_success << "/16\n";
+ std::cout << "Total : " << easy_success + medium_success + hard_success << "/54\n";
}
int main(int argc, char** argv) {