From 4143fcfb20638651f2a5564d96c1491487150ae6 Mon Sep 17 00:00:00 2001 From: Anton Romanov Date: Tue, 7 Feb 2023 16:36:32 +0400 Subject: [PATCH] #86 -- fix fuzzy inference --- .../controller/AssessmentController.java | 2 +- .../assessment/model/Assessment.java | 8 +- .../service}/AssessmentService.java | 68 +++++++++++++++-- .../service/GitRepositoryService.java | 1 - .../extractor/rule/model/AntecedentValue.java | 3 +- .../rule/service/FuzzyInferenceService.java | 75 ++++++++++++------- src/main/resources/templates/assessments.html | 1 + .../java/ru/ulstu/FuzzyInferenceTest.java | 15 ---- 8 files changed, 120 insertions(+), 53 deletions(-) rename src/main/java/ru/ulstu/extractor/{service/assessment => assessment/service}/AssessmentService.java (55%) delete mode 100644 src/test/java/ru/ulstu/FuzzyInferenceTest.java diff --git a/src/main/java/ru/ulstu/extractor/assessment/controller/AssessmentController.java b/src/main/java/ru/ulstu/extractor/assessment/controller/AssessmentController.java index b960b4d..3434377 100644 --- a/src/main/java/ru/ulstu/extractor/assessment/controller/AssessmentController.java +++ b/src/main/java/ru/ulstu/extractor/assessment/controller/AssessmentController.java @@ -5,8 +5,8 @@ import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import ru.ulstu.extractor.assessment.model.FilterBranchForm; +import ru.ulstu.extractor.assessment.service.AssessmentService; import ru.ulstu.extractor.branch.service.BranchService; -import ru.ulstu.extractor.service.assessment.AssessmentService; import springfox.documentation.annotations.ApiIgnore; import java.util.Optional; diff --git a/src/main/java/ru/ulstu/extractor/assessment/model/Assessment.java b/src/main/java/ru/ulstu/extractor/assessment/model/Assessment.java index f6609b4..db5e048 100644 --- a/src/main/java/ru/ulstu/extractor/assessment/model/Assessment.java +++ b/src/main/java/ru/ulstu/extractor/assessment/model/Assessment.java @@ -9,13 +9,15 @@ public class Assessment { private final String firstAntecedentTendency; private final TimeSeriesType secondAntecedent; private final String secondAntecedentTendency; + private final Double degree; - public Assessment(DbRule dbRule) { + public Assessment(DbRule dbRule, Double degree) { this.consequent = dbRule.getConsequent(); this.firstAntecedent = dbRule.getFirstAntecedent(); this.firstAntecedentTendency = dbRule.getFirstAntecedentValue().getAntecedentValue(); this.secondAntecedent = dbRule.getSecondAntecedent(); this.secondAntecedentTendency = dbRule.getSecondAntecedentValue().getAntecedentValue(); + this.degree = degree; } public String getConsequent() { @@ -37,4 +39,8 @@ public class Assessment { public String getSecondAntecedentTendency() { return secondAntecedentTendency; } + + public Double getDegree() { + return degree; + } } diff --git a/src/main/java/ru/ulstu/extractor/service/assessment/AssessmentService.java b/src/main/java/ru/ulstu/extractor/assessment/service/AssessmentService.java similarity index 55% rename from src/main/java/ru/ulstu/extractor/service/assessment/AssessmentService.java rename to src/main/java/ru/ulstu/extractor/assessment/service/AssessmentService.java index 5edd2ec..d221806 100644 --- a/src/main/java/ru/ulstu/extractor/service/assessment/AssessmentService.java +++ b/src/main/java/ru/ulstu/extractor/assessment/service/AssessmentService.java @@ -1,15 +1,18 @@ -package ru.ulstu.extractor.service.assessment; +package ru.ulstu.extractor.assessment.service; import org.springframework.stereotype.Service; import ru.ulstu.extractor.assessment.model.Assessment; import ru.ulstu.extractor.rule.model.AssessmentException; import ru.ulstu.extractor.rule.model.DbRule; +import ru.ulstu.extractor.rule.service.AntecedentValueService; import ru.ulstu.extractor.rule.service.DbRuleService; import ru.ulstu.extractor.rule.service.FuzzyInferenceService; import ru.ulstu.extractor.ts.model.TimeSeries; +import ru.ulstu.extractor.ts.model.TimeSeriesValue; import ru.ulstu.extractor.ts.service.TimeSeriesService; import java.util.ArrayList; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -18,13 +21,16 @@ import java.util.stream.Collectors; @Service public class AssessmentService { private final DbRuleService ruleService; + private final AntecedentValueService antecedentValueService; private final TimeSeriesService timeSeriesService; private final FuzzyInferenceService fuzzyInferenceService; public AssessmentService(DbRuleService ruleService, + AntecedentValueService antecedentValueService, TimeSeriesService timeSeriesService, FuzzyInferenceService fuzzyInferenceService) { this.ruleService = ruleService; + this.antecedentValueService = antecedentValueService; this.timeSeriesService = timeSeriesService; this.fuzzyInferenceService = fuzzyInferenceService; } @@ -35,6 +41,7 @@ public class AssessmentService { try { return getAssessmentsByTimeSeriesTendencies(dbRules, timeSeries); } catch (AssessmentException ex) { + ex.printStackTrace(); return new ArrayList<>(); } } @@ -50,7 +57,11 @@ public class AssessmentService { timeSeries.forEach(ts -> variableValues.put(ts.getTimeSeriesType().name(), timeSeriesService.getLastTimeSeriesTendency(ts) .orElseThrow(() -> new AssessmentException("")))); - return fuzzyInferenceService.getFuzzyInference(dbRules, variableValues); + return fuzzyInferenceService.getFuzzyInference(dbRules, + antecedentValueService.getList(), + variableValues, + getTSsMin(timeSeries), + getTSsMax(timeSeries)); } private List getAssessmentsByTimeSeriesTendencies(List dbRules, List timeSeries) { @@ -64,15 +75,60 @@ public class AssessmentService { || ts.getTimeSeriesType() == dbRule.getSecondAntecedent()) .forEach(ts -> variableValues.put(ts.getTimeSeriesType().name(), timeSeriesService .getLastTimeSeriesTendency(ts) - .orElseThrow(() -> new AssessmentException("")))); - return fuzzyInferenceService.getFuzzyInference(List.of(dbRule), variableValues).stream(); - }).collect(Collectors.toList()); + .orElse(ts.getValues().get(ts.getValues().size() - 1).getValue()))); + return fuzzyInferenceService.getFuzzyInference(List.of(dbRule), + antecedentValueService.getList(), + variableValues, + getTSsMin(timeSeries), + getTSsMax(timeSeries)).stream(); + }) + .sorted(Comparator.comparing(Assessment::getDegree)) + .collect(Collectors.toList()); } private List getAssessmentsByLastValues(List dbRules, List timeSeries) { Map variableValues = new HashMap<>(); timeSeries.forEach(ts -> variableValues.put(ts.getTimeSeriesType().name(), ts.getValues().get(ts.getValues().size() - 1).getValue())); - return fuzzyInferenceService.getFuzzyInference(dbRules, variableValues); + return fuzzyInferenceService.getFuzzyInference(dbRules, + antecedentValueService.getList(), + variableValues, + getTSsMin(timeSeries), + getTSsMax(timeSeries)); } + private Double getMin(List values) { + return values.stream().mapToDouble(v -> v).min().getAsDouble(); + } + + private Map.Entry getTSMin(TimeSeries ts) { + return Map.entry(ts.getTimeSeriesType().name(), + getMin(ts.getValues().stream().map(TimeSeriesValue::getValue).collect(Collectors.toList()))); + } + + private Map getTSsMin(List tss) { + Map res = new HashMap<>(); + tss.forEach(ts -> { + Map.Entry entry = getTSMin(ts); + res.put(entry.getKey(), entry.getValue()); + }); + return res; + } + + private Double getMax(List values) { + return values.stream().mapToDouble(v -> v).max().getAsDouble(); + } + + private Map.Entry getTSMax(TimeSeries ts) { + return Map.entry(ts.getTimeSeriesType().name(), + getMax(ts.getValues().stream().map(TimeSeriesValue::getValue).collect(Collectors.toList()))); + } + + private Map getTSsMax(List tss) { + Map res = new HashMap<>(); + tss.forEach(ts -> { + Map.Entry entry = getTSMax(ts); + res.put(entry.getKey(), entry.getValue()); + }); + return res; + } } diff --git a/src/main/java/ru/ulstu/extractor/gitrepository/service/GitRepositoryService.java b/src/main/java/ru/ulstu/extractor/gitrepository/service/GitRepositoryService.java index 2802edf..2d451e1 100644 --- a/src/main/java/ru/ulstu/extractor/gitrepository/service/GitRepositoryService.java +++ b/src/main/java/ru/ulstu/extractor/gitrepository/service/GitRepositoryService.java @@ -283,7 +283,6 @@ public class GitRepositoryService { List changes = new ArrayList<>(); String[] strings = output.split("\n"); Map> filesContent = getFilesContent(strings); - System.out.println(filesContent); for(Map.Entry> fileSterings: filesContent.entrySet()) { FileChange fileChange = new FileChange(); fileChange.setFile(fileSterings.getKey()); diff --git a/src/main/java/ru/ulstu/extractor/rule/model/AntecedentValue.java b/src/main/java/ru/ulstu/extractor/rule/model/AntecedentValue.java index 39b5eb6..3583e6a 100644 --- a/src/main/java/ru/ulstu/extractor/rule/model/AntecedentValue.java +++ b/src/main/java/ru/ulstu/extractor/rule/model/AntecedentValue.java @@ -11,7 +11,8 @@ public class AntecedentValue extends BaseEntity { public AntecedentValue() { } - public AntecedentValue(String antecedentValue) { + public AntecedentValue(Integer id, String antecedentValue) { + this.setId(id); this.antecedentValue = antecedentValue; } diff --git a/src/main/java/ru/ulstu/extractor/rule/service/FuzzyInferenceService.java b/src/main/java/ru/ulstu/extractor/rule/service/FuzzyInferenceService.java index 38a6a58..6768c87 100644 --- a/src/main/java/ru/ulstu/extractor/rule/service/FuzzyInferenceService.java +++ b/src/main/java/ru/ulstu/extractor/rule/service/FuzzyInferenceService.java @@ -1,16 +1,18 @@ package ru.ulstu.extractor.rule.service; import com.fuzzylite.Engine; -import com.fuzzylite.activation.Highest; +import com.fuzzylite.activation.General; import com.fuzzylite.defuzzifier.Centroid; -import com.fuzzylite.norm.s.BoundedSum; import com.fuzzylite.norm.s.Maximum; import com.fuzzylite.norm.t.AlgebraicProduct; +import com.fuzzylite.norm.t.Minimum; import com.fuzzylite.rule.Rule; import com.fuzzylite.rule.RuleBlock; import com.fuzzylite.term.Triangle; import com.fuzzylite.variable.InputVariable; import com.fuzzylite.variable.OutputVariable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import ru.ulstu.extractor.assessment.model.Assessment; import ru.ulstu.extractor.rule.model.AntecedentValue; @@ -20,10 +22,12 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import java.util.stream.Stream; + +import static java.lang.String.format; @Service public class FuzzyInferenceService { + private final static Logger LOG = LoggerFactory.getLogger(FuzzyInferenceService.class); private final static String OUTPUT_VARIABLE_NAME = "state"; private final static String RULE_TEMPLATE = "if %s is %s and %s is %s then " + OUTPUT_VARIABLE_NAME @@ -36,7 +40,7 @@ public class FuzzyInferenceService { } private String getFuzzyRule(DbRule dbRule) { - return String.format(RULE_TEMPLATE, + return format(RULE_TEMPLATE, dbRule.getFirstAntecedent().name(), dbRule.getFirstAntecedentValue().getAntecedentValue(), dbRule.getSecondAntecedent().name(), @@ -47,6 +51,8 @@ public class FuzzyInferenceService { private RuleBlock getRuleBlock(Engine engine, List dbRules, Map variableValues, + Map min, + Map max, List antecedentValues, List consequentValues) { variableValues.forEach((key, value) -> { @@ -54,10 +60,19 @@ public class FuzzyInferenceService { input.setName(key); input.setDescription(""); input.setEnabled(true); - input.setRange(-0.1, antecedentValues.size() + 1.1); + double delta = antecedentValues.size() > 1 + ? (max.get(key) - min.get(key)) / (antecedentValues.size() - 1) + : (max.get(key) - min.get(key)); + input.setRange(min.get(key), max.get(key)); input.setLockValueInRange(false); for (int i = 0; i < antecedentValues.size(); i++) { - input.addTerm(new Triangle(antecedentValues.get(i).getAntecedentValue(), i - 0.1, i + 2.1)); + input.addTerm( + new Triangle( + antecedentValues.get(i).getAntecedentValue(), + min.get(key) + i * delta - 0.5 * delta, + min.get(key) + i * delta + delta + 0.5 * delta + ) + ); } engine.addInputVariable(input); }); @@ -66,13 +81,13 @@ public class FuzzyInferenceService { output.setName(OUTPUT_VARIABLE_NAME); output.setDescription(""); output.setEnabled(true); - output.setRange(-0.1, consequentValues.size() + 0.1); + output.setRange(0, consequentValues.size() + 0.1); output.setAggregation(new Maximum()); - output.setDefuzzifier(new Centroid(100)); + output.setDefuzzifier(new Centroid(10)); output.setDefaultValue(Double.NaN); output.setLockValueInRange(false); for (int i = 0; i < consequentValues.size(); i++) { - output.addTerm(new Triangle(consequentValues.get(i).toString(), i - 0.1, i + 2.1)); + output.addTerm(new Triangle(consequentValues.get(i).toString(), i, i + 2.1)); } engine.addOutputVariable(output); @@ -80,11 +95,14 @@ public class FuzzyInferenceService { mamdani.setName("mamdani"); mamdani.setDescription(""); mamdani.setEnabled(true); - mamdani.setConjunction(new AlgebraicProduct()); - mamdani.setDisjunction(new BoundedSum()); + mamdani.setConjunction(new Minimum()); + //mamdani.setDisjunction(null); mamdani.setImplication(new AlgebraicProduct()); - mamdani.setActivation(new Highest()); - getRulesFromDb(dbRules, variableValues).forEach(r -> mamdani.addRule(Rule.parse(r, engine))); + mamdani.setActivation(new General()); + getRulesFromDb(dbRules, variableValues).forEach(r -> { + LOG.info(r); + mamdani.addRule(Rule.parse(r, engine)); + }); return mamdani; } @@ -95,21 +113,22 @@ public class FuzzyInferenceService { return engine; } - - public List getFuzzyInference(List dbRules, Map variableValues) { + public List getFuzzyInference(List dbRules, + List antecedentValues, + Map variableValues, + Map min, + Map max) { Engine engine = getFuzzyEngine(); - List antecedentValues = Stream.concat(dbRules.stream().map(DbRule::getFirstAntecedentValue), - dbRules.stream().map(DbRule::getSecondAntecedentValue)).distinct().collect(Collectors.toList()); List consequentValues = dbRules.stream().map(DbRule::getId).collect(Collectors.toList()); - engine.addRuleBlock(getRuleBlock(engine, dbRules, variableValues, antecedentValues, consequentValues)); - String consequent = getConsequent(engine, variableValues); - if (consequent.equals(NO_RESULT)) { + engine.addRuleBlock(getRuleBlock(engine, dbRules, variableValues, min, max, antecedentValues, consequentValues)); + Map.Entry consequent = getConsequent(engine, variableValues); + if (consequent.getKey().equals(NO_RESULT)) { return new ArrayList<>(); } return dbRules .stream() - .filter(r -> r.getId().equals(Integer.valueOf(consequent))) - .map(Assessment::new) + .filter(r -> r.getId().equals(Integer.valueOf(consequent.getKey()))) + .map(r -> new Assessment(r, consequent.getValue())) .collect(Collectors.toList()); } @@ -117,17 +136,17 @@ public class FuzzyInferenceService { private void validateVariables(Map variableValues, List dbDbRules) { for (DbRule dbRule : dbDbRules) { if (!variableValues.containsKey(dbRule.getFirstAntecedent().name())) { - throw new RuntimeException(String.format("Переменной в правиле не задано значение (нет временного ряда): %s ", + throw new RuntimeException(format("Переменной в правиле не задано значение (нет временного ряда): %s ", dbRule.getFirstAntecedent().name())); } if (!variableValues.containsKey(dbRule.getSecondAntecedent().name())) { - throw new RuntimeException(String.format("Переменной в правиле не задано значение (нет временного ряда): %s ", + throw new RuntimeException(format("Переменной в правиле не задано значение (нет временного ряда): %s ", dbRule.getSecondAntecedent().name())); } } } - private String getConsequent(Engine engine, Map variableValues) { + private Map.Entry getConsequent(Engine engine, Map variableValues) { OutputVariable outputVariable = engine.getOutputVariable(OUTPUT_VARIABLE_NAME); for (Map.Entry variableValue : variableValues.entrySet()) { InputVariable inputVariable = engine.getInputVariable(variableValue.getKey()); @@ -135,10 +154,10 @@ public class FuzzyInferenceService { } engine.process(); if (outputVariable != null) { - outputVariable.defuzzify(); + LOG.info("Output: {}", outputVariable.getValue()); } return (outputVariable == null || Double.isNaN(outputVariable.getValue())) - ? NO_RESULT - : outputVariable.highestMembership(outputVariable.getValue()).getSecond().getName(); + ? Map.entry(NO_RESULT, 0.0) + : Map.entry(outputVariable.highestMembershipTerm(outputVariable.getValue()).getName(), outputVariable.getValue()); } } diff --git a/src/main/resources/templates/assessments.html b/src/main/resources/templates/assessments.html index 516628a..a303e2d 100644 --- a/src/main/resources/templates/assessments.html +++ b/src/main/resources/templates/assessments.html @@ -42,6 +42,7 @@ th:text="${assessment.firstAntecedent.description}">' и тенденции '' показателя ''; +
diff --git a/src/test/java/ru/ulstu/FuzzyInferenceTest.java b/src/test/java/ru/ulstu/FuzzyInferenceTest.java deleted file mode 100644 index 99d69ef..0000000 --- a/src/test/java/ru/ulstu/FuzzyInferenceTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package ru.ulstu; - -import org.junit.Assert; -import org.junit.Test; -import ru.ulstu.extractor.rule.service.FuzzyInferenceService; - -public class FuzzyInferenceTest { - - private final FuzzyInferenceService fuzzyInferenceService = new FuzzyInferenceService(); - - @Test - public void test() { - Assert.assertNotNull(fuzzyInferenceService); - } -}