Commit a9b4f760 authored by Bruno López Trigo's avatar Bruno López Trigo

Engadidas explicacións sinxelas para FURIA

parent 3815ea8d
...@@ -289,6 +289,7 @@ public class RuleBuilder { ...@@ -289,6 +289,7 @@ public class RuleBuilder {
for (RuleComponent comp : r.getComponents()) { for (RuleComponent comp : r.getComponents()) {
vcomponent = new VisualComponent(); vcomponent = new VisualComponent();
vcomponent.setAttribute(comp.getAttribute().getId()); vcomponent.setAttribute(comp.getAttribute().getId());
vcomponent.setId(comp.getAttribute().getId());
if (comp instanceof NumericComponent) { if (comp instanceof NumericComponent) {
vcomponent.addValue(new Coordinate(comp.getFirstFuzzyValue(), vcomponent.addValue(new Coordinate(comp.getFirstFuzzyValue(),
((NumericComponent) comp).retrieveValue(comp.getFirstFuzzyValue()))); ((NumericComponent) comp).retrieveValue(comp.getFirstFuzzyValue())));
...@@ -341,6 +342,7 @@ public class RuleBuilder { ...@@ -341,6 +342,7 @@ public class RuleBuilder {
for (RuleComponent comp : r.getComponents()) { for (RuleComponent comp : r.getComponents()) {
vcomponent = new VisualComponent(); vcomponent = new VisualComponent();
vcomponent.setAttribute(config.getAttributeById(comp.getAttribute().getId()).getName()); vcomponent.setAttribute(config.getAttributeById(comp.getAttribute().getId()).getName());
vcomponent.setId(comp.getAttribute().getId());
// Para los componentes numéricos // Para los componentes numéricos
if (comp instanceof NumericComponent) { if (comp instanceof NumericComponent) {
/* /*
......
...@@ -142,6 +142,16 @@ public class RuleInterpreter { ...@@ -142,6 +142,16 @@ public class RuleInterpreter {
double result = this.classifier.classifyFURIA(token, dataset, this.rules.getOptions(), instance); double result = this.classifier.classifyFURIA(token, dataset, this.rules.getOptions(), instance);
this.rules.setSolution(config.getConsequentByPosition((int) result + 1)); this.rules.setSolution(config.getConsequentByPosition((int) result + 1));
this.rules.setUncovered(true); this.rules.setUncovered(true);
if (instance.getSolution() != null) {
if (instance.getSolution().equals(this.rules.getSolution().getId())) {
this.rules.setState("correct");
} else {
this.rules.setState("incorrect");
}
this.rules.setCorrectSolution(config.getConsequentById(instance.getSolution()));
} else {
this.rules.setState("unknown");
}
} }
// Si se aplica abstain, no establecemos solución // Si se aplica abstain, no establecemos solución
else { else {
......
...@@ -23,6 +23,8 @@ import brunolopez.expliclas.exceptions.NotFoundEx; ...@@ -23,6 +23,8 @@ import brunolopez.expliclas.exceptions.NotFoundEx;
import brunolopez.expliclas.models.trees.Classification; import brunolopez.expliclas.models.trees.Classification;
import brunolopez.expliclas.models.trees.Consequent; import brunolopez.expliclas.models.trees.Consequent;
import brunolopez.expliclas.models.Explanation; import brunolopez.expliclas.models.Explanation;
import brunolopez.expliclas.models.fuzzy.VisualRules;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -119,4 +121,7 @@ public interface ExplainerManager { ...@@ -119,4 +121,7 @@ public interface ExplainerManager {
Explanation getLocalExplanation(String token, String dataset, String algorithm, Explanation getLocalExplanation(String token, String dataset, String algorithm,
ArrayList<Classification> classifications, String language) throws FormatEx, IOException, NotFoundEx; ArrayList<Classification> classifications, String language) throws FormatEx, IOException, NotFoundEx;
Explanation getFuzzyLocalExplanation(String token, String dataset, String algorithm, VisualRules rules,
String language) throws FormatEx, IOException, NotFoundEx;
} }
...@@ -28,6 +28,7 @@ import brunolopez.expliclas.utils.MapperJSON; ...@@ -28,6 +28,7 @@ import brunolopez.expliclas.utils.MapperJSON;
import brunolopez.expliclas.models.Explanation; import brunolopez.expliclas.models.Explanation;
import brunolopez.expliclas.models.GlobalConfig; import brunolopez.expliclas.models.GlobalConfig;
import brunolopez.expliclas.models.Matrix; import brunolopez.expliclas.models.Matrix;
import brunolopez.expliclas.models.fuzzy.VisualRules;
import brunolopez.expliclas.utils.FileManager; import brunolopez.expliclas.utils.FileManager;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
...@@ -35,6 +36,8 @@ import java.text.DecimalFormat; ...@@ -35,6 +36,8 @@ import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import simplenlg.framework.CoordinatedPhraseElement; import simplenlg.framework.CoordinatedPhraseElement;
import simplenlg.framework.NLGElement; import simplenlg.framework.NLGElement;
import simplenlg.phrasespec.SPhraseSpec; import simplenlg.phrasespec.SPhraseSpec;
...@@ -1368,4 +1371,202 @@ public class ExplainerManagerImpl implements ExplainerManager { ...@@ -1368,4 +1371,202 @@ public class ExplainerManagerImpl implements ExplainerManager {
} }
@Override
public Explanation getFuzzyLocalExplanation(String token, String dataset, String algorithm, VisualRules rules,
String language) throws FormatEx, IOException, NotFoundEx {
File config, global, log;
try {
config = this.fmanager.getConfig(dataset, language);
log = this.fmanager.getLog(dataset, algorithm);
} catch (NotFoundEx ex) {
config = this.fmanager.getConfig(token, dataset, language);
log = this.fmanager.getLog(token, dataset, algorithm);
}
global = this.fmanager.getGlobalConfig(language);
this.globalConfig = this.mapper.readGlobalConfigJSON(global);
this.extractor = new InfoExtractorFURIA(this.mapper.readConfigJSON(config), this.globalConfig, rules);
return buildFuzzyLocalExplanation(language, (InfoExtractorFURIA) this.extractor);
}
private Explanation buildFuzzyLocalExplanation(String language, InfoExtractorFURIA extractor) {
Explanation explanation = new Explanation();
SPhraseSpec phrase, aux;
CoordinatedPhraseElement coordinate;
ArrayList<SPhraseSpec> phrases = new ArrayList<SPhraseSpec>();
String solution;
HashMap<String, String> labels;
switch (language) {
case "en":
if (extractor.isUncovered()) {
if (extractor.getUncovAction().equals("stretch")) {
solution = extractor.getSolution();
aux = this.generator.generateClause("classifier", "perform", false, "stretching");
phrase = this.generator.generateClause(extractor.getDatasetName(), "be", solution);
phrase = this.generator.generateComplentiserClause(aux, "to determine that", phrase);
explanation.addClause(((ClauseGeneratorEn) this.generator).getRealisation(phrase));
} else if (extractor.getUncovAction().equals("frequent")) {
solution = extractor.getSolution();
aux = this.generator.generateClause("classifier", "vote", false, "for the most frequent class");
phrase = this.generator.generateClause(extractor.getDatasetName(), "be", solution);
phrase = this.generator.generateComplentiserClause(aux, "to determine that", phrase);
explanation.addClause(((ClauseGeneratorEn) this.generator).getRealisation(phrase));
} else {
aux = this.generator.generateClause(extractor.getDatasetName(), "be", false, "unknown");
phrase = this.generator.generateClause("classifier", "reject", "the decision");
phrase = this.generator.generateComplentiserClause(aux, "because", phrase);
explanation.addClause(((ClauseGeneratorEn) this.generator).getRealisation(phrase));
}
} else {
labels = extractor.getAttributeLabels();
solution = extractor.getSolution();
for (Entry<String, String> entry : labels.entrySet()) {
phrases.add(
this.generator.generateClause(entry.getValue(), "be", false, entry.getKey().toLowerCase()));
}
coordinate = this.generator.generateCoordinate(phrases);
phrase = this.generator.generateClause(extractor.getDatasetName(), "be", false, solution + " because "
+ ((ClauseGeneratorEn) this.generator).getRealisation(coordinate).toLowerCase());
explanation.addClause(((ClauseGeneratorEn) this.generator).getRealisation(phrase));
}
if (extractor.isIncorrect()) {
phrase = this.generator.generateClausePreModifier("this classification", false, "However,", "be",
"wrong");
aux = this.generator.generateModalClause("type", "be", "should", false,
extractor.getCorrectSolution() + " instead of " + extractor.getSolution());
phrase = this.generator.generateComplentiserClause(phrase, "because", aux);
explanation.addClause(((ClauseGeneratorEn) this.generator).getRealisation(phrase));
}
break;
case "es":
if (extractor.isUncovered()) {
if (extractor.getUncovAction().equals("stretch")) {
solution = extractor.getSolution();
aux = this.generator.generateClause("El clasificador", "realiza", false, "stretching");
phrase = this.generator.generateClause(extractor.getDatasetName(), "ser", solution);
phrase = this.generator.generateComplentiserClause(aux, "para determinar que", phrase);
explanation.addClause(((ClauseGeneratorEs) this.generator).getRealisation(phrase));
} else if (extractor.getUncovAction().equals("frequent")) {
solution = extractor.getSolution();
aux = this.generator.generateClause("El clasificador", "vota", false, "por la clase más frecuente");
phrase = this.generator.generateClause(extractor.getDatasetName(), "ser", solution);
phrase = this.generator.generateComplentiserClause(aux, "para determinar que", phrase);
explanation.addClause(((ClauseGeneratorEs) this.generator).getRealisation(phrase));
} else {
aux = this.generator.generateClause(extractor.getDatasetName(), "ser", false, "desconocido");
phrase = this.generator.generateClause("el clasificador", "rechaza", "la decisión");
phrase = this.generator.generateComplentiserClause(aux, "porque", phrase);
explanation.addClause(((ClauseGeneratorEs) this.generator).getRealisation(phrase));
}
} else {
labels = extractor.getAttributeLabels();
solution = extractor.getSolution();
for (Entry<String, String> entry : labels.entrySet()) {
phrases.add(this.generator.generateClause(entry.getValue(), "ser", entry.getKey().toLowerCase()));
}
coordinate = this.generator.generateCoordinate(phrases);
phrase = this.generator.generateClause(extractor.getDatasetName(), "ser", false, solution + " porque "
+ ((ClauseGeneratorEs) this.generator).getRealisation(coordinate).toLowerCase());
explanation.addClause(((ClauseGeneratorEs) this.generator).getRealisation(phrase));
}
if (extractor.isIncorrect()) {
phrase = this.generator.generateClausePreModifier("esta clasificación", false, "Sin embargo,", "ser",
"incorrecta");
aux = this.generator.generateModalClause("el tipo", "ser", "deber", false,
extractor.getCorrectSolution() + " en lugar de " + extractor.getSolution());
phrase = this.generator.generateComplentiserClause(phrase, "porque", aux);
explanation.addClause(((ClauseGeneratorEs) this.generator).getRealisation(phrase));
}
break;
case "gl":
if (extractor.isUncovered()) {
if (extractor.getUncovAction().equals("stretch")) {
solution = extractor.getSolution();
aux = this.generator.generateClause("O clasificador", "realiza", false, "stretching");
phrase = this.generator.generateClause(extractor.getDatasetName(), "ser", solution);
phrase = this.generator.generateComplentiserClause(aux, "para determinar que", phrase);
explanation.addClause(((ClauseGeneratorGl) this.generator).getRealisation(phrase));
} else if (extractor.getUncovAction().equals("frequent")) {
solution = extractor.getSolution();
aux = this.generator.generateClause("O clasificador", "vota", false, "pola clase máis frecuente");
phrase = this.generator.generateClause(extractor.getDatasetName(), "ser", solution);
phrase = this.generator.generateComplentiserClause(aux, "para determinar que", phrase);
explanation.addClause(((ClauseGeneratorGl) this.generator).getRealisation(phrase));
} else {
aux = this.generator.generateClause(extractor.getDatasetName(), "ser", false, "descoñecido");
phrase = this.generator.generateClause("o clasificador", "rechaza", "a decisión");
phrase = this.generator.generateComplentiserClause(aux, "porque", phrase);
explanation.addClause(((ClauseGeneratorGl) this.generator).getRealisation(phrase));
}
} else {
labels = extractor.getAttributeLabels();
solution = extractor.getSolution();
for (Entry<String, String> entry : labels.entrySet()) {
phrases.add(this.generator.generateClause(entry.getValue(), "ser", entry.getKey().toLowerCase()));
}
coordinate = this.generator.generateCoordinate(phrases);
phrase = this.generator.generateClause(extractor.getDatasetName(), "ser", false, solution + " porque "
+ ((ClauseGeneratorGl) this.generator).getRealisation(coordinate).toLowerCase());
explanation.addClause(((ClauseGeneratorGl) this.generator).getRealisation(phrase));
}
if (extractor.isIncorrect()) {
phrase = this.generator.generateClausePreModifier("esta clasificación", false, "Non obstante", "ser",
"incorrecta");
aux = this.generator.generateModalClause("o tipo", "ser", "deber", false,
extractor.getCorrectSolution() + " en lugar de " + extractor.getSolution());
phrase = this.generator.generateComplentiserClause(phrase, "porque", aux);
explanation.addClause(((ClauseGeneratorGl) this.generator).getRealisation(phrase));
}
break;
}
return explanation;
}
} }
package brunolopez.expliclas.explainer;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import brunolopez.expliclas.models.DatasetConfig;
import brunolopez.expliclas.models.GlobalConfig;
import brunolopez.expliclas.models.fuzzy.VisualComponent;
import brunolopez.expliclas.models.fuzzy.VisualRule;
import brunolopez.expliclas.models.fuzzy.VisualRules;
import brunolopez.expliclas.models.trees.Attribute;
import brunolopez.expliclas.models.trees.Interval;
import brunolopez.expliclas.models.trees.NumericAttribute;
import brunolopez.expliclas.models.trees.NumericProperty;
import brunolopez.expliclas.models.trees.Property;
public class InfoExtractorFURIA extends InfoExtractor {
private VisualRules rules;
public InfoExtractorFURIA(DatasetConfig config, GlobalConfig global, VisualRules rules) throws FileNotFoundException, IOException {
super(config, global);
this.rules = rules;
}
public boolean isUncovered() {
return this.rules.isUncovered();
}
public String getUncovAction() {
return this.rules.getUncovAction();
}
public boolean isIncorrect() {
return this.rules.getState() != null && this.rules.getState().equals("incorrect");
}
public String getCorrectSolution() {
return this.getConfig().getConsequentById(this.rules.getCorrectSolution().getId()).getName();
}
public String getSolution() {
return this.getConfig().getConsequentById(this.rules.getSolution().getId()).getName();
}
public HashMap<String, String> getAttributeLabels(){
HashMap<String, String> labels = new HashMap<String, String>();
VisualRule solution = this.rules.getRules().get(0);
for(VisualRule rule: this.rules.getRules()) {
if (solution.getActivation() < rule.getActivation()) {
solution = rule;
}
}
Attribute att;
Property property;
for(VisualComponent c: solution.getComponents()) {
att = this.getConfig().getAttributeById(c.getId());
if (att.getType().equals("numericAtt"))
property = att.getPropertyById(getLabelInterval(new Interval(c.getCoordinates().get(0).getX(), c.getCoordinates().get(3).getX()), (NumericAttribute) att));
else
property = att.getPropertyByName(c.getId());
labels.put(property.getName(), att.getName());
}
return labels;
}
/**
* Obtener la etiqueta lingüística correspondiente a un intervalo
*
* @param interval Intervalo a analizar
* @param att Atributo numérico para el que buscamos la etiqueta
* @return Identificador numérico de la etiqueta lingüística
*/
private int getLabelInterval(Interval interval, NumericAttribute att) {
int label = 0;
double best = 0, actual;
Property p;
/*
* Para todas las propiedades del atributo, se realiza la intersección del
* intervalo actual del atributo con el de las propiedades. La propiedad con
* mayor grado de intersección es la que contiene la etiqueta deseada.
*/
for (Property prop : att.getProperties()) {
actual = ((NumericProperty) prop).getInterval().coincidencePercentage(interval);
if (actual > best) {
best = actual;
p = ((NumericAttribute) this.getConfig().getAttributeById(att.getId()))
.getPropertyByInterval(((NumericProperty) prop).getInterval());
label = ((NumericProperty) p).getId();
}
}
return label;
}
}
...@@ -28,6 +28,7 @@ import java.util.ArrayList; ...@@ -28,6 +28,7 @@ import java.util.ArrayList;
*/ */
public class VisualComponent { public class VisualComponent {
private String id;
private String attribute; private String attribute;
private ArrayList<Coordinate> coordinates; private ArrayList<Coordinate> coordinates;
private ArrayList<Coordinate> activationCoordinates; private ArrayList<Coordinate> activationCoordinates;
...@@ -41,6 +42,14 @@ public class VisualComponent { ...@@ -41,6 +42,14 @@ public class VisualComponent {
this.activationCoordinates = new ArrayList<Coordinate>(); this.activationCoordinates = new ArrayList<Coordinate>();
} }
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
/** /**
* Grado de activación del componente * Grado de activación del componente
* *
......
...@@ -44,6 +44,7 @@ import brunolopez.expliclas.explainer.ExplainerManager; ...@@ -44,6 +44,7 @@ import brunolopez.expliclas.explainer.ExplainerManager;
import brunolopez.expliclas.explainer.ExplainerManagerImpl; import brunolopez.expliclas.explainer.ExplainerManagerImpl;
import brunolopez.expliclas.models.Explanation; import brunolopez.expliclas.models.Explanation;
import brunolopez.expliclas.models.SimpleMessage; import brunolopez.expliclas.models.SimpleMessage;
import brunolopez.expliclas.models.fuzzy.VisualRules;
import brunolopez.expliclas.models.trees.Classification; import brunolopez.expliclas.models.trees.Classification;
import brunolopez.expliclas.models.trees.Consequent; import brunolopez.expliclas.models.trees.Consequent;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
...@@ -265,4 +266,66 @@ public class ExplainerService { ...@@ -265,4 +266,66 @@ public class ExplainerService {
} }
@Operation(summary = "Get local explanation for fuzzy algorithms", description = "Get an explanation about a specific classification for fuzzy algorithms", responses = {
@ApiResponse(responseCode = "201", description = "Explanation successfuly obtained", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Explanation.class))
),
@ApiResponse(responseCode = "500", description = "Error building explanation", content = @Content(mediaType = "application/json", schema = @Schema(implementation = SimpleMessage.class))
),
@ApiResponse(responseCode = "404", description = "Classifier not found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = SimpleMessage.class))
),
@ApiResponse(responseCode = "400", description = "Bad classification format", content = @Content(mediaType = "application/json", schema = @Schema(implementation = SimpleMessage.class))
),
@ApiResponse(responseCode = "401", description = "Unhauthorized", content = @Content(mediaType = "application/json", schema = @Schema(implementation = SimpleMessage.class))
) }, tags = "explainer")
@SecurityRequirement(name = "token")
@POST
@Path("{dataset}/{algorithm}/fuzzy")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response getLocalFuzzyExplanation(@Context HttpHeaders httpheaders,
@Parameter(description = "Dataset name") @PathParam("dataset") String dataset,
@Parameter(description = "Algorithm name") @PathParam("algorithm") String algorithm,
@Parameter(description = "Rules") VisualRules rules,
@Parameter(description = "Explanation language {en: english, es: spanish, gl: galician}") @DefaultValue("en") @QueryParam("lang") String lang) {
String header = httpheaders.getHeaderString(HttpHeaders.AUTHORIZATION);
String token = "";
if (header != null)
token = header.substring("Bearer".length()).trim();
Explanation explanation;
try {
switch (lang) {
case "es":
this.manager = new ExplainerManagerImpl(this.generatorEs);
break;
case "gl":
this.manager = new ExplainerManagerImpl(this.generatorGl);
break;
case "en":
this.manager = new ExplainerManagerImpl(this.generatorEn);
break;
default:
return Response.status(Response.Status.BAD_REQUEST).entity(new SimpleMessage("Bad language provided"))
.build();
}
explanation = this.manager.getFuzzyLocalExplanation(token, dataset, algorithm, rules, lang);
return Response.status(Response.Status.CREATED).entity(explanation).build();
} catch (FormatEx ex) {
return Response.status(Response.Status.BAD_REQUEST).entity(new SimpleMessage(ex.getMessage())).build();
} catch (IOException ex) {
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity(new SimpleMessage("Error generating explanation")).build();
} catch (NotFoundEx ex) {
return Response.status(Response.Status.NOT_FOUND).entity(new SimpleMessage(ex.getMessage())).build();
}
}
} }
...@@ -542,6 +542,52 @@ export async function getLocalExplanation( ...@@ -542,6 +542,52 @@ export async function getLocalExplanation(
return response; return response;
} }
export async function getFuzzyLocalExplanation(
dataset,
algorithm,
rules,
lang,
token
) {
const response = await fetch(
API_ROOT +
"/explainer/" +
dataset +
"/" +
algorithm +
"/fuzzy?lang=" +
lang,
{
method: "POST",
headers: {
Authorization: token,
"Content-Type": "application/json"
},
body: JSON.stringify(rules)
}
)
.then(res => {
if (res.status === 201) {
return res.json().then(data => {
return { error: false, status: res.status, data: data };
});
} else {
return res.json().then(data => {
return { error: true, status: res.status, data: data.message };
});
}
})
.catch(error => {
return {
error: true,
status: 401,
data: "Unauthorized, please restart session"
};
});
return response;
}
export async function uploadDataset(name, file, token) { export async function uploadDataset(name, file, token) {
const data = new FormData(); const data = new FormData();
data.append("file", file); data.append("file", file);
......
...@@ -37,6 +37,7 @@ import { ...@@ -37,6 +37,7 @@ import {
getMatrix, getMatrix,
getGlobalExplanation, getGlobalExplanation,
getLocalExplanation, getLocalExplanation,
getFuzzyLocalExplanation,
getClassifications, getClassifications,
getClassificationsFuzzy getClassificationsFuzzy
} from "../components/global/API"; } from "../components/global/API";
...@@ -606,6 +607,34 @@ class ClassifierPanel extends Component { ...@@ -606,6 +607,34 @@ class ClassifierPanel extends Component {
}); });
} }
} else if (this.state.algorithm === "FURIA" && this.state.values) { } else if (this.state.algorithm === "FURIA" && this.state.values) {
response = await getFuzzyLocalExplanation(
this.props.dataset,
this.state.algorithm,
this.state.rules,
this.props.language.id,
this.props.token
);
if (response.error) {
if (response.status === 401) {
this.props.handleGlobalMessage(
"error",
"Unhauthorized, please start session again"
);
this.props.closeSession();
this.setState({
redirect: true
});
} else {
this.setState({
error: true,
message: response.data
});
}
} else {
this.setState({
localExplanation: response.data.clauses
});
}
} }
this.getGlobalExplanation(); this.getGlobalExplanation();
} }
......
Markdown is supported
0%
or