Advanced usage

This document illustrates some advance aspects of the package.

[1]:
import zadeh
[2]:
# Build a demo
service = zadeh.FuzzyVariable(
    zadeh.FloatDomain("service", 0, 10, 100),
    {
        "poor": zadeh.GaussianFuzzySet(1.5, 0),
        "good": zadeh.GaussianFuzzySet(1.5, 5),
        "excellent": zadeh.GaussianFuzzySet(1.5, 10),
    },
)
food = zadeh.FuzzyVariable(
    zadeh.FloatDomain("food", 0, 10, 100),
    {
        "rancid": zadeh.TrapezoidalFuzzySet(-2, 0, 1, 3),
        "delicious": zadeh.TrapezoidalFuzzySet(7, 9, 10, 12),
    },
)
tip = zadeh.FuzzyVariable(
    zadeh.FloatDomain("tip", 0, 30, 100),
    {
        "cheap": zadeh.TriangularFuzzySet(0, 5, 10),
        "average": zadeh.TriangularFuzzySet(10, 15, 20),
        "generous": zadeh.TriangularFuzzySet(20, 25, 30),
    },
)
rule_set = [
    ((service == "poor") | (food == "rancid")) >> (tip == "cheap"),
    (service == "good") >> (tip == "average"),
    ((service == "excellent") | (food == "delicious")) >> (tip == "generous"),
]
fis = zadeh.FIS([food, service], rule_set, tip)

Compiling models

If you have the gcc compiler available in your system, you can automatically fast, compiled versions of them. The outline of the procedure is following:

  • C code for the system is generated.

  • The code is compiled into a dynamic library.

  • The library is linked and interfaced in Python.

  • A FIS subclass allows its usage.

To do all of this, just call the compile method of an existing FIS:

[3]:
fisc=fis.compile()

The time improvement can be checked below:

[4]:
%%timeit
fis.get_crisp_output({"food": 0, "service": 8})
854 µs ± 57.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
[5]:
%%timeit
fisc.get_crisp_output({"food": 0, "service": 8})
10.4 µs ± 229 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

The difference might increase in more complex systems, so its worth considering. However, user-defined functions cannot be automatically converted into C code.

Saving and loading models

Models can be saved and loaded using a json format

[6]:
fis.save("/tmp/mymodel.zadeh")
[7]:
!cat /tmp/mymodel.zadeh
{"variables": [{"name": "food", "values": {"rancid": {"type": "trapezoidal", "a": -2.0, "b": 0.0, "c": 1.0, "d": 3.0}, "delicious": {"type": "trapezoidal", "a": 7.0, "b": 9.0, "c": 10.0, "d": 12.0}}, "domain": {"type": "FloatDomain", "name": "food", "min": 0, "max": 10, "steps": 100}}, {"name": "service", "values": {"poor": {"type": "gaussian", "s": 1.5, "a": 0}, "good": {"type": "gaussian", "s": 1.5, "a": 5}, "excellent": {"type": "gaussian", "s": 1.5, "a": 10}}, "domain": {"type": "FloatDomain", "name": "service", "min": 0, "max": 10, "steps": 100}}], "rules": {"rule_list": [{"antecedent": {"type": "or", "children": [{"type": "is", "variable": "service", "value": "poor"}, {"type": "is", "variable": "food", "value": "rancid"}]}, "consequent": {"type": "is", "variable": "tip", "value": "cheap"}, "weight": 1.0}, {"antecedent": {"type": "is", "variable": "service", "value": "good"}, "consequent": {"type": "is", "variable": "tip", "value": "average"}, "weight": 1.0}, {"antecedent": {"type": "or", "children": [{"type": "is", "variable": "service", "value": "excellent"}, {"type": "is", "variable": "food", "value": "delicious"}]}, "consequent": {"type": "is", "variable": "tip", "value": "generous"}, "weight": 1.0}]}, "target": {"name": "tip", "values": {"cheap": {"type": "triangular", "a": 0.0, "b": 5.0, "c": 10.0}, "average": {"type": "triangular", "a": 10.0, "b": 15.0, "c": 20.0}, "generous": {"type": "triangular", "a": 20.0, "b": 25.0, "c": 30.0}}, "domain": {"type": "FloatDomain", "name": "tip", "min": 0, "max": 30, "steps": 100}}, "defuzzification": "centroid"}
[8]:
fis2=zadeh.FIS.load("/tmp/mymodel.zadeh")

