loss_function_test.cc 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. // Ceres Solver - A fast non-linear least squares minimizer
  2. // Copyright 2015 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. #include "ceres/loss_function.h"
  31. #include <cstddef>
  32. #include "glog/logging.h"
  33. #include "gtest/gtest.h"
  34. namespace ceres {
  35. namespace internal {
  36. namespace {
  37. // Helper function for testing a LossFunction callback.
  38. //
  39. // Compares the values of rho'(s) and rho''(s) computed by the
  40. // callback with estimates obtained by symmetric finite differencing
  41. // of rho(s).
  42. void AssertLossFunctionIsValid(const LossFunction& loss, double s) {
  43. CHECK_GT(s, 0);
  44. // Evaluate rho(s), rho'(s) and rho''(s).
  45. double rho[3];
  46. loss.Evaluate(s, rho);
  47. // Use symmetric finite differencing to estimate rho'(s) and
  48. // rho''(s).
  49. const double kH = 1e-4;
  50. // Values at s + kH.
  51. double fwd[3];
  52. // Values at s - kH.
  53. double bwd[3];
  54. loss.Evaluate(s + kH, fwd);
  55. loss.Evaluate(s - kH, bwd);
  56. // First derivative.
  57. const double fd_1 = (fwd[0] - bwd[0]) / (2 * kH);
  58. ASSERT_NEAR(fd_1, rho[1], 1e-6);
  59. // Second derivative.
  60. const double fd_2 = (fwd[0] - 2 * rho[0] + bwd[0]) / (kH * kH);
  61. ASSERT_NEAR(fd_2, rho[2], 1e-6);
  62. }
  63. } // namespace
  64. // Try two values of the scaling a = 0.7 and 1.3
  65. // (where scaling makes sense) and of the squared norm
  66. // s = 0.357 and 1.792
  67. //
  68. // Note that for the Huber loss the test exercises both code paths
  69. // (i.e. both small and large values of s).
  70. TEST(LossFunction, TrivialLoss) {
  71. AssertLossFunctionIsValid(TrivialLoss(), 0.357);
  72. AssertLossFunctionIsValid(TrivialLoss(), 1.792);
  73. // Check that at s = 0: rho = [0, 1, 0].
  74. double rho[3];
  75. TrivialLoss().Evaluate(0.0, rho);
  76. ASSERT_NEAR(rho[0], 0.0, 1e-6);
  77. ASSERT_NEAR(rho[1], 1.0, 1e-6);
  78. ASSERT_NEAR(rho[2], 0.0, 1e-6);
  79. }
  80. TEST(LossFunction, HuberLoss) {
  81. AssertLossFunctionIsValid(HuberLoss(0.7), 0.357);
  82. AssertLossFunctionIsValid(HuberLoss(0.7), 1.792);
  83. AssertLossFunctionIsValid(HuberLoss(1.3), 0.357);
  84. AssertLossFunctionIsValid(HuberLoss(1.3), 1.792);
  85. // Check that at s = 0: rho = [0, 1, 0].
  86. double rho[3];
  87. HuberLoss(0.7).Evaluate(0.0, rho);
  88. ASSERT_NEAR(rho[0], 0.0, 1e-6);
  89. ASSERT_NEAR(rho[1], 1.0, 1e-6);
  90. ASSERT_NEAR(rho[2], 0.0, 1e-6);
  91. }
  92. TEST(LossFunction, SoftLOneLoss) {
  93. AssertLossFunctionIsValid(SoftLOneLoss(0.7), 0.357);
  94. AssertLossFunctionIsValid(SoftLOneLoss(0.7), 1.792);
  95. AssertLossFunctionIsValid(SoftLOneLoss(1.3), 0.357);
  96. AssertLossFunctionIsValid(SoftLOneLoss(1.3), 1.792);
  97. // Check that at s = 0: rho = [0, 1, -1 / (2 * a^2)].
  98. double rho[3];
  99. SoftLOneLoss(0.7).Evaluate(0.0, rho);
  100. ASSERT_NEAR(rho[0], 0.0, 1e-6);
  101. ASSERT_NEAR(rho[1], 1.0, 1e-6);
  102. ASSERT_NEAR(rho[2], -0.5 / (0.7 * 0.7), 1e-6);
  103. }
  104. TEST(LossFunction, CauchyLoss) {
  105. AssertLossFunctionIsValid(CauchyLoss(0.7), 0.357);
  106. AssertLossFunctionIsValid(CauchyLoss(0.7), 1.792);
  107. AssertLossFunctionIsValid(CauchyLoss(1.3), 0.357);
  108. AssertLossFunctionIsValid(CauchyLoss(1.3), 1.792);
  109. // Check that at s = 0: rho = [0, 1, -1 / a^2].
  110. double rho[3];
  111. CauchyLoss(0.7).Evaluate(0.0, rho);
  112. ASSERT_NEAR(rho[0], 0.0, 1e-6);
  113. ASSERT_NEAR(rho[1], 1.0, 1e-6);
  114. ASSERT_NEAR(rho[2], -1.0 / (0.7 * 0.7), 1e-6);
  115. }
  116. TEST(LossFunction, ArctanLoss) {
  117. AssertLossFunctionIsValid(ArctanLoss(0.7), 0.357);
  118. AssertLossFunctionIsValid(ArctanLoss(0.7), 1.792);
  119. AssertLossFunctionIsValid(ArctanLoss(1.3), 0.357);
  120. AssertLossFunctionIsValid(ArctanLoss(1.3), 1.792);
  121. // Check that at s = 0: rho = [0, 1, 0].
  122. double rho[3];
  123. ArctanLoss(0.7).Evaluate(0.0, rho);
  124. ASSERT_NEAR(rho[0], 0.0, 1e-6);
  125. ASSERT_NEAR(rho[1], 1.0, 1e-6);
  126. ASSERT_NEAR(rho[2], 0.0, 1e-6);
  127. }
  128. TEST(LossFunction, TolerantLoss) {
  129. AssertLossFunctionIsValid(TolerantLoss(0.7, 0.4), 0.357);
  130. AssertLossFunctionIsValid(TolerantLoss(0.7, 0.4), 1.792);
  131. AssertLossFunctionIsValid(TolerantLoss(0.7, 0.4), 55.5);
  132. AssertLossFunctionIsValid(TolerantLoss(1.3, 0.1), 0.357);
  133. AssertLossFunctionIsValid(TolerantLoss(1.3, 0.1), 1.792);
  134. AssertLossFunctionIsValid(TolerantLoss(1.3, 0.1), 55.5);
  135. // Check the value at zero is actually zero.
  136. double rho[3];
  137. TolerantLoss(0.7, 0.4).Evaluate(0.0, rho);
  138. ASSERT_NEAR(rho[0], 0.0, 1e-6);
  139. // Check that loss before and after the approximation threshold are good.
  140. // A threshold of 36.7 is used by the implementation.
  141. AssertLossFunctionIsValid(TolerantLoss(20.0, 1.0), 20.0 + 36.6);
  142. AssertLossFunctionIsValid(TolerantLoss(20.0, 1.0), 20.0 + 36.7);
  143. AssertLossFunctionIsValid(TolerantLoss(20.0, 1.0), 20.0 + 36.8);
  144. AssertLossFunctionIsValid(TolerantLoss(20.0, 1.0), 20.0 + 1000.0);
  145. }
  146. TEST(LossFunction, TukeyLoss) {
  147. AssertLossFunctionIsValid(TukeyLoss(0.7), 0.357);
  148. AssertLossFunctionIsValid(TukeyLoss(0.7), 1.792);
  149. AssertLossFunctionIsValid(TukeyLoss(1.3), 0.357);
  150. AssertLossFunctionIsValid(TukeyLoss(1.3), 1.792);
  151. // Check that at s = 0: rho = [0, 1, -2 / a^2].
  152. double rho[3];
  153. TukeyLoss(0.7).Evaluate(0.0, rho);
  154. ASSERT_NEAR(rho[0], 0.0, 1e-6);
  155. ASSERT_NEAR(rho[1], 1.0, 1e-6);
  156. ASSERT_NEAR(rho[2], -2.0 / (0.7 * 0.7), 1e-6);
  157. }
  158. TEST(LossFunction, ComposedLoss) {
  159. {
  160. HuberLoss f(0.7);
  161. CauchyLoss g(1.3);
  162. ComposedLoss c(&f, DO_NOT_TAKE_OWNERSHIP, &g, DO_NOT_TAKE_OWNERSHIP);
  163. AssertLossFunctionIsValid(c, 0.357);
  164. AssertLossFunctionIsValid(c, 1.792);
  165. }
  166. {
  167. CauchyLoss f(0.7);
  168. HuberLoss g(1.3);
  169. ComposedLoss c(&f, DO_NOT_TAKE_OWNERSHIP, &g, DO_NOT_TAKE_OWNERSHIP);
  170. AssertLossFunctionIsValid(c, 0.357);
  171. AssertLossFunctionIsValid(c, 1.792);
  172. }
  173. }
  174. TEST(LossFunction, ScaledLoss) {
  175. // Wrap a few loss functions, and a few scale factors. This can't combine
  176. // construction with the call to AssertLossFunctionIsValid() because Apple's
  177. // GCC is unable to eliminate the copy of ScaledLoss, which is not copyable.
  178. {
  179. ScaledLoss scaled_loss(nullptr, 6, TAKE_OWNERSHIP);
  180. AssertLossFunctionIsValid(scaled_loss, 0.323);
  181. }
  182. {
  183. ScaledLoss scaled_loss(new TrivialLoss(), 10, TAKE_OWNERSHIP);
  184. AssertLossFunctionIsValid(scaled_loss, 0.357);
  185. }
  186. {
  187. ScaledLoss scaled_loss(new HuberLoss(0.7), 0.1, TAKE_OWNERSHIP);
  188. AssertLossFunctionIsValid(scaled_loss, 1.792);
  189. }
  190. {
  191. ScaledLoss scaled_loss(new SoftLOneLoss(1.3), 0.1, TAKE_OWNERSHIP);
  192. AssertLossFunctionIsValid(scaled_loss, 1.792);
  193. }
  194. {
  195. ScaledLoss scaled_loss(new CauchyLoss(1.3), 10, TAKE_OWNERSHIP);
  196. AssertLossFunctionIsValid(scaled_loss, 1.792);
  197. }
  198. {
  199. ScaledLoss scaled_loss(new ArctanLoss(1.3), 10, TAKE_OWNERSHIP);
  200. AssertLossFunctionIsValid(scaled_loss, 1.792);
  201. }
  202. {
  203. ScaledLoss scaled_loss(new TolerantLoss(1.3, 0.1), 10, TAKE_OWNERSHIP);
  204. AssertLossFunctionIsValid(scaled_loss, 1.792);
  205. }
  206. {
  207. ScaledLoss scaled_loss(new ComposedLoss(new HuberLoss(0.8),
  208. TAKE_OWNERSHIP,
  209. new TolerantLoss(1.3, 0.5),
  210. TAKE_OWNERSHIP),
  211. 10,
  212. TAKE_OWNERSHIP);
  213. AssertLossFunctionIsValid(scaled_loss, 1.792);
  214. }
  215. }
  216. TEST(LossFunction, LossFunctionWrapper) {
  217. // Initialization
  218. HuberLoss loss_function1(1.0);
  219. LossFunctionWrapper loss_function_wrapper(new HuberLoss(1.0), TAKE_OWNERSHIP);
  220. double s = 0.862;
  221. double rho_gold[3];
  222. double rho[3];
  223. loss_function1.Evaluate(s, rho_gold);
  224. loss_function_wrapper.Evaluate(s, rho);
  225. for (int i = 0; i < 3; ++i) {
  226. EXPECT_NEAR(rho[i], rho_gold[i], 1e-12);
  227. }
  228. // Resetting
  229. HuberLoss loss_function2(0.5);
  230. loss_function_wrapper.Reset(new HuberLoss(0.5), TAKE_OWNERSHIP);
  231. loss_function_wrapper.Evaluate(s, rho);
  232. loss_function2.Evaluate(s, rho_gold);
  233. for (int i = 0; i < 3; ++i) {
  234. EXPECT_NEAR(rho[i], rho_gold[i], 1e-12);
  235. }
  236. // Not taking ownership.
  237. HuberLoss loss_function3(0.3);
  238. loss_function_wrapper.Reset(&loss_function3, DO_NOT_TAKE_OWNERSHIP);
  239. loss_function_wrapper.Evaluate(s, rho);
  240. loss_function3.Evaluate(s, rho_gold);
  241. for (int i = 0; i < 3; ++i) {
  242. EXPECT_NEAR(rho[i], rho_gold[i], 1e-12);
  243. }
  244. // Set to nullptr
  245. TrivialLoss loss_function4;
  246. loss_function_wrapper.Reset(nullptr, TAKE_OWNERSHIP);
  247. loss_function_wrapper.Evaluate(s, rho);
  248. loss_function4.Evaluate(s, rho_gold);
  249. for (int i = 0; i < 3; ++i) {
  250. EXPECT_NEAR(rho[i], rho_gold[i], 1e-12);
  251. }
  252. // Set to nullptr, not taking ownership
  253. loss_function_wrapper.Reset(nullptr, DO_NOT_TAKE_OWNERSHIP);
  254. loss_function_wrapper.Evaluate(s, rho);
  255. loss_function4.Evaluate(s, rho_gold);
  256. for (int i = 0; i < 3; ++i) {
  257. EXPECT_NEAR(rho[i], rho_gold[i], 1e-12);
  258. }
  259. }
  260. } // namespace internal
  261. } // namespace ceres