/***********************************************************************
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.aprioriSD;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import org.core.Fichero;
/**
* <p>T�tulo: Clase principal del algoritmo</p>
* <p>Descripci�n: Contiene los metodos esenciales del algoritmo APRIORISD</p>
* @author Alberto Fern�ndez Hilario 31-01-2006.
* @version 1.0
*/
public class aprioriSD {
public aprioriSD() {
}
private int nClases; // Numero m�ximo de clases
private int datos; //n�mero total de datos (transacciones)
private int entradas; //n�mero total de entradas (variables / columnas)
private double Smin;
private double Cmin;
private int[] maximos;
private Dataset train,eval, test;
private ConjDatos datosTrain, datosTest, datosEval;
private int muestPorClaseEval[];
private int muestPorClaseTest[];
private int[][] X; //conjunto de datos de entrada (transacciones);
private int[] C; //conjunto de clases (transacciones junto con los datos de entrada)
private ConjReglas reglas;
private ConjReglas rFinal;
private String ficheroSalida;
private String ficheroSalidaTr;
private String ficheroSalidaTst;
private String miSalida;
private int N;
private int postpoda;
private EvaluaCalidadReglas evReg; // Para evaluar la calidad de las reglas
private long tiempo;
private String[] nombreAtributos;
private String[] nombreClases;
private boolean hayContinuos = false;
public boolean todoBien(){
return (!hayContinuos);
}
/**
* Constructor de la clase pruebas</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 validacion
* @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 _Smin Minimo Support
* @param _Cmin Minimo Confidence
* @param _N N�mero m�ximo de reglas a generar
* @param _postpoda Se refiere al tipo de postpoda
*/
public aprioriSD(String ftrain, String feval, String ftest, String fSalidaTr,
String fSalidaTst, String fsal, double _Smin, double _Cmin,
int _N, int _postpoda) {
int i;
tiempo = System.currentTimeMillis(); // medimos tiempo
ficheroSalida = fsal;
ficheroSalidaTr = fSalidaTr;
ficheroSalidaTst = fSalidaTst;
N = _N;
postpoda = _postpoda;
train = new Dataset();
eval = new Dataset();
test = new Dataset();
try {
//System.out.println("\nLeyendo train: " + ftrain);
train.leeConjunto(ftrain, true);
if (train.hayAtributosContinuos()) {
System.err.println(
"AprioriC may not handle continuous attributes.\nPlease discretize the data base");
//System.exit( -1);
hayContinuos = true;
}
//System.out.println("\nLeyendo eval: " + feval);
eval.leeConjunto(feval, false);
if (eval.hayAtributosContinuos()){
System.err.println("Apriori may not handle 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 trying to read the data-set files:");
System.err.println("-> " + e);
System.exit(0);
}
datos = train.getndatos();
entradas = train.getnentradas();
nClases = train.getnclases();
datosEval = new ConjDatos();
datosEval = creaConjunto(eval);
maximos = train.ordenLexicografico();
//maximos = test.ordenLexicografico();
datosTrain = new ConjDatos();
datosTest = new ConjDatos();
datosTrain = creaConjunto(train); //Leemos los datos de entrenamiento (todos seguidos como un String)
datosTest = creaConjunto(test); //Idem TEST
Cmin = _Cmin;
Smin = (_Smin * datos);
reglas = new ConjReglas();
rFinal = new ConjReglas();
miSalida = new String("");
miSalida = test.copiaCabeceraTest();
nClases = train.getnclases();
X = train.getX();
C = train.getC();
int[] auxiliar = eval.copiaC();
int[] clasesEval = eval.copiaC();
Arrays.sort(auxiliar);
int[] valorClases = new int[nClases];
valorClases[0] = auxiliar[0];
int valor = 0;
for (i = 1; i < nClases; i++) {
int j;
for (j = valor;
(j < auxiliar.length) && (auxiliar[j] == valorClases[i - 1]);
j++) {
;
}
if (j < auxiliar.length) {
valorClases[i] = auxiliar[j];
valor = j;
}
}
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]++;
}
}
}
int[] clasesTest = test.getC();
auxiliar = test.getC();
Arrays.sort(auxiliar);
valorClases = new int[nClases];
valorClases[0] = auxiliar[0];
valor = 0;
for (i = 1; i < nClases; i++) {
int j;
for (j = valor;
(j < auxiliar.length) && (auxiliar[j] == valorClases[i - 1]);
j++) {
;
}
if (j < auxiliar.length) {
valorClases[i] = auxiliar[j];
valor = j;
}
}
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]++;
}
}
}
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)
int[][] 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;
for (int j = 0; (j < tam) && !salir; j++) {
if (mis_datos.isMissing(i, j)) {
vars[j] = Double.NaN;
} else {
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;
}
/**
* Busca y crea los 1-items, es decir, conjuntos de un elemento con un support >= Smin
* @param L ArrayList Lista donde guardar� los 1-items
*/
private void uno_items(ArrayList L) {
int[] auxi = new int[datos];
int S;
int itemAct = 0;
for (int i = 0; i < entradas; i++) { //para cada columna (atributos)
for (int j = 0; j < datos; j++) {
auxi[j] = X[j][i];
}
Arrays.sort(auxi); //Ordeno de menor a mayor la columna
int valor = itemAct;
int j;
for (j = 0; auxi[j] == -1; j++) {
;
}
valor = auxi[j];
S = 0;
for (; valor != maximos[i]; j++) { //A�ado items por columnas en funcion de su support
if (valor == auxi[j]) { //cuento uno mas porque se repite
S++;
} else { //A�ado el item si tiene suficiente Support
if (S >= Smin) { //Tiene mayor support
Item item = new Item(valor, i, S);
L.add(item); //a�ado el item
}
S = 1; //reinicio el contador de support
valor = auxi[j];
}
}
S = datos - j + 1; //para el �ltimo (n� datos - los que ya he contado)
if (S >= Smin) {
Item item = new Item(maximos[i], i, S);
L.add(item);
}
itemAct = maximos[i] + 1;
}
//Ahora contamos las clases (deber�an ser 1-items!!)
for (int j = 0; j < datos; j++) {
auxi[j] = C[j];
}
Arrays.sort(auxi);
//Sigo el mismo esquema que con los otros 1-items
int valor = itemAct;
S = 1;
int j;
for (j = 1; valor != maximos[entradas]; j++) { //A�ado items por columnas en funcion de su support
if (valor == auxi[j]) {
S++;
} else {
if (S >= Smin) { //Tiene mayor support
Item item = new Item(valor, entradas, S);
L.add(item);
}
S = 1;
valor = auxi[j];
}
}
S = datos - j + 1;
if (S >= Smin) {
Item item = new Item(maximos[entradas], entradas, S);
L.add(item);
}
//YA TENGO LOS 1-ITEMS!!
/*
for (int i = 0; i < L.size(); i++){
Item it = (Item)L.get(i);
System.out.println(it.getItem()[0]+": "+it.getSupport());
}
*/
}
/**
* Creacion de candidatos k-items de L(k-1)
* @param L ArrayList Lista de k-1 items
* @param Cand ArrayList Lista que contendr� los k-items Candidatos
*/
private void creaCandidatos(ArrayList L, ArrayList Cand) {
for (int i = 0; i < L.size() - 1; i++) {
Item aux = (Item) L.get(i); //tomamos el item para hacer el subconjunto
for (int j = i + 1; j < L.size(); j++) { //para todos los demas conjuntos
Item aux2 = (Item) L.get(j); //tomamos el item para hacer el subconjunto
Item it = new Item(); //conjunto que voy a crear
boolean seguir = it.creaItem(aux, aux2); //intento hacer uno nuevo con esos 2
if (seguir) {
Cand.add(it); //A�ado si lo he creado correctamente (todos los subconjuntos estan en L)
}
}
}
}
/**
* Paso que elimina aquellos k-items no v�lidos (alguno de sus subconjuntos no pertenece a L)
* @param L ArrayList Lista de k-1 Items
* @param Cand ArrayList Lista de k-items candidatos
* @param k int Valor de k con el que estamos trabajando actualmente
*/
private void pruneStep(ArrayList L, ArrayList Cand, int k) {
for (int i = 0; i < Cand.size(); i++) { //Para todos los candidatos
//Hacemos los 3 subconjuntos:
Item item = (Item) Cand.get(i);
int[] it = item.getItem();
int[] subconjunto = new int[k - 1];
for (int j = 0; j < k - 1; j++) {
subconjunto[j] = it[j]; //primer subconjunto
}
//compruebo:
boolean parar = false, seguir = true;
for (int j = 0; (j < L.size()) && !parar; j++) { //Busco entre todos los elementos de L hasta que encuentre 1 =
Item aux = (Item) L.get(j);
int[] aux2 = aux.getItem();
seguir = true;
for (int l = 0; (l < k - 1) && seguir; l++) { //seguir = true si es igual todo el rato
seguir = (aux2[l] == subconjunto[l]);
}
parar = seguir; //parar = true si el item 'j' y el subconjunto son iguales
}
if (!parar) { //Este subconjunto no estaba en L
Cand.remove(i);
i--; //porque kito uno
} else {
subconjunto[0] = it[0];
for (int j = 2; j < k; j++) {
subconjunto[j - 1] = it[j]; //segundo subconjunto
}
//compruebo:
parar = false;
for (int j = 0; (j < L.size()) && !parar; j++) {
Item aux = (Item) L.get(j);
int[] aux2 = aux.getItem(); //tomamos el item para ver si est�
seguir = true;
for (int l = 0; (l < k - 1) && seguir; l++) {
seguir = (aux2[l] == subconjunto[l]);
}
parar = seguir;
}
if (!parar) { //Este subconjunto no estaba en L
Cand.remove(i);
i--; //porque kito uno
} else {
for (int j = 1; j < k; j++) {
subconjunto[j - 1] = it[j]; //tercer subconjunto
}
//compruebo
parar = false;
for (int j = 0; (j < L.size()) && !parar; j++) {
seguir = true;
Item aux = (Item) L.get(j);
int[] aux2 = aux.getItem(); //tomamos el item para ver si est�
for (int l = 0; (l < k - 1) && seguir; l++) {
seguir = (aux2[l] == subconjunto[l]);
}
parar = seguir;
}
if (!parar) { //Este subconjunto no estaba en L
Cand.remove(i);
i--; //porque kito uno
}
}
}
}
}
/**
* Cuenta las ocurrencias de cada k-item en Cand que aparezca en el conjunto de entrenamiento (transacciones).
* @param Cand ArrayList Lista de k-items Candidatos
* @param k int Valor de k actual
*/
private void contar(ArrayList Cand, int k) {
for (int i = 0; i < Cand.size(); i++) { //Contar los candidatos
Item item = (Item) Cand.get(i);
int[] aux = item.getItem();
int[] columnas = item.getColumnas();
if (aux[1] <= maximos[columnas[0]]) { //Estan en la misma columna
Cand.remove(i); //Ni me molesto en contarlo
i--; //porque he eliminado uno.
} else {
boolean parar = false;
for (int h = 1; (h < k) && !parar; h++) { //Para todos los elementos del k-item
if (aux[h] <= maximos[columnas[h - 1]]) { //Estan en la misma columna
Cand.remove(i); //Ni me molesto en contarlo
i--; //porque he eliminado uno.
parar = true; //dejo de fijarme en lo de las columnas
}
}
if (!parar) { //No he parado, luego estan todos en una columna distinta
//Aqui ya los cuento
int contador = 0;
if (columnas[k - 1] < entradas) { //Ambas son entradas
for (int l = 0; l < datos; l++) {
boolean seguir = true;
for (int j = 0; (j < k) && seguir; j++) {
seguir = (X[l][columnas[j]] == aux[j]);
}
if (seguir) {
contador++; //Son todos iguales
}
}
} else { //El ultimo es una clase
for (int l = 0; l < datos; l++) {
boolean seguir = true;
for (int j = 0; (j < k - 1) && seguir; j++) {
seguir = (X[l][columnas[j]] == aux[j]);
}
seguir = seguir && (C[l] == aux[k - 1]);
if (seguir) {
contador++; //Son todos iguales
}
}
}
if (contador < Smin) {
Cand.remove(i);
i--; //porque he eliminado uno.
} else {
item.setSupport(contador);
//item.print();
}
}
}
}
}
/**
* Para cada ITEM en el conjunto L comprueba si el ultimo valor corresponde a una clase
* y crea una regla si supera el Cmin
* @param L ArrayList Conjunto de Items
*/
private void ponReglas(ArrayList L) {
System.out.println("Finding rules...");
for (int i = 0; i < L.size(); i++) {
Item it = (Item) L.get(i);
int[] aux = it.getItem();
/*
System.out.print("Item["+i+"]: ");
for (int j = 0; j < aux.length; j++){
System.out.print(aux[j]+" ");
}
System.out.println("-> "+it.getSupport());
*/
int[] columnas = it.getColumnas();
int clase = (int) aux[aux.length - 1];
if (clase > maximos[entradas - 1]) { //El ultimo valor corresponde a una clase
Regla r = new Regla(it, nClases);
//r.print();
//System.out.println("-> "+r.getClase());
int contador = 0;
for (int l = 0; l < datos; l++) {
boolean seguir = true;
for (int j = 0; (j < aux.length - 1) && seguir; j++) {
seguir = (X[l][columnas[j]] == aux[j]);
}
if (seguir) {
contador++; //Son todos iguales
}
}
double conf = (double) it.getSupport() / contador;
//System.out.println(" Confi[" + i + "]: " + conf);
if (conf > Cmin) { //s(l) / s(a) > minconf
reglas.addRegla(r);
L.remove(i);
i--;
}
}
}
}
/**
* Cuenta los ejemplos cubiertos por la regla r
* @param r Regla Regla a comprobar
* @param datos ConjDatos Es la lista de ejemplos a comprobar
*/
private void cuentaEjCubiertos(Regla r, ConjDatos datos) {
for (int i = 0; i < datos.size(); i++) {
if (r.cubre(datos.getDato(i)) &&
r.getClase() == datos.getDato(i).getClase()) {
datos.getDato(i).incrementaCubierta();
}
}
}
/**
* 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;
}
/**
* Recalcula el support para cada regla segun los ejemplos que quedan
* @param datos ConjDatos Conjunto de ejemplos restantes
* @return boolean True si no se pueden cubrir mas ejemplos, False en caso contrario (alguna regla tiene S > 0)
*/
private boolean recalculaHeuristica(ConjDatos datos) {
boolean ret = true;
double n, ncond, nclascond, nclas;
double val, peso = 0;
for (int i = 0; i < reglas.size(); i++) {
Regla r = reglas.getRegla(i);
n = 0;
ncond = 0;
nclascond = 0;
nclas = 0;
for (int j = 0; j < datos.size(); j++) {
int cl = datos.getDato(j).getClase();
peso = this.pesoAditivo(datos.getDato(j).getCubierta());
n += peso;
if (r.cubre(datos.getDato(j))) {
ncond += peso;
if (cl == r.getClase()) {
nclascond += peso;
ret = false;
}
}
if (cl == r.getClase()) {
nclas += peso;
}
}
if (n != 0 && ncond != 0) {
val = (ncond / n) * ((nclascond / ncond) - (nclas / n));
} else {
val = Double.MIN_VALUE;
}
r.setHeuristicaWRAcc(val);
}
return ret;
}
/**
* Escribe los ficheros de salida KEEL basicos (train y test) y un informe de las reglas (valores y support)
* @param reglasFinal ConjReglas El conjunto de reglas a analiar
*/
private void generaSalida(ConjReglas reglasFinal) {
reglasFinal.ajusta(train.getCambio());
reglasFinal.adjuntaNombreClases(nombreClases);
reglasFinal.adjuntaNombreClase(nombreAtributos[entradas]);
evReg = new EvaluaCalidadReglas(reglasFinal, datosEval, datosTest,
muestPorClaseEval, muestPorClaseTest,nombreClases);
evReg.ajustaDistribucion(datosEval); //ajusta la distribucion de las reglas para la clasificacion final
Fichero f = new Fichero();
String cad = "";
f.escribeFichero(ficheroSalidaTr,
miSalida + evReg.salida(datosEval, 0));
f.escribeFichero(ficheroSalidaTst, miSalida + evReg.salida(datosTest, 1));
cad = reglasFinal.printString();
cad += "\n\n" + evReg.printString() + "\n TIME (sec): " +
(tiempo / 1000);
f.escribeFichero(ficheroSalida, cad);
System.out.print(cad);
}
public void ejecutar() {
ArrayList L = new ArrayList();
//datosTrain.print();
uno_items(L); //En L se guardaran los 1-items
System.out.println("1-ITEMS COMPUTED!! Total: " + L.size());
//Algoritmo principal
ArrayList Cand = new ArrayList();
for (int k = 2; L.size() > 0; k++) {
Cand.clear();
creaCandidatos(L, Cand); //En Cand se guardan los k-items candidatos
System.out.println(k + "-ITEMS Candidates created!! Total: " +
Cand.size());
//-----------------------
if (k > 3) {
pruneStep(L, Cand, k); //En Cand solo quedan los 'v�lidos' (aunque habr� algunos que no se podr�n contar)
//es decir, aquellos que tengan 2 items en la misma columna!
System.out.println(
"Candidate elimination step. Remaining: " +
Cand.size());
}
//----------------
//System.out.println("");
contar(Cand, k); //count step
L.clear();
L.addAll(Cand); //Meto los candidatos en L
System.out.println(k + "-ITEMS COMPUTED!! Total: " + L.size());
ponReglas(L); //introduzco nuevas reglas en el conjunto si procede
}
//reglas.print(); //a ver qu� sale...
//Ahora realizo el post-procesamiento: Selecci�n de un subconjunto de reglas
System.out.println("\nPost-processing Rules!");
boolean parar = false;
//reglas.print();
ConjDatos auxiliar = datosTrain.copiaConjDatos();
if (postpoda == 0) {
// a) Usar las N mejores reglas (falla si hay reglas que no deducen una de las clases: NO se agotan ej's):
for (int i = 0;
(i < N) && (auxiliar.size() > 0) &&
(reglas.size() > 0) && (!parar);
i++) { //Hasta que me quede con N reglas o no haya ejemplos o no haya reglas
Collections.sort(reglas.getConjReglas()); //Ordeno las reglas en funcion de su support y tama�o
cuentaEjCubiertos(reglas.getUltimaRegla(), auxiliar); //Ahora elimino los ejemplos cubiertos por la mejor regla
Regla r = reglas.getUltimaRegla().copiaRegla();
r.adjuntaNombreAtributos(nombreAtributos);
rFinal.addRegla(r);
reglas.deleteRegla(reglas.size() - 1); //Elimino la regla que acabo de usar
parar = recalculaHeuristica(auxiliar); //Ahora recalculo el support en funcion de los ejemplos (transacciones) que me quedan
}
} else {
// b) Usar las N mejores reglas para cada clase ---
for (int j = 0; j < nClases; j++) { //para cada clase
parar = false;
for (int i = 0;
(i < N) && (auxiliar.size() > 0) &&
(reglas.size() > 0) && (!parar);
i++) { //Hasta que me quede con N reglas o no haya ejemplos o no haya reglas (para la clase)
Collections.sort(reglas.getConjReglas()); //Ordeno las reglas en funcion de su support y tama�o
int l;
boolean seguir = true;
Regla r = new Regla();
for (l = reglas.size() - 1; (l > 0) && seguir; l--) {
r = reglas.getRegla(l);
if (r.getClase() == maximos[entradas - 1] + 1 + j) { //Es la clase para la que busco reglas
seguir = false;
}
}
parar = seguir; //si sigo, no paro xD
if (!parar) {
cuentaEjCubiertos(r, auxiliar); //Ahora elimino los ejemplos cubiertos por la mejor regla
r.adjuntaNombreAtributos(nombreAtributos);
rFinal.addRegla(r.copiaRegla());
reglas.deleteRegla(l + 1); //Elimino la regla que acabo de usar
parar = recalculaHeuristica(auxiliar); //Ahora recalculo el support en funcion de los ejemplos (transacciones) que me quedan
}
}
}
}
System.out.print("Init. Time: " + tiempo + " Final: " +
System.currentTimeMillis());
System.out.print(" Diff: " + (System.currentTimeMillis() - tiempo));
tiempo = System.currentTimeMillis() - tiempo;
generaSalida(rFinal); //Creo el fichero de salida KEEL y el de las reglas
}
}