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: keir@google.com (Keir Mierle)
30
31 #include "ceres/gradient_checking_cost_function.h"
32
33 #include <cmath>
34 #include <vector>
35 #include "ceres/cost_function.h"
36 #include "ceres/internal/scoped_ptr.h"
37 #include "ceres/local_parameterization.h"
38 #include "ceres/loss_function.h"
39 #include "ceres/parameter_block.h"
40 #include "ceres/problem_impl.h"
41 #include "ceres/program.h"
42 #include "ceres/random.h"
43 #include "ceres/residual_block.h"
44 #include "ceres/sized_cost_function.h"
45 #include "ceres/types.h"
46 #include "glog/logging.h"
47 #include "gmock/gmock.h"
48 #include "gmock/mock-log.h"
49 #include "gtest/gtest.h"
50
51 using testing::AllOf;
52 using testing::AnyNumber;
53 using testing::HasSubstr;
54 using testing::ScopedMockLog;
55 using testing::_;
56
57 namespace ceres {
58 namespace internal {
59
60 // Pick a (non-quadratic) function whose derivative are easy:
61 //
62 // f = exp(- a' x).
63 // df = - f a.
64 //
65 // where 'a' is a vector of the same size as 'x'. In the block
66 // version, they are both block vectors, of course.
67 template<int bad_block = 1, int bad_variable = 2>
68 class TestTerm : public CostFunction {
69 public:
70 // The constructor of this function needs to know the number
71 // of blocks desired, and the size of each block.
TestTerm(int arity,int const * dim)72 TestTerm(int arity, int const *dim) : arity_(arity) {
73 // Make 'arity' random vectors.
74 a_.resize(arity_);
75 for (int j = 0; j < arity_; ++j) {
76 a_[j].resize(dim[j]);
77 for (int u = 0; u < dim[j]; ++u) {
78 a_[j][u] = 2.0 * RandDouble() - 1.0;
79 }
80 }
81
82 for (int i = 0; i < arity_; i++) {
83 mutable_parameter_block_sizes()->push_back(dim[i]);
84 }
85 set_num_residuals(1);
86 }
87
Evaluate(double const * const * parameters,double * residuals,double ** jacobians) const88 bool Evaluate(double const* const* parameters,
89 double* residuals,
90 double** jacobians) const {
91 // Compute a . x.
92 double ax = 0;
93 for (int j = 0; j < arity_; ++j) {
94 for (int u = 0; u < parameter_block_sizes()[j]; ++u) {
95 ax += a_[j][u] * parameters[j][u];
96 }
97 }
98
99 // This is the cost, but also appears as a factor
100 // in the derivatives.
101 double f = *residuals = exp(-ax);
102
103 // Accumulate 1st order derivatives.
104 if (jacobians) {
105 for (int j = 0; j < arity_; ++j) {
106 if (jacobians[j]) {
107 for (int u = 0; u < parameter_block_sizes()[j]; ++u) {
108 // See comments before class.
109 jacobians[j][u] = - f * a_[j][u];
110
111 if (bad_block == j && bad_variable == u) {
112 // Whoopsiedoopsie! Deliberately introduce a faulty jacobian entry
113 // like what happens when users make an error in their jacobian
114 // computations. This should get detected.
115 LOG(INFO) << "Poisoning jacobian for parameter block " << j
116 << ", row 0, column " << u;
117 jacobians[j][u] += 500;
118 }
119 }
120 }
121 }
122 }
123
124 return true;
125 }
126
127 private:
128 int arity_;
129 vector<vector<double> > a_;
130 };
131
TEST(GradientCheckingCostFunction,ResidualsAndJacobiansArePreservedTest)132 TEST(GradientCheckingCostFunction, ResidualsAndJacobiansArePreservedTest) {
133 srand(5);
134
135 // Test with 3 blocks of size 2, 3 and 4.
136 int const arity = 3;
137 int const dim[arity] = { 2, 3, 4 };
138
139 // Make a random set of blocks.
140 vector<double*> parameters(arity);
141 for (int j = 0; j < arity; ++j) {
142 parameters[j] = new double[dim[j]];
143 for (int u = 0; u < dim[j]; ++u) {
144 parameters[j][u] = 2.0 * RandDouble() - 1.0;
145 }
146 }
147
148 double original_residual;
149 double residual;
150 vector<double*> original_jacobians(arity);
151 vector<double*> jacobians(arity);
152
153 for (int j = 0; j < arity; ++j) {
154 // Since residual is one dimensional the jacobians have the same
155 // size as the parameter blocks.
156 jacobians[j] = new double[dim[j]];
157 original_jacobians[j] = new double[dim[j]];
158 }
159
160 const double kRelativeStepSize = 1e-6;
161 const double kRelativePrecision = 1e-4;
162
163 TestTerm<-1, -1> term(arity, dim);
164 scoped_ptr<CostFunction> gradient_checking_cost_function(
165 CreateGradientCheckingCostFunction(&term,
166 kRelativeStepSize,
167 kRelativePrecision,
168 "Ignored."));
169 term.Evaluate(¶meters[0],
170 &original_residual,
171 &original_jacobians[0]);
172
173 gradient_checking_cost_function->Evaluate(¶meters[0],
174 &residual,
175 &jacobians[0]);
176 EXPECT_EQ(original_residual, residual);
177
178 for (int j = 0; j < arity; j++) {
179 for (int k = 0; k < dim[j]; ++k) {
180 EXPECT_EQ(original_jacobians[j][k], jacobians[j][k]);
181 }
182
183 delete[] parameters[j];
184 delete[] jacobians[j];
185 delete[] original_jacobians[j];
186 }
187 }
188
TEST(GradientCheckingCostFunction,SmokeTest)189 TEST(GradientCheckingCostFunction, SmokeTest) {
190 srand(5);
191
192 // Test with 3 blocks of size 2, 3 and 4.
193 int const arity = 3;
194 int const dim[arity] = { 2, 3, 4 };
195
196 // Make a random set of blocks.
197 vector<double*> parameters(arity);
198 for (int j = 0; j < arity; ++j) {
199 parameters[j] = new double[dim[j]];
200 for (int u = 0; u < dim[j]; ++u) {
201 parameters[j][u] = 2.0 * RandDouble() - 1.0;
202 }
203 }
204
205 double residual;
206 vector<double*> jacobians(arity);
207 for (int j = 0; j < arity; ++j) {
208 // Since residual is one dimensional the jacobians have the same size as the
209 // parameter blocks.
210 jacobians[j] = new double[dim[j]];
211 }
212
213 const double kRelativeStepSize = 1e-6;
214 const double kRelativePrecision = 1e-4;
215
216 // Should have one term that's bad, causing everything to get dumped.
217 LOG(INFO) << "Bad gradient";
218 {
219 TestTerm<1, 2> term(arity, dim);
220 scoped_ptr<CostFunction> gradient_checking_cost_function(
221 CreateGradientCheckingCostFunction(&term,
222 kRelativeStepSize,
223 kRelativePrecision,
224 "Fuzzy bananas"));
225
226 ScopedMockLog log;
227 EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
228 EXPECT_CALL(log, Log(WARNING, _,
229 AllOf(HasSubstr("(1,0,2) Relative error worse than"),
230 HasSubstr("Fuzzy bananas"))));
231
232 gradient_checking_cost_function->Evaluate(¶meters[0],
233 &residual,
234 &jacobians[0]);
235 }
236
237 // The gradient is correct, so no errors are reported.
238 LOG(INFO) << "Good gradient";
239 {
240 TestTerm<-1, -1> term(arity, dim);
241 scoped_ptr<CostFunction> gradient_checking_cost_function(
242 CreateGradientCheckingCostFunction(&term,
243 kRelativeStepSize,
244 kRelativePrecision,
245 "Ignored."));
246
247 ScopedMockLog log;
248 EXPECT_CALL(log, Log(_, _, _)).Times(0);
249
250 gradient_checking_cost_function->Evaluate(¶meters[0],
251 &residual,
252 &jacobians[0]);
253 }
254
255 for (int j = 0; j < arity; j++) {
256 delete[] parameters[j];
257 delete[] jacobians[j];
258 }
259 }
260
261 // The following three classes are for the purposes of defining
262 // function signatures. They have dummy Evaluate functions.
263
264 // Trivial cost function that accepts a single argument.
265 class UnaryCostFunction : public CostFunction {
266 public:
UnaryCostFunction(int num_residuals,int32 parameter_block_size)267 UnaryCostFunction(int num_residuals, int32 parameter_block_size) {
268 set_num_residuals(num_residuals);
269 mutable_parameter_block_sizes()->push_back(parameter_block_size);
270 }
~UnaryCostFunction()271 virtual ~UnaryCostFunction() {}
272
Evaluate(double const * const * parameters,double * residuals,double ** jacobians) const273 virtual bool Evaluate(double const* const* parameters,
274 double* residuals,
275 double** jacobians) const {
276 for (int i = 0; i < num_residuals(); ++i) {
277 residuals[i] = 1;
278 }
279 return true;
280 }
281 };
282
283 // Trivial cost function that accepts two arguments.
284 class BinaryCostFunction: public CostFunction {
285 public:
BinaryCostFunction(int num_residuals,int32 parameter_block1_size,int32 parameter_block2_size)286 BinaryCostFunction(int num_residuals,
287 int32 parameter_block1_size,
288 int32 parameter_block2_size) {
289 set_num_residuals(num_residuals);
290 mutable_parameter_block_sizes()->push_back(parameter_block1_size);
291 mutable_parameter_block_sizes()->push_back(parameter_block2_size);
292 }
293
Evaluate(double const * const * parameters,double * residuals,double ** jacobians) const294 virtual bool Evaluate(double const* const* parameters,
295 double* residuals,
296 double** jacobians) const {
297 for (int i = 0; i < num_residuals(); ++i) {
298 residuals[i] = 2;
299 }
300 return true;
301 }
302 };
303
304 // Trivial cost function that accepts three arguments.
305 class TernaryCostFunction: public CostFunction {
306 public:
TernaryCostFunction(int num_residuals,int32 parameter_block1_size,int32 parameter_block2_size,int32 parameter_block3_size)307 TernaryCostFunction(int num_residuals,
308 int32 parameter_block1_size,
309 int32 parameter_block2_size,
310 int32 parameter_block3_size) {
311 set_num_residuals(num_residuals);
312 mutable_parameter_block_sizes()->push_back(parameter_block1_size);
313 mutable_parameter_block_sizes()->push_back(parameter_block2_size);
314 mutable_parameter_block_sizes()->push_back(parameter_block3_size);
315 }
316
Evaluate(double const * const * parameters,double * residuals,double ** jacobians) const317 virtual bool Evaluate(double const* const* parameters,
318 double* residuals,
319 double** jacobians) const {
320 for (int i = 0; i < num_residuals(); ++i) {
321 residuals[i] = 3;
322 }
323 return true;
324 }
325 };
326
327 // Verify that the two ParameterBlocks are formed from the same user
328 // array and have the same LocalParameterization object.
ParameterBlocksAreEquivalent(const ParameterBlock * left,const ParameterBlock * right)329 void ParameterBlocksAreEquivalent(const ParameterBlock* left,
330 const ParameterBlock* right) {
331 CHECK_NOTNULL(left);
332 CHECK_NOTNULL(right);
333 EXPECT_EQ(left->user_state(), right->user_state());
334 EXPECT_EQ(left->Size(), right->Size());
335 EXPECT_EQ(left->Size(), right->Size());
336 EXPECT_EQ(left->LocalSize(), right->LocalSize());
337 EXPECT_EQ(left->local_parameterization(), right->local_parameterization());
338 EXPECT_EQ(left->IsConstant(), right->IsConstant());
339 }
340
TEST(GradientCheckingProblemImpl,ProblemDimensionsMatch)341 TEST(GradientCheckingProblemImpl, ProblemDimensionsMatch) {
342 // Parameter blocks with arbitrarily chosen initial values.
343 double x[] = {1.0, 2.0, 3.0};
344 double y[] = {4.0, 5.0, 6.0, 7.0};
345 double z[] = {8.0, 9.0, 10.0, 11.0, 12.0};
346 double w[] = {13.0, 14.0, 15.0, 16.0};
347
348 ProblemImpl problem_impl;
349 problem_impl.AddParameterBlock(x, 3);
350 problem_impl.AddParameterBlock(y, 4);
351 problem_impl.SetParameterBlockConstant(y);
352 problem_impl.AddParameterBlock(z, 5);
353 problem_impl.AddParameterBlock(w, 4, new QuaternionParameterization);
354 problem_impl.AddResidualBlock(new UnaryCostFunction(2, 3), NULL, x);
355 problem_impl.AddResidualBlock(new BinaryCostFunction(6, 5, 4) ,
356 NULL, z, y);
357 problem_impl.AddResidualBlock(new BinaryCostFunction(3, 3, 5),
358 new TrivialLoss, x, z);
359 problem_impl.AddResidualBlock(new BinaryCostFunction(7, 5, 3),
360 NULL, z, x);
361 problem_impl.AddResidualBlock(new TernaryCostFunction(1, 5, 3, 4),
362 NULL, z, x, y);
363
364 scoped_ptr<ProblemImpl> gradient_checking_problem_impl(
365 CreateGradientCheckingProblemImpl(&problem_impl, 1.0, 1.0));
366
367 // The dimensions of the two problems match.
368 EXPECT_EQ(problem_impl.NumParameterBlocks(),
369 gradient_checking_problem_impl->NumParameterBlocks());
370 EXPECT_EQ(problem_impl.NumResidualBlocks(),
371 gradient_checking_problem_impl->NumResidualBlocks());
372
373 EXPECT_EQ(problem_impl.NumParameters(),
374 gradient_checking_problem_impl->NumParameters());
375 EXPECT_EQ(problem_impl.NumResiduals(),
376 gradient_checking_problem_impl->NumResiduals());
377
378 const Program& program = problem_impl.program();
379 const Program& gradient_checking_program =
380 gradient_checking_problem_impl->program();
381
382 // Since we added the ParameterBlocks and ResidualBlocks explicitly,
383 // they should be in the same order in the two programs. It is
384 // possible that may change due to implementation changes to
385 // Program. This is not exepected to be the case and writing code to
386 // anticipate that possibility not worth the extra complexity in
387 // this test.
388 for (int i = 0; i < program.parameter_blocks().size(); ++i) {
389 ParameterBlocksAreEquivalent(
390 program.parameter_blocks()[i],
391 gradient_checking_program.parameter_blocks()[i]);
392 }
393
394 for (int i = 0; i < program.residual_blocks().size(); ++i) {
395 // Compare the sizes of the two ResidualBlocks.
396 const ResidualBlock* original_residual_block =
397 program.residual_blocks()[i];
398 const ResidualBlock* new_residual_block =
399 gradient_checking_program.residual_blocks()[i];
400 EXPECT_EQ(original_residual_block->NumParameterBlocks(),
401 new_residual_block->NumParameterBlocks());
402 EXPECT_EQ(original_residual_block->NumResiduals(),
403 new_residual_block->NumResiduals());
404 EXPECT_EQ(original_residual_block->NumScratchDoublesForEvaluate(),
405 new_residual_block->NumScratchDoublesForEvaluate());
406
407 // Verify that the ParameterBlocks for the two residuals are equivalent.
408 for (int j = 0; j < original_residual_block->NumParameterBlocks(); ++j) {
409 ParameterBlocksAreEquivalent(
410 original_residual_block->parameter_blocks()[j],
411 new_residual_block->parameter_blocks()[j]);
412 }
413 }
414 }
415
416 } // namespace internal
417 } // namespace ceres
418