Commit 0e1c7d2b authored by Bruno López Trigo's avatar Bruno López Trigo

Engadida documentación actualizada

parent 7ab07ed7
......@@ -31,6 +31,11 @@ import weka.core.OptionHandler;
import weka.core.Utils;
import weka.estimators.EstimatorUtils;
/*
*
* Implementación del módulo constructor
*
*/
public class BuilderManagerImpl implements BuilderManager {
private final MapperJSON mapper;
......@@ -41,6 +46,12 @@ public class BuilderManagerImpl implements BuilderManager {
this.fmanager = new FileManager();
}
/*
*
* Construye el modelo de clasificación de WEKA, adicionalmente genera los
* archivos de configuración del dataset.
*
*/
@Override
public DatasetConfig buildModels(String token, String datasetName, String model, String[] options)
throws ConflictEx, FormatEx, NotFoundEx {
......@@ -56,6 +67,7 @@ public class BuilderManagerImpl implements BuilderManager {
}
try {
// Leer el dataset de entrenamiento y construir instancias
readerLRN = new FileReader(dataset.getAbsolutePath());
Instances instancesLRN = new Instances(readerLRN);
instancesLRN.setClassIndex(instancesLRN.numAttributes() - 1);
......@@ -68,6 +80,12 @@ public class BuilderManagerImpl implements BuilderManager {
PrintStream log;
PrintStream predictions;
/*
*
* Construír el modelo de clasificación con WEKA con las opciones indicadas por
* el usuario
*
*/
switch (model) {
case "J48":
c45model = new J48();
......@@ -137,40 +155,54 @@ public class BuilderManagerImpl implements BuilderManager {
double[] minMax = new double[2];
double distance;
if (instancesLRN.numAttributes() < 100) {
for (int i = 0; i < instancesLRN.numAttributes(); i++) {
log.println(" " + instancesLRN.attribute(i).name());
if (i != (instancesLRN.numAttributes() - 1)) {
if (instancesLRN.attribute(i).isNumeric()) {
config.getAttributes().add(new NumericAttribute(instancesLRN.attribute(i).name(),
instancesLRN.attribute(i).name()));
EstimatorUtils.getMinMax(instancesLRN, i, minMax);
distance = (minMax[1] - minMax[0]) / 3;
((NumericAttribute) config.getAttributes().get(i))
.setInterval(new Interval(minMax[0], minMax[1]));
config.getAttributes().get(i).getProperties()
.add(new NumericProperty("Low", new Interval(minMax[0], minMax[0] + distance), 0));
config.getAttributes().get(i).getProperties().add(new NumericProperty("Medium",
new Interval(minMax[0] + distance, minMax[0] + 2 * distance), 1));
config.getAttributes().get(i).getProperties().add(
new NumericProperty("High", new Interval(minMax[0] + 2 * distance, minMax[1]), 2));
} else {
config.getAttributes().add(new CategoricAttribute(instancesLRN.attribute(i).name(),
instancesLRN.attribute(i).name()));
for (int j = 0; j < instancesLRN.attribute(i).numValues(); j++)
config.getAttributes().get(i).getProperties().add(new CategoricProperty(
instancesLRN.attribute(i).value(j), instancesLRN.attribute(i).value(j)));
}
// Genera la configuración del experto
for (int i = 0; i < instancesLRN.numAttributes(); i++) {
log.println(" " + instancesLRN.attribute(i).name());
if (i != (instancesLRN.numAttributes() - 1)) {
/*
*
* Para el caso de los atributos numéricos dividir el atributo en 3 intervalos
* idénticos con etiquetas (Low, Medium, High)
*
*/
if (instancesLRN.attribute(i).isNumeric()) {
config.getAttributes().add(new NumericAttribute(instancesLRN.attribute(i).name(),
instancesLRN.attribute(i).name()));
EstimatorUtils.getMinMax(instancesLRN, i, minMax);
distance = (minMax[1] - minMax[0]) / 3;
((NumericAttribute) config.getAttributes().get(i))
.setInterval(new Interval(minMax[0], minMax[1]));
config.getAttributes().get(i).getProperties()
.add(new NumericProperty("Low", new Interval(minMax[0], minMax[0] + distance), 0));
config.getAttributes().get(i).getProperties().add(new NumericProperty("Medium",
new Interval(minMax[0] + distance, minMax[0] + 2 * distance), 1));
config.getAttributes().get(i).getProperties()
.add(new NumericProperty("High", new Interval(minMax[0] + 2 * distance, minMax[1]), 2));
}
/*
*
* Para el caso de los atributos categóricos las etiquetas son los posibles valores
* del atributo
*
*/
else {
config.getAttributes().add(new CategoricAttribute(instancesLRN.attribute(i).name(),
instancesLRN.attribute(i).name()));
for (int j = 0; j < instancesLRN.attribute(i).numValues(); j++)
config.getAttributes().get(i).getProperties().add(new CategoricProperty(
instancesLRN.attribute(i).value(j), instancesLRN.attribute(i).value(j)));
}
}
} else {
log.println(" [list of attributes omitted]");
}
// Añadir a la configuración los consecuentes o clases del problema
for (int i = 0; i < instancesLRN.classAttribute().numValues(); i++) {
config.getConsequents().add(new Consequent((i + 1), instancesLRN.classAttribute().value(i),
......@@ -201,6 +233,12 @@ public class BuilderManagerImpl implements BuilderManager {
Evaluation evalLRN = new Evaluation(instancesLRN, null);
/*
*
* Evaluar el modelo usando un 10 cross-validation por defecto
*
*/
switch (model) {
case "J48":
evalLRN.crossValidateModel(c45model, instancesLRN, 10, new Random(1));
......@@ -221,6 +259,7 @@ public class BuilderManagerImpl implements BuilderManager {
File configLocation;
// Si la configuración no existe se lanza la excepción y se crea la nueva
try {
configLocation = this.fmanager.getConfig(token, datasetName, "en");
config = this.mapper.readConfigJSON(configLocation);
......@@ -232,6 +271,11 @@ public class BuilderManagerImpl implements BuilderManager {
evalLRN = new Evaluation(instancesLRN);
/*
*
* Evaluar el modelo con las instancias de entrenamiento
*
*/
switch (model) {
case "J48":
evalLRN.evaluateModel(c45model, instancesLRN, (Object[]) new String[1]);
......@@ -247,6 +291,7 @@ public class BuilderManagerImpl implements BuilderManager {
break;
}
// Escribir las predicciones en el archivo de predicciones
for (Prediction p : evalLRN.predictions()) {
predictions.println(p.actual() + " " + p.predicted());
}
......@@ -264,44 +309,61 @@ public class BuilderManagerImpl implements BuilderManager {
return config;
}
/*
*
* Construye el modelo de clasificación de WEKA para FURIA para clasificar una
* instancia concreta. Este método se utiliza para clasificar cuando se produce
* stretching, vote for the most frequent class o abstain.
*
*/
@Override
public double classifyFURIA(String token, String datasetName, String[] options, brunolopez.expliclas.models.Instance instance)
throws FormatEx, NotFoundEx {
public double classifyFURIA(String token, String datasetName, String[] options,
brunolopez.expliclas.models.Instance instance) throws FormatEx, NotFoundEx {
FileReader readerLRN;
File dataset = this.fmanager.getDataset(token, datasetName);
double value = -1;
try {
// Leemos el dataset de entrenamiento para generar las instancias
readerLRN = new FileReader(dataset.getAbsolutePath());
Instances instancesLRN = new Instances(readerLRN);
instancesLRN.setClassIndex(instancesLRN.numAttributes() - 1);
FURIA furia = null;
// Construimos el modelo de WEKA para FURIA
furia = new FURIA();
furia.setOptions(options);
furia.buildClassifier(instancesLRN);
// Generamos una nueva instancia
Instance wekaInstance = new DenseInstance(instancesLRN.instance(0).numAttributes());
for(int i=0; i<(instancesLRN.instance(0).numAttributes() - 1); i++) {
if(instancesLRN.instance(0).attribute(i).isString() || instancesLRN.instance(0).attribute(i).isNominal()) {
wekaInstance.setValue(instancesLRN.instance(0).attribute(i), instance.getCategoricValue(instancesLRN.instance(0).attribute(i).name()));
// Añadimos a la nueva instancia los valores que queremos clasificar
for (int i = 0; i < (instancesLRN.instance(0).numAttributes() - 1); i++) {
if (instancesLRN.instance(0).attribute(i).isString()
|| instancesLRN.instance(0).attribute(i).isNominal()) {
wekaInstance.setValue(instancesLRN.instance(0).attribute(i),
instance.getCategoricValue(instancesLRN.instance(0).attribute(i).name()));
} else {
wekaInstance.setValue(instancesLRN.instance(0).attribute(i), instance.getNumericValue(instancesLRN.instance(0).attribute(i).name()));
wekaInstance.setValue(instancesLRN.instance(0).attribute(i),
instance.getNumericValue(instancesLRN.instance(0).attribute(i).name()));
}
}
// Especificamos que la instancia es del mismo tipo que las instancias de entrenamiento
wekaInstance.setDataset(instancesLRN);
// Especificamos una clase cualquiera de salida (en este caso la primera)
wekaInstance.setClassValue(instancesLRN.instance(0).classValue());
// Realizamos la clasificación
value = furia.classifyInstance(wekaInstance);
} catch (Exception ex) {
throw new FormatEx("Error building log, check format");
}
return value;
}
}
......@@ -7,8 +7,6 @@ import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import brunolopez.expliclas.builder.BuilderManager;
import brunolopez.expliclas.builder.BuilderManagerImpl;
import brunolopez.expliclas.exceptions.FormatEx;
import brunolopez.expliclas.exceptions.NotFoundEx;
import brunolopez.expliclas.models.DatasetConfig;
......@@ -17,18 +15,27 @@ import brunolopez.expliclas.models.Position;
import brunolopez.expliclas.utils.FileManager;
import brunolopez.expliclas.utils.MapperJSON;
/*
*
* Clase constructora de matrices de confusión
*
*/
public class MatrixBuilder {
private BufferedReader reader;
private final FileManager fmanager;
private final BuilderManager builder;
public MatrixBuilder(File input) throws FileNotFoundException {
this.reader = new BufferedReader(new FileReader(input));
this.fmanager = new FileManager();
this.builder = new BuilderManagerImpl();
}
/*
*
* Construcción de la matriz de cross-validation, se limita a la lectura
* del modelo de WEKA
*
*/
public Matrix readMatrix() throws IOException {
String line = "";
......@@ -70,6 +77,12 @@ public class MatrixBuilder {
}
/*
*
* Construcción de la matriz de confusión de entrenamiento y test, requiere de la lectura
* de los archivos de predicciones
*
*/
public Matrix buildMatrixInstances(String token, String name, String algorithm, String type, boolean abstain) throws NotFoundEx, IOException, FormatEx {
File predictions;
......@@ -82,26 +95,32 @@ public class MatrixBuilder {
configFile = this.fmanager.getConfig(token, name, "en");
DatasetConfig config = mapper.readConfigJSON(configFile);
// Se obtienen las predicciones
if (type.equals("train")) {
predictions = this.fmanager.getPredictions(token, name, algorithm);
} else {
predictions = this.fmanager.getPredictionsTest(token, name, algorithm);
}
// Se crea una matriz, indicando se tiene columna de abstenciones o no
m = new Matrix(config.getConsequents().size(), abstain);
this.reader = new BufferedReader(new FileReader(predictions));
String line = reader.readLine();
int instance = 1;
while (line != null) {
// Si la predicción es "NaN" entonces hay una abstención
if(line.split(" ")[1].equals("NaN")) {
p = new Position((int) Float.parseFloat(line.split(" ")[0]), config.getConsequents().size());
} else {
p = new Position((int) Float.parseFloat(line.split(" ")[0]), (int) Float.parseFloat(line.split(" ")[1]));
}
// Si la predicción es incorrecta se añade a la lista de instancias confundidas la instancia
if (p.getRow() != p.getColumn()) {
m.addConfused(p, instance);
}
// Se incrementa en la matriz la posición correspondiente a la predicción
m.increment(p.getRow(), p.getColumn());
line = reader.readLine();
instance++;
......
......@@ -2,7 +2,6 @@ package brunolopez.expliclas.classifiers;
import brunolopez.expliclas.builder.BuilderManager;
import brunolopez.expliclas.builder.BuilderManagerImpl;
import brunolopez.expliclas.exceptions.ConflictEx;
import brunolopez.expliclas.exceptions.FormatEx;
import brunolopez.expliclas.exceptions.NotFoundEx;
import brunolopez.expliclas.models.DatasetConfig;
......@@ -14,6 +13,9 @@ import brunolopez.expliclas.models.fuzzy.RuleComponent;
import brunolopez.expliclas.models.fuzzy.Rules;
import brunolopez.expliclas.models.fuzzy.VisualRules;
/*
* Clase que interpreta reglas borrosas
*/
public class RuleInterpreter {
private Rules rules;
......@@ -26,10 +28,12 @@ public class RuleInterpreter {
this.classifier = new BuilderManagerImpl();
}
// Clasificación de instancias construyendo la solución
public VisualRules classify(String token, String dataset, Instance instance, DatasetConfig config) throws FormatEx, NotFoundEx {
double minValue = 1;
for(Rule r: this.rules.getRules()) {
// Computamos el grado de activación de los componentes de la regla
for(RuleComponent comp: r.getComponents()) {
if(comp instanceof NumericComponent) {
((NumericComponent) comp).computeValue(instance.getNumericValue(comp.getAttribute().getId()));
......@@ -37,6 +41,7 @@ public class RuleInterpreter {
((CategoricComponent) comp).computeValue(instance.getCategoricValue(comp.getAttribute().getId()));
}
}
// Si la TNorm era el producto, entonces realizamos el producto de componentes
if(this.rules.isProduct()) {
for(int i=0; i<r.getComponents().size(); i++) {
if(i==0) {
......@@ -45,7 +50,9 @@ public class RuleInterpreter {
r.setActivation(r.getActivation()*r.getComponents().get(i).getActivation());
}
}
} else {
}
// Si la TNorm era el mínimo, entonces calculamos el mínimo de los componentes
else {
for(RuleComponent comp: r.getComponents()) {
if(minValue > comp.getActivation()) {
minValue = comp.getActivation();
......@@ -53,12 +60,15 @@ public class RuleInterpreter {
}
r.setActivation(minValue);
}
// Calculamos la activación final de la regla ponderada con el CF
r.setActivation(r.getActivation()*r.getCF());
minValue = 1;
}
int winner = -1;
double maxValue = 0;
// Obtenemos la solución final como la regla con mayor grado de activación
for(int i=0; i<this.rules.getRules().size(); i++) {
if(this.rules.getRules().get(i).getActivation() > maxValue) {
maxValue = this.rules.getRules().get(i).getActivation();
......@@ -66,6 +76,14 @@ public class RuleInterpreter {
}
}
/*
* Si tenemos solución final establecemos los estados:
*
* - correct: Si la clasificación es correcta
* - incorrect: Si la clasificación es incorrecta
* - unknown: Si no se puede determinar si la clasificación es correcta o incorrecta
*
*/
if(winner != -1) {
this.rules.setSolution(this.rules.getRules().get(winner).getConsequent());
if(instance.getSolution() != null) {
......@@ -78,14 +96,22 @@ public class RuleInterpreter {
} else {
this.rules.setState("unknown");
}
} else if(this.rules.getUncovAction().equals("stretch") || this.rules.getUncovAction().equals("frequent")){
}
/*
* Si no tenemos solución final y se aplica stretching o vote for most frequent class.
* Entonces clasificamos mediante el modelo de WEKA.
*/
else if(this.rules.getUncovAction().equals("stretch") || this.rules.getUncovAction().equals("frequent")){
double result = this.classifier.classifyFURIA(token, dataset, this.rules.getOptions(), instance);
this.rules.setSolution(config.getConsequentByPosition((int) result + 1));
this.rules.setUncovered(true);
} else {
}
// Si se aplica abstain, no establecemos solución
else {
this.rules.setUncovered(true);
}
// Solicitamos la construcción de reglas adaptadas para representar en D3
return this.builder.buildVisualRules(this.rules, config);
}
......
......@@ -19,6 +19,9 @@ import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/*
* Clase que implementa el módulo de datasets
*/
public class DatasetManagerImpl implements DatasetManager {
private final FileManager fmanager;
......@@ -27,6 +30,7 @@ public class DatasetManagerImpl implements DatasetManager {
this.fmanager = new FileManager();
}
// Realizar el listado de todos los datasets
@Override
public ArrayList<Dataset> listDatasets(String token) throws NotFoundEx {
......@@ -65,6 +69,7 @@ public class DatasetManagerImpl implements DatasetManager {
}
// Obtener un dataset concreto
@Override
public Dataset getDataset(String token, String name) throws NotFoundEx, IOException {
......@@ -83,6 +88,7 @@ public class DatasetManagerImpl implements DatasetManager {
}
// Obtener un dataset de pruebas concreto
@Override
public Dataset getTestDataset(String token, String name) throws NotFoundEx, IOException {
......@@ -101,6 +107,7 @@ public class DatasetManagerImpl implements DatasetManager {
}
// Construir un dataset, es decir, leer todas las instancias del dataset
private Dataset buildDataset(String name, File datasetFile) throws IOException {
Dataset dataset = new Dataset(name);
......@@ -144,6 +151,7 @@ public class DatasetManagerImpl implements DatasetManager {
}
// Borrar los datasets de un usuario
@Override
public ArrayList<Dataset> deleteDatasets(String token) throws NotFoundEx {
......@@ -182,6 +190,7 @@ public class DatasetManagerImpl implements DatasetManager {
}
// Subir un dataset
@Override