Документация Engee
Notebook

Система нечёткого вывода для принятия решений

В данном примере демонстрируется процесс создания системы нечёткого вывода. Такие системы формализуют человеческие рассуждения и экспертные знания в виде нечётких множеств, их функций принадлежности и решающих правил.

Установка и подключение библиотек

Чтобы создать систему нечёткого вывода воспользуемся библиотекой языка программирования Julia, которая называется FuzzyLogic. Для её установки в Engee требуется выполнить следующую кодовую ячейку:

In [ ]:
Pkg.add("FuzzyLogic")

Подключение установленной библиотеки FuzzyLogic:

In [ ]:
using FuzzyLogic

Построение системы нечёткого вывода

В коде ниже осуществляется построение системы нечёткого вывода Мамдани (Mamdani fuzzy inference system) с помощью макроса @mamfis. Эта система включает в себя определение функций принадлежности (membership functions) для входных и выходных переменных.

В начале кода мы объявляем функцию tipper, которая принимает два аргумента: service (обслуживание) и food (еда). Эти переменные представляют собой входные данные, которые будут использоваться для оценки чаевых, которые посетитель ресторана хочет оставить.

Для переменной service мы определяем три нечётких множества с помощью гауссовских функций принадлежности:

  • poor (плохое) с центром 0 и стандартным отклонением 1.5
  • good (хорошее) с центром 5 и стандартным отклонением 1.5
  • excellent (отличное) с центром 10 и стандартным отклонением 1.5

Для переменной food мы определяем два нечётких множества с помощью трапециевидных функций принадлежности:

  • rancid (протухшее) с координатами (-2, 0, 1, 3)
  • delicious (вкусное) с координатами (7, 9, 10, 12)

Переменная tip (чаевые) имеет три нечётких множества, заданных треугольными функциями принадлежности:

  • cheap (небольшие) с координатами (0, 5, 10)
  • average (средние) с координатами (10, 15, 20)
  • generous (щедрые) с координатами (20, 25, 30)

Далее определяются правила нечёткой логики, связывающие качество обслуживания и еды с чаевыми. Например, если обслуживание плохое или еда некачественная, чаевые будут низкими, если обслуживание хорошее, а качество еды удовлетворительное, чаевые будут средними и так далее.

In [ ]:
fis = @mamfis function tipper(service, food)::tip
    service := begin
      domain = 0:10
      poor = GaussianMF(0.0, 1.5)
      good = GaussianMF(5.0, 1.5)
      excellent = GaussianMF(10.0, 1.5)
    end

    food := begin
      domain = 0:10
      rancid = TrapezoidalMF(-2, 0, 1, 3)
      delicious = TrapezoidalMF(7, 9, 10, 12)
    end

    tip := begin
      domain = 0:30
      cheap = TriangularMF(0, 5, 10)
      average = TriangularMF(10, 15, 20)
      generous = TriangularMF(20, 25, 30)
    end

    service == poor || food == rancid --> tip == cheap
    service == good --> tip == average
    service == excellent || food == delicious --> tip == generous
end
Out[0]:
tipper

Inputs:
-------
service ∈ [0, 10] with membership functions:
    poor = GaussianMF{Float64}(0.0, 1.5)
    good = GaussianMF{Float64}(5.0, 1.5)
    excellent = GaussianMF{Float64}(10.0, 1.5)

food ∈ [0, 10] with membership functions:
    rancid = TrapezoidalMF{Int64}(-2, 0, 1, 3)
    delicious = TrapezoidalMF{Int64}(7, 9, 10, 12)


Outputs:
--------
tip ∈ [0, 30] with membership functions:
    cheap = TriangularMF{Int64}(0, 5, 10)
    average = TriangularMF{Int64}(10, 15, 20)
    generous = TriangularMF{Int64}(20, 25, 30)


Inference rules:
----------------
(service is poor ∨ food is rancid) --> tip is cheap
service is good --> tip is average
(service is excellent ∨ food is delicious) --> tip is generous


Settings:
---------
- MinAnd()
- MaxOr()
- MinImplication()
- MaxAggregator()
- CentroidDefuzzifier(100)

Пример использования системы нечёткого вывода в виде функции fis:

In [ ]:
fis(service=2, food=4)
Out[0]:
1-element Dictionaries.Dictionary{Symbol, Float64}
 :tip │ 7.788531995027619

Генерация функции не зависящей от библиотеки, с помощью compilefis:

