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 {
for (RuleComponent comp : r.getComponents()) {
vcomponent = new VisualComponent();
vcomponent.setAttribute(comp.getAttribute().getId());
vcomponent.setId(comp.getAttribute().getId());
if (comp instanceof NumericComponent) {
vcomponent.addValue(new Coordinate(comp.getFirstFuzzyValue(),
((NumericComponent) comp).retrieveValue(comp.getFirstFuzzyValue())));
......@@ -341,6 +342,7 @@ public class RuleBuilder {
for (RuleComponent comp : r.getComponents()) {
vcomponent = new VisualComponent();
vcomponent.setAttribute(config.getAttributeById(comp.getAttribute().getId()).getName());
vcomponent.setId(comp.getAttribute().getId());
// Para los componentes numéricos
if (comp instanceof NumericComponent) {
/*
......
......@@ -142,6 +142,16 @@ public class RuleInterpreter {
double result = this.classifier.classifyFURIA(token, dataset, this.rules.getOptions(), instance);
this.rules.setSolution(config.getConsequentByPosition((int) result + 1));
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
else {
......
......@@ -23,6 +23,8 @@ import brunolopez.expliclas.exceptions.NotFoundEx;
import brunolopez.expliclas.models.trees.Classification;
import brunolopez.expliclas.models.trees.Consequent;
import brunolopez.expliclas.models.Explanation;
import brunolopez.expliclas.models.fuzzy.VisualRules;
import java.io.IOException;
import java.util.ArrayList;
......@@ -119,4 +121,7 @@ public interface ExplainerManager {
Explanation getLocalExplanation(String token, String dataset, String algorithm,
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;
import brunolopez.expliclas.models.Explanation;
import brunolopez.expliclas.models.GlobalConfig;
import brunolopez.expliclas.models.Matrix;
import brunolopez.expliclas.models.fuzzy.VisualRules;
import brunolopez.expliclas.utils.FileManager;
import java.io.File;
import java.io.IOException;
......@@ -35,6 +36,8 @@ import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import simplenlg.framework.CoordinatedPhraseElement;
import simplenlg.framework.NLGElement;
import simplenlg.phrasespec.SPhraseSpec;
......@@ -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;
*/
public class VisualComponent {
private String id;
private String attribute;
private ArrayList<Coordinate> coordinates;
private ArrayList<Coordinate> activationCoordinates;
......@@ -41,6 +42,14 @@ public class VisualComponent {
this.activationCoordinates = new ArrayList<Coordinate>();
}
public void setId(String id) {
this.id = id;
}
public String getId() {
return id;
}
/**
* Grado de activación del componente
*
......
......@@ -44,6 +44,7 @@ import brunolopez.expliclas.explainer.ExplainerManager;
import brunolopez.expliclas.explainer.ExplainerManagerImpl;
import brunolopez.expliclas.models.Explanation;
import brunolopez.expliclas.models.SimpleMessage;
import brunolopez.expliclas.models.fuzzy.VisualRules;
import brunolopez.expliclas.models.trees.Classification;
import brunolopez.expliclas.models.trees.Consequent;
import io.swagger.v3.oas.annotations.Operation;
......@@ -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(
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) {
const data = new FormData();
data.append("file", file);
......
......@@ -37,6 +37,7 @@ import {
getMatrix,
getGlobalExplanation,
getLocalExplanation,
getFuzzyLocalExplanation,
getClassifications,
getClassificationsFuzzy
} from "../components/global/API";
......@@ -606,6 +607,34 @@ class ClassifierPanel extends Component {
});
}
} 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();
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment