/*********************************************************************** This file is part of KEEL-software, the Data Mining tool for regression, classification, clustering, pattern mining and so on. Copyright (C) 2004-2010 F. Herrera (herrera@decsai.ugr.es) L. Sánchez (luciano@uniovi.es) J. Alcalá-Fdez (jalcala@decsai.ugr.es) S. García (sglopez@ujaen.es) A. Fernández (alberto.fernandez@ujaen.es) J. Luengo (julianlm@decsai.ugr.es) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/ **********************************************************************/ package keel.Algorithms.Decision_Trees.C45_Binarization; import java.util.ArrayList; import keel.Dataset.Attributes; import org.core.Files; /** * <p>Title: OVO</p> * <p>Description: This class implements the Binarization methodology (OVO and OVO ) * <p>Company: KEEL </p> * @author Mikel Galar (University of Navarra) 21/10/2010 * @author Alberto Fernandez (University of Jaen) 15/05/2014 * @version 1.2 * @since JDK1.6 */ public class OVO { Multiclassifier classifier; int nClasses, count; String sOvo; BTS bts; Nesting nesting; double[][] tablas; // ascending order (DynOVO) int kvecinos; //DynOVO ENN metodo; boolean test,dynOVO; /** * It constructs the new OVO instance depending on the aggregation that is * going to be used * @param classifier the multi-classifier containing the base classifiers * @param sOvo the aggregation to be used * @param dynOVO whether the final aggregation is the dynamic OVO */ public OVO(Multiclassifier classifier, String sOvo, boolean dynOVO) { this.classifier = classifier; nClasses = classifier.nClasses; this.sOvo = sOvo; this.dynOVO = dynOVO; /* See whether the selected aggregation is correct or not */ System.out.println("go to computeClassScores"); try {computeClassScores(null);} catch(NullPointerException e){} System.out.println("finished computeClassScores"); System.out.println("Finishing operations..."); if (sOvo.equals("BTS")) { bts = new BTS(classifier, classifier.threshold, this); } else if (sOvo.equals("NESTING")) nesting = new Nesting(classifier, this); tablas = new double[classifier.train.getnData()][nClasses * nClasses]; //DynOVO kvecinos = nClasses; createConfFile(); metodo = new ENN("ENNConf.txt"); System.out.println("OVO CREATED!"); } private void createConfFile(){ String content = new String(); String train = classifier.input_validation_name; String test = classifier.input_test_name; content += "algorithm = Decremental Reduction Optimization Procedure 3\n"; content += "inputData = \""+train+"\" \""+test+"\" \"tst.dat\"\n"; content += "outputData = \"training.txt\" \"tstOutput.dat\"\n\n"; content += "Number of Neighbors = 3\n"; content += "Distance Function = HVDM\n"; Files.writeFile("ENNConf.txt", content); } public void clearTables(boolean test){ count = 0; this.test = test; } /** * It finishes the operations needed before classifying but after * constructing the classifiers (for BTS!) */ public void classifierTrainFinished() { /* Initialize and construct the binary tree if BTS is the selected aggregation */ if (sOvo.equals("BTS")) { bts.initialize(); bts.construct(); } } /** * It computes the confidence vector for the classes using the method * indicated in the config file * @param example the example to be classified * @return the confidence vector */ protected String computeClassScores(double[] example) { String output = new String("?"); /* In case of DDAG or BTS, use the example */ if (sOvo.equals("DDAG")) return computeClassDDAG(example); else if (sOvo.equals("BTS")) return bts.computeClass(example); double[][] tabla = classifier.ovo_table(example); for (int i = 0; i < nClasses; i ++){ for (int j = 0; j < nClasses; j++){ tablas[count][j + i * nClasses] = tabla[i][j]; } } double[] max = null; /* In case the rest of the cases, use the obtained score matrix */ if (sOvo.equals("VOTE")) max = computeClassScoresVote(tabla); else if (sOvo.equals("WEIGHTED")) max = computeClassScoresWeighted(tablas[count]); //for DynOVO extension else if (sOvo.equals("PC")) max = computeClassScoresPC(tabla); else if (sOvo.equals("ND")) max = computeClassScoresND(tabla); else if (sOvo.equals("LVPC")) max = computeClassScoresLVPC(tabla); else if (sOvo.equalsIgnoreCase("WuJMLR")) max = method2WuJMLR(tabla); else if (sOvo.equals("NESTING")) { max = computeClassScoresVote(tabla); // See if there is a tie using voting strategy int nMax = 1; double maxi = max[0]; for (int i = 1; i < max.length; i++) { if (maxi < max[i]) { maxi = max[i]; nMax = 1; } else if (maxi == max[i]) nMax++; } /** * It depends on the phase: if we are classifying in train (creating the nesting) * we return the "tie", otherwise, we use the nested OVO! */ if (nesting.creating && nMax > 1) { System.out.println("There is a Tie!"); return "tie"; } else if (nMax > 1) { output = nesting.method.ovo.computeClassScores(example); return output; } } else { System.out.println("The OVO method is not correct"); System.exit(-1); } if (dynOVO) return computeClassScoresDynamic(count++); else output = getOutputTies(max); return output; } /** * It computes the confidence vector for the classes using the DDAG approach * @param example the example to be classified * @return the confidence vector */ protected String computeClassDDAG(double[] example) { ArrayList<Integer> list = new ArrayList<Integer>(); for (int i = 0; i < nClasses; i++) list.add(new Integer(i)); int clase; int x, y; int eliminado = -1; while (list.size() != 1) { /* Delete a class from the list in each iteration */ for (int i = 0; i < list.size() - 1 && eliminado == -1; i++) { for (int j = list.size() - 1; j > i && eliminado == -1; j--) { x = list.get(i).intValue(); y = list.get(j).intValue(); clase = classifier.obtainClass(x, y, example); if (clase == x) eliminado = list.remove(list.size() - 1); else if (clase == y) eliminado = list.remove(0); } } /* If there is no class deleted, obtain the output class */ if (eliminado == -1) { double[] max = new double[nClasses]; for (int k = 1; k < nClasses; k++) if (list.contains(new Integer(k))) max[k] = 1; return getOutputTies(max); } else eliminado = -1; } return classifier.train.getOutputValue(list.get(0)); } /** * It computes the confidence vector for the classes using the (classic) voting method * @param tabla array containing the outputs of the classifiers for the instance * @return the confidence vector */ protected double[] computeClassScoresVote(double[][] tabla) { double[] max = new double[nClasses]; for (int i = 0; i < nClasses; i++) { double sum_clase = 0.0; for (int j = 0; j < nClasses; j++) { sum_clase += tabla[i][j] > tabla[j][i] ? 1 : 0; } max[i] = sum_clase; } return max; } /** * It computes the confidence vector for the classes using the Weighted voting method * @param tabla array containing the outputs of the classifiers for the instance * @return the confidence vector */ protected double[] computeClassScoresWeighted(double[][] tabla) { double [] max = new double[tabla.length]; for (int i = 0; i < tabla.length; i++) { double sum_clase = 0.0; for (int j = 0; j < tabla.length; j++) { sum_clase += tabla[i][j]; } max[i] = sum_clase / (double)tabla.length; } return max; } /** * It computes the confidence vector for the classes using Pairwise Coupling method * @param r array containing the outputs of the classifiers for the instance * @return the confidence vector */ protected double[] computeClassScoresPC(double[][] r) // r = tabla { double[][] n = new double[nClasses][nClasses]; for (int i = 0; i < r.length; i++) { for (int j = 0; j < r.length; j++) { n[i][j] = classifier.train.numberOfExamples(i) + classifier.train.numberOfExamples(j); if (r[i][j] == 0 && r[j][i] == 0 && i != j) { r[i][j] = r[j][i] = 0.5; } //else // n[i][j] = n[i][j] / classifier.train.getnData(); // n[i][j] = n[i][j] / (r[i][j] * (1 - r[i][j])); } } // Initialize p and u array double[] p = new double[r.length]; for (int i = 0; i < p.length; i++) { for (int j = 0; j < p.length; j ++) if (i != j) p[i] += r[i][j]; p[i] = p[i] * (2 / (double)nClasses) / (double)(nClasses - 1); } double[][] u = new double[r.length][r.length]; for (int i = 0; i < r.length; i++) { for (int j = i + 1; j < r.length; j++) { u[i][j] = p[i] / (p[i] + p[j]); } } // firstSum doesn't change double[] firstSum = new double[p.length]; for (int i = 0; i < p.length; i++) { for (int j = i + 1; j < p.length; j++) { firstSum[i] += n[i][j] * r[i][j]; firstSum[j] += n[i][j] * (1 - r[i][j]); } } // Iterate until convergence boolean changed; do { changed = false; double[] secondSum = new double[p.length]; for (int i = 0; i < p.length; i++) { for (int j = i + 1; j < p.length; j++) { secondSum[i] += n[i][j] * u[i][j]; secondSum[j] += n[i][j] * (1 - u[i][j]); } } for (int i = 0; i < p.length; i++) { if ( (firstSum[i] == 0) || (secondSum[i] == 0)) { if (p[i] > 0) { changed = true; } p[i] = 0; } else { double factor = firstSum[i] / secondSum[i]; double pOld = p[i]; p[i] *= factor; if (Math.abs(pOld - p[i]) > 1.0e-3) { changed = true; } } } normalize(p); for (int i = 0; i < r.length; i++) { for (int j = i + 1; j < r.length; j++) { u[i][j] = p[i] / (p[i] + p[j]); } } } while (changed); return p; } /** * It computes the confidence vector for the classes using the second method * from the multiclass_prob paper by Wu, Lin, and Weng * @param r array containing the outputs of the classifiers for the instance * @return the confidence vector */ private double[] method2WuJMLR(double[][] r) { int k = this.nClasses; double[] p = new double[k]; int t,j; int iter = 0, max_iter=Math.max(100,k); double[][] Q=new double[k][k]; double[] Qp= new double[k]; double pQp, eps=0.005/k; for (int i = 0; i < r.length; i++) for (int i2 = 0; i2 < r.length; i2++) { if (i != i2 && r[i][i2] == 0 & r[i2][i] == 0) { r[i][i2] = 0.5; r[i2][i] = 1 - r[i][i2]; } if (i != i2 && r[i][i2] == 0) { r[i][i2] = 0.000000000000001;//Double.MIN_VALUE; r[i2][i] = 1 - r[i][i2];//0.999999999999999; } /* else if (i == i2) r[i][i2] = 1.0; */ } for (t=0;t<k;t++) { p[t]=1.0/k; // Valid if k = 1 Q[t][t]=0; for (j=0;j<t;j++) { Q[t][t]+=r[j][t]*r[j][t]; Q[t][j]=Q[j][t]; } for (j=t+1;j<k;j++) { Q[t][t]+=r[j][t]*r[j][t]; Q[t][j]=-r[j][t]*r[t][j]; } } for (iter=0;iter<max_iter;iter++) { // stopping condition, recalculate QP,pQP for numerical accuracy pQp=0; for (t=0;t<k;t++) { Qp[t]=0; for (j=0;j<k;j++) Qp[t]+=Q[t][j]*p[j]; pQp+=p[t]*Qp[t]; } double max_error=0; for (t=0;t<k;t++) { double error=Math.abs(Qp[t]-pQp); if (error>max_error) max_error=error; } if (max_error<eps) break; for (t=0;t<k;t++) { double diff=(-Qp[t]+pQp)/Q[t][t]; p[t]+=diff; pQp=(pQp+diff*(diff*Q[t][t]+2*Qp[t]))/(1+diff)/(1+diff); for (j=0;j<k;j++) { Qp[j]=(Qp[j]+diff*Q[t][j])/(1+diff); p[j]/=(1+diff); } } } if (iter>=max_iter) System.out.println("Exceeds max_iter in multiclass_prob\n"); return p; } /** * It computes the confidence vector for the classes using the non-dominance criterion * @param tabla array containing the outputs of the classifiers for the instance * @return the confidence vector */ protected double[] computeClassScoresND(double[][] tabla) { /* Compute the strict preference relation */ for (int x = 0; x < nClasses; x++) for(int y = x + 1; y < nClasses; y++) { double aux_tabla = tabla[x][y]; tabla[x][y] -= tabla[y][x]; tabla[y][x] -= aux_tabla; if (tabla[x][y] < 0) { tabla[x][y] = 0; } if (tabla[y][x] < 0) { tabla[y][x] = 0; } } /* Compute the non-cominance degree of each class */ double[] max = new double[nClasses]; double max_clase; for (int i = 0; i < nClasses; i++) { max_clase = -1.0; for (int j = 0; j < nClasses; j++) { if (tabla[j][i] > max_clase) { max_clase = tabla[j][i]; } } max[i] = 1.0 - max_clase; } return max; } /** * It computes the confidence vector for the classes using LVPC (Learning * valued preference for Classification) method * @param tabla array containing the outputs of the classifiers for the instance * @return the confidence vector */ protected double[] computeClassScoresLVPC(double[][] tabla) { // retrieve the P,C,I matrices double[][][] relationSet = relationsForInstance(tabla); double[][] preferenceRelation = relationSet[0]; double[][] conflictRelation = relationSet[1]; double[][] ignoranceRelation = relationSet[2]; // calculate the class scores double[] classScores = new double[preferenceRelation.length]; for (int i = 0; i < preferenceRelation.length; i++) { for (int j = 0; j < preferenceRelation[i].length; j++) { if (i != j) { classScores[i] += preferenceRelation[i][j] + conflictRelation[i][j] * 0.5 + ignoranceRelation[i][j] * (classifier.aprioriClassDistribution[i] / (classifier.aprioriClassDistribution[i] + classifier.aprioriClassDistribution[j])) ; } } if (Double.isNaN(classScores[i])) { classScores[i] = 0; } } if (sum(classScores) == 0) { return classScores; } normalize(classScores); return classScores; } protected double[] computeClassScoresWeighted(double[] tabla) { double [] max = new double[nClasses]; for (int i = 0; i < nClasses; i++) { double sum_clase = 0.0; for (int j = 0; j < nClasses; j++) { sum_clase += tabla[i * nClasses + j];// > tabla[j * nClasses + i] ? tabla[i * nClasses + j] : 0; } max[i] = sum_clase / (double)nClasses; } return max; } protected String computeClassScoresDynamic(int i) { // String output = new String("?"); double[] max; String output = "?"; double[] tabla = tablas[i]; boolean distEu = true; if (Attributes.hasNominalAttributes()) distEu = false; if (Attributes.hasRealAttributes() || Attributes.hasIntegerAttributes()) distEu = true; kvecinos = (int)(nClasses * 3); // 3 está probado! int kvecinosAux = kvecinos; metodo.setK(kvecinos); int[] vecinos = null, votos = null; double[] distancias = null; int classesInNeighborhood = 0; double limit = 1; while (classesInNeighborhood <= limit && kvecinosAux <= kvecinos * 2) { vecinos = new int[kvecinosAux]; distancias = new double[kvecinosAux]; votos = new int[nClasses]; metodo.evaluaKNN(i, vecinos, distancias, votos, test, distEu); classesInNeighborhood = 0; for (int j = 0; j < votos.length; j++) { if (votos[j] > 0) classesInNeighborhood++; } kvecinosAux++; metodo.setK(kvecinosAux); } double probs[] = new double[nClasses]; double sum_probs = 0; int nClassesTest = 0; for (int j = 0; j < vecinos.length; j++) { if (vecinos[j] != -1) { probs[classifier.train.getOutputAsInteger()[vecinos[j]]] += 1.0 / distancias[j]; sum_probs += 1.0 / distancias[j]; } } if (sum_probs > 0){ for (int j = 0; j < nClasses; j++) { if (votos[j] > 0) nClassesTest++; probs[j] /= sum_probs; } } double[][] tablaNew = new double[nClassesTest][nClassesTest]; int fila = 0; if (sum_probs > 0) { // (classesInNeighborhood > 1) {// max = new double[nClasses]; for (int j = 0; j < nClasses; j++) { double sum_clase = 0; double tot = 0; if (votos[j] > 0) { int columna = 0; for (int k = 0; k < nClasses; k++) { if (votos[k] > 0) { sum_clase += tabla[j * nClasses + k]; tot++; tablaNew[fila][columna] = tabla[j * nClasses + k]; columna++; } else tabla[j * nClasses + k] = 0; } fila++; } else { for (int k = 0; k < nClasses; k++) { tabla[j * nClasses + k] = 0; } } if (tot != 0) max[j] = sum_clase / tot;//* probs[j]; //(double)nClasses;// * probs[j]; else max[j] = -1; } double[] max2 = null; //this.computeClassScoresPC(tablaNew); //method2WuJMLR max2 = computeClassScoresWeighted(tablaNew); fila = 0; max = new double[nClasses]; for (int j = 0; j < nClasses; j++) { if (votos[j] > 0) { max[j] = max2[fila]; fila++; } else max[j] = -1; } } else { max = computeClassScoresWeighted(tabla); } output = getOutputTies(max); return output; } /** * It obtains the class scores for the OVO scheme * @param example * @return */ protected String computeClassScoresOVA(double[] example) { String output = new String("?"); double[] grado_asoc = classifier.ova_table(example); output = getOutputTies(grado_asoc); return output; } /** * Retrieves the preference, conflict and ignorance matrices for a single instance * @param inst The instance for which the PCI-matrices shall be created * @return [0][][] preference matrix, [1][][] conflict matrix, [2][][] ignorance matrix * @throws Exception */ public double[][][] relationsForInstance(double[][] tabla) { double[][] prefRelation = new double[nClasses][nClasses]; double[][] conflictRelation = new double[nClasses][nClasses]; double[][] ignoranceRelation = new double[nClasses][nClasses]; double s0, s1; for (int x = 0; x < nClasses; x++) for (int y = 0; y < nClasses; y++) { s0 = tabla[x][y]; s1 = tabla[y][x]; double min = s0 < s1 ? s0 : s1; double max = s0 > s1 ? s0 : s1; prefRelation[x][y] = s0 - min; prefRelation[y][x] = s1 - min; conflictRelation[x][y] = min; conflictRelation[y][x] = min; ignoranceRelation[x][y] = 1 - max; ignoranceRelation[y][x] = 1 - max; } return new double[][][] { prefRelation, conflictRelation, ignoranceRelation}; } /** * It returns the output class for the array containing the confidences for each class * @param max Array with the confidences for each class * @return The output class */ String getOutputTies(double[] max) { /* * Tie-breaking step 1: Find out which classes gain the maximum score */ double maxValue = max[maxIndex(max)]; double[] ties = new double[max.length]; for (int i = 0; i < max.length; i++) { if (max[i] == maxValue) { ties[i] = classifier.aprioriClassDistribution[i]; } } max = new double[max.length]; max[maxIndex(ties)] = 1; /* * Tie-breaking step 2: Check whether the tying classes have the same a priori * class probability and count these classes. */ int tieValues = 0; maxValue = ties[maxIndex(ties)]; for (int i = 0; i < ties.length; i++) { if (ties[i] == maxValue) { tieValues++; } } /* * Tie-breaking step 3: If the tying classes have the same a priori probabilities, * then use randomization to determine the winner among these classes */ if (tieValues > 1) { tieValues = 0; maxValue = ties[maxIndex(ties)]; int[] stillTying = new int[ties.length]; for (int i = 0; i < max.length; i++) { if (ties[i] == maxValue) { stillTying[tieValues] = i; tieValues++; } } return classifier.train.getOutputValue(stillTying[0]); } return classifier.train.getOutputValue(maxIndex(max)); } /** * Normalizes the doubles in the array by their sum. * * @param doubles the array of double * @exception IllegalArgumentException if sum is Zero or NaN */ public static void normalize(double[] doubles) { double sum = 0; for (int i = 0; i < doubles.length; i++) { if (!Double.isNaN(doubles[i])) sum += doubles[i]; } normalize(doubles, sum); } /** * Normalizes the doubles in the array using the given value. * * @param doubles the array of double * @param sum the value by which the doubles are to be normalized * @exception IllegalArgumentException if sum is zero or NaN */ public static void normalize(double[] doubles, double sum) { if (Double.isNaN(sum)) { throw new IllegalArgumentException("Can't normalize array. Sum is NaN."); } if (sum == 0) { return; } for (int i = 0; i < doubles.length; i++) { doubles[i] /= sum; } } /** * It obtains the sum of the values of an array * @param array the array containing the values * @return the sum of the values in the array */ double sum(double[] array) { double sum = 0.0; for (int i = 0; i < array.length; i++) { sum += array[i]; } return sum; } /** * This returns the index containing the maximum value of an array * @param array the array from which to obtain the index of the maximum value * @return the index containing the maximum value of the array */ int maxIndex(double[] array) { double max = array[0]; int index = 0; for (int i = 1; i < array.length; i++) { if (array[i] > max) { max = array[i]; index = i; } } return index; } }