Введение
Так уж повелось, что любую статью о нечеткой логике принято начинать с упоминания имени Лотфи Заде. И я не стану исключением. Дело в том, что этот человек стал не только отцом-основателем целой научной теории, написав в 1965 году фундаментальный труд "Fuzzy Sets", но и проработал различные возможности ее практического применения. Он описал свой подход в 1973 году в тексте "Outline of a New Approach to the Analysis of Complex Systems and Decision Processes" (опубликованном в журнале IEEE Transactions on Systems). Примечательно, что сразу после его выхода одна предприимчивая датская фирма весьма успешно применила изложенные в нем принципы для усовершенствования своей системы управления сложным производственным процессом. Но при всех заслугах Л. Заде, не менее важный вклад внесли последователи этой теории. Например, английский математик Э. Мамдани (Ebrahim Mamdani). В 1975 году он разработал алгоритм, который был предложен в качестве метода для управления паровым двигателем. Предложенный им алгоритм, основанный на нечетком логическом выводе, позволил избежать чрезмерно большого объема вычислений и был по достоинству оценен специалистами. Этот алгоритм в настоящее время получил наибольшее практическое применение в задачах нечеткого моделирования.Основные определения
Прежде чем начать знакомство с алгоритмом важно кратко ознакомиться со следующими определениями: Нечеткая переменная - это кортеж вида <α, X, Α>, где: α - имя нечеткой переменной; X - её область определения; A - нечеткое множество на универсуме X. Пример: Нечеткая переменная <"Тяжелый бронежилет", {x| 0 кг < x < 35 кг}, B={x, μ(x)}> характеризует массу военного бронежилета. Будем считать его тяжелым, если его масса > 16 кг (рис. 1).Рис. 1. График функции принадлежности μ(x) для нечеткого множества B
Рис. 2. Графики функций принадлежности значений лингвистической переменной "Бронежилет"
2) IF (Муж трезвый) AND (Зарплата высокая) THEN (Жена довольная). Все. Этого минимума достаточно для понимая принципов работы алгоритма.
Алгоритм Мамдани
Данный алгоритм описывает несколько последовательно выполняющихся этапов (рис. 3). При этом каждый последующий этап получает на вход значения полученные на предыдущем шаге.Рис. 3. Диаграмма деятельности процесса нечеткого вывода
Обозначения:
n - число правил нечетких продукций (numberOfRules).
m - кол-во входных переменных (numberOfInputVariables).
s - кол-во выходных переменных (numberOfOutputVariables).
k - общее число подусловий в базе правил (numberOfConditions).
q - общее число подзаключений в базе правил (numberOfConclusions).
Примечание: Данные обозначения будут использоваться в последующих этапах.
В скобках указаны имена соответствующих переменных в исходном коде.
2. Фаззификация входных переменных
Этот этап часто называют приведением к нечеткости. На вход поступают сформированная база правил и массив
входных данных А = {a1, ..., am}. В этом массиве содержатся значения всех входных
переменных. Целью этого этапа является получение значений истинности для всех подусловий из базы правил.
Это происходит так: для каждого из подусловий находится значение bi =
μ(ai). Таким образом получается множество значений
bi (i = 1..k).
Реализация:
private double[]
fuzzification(double[] inputData) {
int i = 0;
double[] b = new double[numberOfConditions];
for (Rule rule : rules) {
for (Condition condition : rule.getConditions()) {
int j = condition.getVariable().getId();
FuzzySet term = condition.getTerm();
b[i] = term.getValue(inputData[j]);
i++;
}
}
return b;
}
Примечание: Массив входных данных сформирован таким образом,
что i-ый элемент массива соответствует i-ой входной переменной
(номер переменной храниться в целочисленном поле "id").
3. Агрегирование подусловий
Как уже упоминалось выше, условие правила может быть составным, т.е. включать подусловия, связанные
между собой при помощи логической операции "AND". Целью этого этапа является определение степени
истинности условий для каждого правила системы нечеткого вывода. Упрощенно говоря, для каждого условия
находим минимальное значение истинности всех его подусловий. Формально это выглядит так:
cj = min{bi}.
Где:
j = 1..n;
i - число из множества номеров подусловий в которых участвует j-ая входная переменная.
int i = 0;
double[] b = new double[numberOfConditions];
for (Rule rule : rules) {
for (Condition condition : rule.getConditions()) {
int j = condition.getVariable().getId();
FuzzySet term = condition.getTerm();
b[i] = term.getValue(inputData[j]);
i++;
}
}
return b;
}
Реализация:
private double[] aggregation(double[] b) {
int i = 0;
int j = 0;
double[] c = new double[numberOfInputVariables];
for (Rule rule : rules) {
double truthOfConditions = 1.0;
for (Condition condition : rule.getConditions()) {
truthOfConditions = Math.min(truthOfConditions, b[i]);
  i++;
}
c[j] = truthOfConditions;
j++;
}
return c;
}
4. Активизация подзаключений
На этом этапе происходит переход от условий к подзаключениям. Для каждого подзаключения находится степень истинности di = ci*Fi, где i = 1..q. Затем, опять же каждому i-му подзаключению, сопоставляется множество Di с новой функцией принадлежности. Её значение определяется как минимум из di и значения функции принадлежности терма из подзаключения. Этот метод называется min-активизацией, который формально записывается следующим образом:
μ'i(x) = min {di, μi(x)}.
Где:
μ'i(x) - "активизированная" функция принадлежности;
μi(x) - функция принадлежности терма;
di - степень истинности i-го подзаключения.
Итак, цель этого этапа - это получение совокупности "активизированных" нечетких множеств
Di для каждого из подзаключений в базе правил (i = 1..q).
int i = 0;
int j = 0;
double[] c = new double[numberOfInputVariables];
for (Rule rule : rules) {
double truthOfConditions = 1.0;
for (Condition condition : rule.getConditions()) {
truthOfConditions = Math.min(truthOfConditions, b[i]);
  i++;
}
c[j] = truthOfConditions;
j++;
}
return c;
}
Реализация:
private List<ActivatedFuzzySet>
activation(double[] c) {
int i = 0;
List<ActivatedFuzzySet> activatedFuzzySets =
new ArrayList<ActivatedFuzzySet>();
double[] d = new double[numberOfConclusions];
for (Rule rule : rules) {
for (Conclusion conclusion : rule.getConclusions()) {
d[i] = c[i]*conclusion.getWeight();
ActivatedFuzzySet activatedFuzzySet =
(ActivatedFuzzySet) conclusion.getTerm();
activatedFuzzySet.setTruthDegree(d[i]);
activatedFuzzySets.add(activatedFuzzySet);
i++;
}
}
return activatedFuzzySets; }
int i = 0;
List<ActivatedFuzzySet> activatedFuzzySets =
new ArrayList<ActivatedFuzzySet>();
double[] d = new double[numberOfConclusions];
for (Rule rule : rules) {
for (Conclusion conclusion : rule.getConclusions()) {
d[i] = c[i]*conclusion.getWeight();
ActivatedFuzzySet activatedFuzzySet =
(ActivatedFuzzySet) conclusion.getTerm();
activatedFuzzySet.setTruthDegree(d[i]);
activatedFuzzySets.add(activatedFuzzySet);
i++;
}
}
return activatedFuzzySets; }
private double getActivatedValue(double x) {
return Math.min(super.getValue(x), truthDegree);
}
Реализация:
private List <UnionOfFuzzySets>
accumulation(List<ActivatedFuzzySet> activatedFuzzySets) {
List<UnionOfFuzzySets> unionsOfFuzzySets =
new ArrayList<UnionOfFuzzySets>(numberOfOutputVariables);
for (Rule rule : rules) {
for (Conclusion conclusion : rule.getConclusions()) {
int id = conclusion.getVariable().getId();
unionsOfFuzzySets.get(id).addFuzzySet(activatedFuzzySets.get(id));
}
}
return unionsOfFuzzySets;
}
List<UnionOfFuzzySets> unionsOfFuzzySets =
new ArrayList<UnionOfFuzzySets>(numberOfOutputVariables);
for (Rule rule : rules) {
for (Conclusion conclusion : rule.getConclusions()) {
int id = conclusion.getVariable().getId();
unionsOfFuzzySets.get(id).addFuzzySet(activatedFuzzySets.get(id));
}
}
return unionsOfFuzzySets;
}
private double getMaxValue(double x) {
double result = 0.0;
for (FuzzySet fuzzySet : fuzzySets) {
result = Math.max(result, fuzzySet.getValue(x));
}
return result;
}
Реализация:
private double[]
defuzzification(List<UnionOfFuzzySets> unionsOfFuzzySets) {
double[] y = new double[numberOfOutputVariables];
for(int i = 0; i < numberOfOutputVariables; i++) {
double i1 = integral(unionsOfFuzzySets.get(i), true);
double i2 = integral(unionsOfFuzzySets.get(i), false);
y[i] = i1 / i2;
}
return y;
}
double[] y = new double[numberOfOutputVariables];
for(int i = 0; i < numberOfOutputVariables; i++) {
double i1 = integral(unionsOfFuzzySets.get(i), true);
double i2 = integral(unionsOfFuzzySets.get(i), false);
y[i] = i1 / i2;
}
return y;
}