Importing models from MATLAB files

MATLAB .fis models can be imported into zadeh, with limited support at the moment of writing.

[9]:
!cat ../tests/data/tipper.fis
% $Revision: 1.1 $
[System]
Name='tipper'
Type='mamdani'
NumInputs=2
NumOutputs=1
NumRules=3
AndMethod='min'
OrMethod='max'
ImpMethod='min'
AggMethod='max'
DefuzzMethod='centroid'

[Input1]
Name='service'
Range=[0 10]
NumMFs=3
MF1='poor':'gaussmf',[1.5 0]
MF2='good':'gaussmf',[1.5 5]
MF3='excellent':'gaussmf',[1.5 10]

[Input2]
Name='food'
Range=[0 10]
NumMFs=2
MF1='rancid':'trapmf',[0 0 1 3]
MF2='delicious':'trapmf',[7 9 10 10]

[Output1]
Name='tip'
Range=[0 30]
NumMFs=3
MF1='cheap':'trimf',[0 5 10]
MF2='average':'trimf',[10 15 20]
MF3='generous':'trimf',[20 25 30]

[Rules]
1 1, 1 (1) : 2
2 0, 2 (1) : 1
3 2, 3 (1) : 2
[10]:
fis3=zadeh.FIS.from_matlab("../tests/data/tipper.fis")
fis3.rules
[10]:
FuzzyRuleSet<if ((service is poor) or (food is rancid)) then (tip is cheap) [1.000000]
if ((service is good) and (food is not delicious)) then (tip is average) [1.000000]
if ((service is excellent) or (food is delicious)) then (tip is generous) [1.000000]>

Tuning models

Suppose you have a built a FIS and you gather some data to validate it. Could it be improved by altering the formal definitions used? You could use the data to evaluate the model with different parameters and choose the best option. The zadeh package provides a scikit-learn-based interface to do so.

[11]:
# Generate synthetic data to test the tuning
# Assume the tipping model is exactly off by two units of tip
import numpy as np
import pandas as pd
df = pd.DataFrame(
    [
        {
            "food": f,
            "service": s,
            "tip": fis.get_crisp_output({"food": f, "service": s}) - 2.0,
        }
        for f in np.linspace(0, 10)
        for s in np.linspace(0, 10)
    ]
)
df
[11]:
food service tip
0 0.0 0.000000 3.074485
1 0.0 0.204082 3.115666
2 0.0 0.408163 3.175973
3 0.0 0.612245 3.262110
4 0.0 0.816327 3.381884
... ... ... ...
2495 10.0 9.183673 22.618116
2496 10.0 9.387755 22.737890
2497 10.0 9.591837 22.824027
2498 10.0 9.795918 22.884334
2499 10.0 10.000000 22.925515

2500 rows × 3 columns

For this constructed data, our model will overestimate the real tips. We could try to modify the definitions of the tip values to see what matches best (which should be lower values).

[12]:
# Grid parameter tuning
tuner = zadeh.FuzzyGridTune(
    fis,
    {
        "target_tip_cheap_b": [3, 5, 7],
        "target_tip_average_b": [13, 15, 17],
        "target_tip_generous_b": [23, 25, 27],
    },
    scoring="neg_root_mean_squared_error",  # Equivalent to minimizing the RMSE
    n_jobs=4,  # Parallel jobs
)

[13]:
tuner.fit(df)
[14]:
# The best parameters can be checked
tuner.best_params_
[14]:
{'target_tip_average_b': 13,
 'target_tip_cheap_b': 3,
 'target_tip_generous_b': 23}
[15]:
# The tuned FIS is available
tuner.tuned_fis_
[15]:
<zadeh.fis.FIS at 0x7f90a7d2a370>

The following table summarizes the parameter syntax:

Meaning

Syntax

Example

Example value

Parameter of a value of an input

var_<variable>_<value>_<parameter>

var_food_rancid_b

2.0

Parameter of a value of the output

target_<name>_<value>_<parameter>

target_tip_cheap_c

3.0

Deffuzification method

defuzzification

defuzzification

centroid

Serving models

Created models can be served using a Flask server.

Consider the following example for model deployment

[16]:
from zadeh import server
fis_flask = server.FISFlask("myserver", fis)
app = fis_flask.app

# This app object can be used to deploy the model
# For example, to run a development server:
# app.run(debug=False, host='0.0.0.0')
# The object can be provied to middleware such as gunicorn for production deployment
[ ]: