1 /*
2 File: Polynomial.cpp
3 Created by: Oleksii Pokotylo
4 First published: 07.05.2014
5 Last revised: 07.05.2014
6
7 Contains the polynomial classifier the DD-plot classification.
8
9 For a description of the algorithm, see:
10 Li, J., Cuesta-Albertos, J. A. and Liu, R. Y. (2012). DD-classifier: Nonparametric classification procedure based on
11 DD-plot, Journal of the American Statistical Association 107(498): 737 - 753.
12 */
13
14 #include "stdafx.h"
15
16 #include <limits>
17 #include <boost/math/special_functions/binomial.hpp>
18
19 #include "asa047.h"
20
21 #ifndef _MSC_VER
22 #include <Rcpp.h>
23 using namespace Rcpp;
24 #endif
25
26 /**
27 Calculates the empirical risk for two classes on the basis of given depths
28
29 @param polynomial : Polynomial as a vector of coefficients starting with the first degree(a0 = 0 always)
30 @param points : nx2 matrix of depths, where each column contains the depths against the corresponding class
31 @param numClass1: Number of points belonging to the first class
32 @return polynomial as a vector of coefficients starting with the first degree (a0 = 0 always)
33
34 @throws empirical risk
35 */
GetEmpiricalRisk(TPoint & polynomial,TDMatrix points,unsigned numClass1,unsigned numClass2)36 double GetEmpiricalRisk(TPoint& polynomial, TDMatrix points, unsigned numClass1, unsigned numClass2){
37 unsigned degree = polynomial.size();
38 unsigned n = numClass1 + numClass2;
39
40 double risk = 0;
41 int sign = 1;
42 for (unsigned i = 0; i < n; i++){
43 if (i >= numClass1)
44 sign = -1;
45
46 double val = points[i][0];
47 double res = 0;
48 for (unsigned j = 0; j<degree; j++){
49 res += polynomial[j] * std::pow(val, j+1);
50 }
51 if ((points[i][1] - res)*sign > 0){ // for class1 depths[i,2] > res, for class 2 <
52 risk++;
53 }
54 }
55
56 return risk / n;
57 }
58
59 /**
60 Calculates the coefficients of the polynomial of a given degree
61 going through given points and the origin
62
63 @param degree : degree of the polynomial, should be equal the number of points
64 @param points : degreex2 points for the polynomial to path through
65
66 @return polynomial as a vector of coefficients starting with the first degree (a0 = 0 always)
67
68 @throws runtime_error in case of singularity
69 */
GetPolynomial(unsigned degree,TDMatrix points,TPoint & polynomial)70 bool GetPolynomial(unsigned degree, TDMatrix points, TPoint& polynomial) {
71
72 bMatrix A(degree, degree);
73 for (unsigned i = 0; i < degree; i++){
74 for (unsigned j = 0; j < degree; j++){
75 A(i, j) = (std::pow(points[i][0], j + 1));
76 }
77 }
78
79 bVector b(degree);
80 for (unsigned i = 0; i < degree; i++){
81 b[i] = points[i][1];
82 }
83
84 bPM pm(A.size1());
85 bPM::size_type singular = boost::numeric::ublas::lu_factorize(A, pm);
86 if (singular != 0) return false;
87 boost::numeric::ublas::lu_substitute(A, pm, b);
88
89 for (unsigned i = 0; i < degree; i++){
90 if (!(b[i] < std::numeric_limits<double>::max()) || b[i] < -std::numeric_limits<double>::max()){ return false; }
91 polynomial[i] = b[i];
92 }
93
94 return true;
95 }
96
97 /**
98 Chooses the best in classification sense polynomial among
99 "cardinality" randomly chosen polynomials, passing through
100 "degree" arbitrary points
101
102 @param points: nx2 matrix of points where first column is an absciss, n = numClass1 + numClass2
103 @param numClass1: Number of points belonging to the first class
104 @param degree: Degree of the polynomial
105 @param n_polynomials: Number of randomly chosen polynomials
106
107 @return polynomial as a vector of coefficients starting with the first degree (a0 = 0 always)
108 */
GetRandomMinPolynomial(TDMatrix points,int numClass1,int numClass2,int degree,int n_polynomials)109 TPoint GetRandomMinPolynomial(TDMatrix points, int numClass1, int numClass2, int degree, int n_polynomials){
110 int n = numClass1 + numClass2;
111 vector<int> usedIndexesX(n);
112 vector<int> usedIndexesY(n);
113 int nx = 0, ny = 0;
114
115 for (int i = 0; i<n; i++){
116 if (points[i][0] != 0){
117 usedIndexesX[nx++] = i;
118 if (points[i][1] != 0)
119 usedIndexesY[ny++] = i;
120 }
121 }
122
123 // 0.3 of all combination
124 int numOfCombinations = boost::math::binomial_coefficient<double>(nx - 1, degree - 1) * ny * 0.3;
125 int numCandidates = (numOfCombinations > n_polynomials
126 ? n_polynomials
127 : numOfCombinations);
128
129 TPoint minPolynomial(degree);
130 double minEmpRisk = 1;
131 TDMatrix sample = new double*[degree];
132 for (int i = 0; i < numCandidates; i++){
133 // generate sample
134 set<int> smp;
135 smp.insert(usedIndexesY[random(ny)]);
136 while ((int)smp.size() < degree){
137 smp.insert(usedIndexesX[random(nx)]);
138 }
139
140 set <int>::const_iterator s = smp.begin();
141 for (int j = 0; j < degree; j++, s++) {
142 sample[j] = points[*s];
143 }
144
145 try{
146 TPoint pol(degree);
147 if(!GetPolynomial(degree, sample, pol))
148 continue;
149 double rsk = GetEmpiricalRisk(pol, points, numClass1, numClass2);
150 if (rsk < minEmpRisk) {
151 minPolynomial = pol;
152 minEmpRisk = rsk;
153 }
154 }
155 catch (runtime_error &e){ /* singular matrix*/ }
156 catch (...){ /* NA or inf */ }
157 }
158 delete[] sample;
159 return minPolynomial;
160 }
161
162 static int _degree;
163 static TDMatrix _points;
164 static int _numClass1;
165 static int _numClass2;
166
167 /**
168 Calculates the empirical risk for two classes on the basis of given depths
169 and approximates it to get continuous derivative
170
171 @param polynomial : Polynomial as a vector of coefficients starting with the first degree(a0 = 0 always)
172 @param _points : nx2 matrix of depths, where each column contains the depths against the corresponding class
173 @param _numClass1: Number of points belonging to the first class
174 @param _numClass2: Number of points belonging to the first class
175 @return polynomial as a vector of coefficients starting with the first degree (a0 = 0 always)
176 */
GetEmpiricalRiskSmoothed(double polynomial[])177 double GetEmpiricalRiskSmoothed(double polynomial[]){
178 const float smoothingConstant = 100;
179
180 double risk = 0;
181 int sign = 1;
182 for (int i = 0; i < _numClass1 + _numClass2; i++){
183 if (i >= _numClass1)
184 sign = -1;
185
186 double val = (_points)[i][0];
187 double res = 0;
188 for (int j = 0; j < _degree; j++){
189 res += polynomial[j] * std::pow(val, j+1);
190 }
191 risk += 1 / (1 + exp(-smoothingConstant*((_points)[i][1] - res)*sign));
192 }
193
194 return risk / _numClass1 + _numClass2;
195 }
196
197
nlm_optimize(TDMatrix points,TPoint & minCandidate,int numClass1,int numClass2)198 TPoint nlm_optimize(TDMatrix points, TPoint& minCandidate, int numClass1, int numClass2){
199 /* static variables for GetEmpiricalRiskSmoothed */
200 _points = points;
201 _numClass1 = numClass1;
202 _numClass2 = numClass2;
203 _degree = minCandidate.size();
204
205 double* start = new double[_degree];
206 std::copy(minCandidate.begin(), minCandidate.end(), start);
207
208 int icount;
209 int ifault;
210 int kcount;
211 int konvge;
212 int n = _degree;
213 int numres;
214 double reqmin;
215 double *step;
216 double *xmin;
217 double ynewlo;
218
219 step = new double[n];
220 xmin = new double[n];
221
222 reqmin = 1.0E-06;
223
224 for (int i = 0; i < n; i++)
225 {
226 // determines the size and shape of the initial simplex.
227 // The relative magnitudes of its elements should reflect the units of the variables.
228 step[i] = 1.0;
229 }
230
231 konvge = 10;
232 kcount = 500;
233 /*
234 cout << "\n";
235 cout << " Starting point X:\n";
236 cout << "\n";
237 for (i = 0; i < n; i++)
238 {
239 cout << " " << start[i] << "";
240 }
241 cout << "\n";
242
243 ynewlo = GetEmpiricalRiskSmoothed(start);
244
245 cout << "\n";
246 cout << " F(X) = " << ynewlo << "\n";
247 */
248 nelmin(GetEmpiricalRiskSmoothed, n, start, xmin, &ynewlo, reqmin, step,
249 konvge, kcount, &icount, &numres, &ifault);
250 /*
251 cout << "\n";
252 cout << " Return code IFAULT = " << ifault << "\n";
253 cout << "\n";
254 cout << " Estimate of minimizing value X*:\n";
255 cout << "\n";
256 for (i = 0; i < n; i++)
257 {
258 cout << " " << setw(14) << xmin[i] << "\n";
259 }
260
261 cout << "\n";
262 cout << " F(X*) = " << ynewlo << "\n";
263
264 cout << "\n";
265 cout << " Number of iterations = " << icount << "\n";
266 cout << " Number of restarts = " << numres << "\n";
267 */
268 TPoint minpol = TPoint(xmin, xmin + _degree);
269
270 delete[] start;
271 delete[] step;
272 delete[] xmin;
273
274 return minpol;
275 }
276
277 /**
278 Chooses the best in classification sense polynomial
279
280 @param points: nx2 matrix of points where first column is an abscissa, n = numClass1 + numClass2
281 @param numClass1: Number of points belonging to the first class
282 @param degree: Degree of the polynomial
283 @param presize: if true - run evaluation 5 times
284
285 @return polynomial as a vector of coefficients starting with the first degree (a0 = 0 always)
286 */
GetOptPolynomial(TDMatrix points,unsigned numClass1,unsigned numClass2,unsigned degree,bool presize)287 TPoint GetOptPolynomial(TDMatrix points, unsigned numClass1, unsigned numClass2, unsigned degree, bool presize /*default = FALSE*/){
288
289 double minError = 100.1;
290 TPoint minPol;
291
292 for (int i = 0; i < (presize ? 3 : 1); i++){
293 TPoint minCandidate = GetRandomMinPolynomial(points, numClass1, numClass2, degree, 10 ^ degree);
294 double err = GetEmpiricalRisk(minCandidate, points, numClass1, numClass2);
295 if (err < minError){
296 minPol = minCandidate;
297 minError = err;
298 }
299
300 //#define DEBUG
301 #ifdef DEBUG
302 Rcpp::Rcout << "candminPol: ";
303 for (int i = 0; i< degree; i++){
304 Rcpp::Rcout << minCandidate[i] << " ";
305 }
306 Rcpp::Rcout << " ; error = "<< err << " \n";
307 #endif
308
309 TPoint optPolynomial = nlm_optimize(points, minCandidate, numClass1, numClass2);
310 err = GetEmpiricalRisk(optPolynomial, points, numClass1, numClass2);
311 if (err <= minError){
312 minPol = optPolynomial;
313 minError = err;
314 }
315
316 #ifdef DEBUG
317 Rcpp::Rcout << "minPol: ";
318 for (int i = 0; i< minPol.size(); i++){
319 Rcpp::Rcout << minPol[i] << " ";
320 }
321 Rcpp::Rcout << " ; error = "<< minError << " \n";
322 #endif
323 }
324
325 return(minPol);
326 }
327
328
329 /**
330 Calculates classification error of "degree" - degree polynomial using cross - validation approach
331
332 @param points: nx2 matrix of points where first column is an absciss, n = numClass1 + numClass2
333 @param numClass1: Number of points belonging to the first class
334 @param degree: Degree of the polynomial
335 @param chunkNumber: Number of chunks in which data set should be splitted, chunkNumber should be a divider of n(n%%chunkNumber = 0)
336
337 @return Number of errors
338 */
GetCvError(TDMatrix points,int numClass1,int numClass2,int degree,int chunkNumber)339 double GetCvError(TDMatrix points, int numClass1, int numClass2, int degree, int chunkNumber){
340
341 int n = numClass1 + numClass2;
342 int chunkSize = ceil((double)n / chunkNumber);
343
344 TDMatrix learnpoints = new double*[n - chunkSize+1];
345 TDMatrix checkpoints = new double*[chunkSize];
346
347 int chunk = 0;
348 int n1 = 0; // number of Class1 points in checkpoints
349 for (int j = 0, l = 0, c = 0; j < n; j++){
350 if (j%chunkNumber)
351 learnpoints[l++] = points[j];
352 else
353 checkpoints[c++] = points[j];
354 if (j < numClass1 && (j%chunkNumber == 0)) n1++;
355 }
356
357 double err = 0;
358 bool bigch = true;
359 for (; chunk < chunkNumber; chunk++){
360
361 if (chunk > 0){
362 if (bigch && (chunkNumber)*(chunkSize - 1) + chunk == n){
363 bigch = false;
364 chunkSize--;
365 //checkpoints.resize(chunkSize);
366 //learnpoints.resize(n - chunkSize);
367 learnpoints[n - chunkSize - 1] = points[n - 1];
368 }
369
370 for (int i = 0; i < chunkSize; i++){
371 checkpoints[i] = learnpoints[(chunkNumber - 1)*i + chunk - 1];
372 learnpoints[(chunkNumber - 1)*i + chunk - 1] = points[chunkNumber*i + chunk - 1];
373 if (chunkNumber*i + chunk == numClass1)
374 n1--;
375 }
376 }
377
378 TPoint minPolynomial = GetOptPolynomial(learnpoints, numClass1 - n1, numClass2 - chunkSize + n1, degree, false);
379 double curErr = GetEmpiricalRisk(minPolynomial, checkpoints, n1, chunkSize - n1);
380 err += curErr;// chunkSize;
381 }
382
383 delete[] learnpoints;
384 delete[] checkpoints;
385 return err/n;
386 }
387
PolynomialLearnCV(TDMatrix input,int numClass1,int numClass2,int maxDegree,int chunkNumber,int * degree,int * axis)388 TPoint PolynomialLearnCV(TDMatrix input, int numClass1, int numClass2, int maxDegree, int chunkNumber, int *degree, int *axis){
389 int numPoints = numClass1 + numClass2;
390
391 int polOptDegree = 0;
392 double polOptError = numPoints;
393 int polOptAxis = 0;
394
395 TDMatrix input2 = newM(numPoints, 2); // copy
396 for (int i = 0, tmp; i < numPoints; i++){ input2[i][0] = input[i][1]; input2[i][1] = input[i][0]; } // swap columns
397
398 for (int degree = 1; degree <= maxDegree; degree++){
399 double polError = GetCvError(input, numClass1, numClass2, degree, chunkNumber);
400 //cout << degree << " " << polError << "\n";
401 if (polError < polOptError){
402 polOptAxis = 0;
403 polOptDegree = degree;
404 polOptError = polError;
405 }
406 polError = GetCvError(input2, numClass1, numClass2, degree, chunkNumber);
407 //cout << degree << " " << polError << "\n";
408 if (polError < polOptError){
409 polOptAxis = 1;
410 polOptDegree = degree;
411 polOptError = polError;
412 }
413 }
414
415 //cout << polOptDegree << " " << polOptError << "\n";
416 TPoint polynomial = polOptAxis == 0
417 ? GetOptPolynomial(input, numClass1, numClass2, polOptDegree, true)
418 : GetOptPolynomial(input2, numClass1, numClass2, polOptDegree, true);
419
420 deleteM(input2);
421
422 *axis = polOptAxis;
423 *degree = polOptDegree;
424 return polynomial;
425 }
426