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