Sugeno and Tsukamoto inference¶
Mamdani systems defuzzify a whole output set. Two other families skip that step by giving every rule a crisp number and averaging the numbers, weighted by how strongly each rule fires. They are cheaper (no sampling grid), smooth, and the natural form for control and for models fitted to data (see ANFIS).
SugenoFIS(Takagi-Sugeno-Kang): each rule's consequent is a constant (zero order) or a linear function of the inputs (first order).TsukamotoFIS: each rule's consequent is a monotonic membership function, and the rule reads its crisp value off that function at its firing strength.
Both use the rule language of the Mamdani page: the same syntax, and_method
("min" or "prod"), or_method ("max" or "probor"), default, explain and
NoRuleFiredError.
Sugeno¶
Let rule \(k\) fire with strength \(\alpha_k\) (rule weight times the antecedent degree) and have crisp value \(z_k\). The output is
or \(y = \sum_k \alpha_k z_k\) for "weighted_sum", over the rules that fire. A constant
consequent gives \(z_k = c\); a Linear consequent gives
\(z_k = c_0 + \sum_i c_i x_i\) evaluated at the crisp inputs.
Output terms are not membership functions, so outputs are
SugenoOutput objects that map each term name to
a number or a Linear. Inputs are ordinary linguistic variables.
from bulanik.inference import Linear, SugenoFIS, SugenoOutput
from bulanik.sets import LinguisticVariable, linear_decreasing, linear_increasing
x = LinguisticVariable("x", (0, 10), {
"lo": linear_decreasing(0, 10), "hi": linear_increasing(0, 10)})
y = SugenoOutput("y", {"a": 1.0, "b": Linear({"x": 2.0}, 1.0)}) # constant and first order
rules = ["IF x IS lo THEN y IS a", "IF x IS hi THEN y IS b"]
fis = SugenoFIS([x], [y], rules)
print(fis.evaluate({"x": 2.5})) # (0.75 * 1 + 0.25 * 6) / 1.0
weighted = ["IF x IS lo THEN y IS a WITH 0.5", "IF x IS hi THEN y IS b"]
print(SugenoFIS([x], [y], weighted).evaluate({"x": 2.5}))
print(SugenoFIS([x], [y], weighted, output_method="weighted_sum").evaluate({"x": 2.5}))
print(y)
{'y': 2.25}
{'y': 3.0}
{'y': 1.875}
SugenoOutput('y', {'a': 1.0, 'b': Linear({'x': 2.0}, 1.0)})
A Linear may only name input variables of the system (checked at construction); extra
inputs are ignored when it is called, and a missing one is an error. When the consequents
are linear in the inputs and the rules partition the input space, the output is smooth:
the Sugeno system is a blend of local linear models. A two-input control surface is drawn
with plot.surface.
explain reports the degrees, the activations and the outputs, exactly as in Mamdani.
from bulanik.inference import SugenoFIS, SugenoOutput
from bulanik.sets import LinguisticVariable, linear_decreasing, linear_increasing
x = LinguisticVariable("x", (0, 10), {
"lo": linear_decreasing(0, 10), "hi": linear_increasing(0, 10)})
y = SugenoOutput("y", {"a": 0.0, "b": 100.0})
fis = SugenoFIS([x], [y], ["IF x IS lo THEN y IS a", "IF x IS hi THEN y IS b"])
info = fis.explain({"x": 2.5})
print([a.strength for a in info.activations], info.outputs)
[0.75, 0.25] {'y': 25.0}
Tsukamoto¶
Every output term must be monotonic: linear_increasing, linear_decreasing or
sigmoid. A fired rule with strength \(\alpha_k\) and consequent term \(B_k\) gives the crisp
value \(z_k = \mu_{B_k}^{-1}(\alpha_k)\), the point where \(B_k\) reaches the level
\(\alpha_k\) (see inverse), and the output is the
same weighted average as for Sugeno,
A strongly fired "high" rule therefore pushes the output towards the top of its ramp, a weakly fired one stays near the foot. Each \(z_k\) is clamped into the output's universe (the inverse of a sigmoid or a ramp can fall outside it), and so is the final average; inputs are never clamped.
from bulanik.inference import TsukamotoFIS
from bulanik.sets import LinguisticVariable, linear_decreasing, linear_increasing
x = LinguisticVariable("x", (0, 10), {
"lo": linear_decreasing(0, 10), "hi": linear_increasing(0, 10)})
z = LinguisticVariable("z", (0, 20), {
"low": linear_decreasing(0, 10), "high": linear_increasing(0, 20)})
fis = TsukamotoFIS([x], [z], ["IF x IS lo THEN z IS low", "IF x IS hi THEN z IS high"])
for value in (0, 2.5, 5, 10):
print(value, fis.evaluate({"x": value}))
info = fis.explain({"x": 2.5})
print([a.strength for a in info.activations]) # z = (0.75 * 2.5 + 0.25 * 5) / 1.0 = 3.125
0 {'z': 0.0}
2.5 {'z': 3.125}
5 {'z': 7.5}
10 {'z': 20.0}
[0.75, 0.25]
Choosing between the three¶
| Mamdani | Sugeno | Tsukamoto | |
|---|---|---|---|
| Consequent | fuzzy set | constant or linear function | monotonic fuzzy set |
| Defuzzification | on a sampled grid (resolution) |
weighted average, closed form | weighted average of inverses |
| Output is | explainable by the picture of the aggregated set | a blend of local models | a blend of inverted ramps |
| Use it for | expert knowledge in words | control and data-fitted models | monotone effects, sigmoids |
Use Mamdani when people will read the rule base and the aggregated set; Sugeno when you want speed, smoothness or a model you will tune (the sezgi example 12 tune Sugeno with sezgi tunes the constants); Tsukamoto for a monotone consequent where the degree of firing should map to a position.
When no rule fires¶
If \(\sum_k \alpha_k = 0\) for an output, there is nothing to average. The behaviour is the
same as in Mamdani: a NoRuleFiredError
with .output and .explanation, or the default value (a finite number per output; for
Tsukamoto it must lie in the universe) if you gave one.
from bulanik.inference import NoRuleFiredError, SugenoFIS, SugenoOutput
from bulanik.sets import LinguisticVariable, triangular
x = LinguisticVariable("x", (0, 10), {"lo": triangular(0, 0, 3)})
y = SugenoOutput("y", {"a": 1.0})
rules = ["IF x IS lo THEN y IS a"]
try:
SugenoFIS([x], [y], rules).evaluate({"x": 8})
except NoRuleFiredError as error:
print(error.output, [a.strength for a in error.explanation.activations])
print(SugenoFIS([x], [y], rules, default={"y": -1.0}).evaluate({"x": 8}))
y [0.0]
{'y': -1.0}
Pitfalls¶
A rule's weight scales its strength but also its pull. With the default
weighted_average a lone fired rule with weight 0.1 still returns its own \(z_k\) (the
weight cancels in the ratio); it only matters against other rules. With weighted_sum the
weight scales the output directly.
weighted_sum is not an average. The result is outside the range of the consequents
whenever the strengths sum to more than 1, which happens as soon as the terms overlap
beyond a partition of unity (memberships adding to 1 at every input).
from bulanik.inference import SugenoFIS, SugenoOutput
from bulanik.sets import LinguisticVariable, gaussian
x = LinguisticVariable("x", (0, 10), {"a": gaussian(5, 3), "b": gaussian(5, 3)})
y = SugenoOutput("y", {"p": 10.0, "q": 10.0})
rules = ["IF x IS a THEN y IS p", "IF x IS b THEN y IS q"]
print(SugenoFIS([x], [y], rules).evaluate({"x": 5})) # 10: an average
print(SugenoFIS([x], [y], rules, output_method="weighted_sum").evaluate({"x": 5})) # 20: overshoots
{'y': 10.0}
{'y': 20.0}
Tsukamoto needs monotonic outputs. A triangle or a Gaussian output term raises
ValueError at construction (its inverse is not a function). A sigmoid's inverse is
clamped near 0 and 1 (SIGMOID_EPS), so a rule fired at exactly 1 reads a finite value.
A Linear must name known inputs, and must be called with every input it uses:
coefficients for a variable the system does not have raise ValueError when the system
is built.
NOT and weights follow the Mamdani rules: NOT only after IS in an antecedent,
weights in \([0, 1]\).
from bulanik.inference import Linear, SugenoFIS, SugenoOutput, TsukamotoFIS
from bulanik.sets import LinguisticVariable, linear_decreasing, linear_increasing, triangular
def show(make):
try:
print(make())
except Exception as error:
print(f"{type(error).__name__}: {error}")
x = LinguisticVariable("x", (0, 10), {"lo": linear_decreasing(0, 10), "hi": linear_increasing(0, 10)})
tri = LinguisticVariable("z", (0, 10), {"mid": triangular(0, 5, 10)})
show(lambda: TsukamotoFIS([x], [tri], ["IF x IS lo THEN z IS mid"]))
y = SugenoOutput("y", {"a": Linear({"t": 1.0})})
show(lambda: SugenoFIS([x], [y], ["IF x IS lo THEN y IS a"]))
show(lambda: Linear({"x": 1.0})({}))
show(lambda: SugenoOutput("y", {}))
show(lambda: SugenoOutput("y", {"a": float("nan")}))
show(lambda: SugenoFIS([x], [SugenoOutput("y", {"a": 1.0})], ["IF x IS lo THEN y IS a"],
output_method="median"))
ValueError: output term 'mid' of 'z' is not monotonic (triangular); Tsukamoto needs linear_increasing, linear_decreasing or sigmoid
ValueError: term 'a' of 'y' uses unknown input 't'; valid: x
ValueError: missing input 'x' for Linear
ValueError: output 'y' needs at least one term
ValueError: term 'a' of 'y' must be finite, got nan
ValueError: unknown output_method 'median' for SugenoFIS; valid: weighted_average, weighted_sum
See also¶
- Mamdani inference for the rule language and options both systems share; ANFIS fits a first-order Sugeno system to data.
- Interval type-2 for
IT2SugenoFIS. - API:
bulanik.inference. - Examples: 11 Sugeno controller, 13 Tsukamoto.
- Takagi and Sugeno (1985), "Fuzzy identification of systems and its applications to modeling and control", IEEE Transactions on Systems, Man, and Cybernetics; Tsukamoto (1979), "An approach to fuzzy reasoning method", in Advances in Fuzzy Set Theory and Applications.