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