Skip to content

Rule learning

Learning a rule base from data (Wang-Mendel)

wang_mendel generates a Mamdani rule base from data: each sample becomes one rule (picking the highest-membership term per variable), and antecedent conflicts are resolved by rule degree.

import numpy as np
import fuzzytool as fz

X = np.random.default_rng(0).uniform(0, 10, size=(300, 2))
y = (X[:, 0] + X[:, 1]) / 2

a = fz.Variable("a", (0, 10), terms=["lo", "mid", "hi"])
b = fz.Variable("b", (0, 10), terms=["lo", "mid", "hi"])
out = fz.Variable("out", (0, 10), terms=["lo", "mid", "hi"])

sys = fz.wang_mendel(X, y, [a, b], out)   # a ready-to-use Mamdani system
sys(a=8, b=7)    # -> 5.88  (learned system approximates the mean (a + b) / 2)

The input variables supply the partition; the learned system is a plain Mamdani, so it also supports batch predict and serialization.

use_weights=True gives each rule a weight equal to its normalized degree, so rules backed by strongly-matching samples outweigh those scraped from the edge of a partition.

Audit what you learn

Learned rule bases emit one rule per occupied partition cell, and a good share of those turn out to be duplicates or dead. Run audit and prune before shipping one.

Classification (Chi)

chi is the classification counterpart of Wang-Mendel: same antecedents, class labels as consequents, plus a certainty factor per rule. See Fuzzy classification.

clf = fz.chi(X, y_labels, [a, b])

Scatter partition: one rule per cluster

A grid partition needs a term per variable per rule, so the rule count grows with the number of inputs. A scatter partition lets clustering find where the data actually is, and turns each cluster into one rule — the rule count follows the structure of the data instead.

subtractive_clustering (Chiu, 1994) finds the centers without being told how many there are:

centers = fz.subtractive_clustering(X, radii=0.4)

chiu_tsk builds a complete Takagi-Sugeno system from those centers, fitting the consequents by least squares:

sys = fz.chiu_tsk(X, y, radii=0.4, names=["a", "b"])
sys(a=8, b=7)
sys.variables_          # the Gaussian partition it generated

cmeans_tsk does the same from a fuzzy c-means partition, when you would rather choose the number of rules directly:

sys = fz.cmeans_tsk(X, y, c=8, names=["a", "b"])

Both return an ordinary TSK system — explainable, auditable, serializable. Pass ridge=1e-6 when clusters overlap heavily and the least-squares fit is ill-conditioned, and order=0 for constant instead of affine consequents.

Tsukamoto inference

Tsukamoto consequents are monotonic membership functions; a rule firing with strength w outputs the value where its consequent reaches w (its inverse), and the system returns the firing-weighted average — no defuzzification.

import fuzzytool as fz

x = fz.Variable("x", (0, 10), terms=["lo", "hi"])
sys = fz.Tsukamoto()
sys.rule(x["lo"], fz.ramp_down(0, 30))   # monotonic consequents only
sys.rule(x["hi"], fz.ramp_up(0, 30))
sys(x=7)    # -> 21.0

Use ramp_up, ramp_down, or sigmoid — each exposes the inverse Tsukamoto needs.