In [ ]:
fis_ex = compilefis(fis)
Out[0]:
:(function tipper(service, food)
      poor = exp(-((service - 0.0) ^ 2) / 4.5)
      good = exp(-((service - 5.0) ^ 2) / 4.5)
      excellent = exp(-((service - 10.0) ^ 2) / 4.5)
      rancid = max(min((food - -2) / 2, 1, (3 - food) / 2), 0)
      delicious = max(min((food - 7) / 2, 1, (12 - food) / 2), 0)
      ant1 = max(poor, rancid)
      ant2 = good
      ant3 = max(excellent, delicious)
      tip_agg = collect(LinRange{Float64}(0.0, 30.0, 101))
      @inbounds for (i, x) = enumerate(tip_agg)
              cheap = max(min((x - 0) / 5, (10 - x) / 5), 0)
              average = max(min((x - 10) / 5, (20 - x) / 5), 0)
              generous = max(min((x - 20) / 5, (30 - x) / 5), 0)
              tip_agg[i] = max(max(min(ant1, cheap), min(ant2, average)), min(ant3, generous))
          end
      tip = ((2 * sum((mfi * xi for (mfi, xi) = zip(tip_agg, LinRange{Float64}(0.0, 30.0, 101)))) - first(tip_agg) * 0) - last(tip_agg) * 30) / ((2 * sum(tip_agg) - first(tip_agg)) - last(tip_agg))
      return tip
  end)

Функцию, полученную с помощью кодогенерации, можно записать в файл. Для этого переменная fis_ex имеющая формат Expr, конвертируется в формат String, для дальнейшей записи:

In [ ]:
text_function = string(fis_ex)
Out[0]:
"function tipper(service, food)\n    poor = exp(-((service - 0.0) ^ 2) / 4.5)\n    good = exp(-((service - 5.0) ^ 2) / 4.5)\n    excellent = exp(-((service - 10.0) ^ 2) / 4.5)\n    rancid = max(min((food - -2) / 2, 1, (3 - food) / 2), 0)\n    delicious = max(min((food - 7) / 2" ⋯ 444 bytes ⋯ ", min(ant2, average)), min(ant3, generous))\n        end\n    tip = ((2 * sum((mfi * xi for (mfi, xi) = zip(tip_agg, LinRange{Float64}(0.0, 30.0, 101)))) - first(tip_agg) * 0) - last(tip_agg) * 30) / ((2 * sum(tip_agg) - first(tip_agg)) - last(tip_agg))\n    return tip\nend"

Запись функции в строковом формате в файл:

In [ ]:
f = open("fis.jl","w") # создание файла
write(f, text_function) # запись в файл
close(f) # закрытие файла

Определение функции tipper, в рабочем пространстве Engee с помощью функции include из созданного файла:

In [ ]:
include("fis.jl")
Out[0]:
tipper (generic function with 1 method)

Использование функции из файла формата .jl.

In [ ]:
tipper(2, 3)
Out[0]:
7.7885319950276175

Полученную функцию можно использовать в других проектах, не подключая библиотеку FuzzyLogic.

Визуализация функций принадлежности

Подключение библиотеки для визуализации графиков:

In [ ]:
using Plots

Отображение функций принадлежности для переменной service:

In [ ]:
plot(fis, :service)
Out[0]:

Отображение функций принадлежности для переменной food:

In [ ]:
plot(fis, :food)
Out[0]:

Отображение функций принадлежности для переменной tip:

In [ ]:
plot(fis, :tip)
Out[0]:

Построение поверхности отклика

Если система нечёткого вывода имеет 2 входа, то можно построить её поверхность отклика. Это поверхность, отображающая, как выходные данные изменяются в зависимости от входных данных:

In [ ]:
# Определяем векторы
s = collect(0:0.2:10) # Вектор s от 0 до 10
f = collect(0:0.2:10) # Вектор f от 0 до 10

# Создаем матрицы значений
tip_matrix = zeros(length(s), length(f))

# Заполняем матрицу значениями отклика
for (i, service) in enumerate(s)
    for (j, food) in enumerate(f)
        tip_matrix[i, j] = tipper(service, food)
    end
end

# Построение поверхности отклика
surface(s, f, tip_matrix, xlabel="Service", ylabel="Food", zlabel="Tip", title="Поверхность отклика", color=:inferno)
Out[0]:

Вывод

В данном примере были расмотрены функции библиотеки FuzzyLogic. С помощью них была построена система нечёткого вывода, которая позволяет моделировать и обрабатывать неопределённые и неточные данные, используя нечёткую логику. В контексте принятия решений и вычислений система нечёткого вывода формализует человеческие рассуждения и экспертные знания, позволяя принимать более гибкие и адаптивные решения в условиях неопределенности по сравнению с традиционными методами.