1 // This is mul/clsfy/tests/test_binary_hyperplane.cxx
2 // Copyright: (C) 2000 British Telecommunications PLC
3 #include <iostream>
4 #include <iomanip>
5 #include <ios>
6 #include <string>
7 #include <algorithm>
8 #include <cmath>
9 #include "testlib/testlib_test.h"
10 //:
11 // \file
12 // \brief Tests the clsfy_binary_pdf_classifier class
13 // \author Ian Scott
14 // Test construction, IO etc.
15
16 #ifdef _MSC_VER
17 # include "vcl_msvc_warnings.h"
18 #endif
19 #include <clsfy/clsfy_rbf_parzen.h>
20 #include <clsfy/clsfy_k_nearest_neighbour.h>
21 #include <clsfy/clsfy_binary_hyperplane.h>
22 #include <clsfy/clsfy_binary_hyperplane_ls_builder.h>
23 #include <clsfy/clsfy_binary_hyperplane_gmrho_builder.h>
24 #include <vpdfl/vpdfl_axis_gaussian.h>
25 #include <vpdfl/vpdfl_axis_gaussian_sampler.h>
26 #include "vnl/vnl_random.h"
27 #include "vnl/vnl_math.h"
28 #include "vsl/vsl_binary_loader.h"
29 #include "vsl/vsl_vector_io.h"
30 #include <mbl/mbl_data_array_wrapper.h>
31 #include "vpl/vpl.h" // vpl_unlink()
32
33 #ifndef LEAVE_FILES_BEHIND
34 #define LEAVE_FILES_BEHIND 0
35 #endif
36
37 void test_clsfy_geman_mcclure_build();
38 //: Tests the clsfy_binary_hyperplane and clsfy_binary_hyperplane_builder classes
test_binary_hyperplane()39 void test_binary_hyperplane()
40 {
41 std::cout << "*****************************\n"
42 << " Testing clsfy_binary_linear\n"
43 << "*****************************\n";
44
45 std::vector<vpdfl_axis_gaussian_sampler *> generator(2);//
46 constexpr unsigned nDims = 2;
47 vnl_vector<double> mean0(nDims), var0(nDims), mean1(nDims), var1(nDims);
48 vpdfl_axis_gaussian PDF0, PDF1;
49
50 mean0.fill(-1.0); mean0(1) = -0.5;
51 mean1.fill(1.0);
52 var0.fill(0.6); var0(0) = 0.8;
53 var1.fill(0.1); var1(0) = 0.8;
54 PDF0.set(mean0, var0);
55 PDF1.set(mean1, var1);
56
57 generator[0] = (vpdfl_axis_gaussian_sampler *)PDF0.new_sampler();
58 generator[1] = (vpdfl_axis_gaussian_sampler *)PDF1.new_sampler();
59 vnl_random rng;
60 rng.reseed(111333);
61
62 constexpr unsigned nSamples = 50;
63 constexpr unsigned nTestSamples = 501;
64 std::vector<vnl_vector<double> > trainingVectors(nSamples);
65 std::vector<vnl_vector<double> > testVectors(nTestSamples);
66 std::vector<unsigned> labels(nSamples);
67 std::vector<unsigned> testLabels(nTestSamples);
68 vnl_vector<double> s;
69
70 std::cout << "Generating test data\n";
71 for (unsigned int i=0; i<nSamples; i++)
72 {
73 int c = rng.lrand32(0,1);
74 labels[i] = c;
75 generator[c]->sample(s);
76 trainingVectors[i] = s;
77 }
78 for (unsigned i=0; i<nTestSamples; i++)
79 {
80 int c = rng.lrand32(0, 1);
81 testLabels[i] = c;
82 generator[c]->sample(s);
83 testVectors[i] = s;
84 }
85 delete generator[0];
86 delete generator[1];
87
88 std::cout << "****************The Training set****************\n";
89
90 vnl_vector<double> x(nDims);
91 std::vector<double> out(1);
92 x.fill(0.0);
93 std::cout << "x(2) varies across from -2 to + 2\n"
94 << "x(1) varies down from 2 to -2\n";
95
96 #if 0
97
98 clsfy_k_nearest_neighbour knn;
99 knn.set(trainingVectors, labels);
100 knn.set_k(3);
101 std::cout << "\nKNN output\n" << std::setprecision(4);
102 for (x(0) = 2; x(0) >= -2 ; x(0) -= 0.25)
103 {
104 for (x(1) = -2; x(1) <= 2 ; x(1) += 0.25)
105 {
106 knn.class_probabilities(out, x);
107 std::cout << std::fixed << std::setw(3) << out[0] << ' ';
108 }
109 std::cout << std::endl;
110 }
111
112 for (x(0) = 2; x(0) >= -2; x(0) -= 0.25)
113 {
114 for (x(1) = -2; x(1) <= 2 ; x(1) += 0.25)
115 {
116 std::cout << knn.classify(x);
117 }
118 std::cout << std::endl;
119 }
120
121 clsfy_rbf_parzen win;
122 win.set(trainingVectors, labels);
123 win.set_rbf_width(0.2);
124 win.set_power(10);
125 std::cout << "x(2) varies across from -2 to +2\n"
126 << "x(1) varies down from 2 to -2\n\n"
127 << "Training data distribution\n"
128 << std::setprecision(1);
129 for (x(0) = 2; x(0) >= -2 ; x(0) -= 0.25)
130 {
131 for (x(1) = -2; x(1) <= 2 ; x(1) += 0.25)
132 {
133 double v = win.weightings(x);
134 if (v < 0.05)
135 std::cout << " . ";
136 else
137 std::cout << std::fixed << std::setw(4) << v << ' ';
138 }
139 std::cout << std::endl;
140 }
141 #endif
142 std::cout<<"======== TESTING BUILDING ===========\n" << std::setprecision(6);
143
144 clsfy_binary_hyperplane_ls_builder builder;
145
146 auto *pClassifier =
147 (clsfy_binary_hyperplane*) builder.new_classifier();
148 std::cout << "Finding Least Squares Separator using least squares\n";
149 mbl_data_array_wrapper<vnl_vector<double> > training_set(trainingVectors);
150 std::cout << "Error on Training set ";
151 double train_error = builder.build(*pClassifier, training_set, labels);
152 std::cout << train_error << std::endl;
153 TEST_NEAR("Train error on classifier is good enough", train_error, 0.0, 0.05);
154
155 std::cout << "****************Testing over test set**************\n";
156
157 mbl_data_array_wrapper<vnl_vector<double> > test_set_inputs(testVectors);
158
159 double test_error = clsfy_test_error(*pClassifier, test_set_inputs, testLabels);
160 std::cout << "Error on Testing set " << test_error << std::endl;
161
162 TEST_NEAR("Test error on classifier is good enough", test_error, 0.0, 0.1);
163
164 // print input, print output
165
166 x.fill(0.0);
167 std::cout << "x(2) varies across from -2 to +2\n"
168 << "x(1) varies down from 2 to -2\n" << std::setprecision(4);
169 for (x(0) = 2; x(0) >= -2 ; x(0) -= 0.25)
170 {
171 for (x(1) = -2; x(1) <= 2 ; x(1) += 0.25)
172 {
173 pClassifier->class_probabilities(out, x);
174 std::cout << out[0] << ' ';
175 }
176 std::cout << std::endl;
177 }
178
179 for (x(0) = 2; x(0) >= -2 ; x(0) -= 0.25)
180 {
181 for (x(1) = -2; x(1) <= 2 ; x(1) += 0.25)
182 {
183 std::cout << pClassifier->classify(x) << ' ';
184 }
185 std::cout << std::endl;
186 }
187
188 std::cout<<"======== TESTING I/O ===========\n";
189
190 std::string test_path = "test_binary_hyperplane.bvl.tmp";
191
192 vsl_b_ofstream bfs_out(test_path);
193 TEST(("Opened " + test_path + " for writing").c_str(), (!bfs_out ), false);
194 vsl_b_write(bfs_out, *pClassifier);
195 bfs_out.close();
196
197 clsfy_binary_hyperplane classifier_in;
198
199 vsl_b_ifstream bfs_in(test_path);
200 TEST(("Opened " + test_path + " for reading").c_str(), (!bfs_in ), false);
201 vsl_b_read(bfs_in, classifier_in);
202
203 bfs_in.close();
204 #if !LEAVE_FILES_BEHIND
205 vpl_unlink(test_path.c_str());
206 #endif
207
208 std::cout<<"Saved : " << *pClassifier << std::endl
209 <<"Loaded: " << classifier_in << std::endl;
210
211 TEST("saved classifier = loaded classifier",
212 pClassifier->weights() == classifier_in.weights() &&
213 pClassifier->bias() == classifier_in.bias(),
214 true);
215
216 TEST_NEAR("saved classifier(x) = loaded classifier(x)",
217 pClassifier->log_l(vnl_vector<double>(nDims, 0.25)),
218 classifier_in.log_l(vnl_vector<double>(nDims, 0.25)), 1.0e-10);
219
220 std::cout << std::setprecision(6) << std::resetiosflags(std::ios::floatfield);
221
222 delete pClassifier;
223
224 test_clsfy_geman_mcclure_build();
225 }
226
test_clsfy_geman_mcclure_build()227 void test_clsfy_geman_mcclure_build()
228 {
229 std::vector<vpdfl_axis_gaussian_sampler *> generator(2);//
230 constexpr unsigned nDims = 2;
231 vnl_vector<double> meanPos(nDims), varPos(nDims), meanNeg(nDims), varNeg(nDims),origin(nDims);
232 double d=1.0/vnl_math::sqrt2;
233 origin.fill(0.0);
234 std::vector<vnl_vector<double> > basisPos(2);
235 basisPos[0].set_size(2);basisPos[0][0] = -d;basisPos[0][1] = d;
236 basisPos[1].set_size(2);basisPos[1][0] = d;basisPos[1][1] = d;
237
238 std::vector<vnl_vector<double> > basisNeg(2);
239 double dx = std::cos(0.96), dy = std::sin(0.96); //(0.96 is a bit more than 45 degrees in radians)
240 basisNeg[0].set_size(2);basisNeg[0][0] = -dx;basisNeg[0][1] = dy;
241 basisNeg[1].set_size(2);basisNeg[1][0] = dx;basisNeg[1][1] = dy;
242
243 vpdfl_axis_gaussian PDF0, PDF1;
244
245 meanPos.fill(0.0);
246 meanPos[0] = -2.75; meanPos[1] = 2.75;
247 meanNeg.fill(0.0);
248
249 vnl_vector<double > deltaMeans = meanPos - meanNeg;
250 varNeg[0]= 0.75; varNeg[1] = 3.0;
251 varPos[0] = 1.0; varPos[1] = 4.0;
252 PDF0.set(origin, varNeg);
253 PDF1.set(origin, varPos);
254
255 generator[0] = (vpdfl_axis_gaussian_sampler *)PDF0.new_sampler();
256 generator[1] = (vpdfl_axis_gaussian_sampler *)PDF1.new_sampler();
257 vnl_random rng;
258 rng.reseed(111333);
259
260 constexpr unsigned nSamples = 200;
261 constexpr unsigned nTestSamples = 1000;
262 std::vector<vnl_vector<double> > trainingVectors(nSamples);
263 std::vector<vnl_vector<double> > testVectors(nTestSamples);
264 std::vector<unsigned> labels(nSamples);
265 std::vector<unsigned> testLabels(nTestSamples);
266 vnl_vector<double> sbase;
267 vnl_vector<double> s;
268
269 std::cout << "Generating test data\n";
270 unsigned nall = nSamples+nTestSamples;
271 for (unsigned int i=0; i<nall; i++)
272 {
273 unsigned indicator=0;
274 bool outlier=false;
275 if (i%3==0)
276 {
277 indicator=1;
278 }
279 if (i<nSamples)
280 {
281 if ( indicator)
282 {
283 if (i%24==0)
284 {
285 outlier = true;
286 }
287 }
288 else if (i%20 == 0)
289 {
290 outlier = true;
291 }
292 }
293 generator[indicator]->sample(sbase);
294 std::vector<vnl_vector<double> >& basis = (indicator ? basisPos : basisNeg);
295 vnl_vector<double>& mean=(indicator ? meanPos : meanNeg);
296 s = sbase[0]*basis[0] + sbase[1]*basis[1];
297 if (outlier)
298 {
299 if (indicator)
300 {
301 s += 4.0*deltaMeans; //Shift miles away from the boundary
302 }
303 else
304 {
305 s -= 2.0*deltaMeans; // Shift inwards away from the boundary
306 }
307 }
308 if (i<nSamples)
309 {
310 trainingVectors[i] = mean + s;
311 labels[i] = indicator;
312 }
313 else
314 {
315 unsigned j = i-nSamples;
316 testLabels[j] = indicator;
317 testVectors[j] = mean + s;
318 }
319 }
320
321 delete generator[0];
322 delete generator[1];
323
324 std::cout<<"======== TESTING BUILDING ===========\n" << std::setprecision(6);
325
326 //First do an ordinary least squares build for comparison
327 clsfy_binary_hyperplane_ls_builder builder;
328
329 auto *pClassifier =
330 (clsfy_binary_hyperplane*) builder.new_classifier();
331 std::cout << "Finding Least Squares Separator using least squares\n";
332 mbl_data_array_wrapper<vnl_vector<double> > training_set(trainingVectors);
333 std::cout << "Error on Training set ";
334 double train_errorLS = builder.build(*pClassifier, training_set, labels);
335 std::cout << train_errorLS << std::endl;
336 TEST_NEAR("Train error on classifier is good enough", train_errorLS, 0.0, 0.15);
337 vnl_vector<double> weightsLS = pClassifier->weights();
338 std::cout << "****************Testing over test set**************\n";
339
340 mbl_data_array_wrapper<vnl_vector<double> > test_set_inputs(testVectors);
341
342 double test_errorLS = clsfy_test_error(*pClassifier, test_set_inputs, testLabels);
343 std::cout << "Error on Testing set " << test_errorLS << std::endl;
344
345 TEST_NEAR("Test error on classifier is good enough", test_errorLS, 0.0, 0.25);
346
347 //Now run the gmrho build but with sigma forced large so that the GM function approximates
348 // to ordinary least square
349 clsfy_binary_hyperplane_gmrho_builder builderGM;
350 std::cout<<std::endl<<"Now training using Geman-McClure builder"<<std::endl
351 << "Error on Training set ";
352 builderGM.set_auto_estimate_sigma(false);
353 builderGM.set_sigma_preset(10.0);
354
355 #if 0 // commented out
356 {
357 using namespace clsfy_binary_hyperplane_gmrho_builder_helpers;
358 //Now copy from the urggghh data wrapper into a sensible data structure (matrix!)
359 training_set.reset();
360 unsigned num_vars_ = training_set.current().size();
361 unsigned num_examples_ = training_set.size();
362 vnl_matrix<double> data(num_examples_,num_vars_,0.0);
363 unsigned i=0;
364 do
365 {
366 double* row=data[i++];
367 std::copy(training_set.current().begin(),training_set.current().end(),row);
368 } while (training_set.next());
369
370 Set up category regression values determined by output class
371 vnl_vector<double> y(num_examples_,0.0);
372 std::transform(labels.begin(),labels.end(),
373 y.begin(),
374 category_value(std::count(labels.begin(),labels.end(),1u),labels.size()));
375
376 vnl_vector<double> weights(weightsLS);
377
378 weights.set_size(num_vars_+1);
379
380 weights.update(weightsLS,0);
381 weights[num_vars_] = pClassifier->bias();
382 gmrho_sum costFn(data,y,1.5);
383 double epsilon=0.001;
384 vnl_vector<double> delta(num_vars_+1,epsilon);
385 vnl_vector<double> weights0(weights);
386 weights0+= 2.0*delta;
387 double f0 = costFn.f(weights0);
388 vnl_vector<double > gradf0(num_vars_+1,0.0);
389 vnl_vector<double > gradf1(num_vars_+1,0.0);
390 epsilon=1.0E-6;
391 for (unsigned i=0; i<weights.size();i++)
392 {
393 weights = weights0;
394 weights[i] += epsilon;
395 double f1 = costFn.f(weights);
396 weights = weights0;
397 weights[i] -= epsilon;
398 double f2 = costFn.f(weights);
399 gradf0[i] = (f1 - f2)/(2.0*epsilon);
400 }
401 costFn.gradf(weights0,gradf1);
402 vnl_vector<double> dg = gradf1-gradf0;
403 TEST_NEAR("GM cost function gradient is consistent", dg.inf_norm(), 0.0, 1.0E-4);
404 }
405 #endif // 0
406
407 clsfy_builder_base* pBase = &builderGM;
408 double train_error = pBase->build(*pClassifier, training_set, 1,labels);
409 std::cout << train_error << std::endl;
410 TEST_NEAR("Train error on classifier is good enough", train_error, 0.0, 0.15);
411 vnl_vector<double > weights = pClassifier->weights();
412 weights.normalize();
413 weightsLS.normalize();
414 vnl_vector<double> diff=weightsLS - weights;
415 TEST_NEAR("Hyperplane close to LS Build at high sigma", diff.inf_norm(), 0.0, 0.02);
416 std::cout << "****************Testing over test set**************\n";
417
418 double test_error = clsfy_test_error(*pClassifier, test_set_inputs, testLabels);
419 std::cout << "Error on Testing set " << test_error << std::endl;
420 TEST_NEAR("Test error on classifier is good enough", test_error, 0.0, 0.2);
421 double dtest=std::fabs(test_error - test_errorLS);
422 TEST_NEAR("Test error is similar to LS build at high sigma", dtest, 0.0, 0.005);
423
424 builderGM.set_auto_estimate_sigma(true);
425 builderGM.set_sigma_preset(1.0);
426
427 std::cout<<"Now doing Geman McClure build with automatic sigma scaling..."<<std::endl;
428
429 double train_error2 = pBase->build(*pClassifier, training_set,1, labels);
430 std::cout << train_error2 << std::endl;
431 TEST_NEAR("Train error on classifier is good enough", train_error2, 0.0, 0.05);
432 weights = pClassifier->weights();
433 weights.normalize();
434 diff=weightsLS - weights;
435 TEST_NEAR("Hyperplane still fairly close to LS Build", diff.inf_norm(), 0.0, 0.25);
436 std::cout << "****************Testing over test set**************\n";
437
438 double test_error2 = clsfy_test_error(*pClassifier, test_set_inputs, testLabels);
439 std::cout << "Error on Testing set " << test_error2 << std::endl;
440 TEST("Test error on classifier has improved", test_error2<test_error, true);
441 TEST_NEAR("Test error on classifier is good enough", test_error2, 0.0, 0.10);
442
443 delete pClassifier;
444 }
445
446 TESTMAIN(test_binary_hyperplane);
447