/*********************************************************************** 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.Subgroup_Discovery.CN2SD; import java.io.IOException; import java.util.Arrays; import java.util.ArrayList; import java.util.Collections; import org.core.Fichero; /** * <p>Title: Clase CN2SD</p> * * <p>Description: Contiene los metodos principales del algoritmo cn2sd</p> * * <p>Copyright: Copyright Alberto (c) 2006</p> * * <p>Company: Mi casa </p> * * @author Alberto Fern�ndez * @version 1.0 */ public class CN2SD { //Rutas y nombres de ficheros E/S private String ficheroSalida; private String ficheroSalidaTr; private String ficheroSalidaTst; private int muestPorClaseTrain[]; private int muestPorClaseEval[]; private int muestPorClaseTest[]; private int[] valorClases; private int nClases; // Numero m�ximo de clases private Complejo almacenSelectores; // Almacena todos los posibles selectores private int tamanoEstrella; private ConjReglas conjReglasFinal; //Conjunto final de reglas private EvaluaCalidadReglas evReg; // Para evaluar la calidad de las reglas //Matrices de datos private ConjDatos datosTrain; private ConjDatos datosEval; private ConjDatos datosTest; private int clasesTrain[]; private int clasesTest[]; private long tiempo; private String miSalida; private double nu; private double seCubre; private int multiplicativo; private int nAtributos; private double div; private double umbral = 0.0; private int eficacia = 0; private String[] nombreAtributos; private String[] nombreClases; private boolean hayContinuos = false; public boolean todoBien(){ return (!hayContinuos); } /** * Constructor de la clase CN2</br> * Simplemente nos ocupamos de hacer una copia local a la clase de los nombres * de los ficheros para su posterior uso;<br/> * Despues obtenemos todos los datos del fichero y los guardamos en un formato * reconocible por el programa.</br> * Por �ltimo crea todos los posibles selectores que se puedan dar para el conjunto * concreto de datos y los almacena. * @param ftrain Nombre del fichero donde reside el conjunto de entrenamiento * @param feval Nombre del fichero donde reside el conjunto de validaci�n * @param ftest Nombre del fichero donde reside el conjunto de test * @param fSalidaTr Nombre del fichero de salida donde guardaremos el resultado de entrenamiento * @param fSalidaTst Nombre del fichero de salida donde guardaremos el resultado de test * @param fsal Nombre del fichero donde guardaremos los datos generales de salida (reglas, tiempo...) * @param tamEstrella es el tama�o m�ximo de la estrella para la busqueda (beam search) * @param _nu es el valor del peso multiplicativo para el peso de los ejemplos * @param _seCubre porcentaje minimo para decir que una clase de ejemplos ya queda cubierta por un conjunto de reglas * @param _multi Se refiere a si se utilizar� el peso multiplicativo (1) o el aditivo (0) * @param _eficacia Usamos un m�todo supuestamente mas eficaz (m�s selectores) o m�s eficiente */ public CN2SD(String ftrain, String feval, String ftest, String fSalidaTr, String fSalidaTst, String fsal, int tamEstrella, double _nu, double _seCubre, int _multi, int _eficacia) { int i; System.out.println("Executing CN2SD"); ficheroSalida = fsal; nu = _nu; seCubre = _seCubre; multiplicativo = _multi; eficacia = _eficacia; ficheroSalidaTr = fSalidaTr; ficheroSalidaTst = fSalidaTst; Dataset train = new Dataset(); //ficheroTrain); Dataset eval = new Dataset(); //ficheroTest); } Dataset test = new Dataset(); //ficheroTest); } try { //System.out.println("\nLeyendo train: " + ftrain); train.leeConjunto(ftrain, true); if (train.hayAtributosContinuos()){ System.err.println("CN2SD may not work properly with continuous attributes.\nPlease discretize the data base"); //System.exit(-1); hayContinuos = true; } //System.out.println("\nLeyendo validacion: " + feval); eval.leeConjunto(feval, false); if (eval.hayAtributosContinuos()){ System.err.println("CN2SD may not work properly with continuous attributes.\nPlease discretize the data base"); //System.exit(-1); hayContinuos = true; } //System.out.println("\nLeyendo test: " + ftest); test.leeConjunto(ftest, false); } catch (IOException e) { System.err.println("There was a problem while reading the data-set files:"); System.err.println("-> " + e); System.exit(0); } miSalida = new String(""); miSalida = test.copiaCabeceraTest(); // obtenemos conjuntos de training y de test System.out.println("\nGenerating datasets"); datosTrain = new ConjDatos(); datosEval = new ConjDatos(); datosTest = new ConjDatos(); train.calculaMasComunes(); eval.calculaMasComunes(); test.calculaMasComunes(); datosTrain = creaConjunto(train); //Leemos los datos de entrenamiento (todos seguidos como un String) datosEval = creaConjunto(eval); datosTest = creaConjunto(test); //Idem TEST clasesTrain = train.getC(); nClases = train.getnclases(); nAtributos = train.getnentradas(); int[] auxiliar = train.getC(); Arrays.sort(auxiliar); valorClases = new int[nClases]; valorClases[0] = auxiliar[0]; int valor = 0; for (i = 1; i < nClases; i++) { int j; for (j = valor; auxiliar[j] == valorClases[i - 1]; j++) { ; } valorClases[i] = auxiliar[j]; valor = j; } muestPorClaseTrain = new int[nClases]; for (int j = 0; j < nClases; j++) { muestPorClaseTrain[j] = 0; for (i = 0; i < datosTrain.size(); i++) { if (valorClases[j] == clasesTrain[i]) { muestPorClaseTrain[j]++; } } } clasesTest = test.getC(); muestPorClaseTest = new int[nClases]; for (int j = 0; j < nClases; j++) { muestPorClaseTest[j] = 0; for (i = 0; i < datosTest.size(); i++) { if (valorClases[j] == clasesTest[i]) { muestPorClaseTest[j]++; } } } int [] clasesEval; clasesEval = eval.getC(); muestPorClaseEval = new int[nClases]; for (int j = 0; j < nClases; j++) { muestPorClaseEval[j] = 0; for (i = 0; i < datosEval.size(); i++) { if (valorClases[j] == clasesEval[i]) { muestPorClaseEval[j]++; } } } tamanoEstrella = tamEstrella; // Establace el tama�o de la estrella tiempo = System.currentTimeMillis(); // medimos tiempo div = (double) 1.0 / datosTrain.size(); nombreAtributos = train.dameNombres(); nombreClases = train.dameClases(); if (nombreClases == null){ nombreClases = new String[nClases]; for (i = 0; i < nClases; i++){ nombreClases[i] = ""+valorClases[i]; } } } /** * Crea un conjunto de datos (atributos/clase) segun los obtenidos de un fichero de datos * @param mis_datos Debe ser un conjunto de datos leido del fichero (mirar doc Dataset.java) * @return El conjunto de datos ya creado, es decir, una lista enlazada de muestras (consultar ConjDatos.java y Muestra.java) */ private ConjDatos creaConjunto(Dataset mis_datos) { ConjDatos datos = new ConjDatos(); //Creo un nuevo conjunto de datos int tam = mis_datos.getnentradas(); //Pillo el n�mero de atributos de entrada (suponemos una sola salida [clase]) double[] vars = new double[tam]; //Creamos el vector que guardar� los valores de los atributos (aun siendo enteros o enum) double[][] X; int[] C; int clase = 0; //Variable que contendr� el valor para la clase boolean salir = false; X = mis_datos.getX(); C = mis_datos.getC(); for (int i = 0; i < mis_datos.getndatos(); i++) { salir = false; //System.out.print("\n"+i+":"); for (int j = 0; (j < tam); j++) { //System.out.print(" "+X[i][j]); if (mis_datos.isMissing(i, j)) { vars[j] = mis_datos.masComun(j); } else { //CAMBIAR POR OTROS METODOS DE MANEJO DE VALORES PERDIDOS (15-NN). vars[j] = X[i][j]; //Double.parseDouble(mis_datos.getDatosIndex(i, j)); //pillo el valor del atributo j para el ejemplo i } } //if (!salir) { clase = C[i]; //Integer.parseInt(mis_datos.getDatosIndex(i, tam)); Muestra m = new Muestra(vars, clase, tam); //Creo un nuevo dato del conjunto con sus variables, su clase y el num variables m.setPosFile(i); datos.addDato(m); //} } return datos; } /** * Realiza el algoritmo CN2SD */ public void ejecutar() { hazSelectores(); //Creo todos los posibles selectores para las reglas ConjDatos datosTrainAux = new ConjDatos(); datosTrainAux = datosTrain.copiaConjDatos(); CN2desordenado(datosTrainAux, valorClases); tiempo = System.currentTimeMillis() - tiempo; /** Elimino posibles reglas repetidas **/ for (int i = 0; (i < conjReglasFinal.size()-1); i++){ boolean compara = false; for (int j = i+1; (j < conjReglasFinal.size())&&(!compara); j++){ compara = conjReglasFinal.getRegla(i).esIgual(conjReglasFinal.getRegla(j)); } if (compara){ //est� //System.out.println("entro"); conjReglasFinal.deleteRegla(i); //lo borro i--; } } evReg = new EvaluaCalidadReglas(conjReglasFinal, datosEval, datosTest, muestPorClaseEval, muestPorClaseTest, multiplicativo, nu,nombreClases); generaSalida(); } /** * Crea el conjunto total selectores para obtener as� todas las posibles reglas */ private void hazSelectores() { int totalAtributos = datosTrain.getDato(0).getNatributos(); int ejemplos = datosTrain.size(); double[][] lista = new double[ejemplos + 1][totalAtributos]; //Para ver los != for (int i = 0; i < totalAtributos; i++) { // para todos los atributos [columnas] lista[0][i] = datosTrain.getDato(0).getMuest()[i]; //Inicializo lista[1][i] = Double.POSITIVE_INFINITY; //marcador } for (int i = 0; i < totalAtributos; i++) { // para todos los atributos [columnas] for (int j = 1; j < ejemplos; j++) { //Para todos los ejemplos double valor = datosTrain.getDato(j).getMuest()[i]; int k = 0; while (!(Double.isInfinite(lista[k][i]))) { //Mientras no vea la marca if (lista[k][i] == valor) { //Si est� repe break; } k++; } if (Double.isInfinite(lista[k][i])) { //Si se ha recorrido toda la lista lista[k][i] = valor; lista[k + 1][i] = Double.POSITIVE_INFINITY; } } } almacenSelectores = new Complejo(nClases); //Aqui voy a almacenar los selectores (numVariable,operador,valor) for (int i = 0; i < totalAtributos; i++) { // para todos los atributos [columnas] for (int h = 0; h < ejemplos; h++) { //Para todos los ejemplos if (Double.isInfinite(lista[h][i])) { break; //Siguiente atributo, ya los he gastado todos } for (int j = 0; j < 4; j++) { // Para 3 posibles valores en la comparac <>,<=,> Selector s = new Selector(i, j, lista[h][i]); //Tomo el valor de cada atributo (columna) [atr,op,valor] almacenSelectores.addSelector(s); //Lo meto si no esta repetido } } } //Ahora hago los del operador = (valores disjuntos) if (eficacia == 1) { for (int i = 0; i < totalAtributos; i++) { // para todos los atributos [columnas] int total; for (total = 0; !(Double.isInfinite(lista[total][i])); total++) { ; } ArrayList list = new ArrayList(); ArrayList listaAux = new ArrayList(); for (int j = 0; j < total - 1; j++) { for (int k = j + 1; k < total; k++) { double[] valores = new double[2]; valores[0] = lista[j][i]; valores[1] = lista[k][i]; /*System.out.print("\n Selector -> Atributo(" + i + ") = " + valores[0]); for (int x = 1; x < 2; x++) { System.out.print(" � " + valores[x]); }*/ listaAux.add(valores); Selector s = new Selector(i, 0, valores); //Tomo el valor de cada atributo (columna) [atr,op,valor] almacenSelectores.addSelector(s); //Lo meto si no esta repetido } } //Ahora voy a crear todos los subconjuntos a partir de los de tama�o 2: for (int l = 3; l < total - 2; l++) { double[] auxi = new double[l - 1]; double[] auxi2 = new double[l - 1]; list.addAll(listaAux); listaAux.clear(); while (!list.isEmpty()) { boolean salir = false; auxi = (double[]) list.remove(0); //lo cojo y lo elimino for (int j = 0; (j < list.size()) && (!salir); j++) { auxi2 = (double[]) list.get(j); for (int k = 0; (k < auxi.length - 1) && (!salir); k++) { salir = !(auxi[k] == auxi2[k]); } if (!salir) { double[] valores = new double[l]; //contendra los subconjuntos for (int k = 0; k < l - 1; k++) { valores[k] = auxi[k]; } valores[l - 1] = auxi2[l - 2]; listaAux.add(valores); Selector s = new Selector(i, 0, valores); /*System.out.print("\n Selector -> Atributo(" + i + ") = " + valores[0]); for (int x = 1; x < l; x++) { System.out.print(" � " + valores[x]); }*/ almacenSelectores.addSelector(s); } } } } } } } /** * Procedimiento CN2desordenado. Genera reglas desordenadas para cada clase del conj. entrenamiento * @param datosTrainAux ConjDatos Conjunto de ejemplos de entrenamiento * @param valorClases int[] Clases de entrenamiento */ private void CN2desordenado(ConjDatos datosTrainAux, int[] valorClases) { conjReglasFinal = new ConjReglas(); conjReglasFinal.adjuntaNombreClases(nombreClases); conjReglasFinal.adjuntaNombreClase(nombreAtributos[nAtributos]); System.out.println("\n Extracting rules for the different classes:"); for (int i = 0; i < nClases; i++) { //Para cada clase en train CN2paraUnaClase(datosTrainAux, valorClases[i]); //conjReglasFinal.addReglas(conjReglasAux); } } /** * Obtiene las reglas para una clase concreta. * @param train ConjDatos datos de ejemplo de entrenamiento * @param clase int Clase para la que obtendremos las reglas */ private void CN2paraUnaClase(ConjDatos train, int clase) { //ConjReglas reglas = new ConjReglas(); * @return ConjReglas Las reglas (desordenadas) que explican 'train' para la clase 'clase' boolean continuar = false; int contador = 0; int quedan = muestPorClaseTrain[clase]; System.out.println("\n We search for the best complex for class " + nombreClases[clase]+" ["+quedan+" examples remaining]"); do { continuar = false; Complejo mejorComplex = encontrarMejorComplejo(train, clase); if (mejorComplex != null) { mejorComplex.adjuntaNombreAtributos(nombreAtributos); System.out.println("\n\nComplex Found:"); mejorComplex.print(); conjReglasFinal.addRegla(mejorComplex); for (int i = 0; i < train.size(); i++) { Muestra m = train.getDato(i); if ((mejorComplex.cubre(m)) && (mejorComplex.getClase() == m.getClase())) { //Cubre al ejemplo y es un verdadero positivo m.incrementaCubierta(); if (m.getCubierta() == 1) { contador++; quedan--; } } } continuar = true; } double porc = (double) contador / muestPorClaseTrain[clase]; if (porc >= seCubre) { continuar = false; } System.out.println("\nPorcentage of examples covered -> " + porc * 100 + "% Remaining:<" + quedan + ">"); } while (continuar); //return reglas; } /** * Procedimiento que se encarga de descubrir el mejor complejo para los datos dados * @param train ConjDatos * @param clase int * @return Complejo */ private Complejo encontrarMejorComplejo(ConjDatos train, int clase) { Complejo mejorComplex = new Complejo(nClases); ConjReglas star = new ConjReglas(); //boolean continuar = true; //Creo la estrella inicial for (int i = 0; i < almacenSelectores.size(); i++) { Complejo aux = new Complejo(nClases); aux.setClase(clase); aux.addSelector(almacenSelectores.getSelector(i)); evaluarComplejo(aux, train); star.addRegla(aux); } //Ordenar Collections.sort(star.getConjReglas()); //Comprobar significancia estadistica mejor complejo (opcional) //.... //Obtener mejor complejo star.eliminaSubsumidos(tamanoEstrella); /*boolean compara = true; for (int j = 0; (j < star.size())&&(compara); j++){ compara = false; for (int i = 0; (i < conjReglasFinal.size())&&(!compara); i++){ compara = star.getRegla(j).esIgual(conjReglasFinal.getRegla(i)); } if (compara){ //est� star.deleteRegla(j); //lo borro j--; } }*/ mejorComplex = star.getRegla(0); for (int j = star.size() - 1; star.size() > tamanoEstrella; star.deleteRegla(j), j--) { ; //Borramos -> busqueda dirigida } //star.print(); //do { for (int tam = 1; tam < nAtributos; tam++) { //a) Especializar todo complejo en Star ConjReglas newStar = new ConjReglas(); for (int i = 0; i < almacenSelectores.size(); i++) { Selector s = almacenSelectores.getSelector(i); for (int j = 0; j < star.size(); j++) { Complejo aux2 = star.getRegla(j); //complejo a especializar Complejo aux = new Complejo(nClases); //complejo especializado boolean sigue = true; for (int h = 0; (h < aux2.size()) && (sigue); h++) { Selector s2 = aux2.getSelector(h); aux.addSelector(s2); if (s2.compareTo(s) < 2) { //mismo atributo sigue = false; //no lo a�ado } } if (sigue) { //El selector no esta repetido en el complejo dado de "star" aux.addSelector(s); aux.setClase(clase); evaluarComplejo(aux, train); newStar.addRegla(aux); } } } Collections.sort(newStar.getConjReglas()); //Comprobar significancia estadistica mejor complejo (opcional) //.... //Obtener mejor complejo //esSignificativa(mejorComplex); //mejorComplex = newStar.getUltimaRegla(); eliminaNoValidos(newStar); //elimino repetidos y subsumidos para quedarme las tamEstrella mejores reglas //Compruebo que el mejor complejo no est� ya en mi conjunto de reglas /*compara = true; for (int j = 0; (j < newStar.size())&&(compara); j++){ compara = false; for (int i = 0; (i < conjReglasFinal.size())&&(!compara); i++){ compara = newStar.getRegla(j).esIgual(conjReglasFinal.getRegla(i)); } if (compara){ //est� newStar.deleteRegla(j); //lo borro j--; } }*/ if (mejorComplex.compareTo(newStar.getRegla(0)) == 1) { //es peor mejorComplex = newStar.getRegla(0); } //for (; newStar.size() > tamanoEstrella; newStar.deleteRegla(0)) { for (int j = newStar.size() - 1; newStar.size() > tamanoEstrella; newStar.deleteRegla(j), j--) { ; //Borramos -> busqueda dirigida } star.deleteAll(); star.addReglas(newStar); System.out.print("\nNew Star created of size " + (tam + 1)); //star.print(); } //while(continuar); return mejorComplex; } /** * Se encarga de eliminar los complejos no validos en newStar: Estan en 'star' o son nulos (at1 = 0 ^ at1 <> 0) * @param newStar ConjReglas El nuevo conjunto de complejos que estamos creando */ private void eliminaNoValidos(ConjReglas newStar) { //Primero quitamos los nulos: se repiten atributos! //newStar.eliminaNulos(); //Esta hecho conforme se construye [tienen distinto atributo] //Eliminamos los complejos que esten repetidos dentro de newStar! //newStar.eliminaRepetidos(tamanoEstrella); //Elimino hasta quedarme con "tamEstrella" newStar.eliminaSubsumidos(tamanoEstrella); //Elimino reglas que sean sem�nticamente iguales (At = 1, At <> 0, At = [0,1]) } /** * Test de significancia estad�stica. El complejo c es significativo si su valor superar un umbral dado. * <br/>El c�lculo se realiza como 2*SUM[fi�log(fi/ei)] donde: * <br/>fi es la distribuci�n de ejemplos cubiertos por c -> N� ej's cubiertos clase i / n� ejemplos * <br/>ei es la distribuci�n de ejemplos cubiertos supuestamente aleatoriamente -> N� ej's clase i / n� ejemplos * @param c Complejo El complejo a analizar * @return boolean True si es significativa (supera el umbral dado) false en otro caso */ private boolean esSignificativa(Complejo c) { double significancia = 0; double pCond = 0; for (int j = 0; j < nClases; j++) { pCond += c.getDistribucionClaseEj(j); } pCond *= 1.0 / datosTrain.size(); for (int j = 0; j < nClases; j++) { double logaritmo = (double) c.getDistribucionClaseEj(j) / (this.muestPorClaseTrain[j] * pCond); if (logaritmo != 0) { logaritmo = Math.log(logaritmo); logaritmo *= (double) c.getDistribucionClaseEj(j); significancia += logaritmo; } } significancia *= 2.0; //System.out.println("Umbral -> " + significancia); return (significancia >= umbral); } /** * ReCalcula el peso multiplicativo para un ejemplo * @param i el n�mero de reglas que cubren al ejemplo * @return el nuevo peso */ private double pesoMultiplicativo(int i) { double aux; aux = Math.pow(nu, i); return aux; } /** * ReCalcula el peso aditivo para un ejemplo * @param i int el n�mero de reglas que cubren al ejemplo * @return double el nuevo peso */ private double pesoAditivo(int i) { double aux; aux = 1.0 / (i + 1); return aux; } /** Evaluacion de los complejos sobre el conjunto de ejemplo para ver cuales se * cubren de cada clase * @param c Complejo a evaluar * @param e Conjunto de datos */ private void evaluarComplejo(Complejo c, ConjDatos e) { int i; double n, ncond, nclascond, nclas; int cl; double val, peso = 0; n = 0; ncond = 0; nclascond = 0; nclas = 0; c.borraDistrib(); for (i = 0; i < e.size(); i++) { cl = e.getDato(i).getClase(); if (multiplicativo == 1) { peso = this.pesoMultiplicativo(e.getDato(i).getCubierta()); } else { peso = this.pesoAditivo(e.getDato(i).getCubierta()); } n += peso; if (c.cubre(e.getDato(i))) { c.incrementaDistrib(cl); ncond += peso; if (cl == c.getClase()) { nclascond += peso; } } if (cl == c.getClase()) { nclas += peso; } } if (n != 0 && ncond != 0) { val = (ncond / n) * ((nclascond / ncond) - (nclas / n)); } else { val = Double.MIN_VALUE; } c.setHeuristica(val); c.ajustaDistrib(); } /** * Calcula los datos estad�sticos necesarios y crea los ficheros KEEL de salida */ private void generaSalida() { Fichero f = new Fichero(); String cad = ""; //System.out.println("\n Estas son las reglas encontradas:"); //conjReglasFinal.print(); cad = conjReglasFinal.printString(); cad += "\n\n" + evReg.printString() + ";\n Time; " + (tiempo / 1000); f.escribeFichero(ficheroSalida, cad); f.escribeFichero(ficheroSalidaTr, miSalida + evReg.salida(datosTrain)); f.escribeFichero(ficheroSalidaTst, miSalida + evReg.salida(datosTest)); } }