Skip to content

Fuzzy classification

Mamdani and TSK answer with a number. When the answer is a label — approve or decline, fraud or clean, setosa or virginica — the natural fuzzy model is a rule base whose consequents are class labels, each carrying a certainty factor:

IF score is poor AND dti is high THEN class = default   (cf 0.82)

Inference is a vote. Every rule contributes its firing strength times its certainty to its own class, and the strongest class wins. Because the vote is just arithmetic over rules you can read, the model explains every prediction it makes.

Writing a classifier by hand

import fuzzytool as fz

score = fz.Variable("score", (300, 850), terms=["poor", "fair", "good"],
                    shoulders=True)
dti = fz.Variable("dti", (0, 50), terms=["low", "moderate", "high"],
                  shoulders=True)

clf = fz.FuzzyClassifier()
clf.rule(score["poor"] | dti["high"], "decline", weight=0.9)
clf.rule(score["fair"] & dti["moderate"], "review", weight=0.6)
clf.rule(score["good"] & dti["low"], "approve", weight=0.95)

clf(score=810, dti=8)            # -> 'approve'

The rule weight is the certainty factor: how much you trust that rule. A rule scraped from an ambiguous region should not outvote a rule backed by a clean one, and the weight is what encodes that.

Learning one from data

chi implements the classic Chi-Yuen-Pedrycz method: each sample votes for the (partition cell, class) pair it matches best, each cell predicts its winning class, and the certainty factor comes out of how lopsided that cell's vote was.

import numpy as np
from fuzzytool.learn import chi

rng = np.random.default_rng(0)
X = rng.uniform(0, 10, size=(300, 2))
y = np.where(X[:, 0] + X[:, 1] > 10, "high", "low")

inputs = [fz.Variable(f"x{j}", (0, 10), terms=["lo", "mid", "hi"], shoulders=True)
          for j in range(2)]
clf = chi(X, y, inputs)

clf.score(y, x0=X[:, 0], x1=X[:, 1])     # accuracy
print(clf.summary())                     # the whole model, in words

Rules from pure cells come out with certainty near 1; rules from cells where the classes overlap come out near 0. min_certainty=0.5 drops the latter, trading a little accuracy for a much shorter, more honest rule base.

Probabilities and the reject option

clf.predict(x0=X[:, 0], x1=X[:, 1])          # labels, one per sample
clf.predict_proba(x0=X[:, 0], x1=X[:, 1])    # confidence shares, rows sum to 1

predict_proba returns the normalized vote — a confidence share, not a calibrated probability. Say so when you report it.

What happens where no rule fires is a policy you choose:

on_no_rule Behavior
"majority" (default) predict the rule base's most-supported class
"none" predict None — an explicit reject option
"raise" fail loudly

"none" is usually the right choice in a decision pipeline: a fuzzy classifier that admits "this input is outside anything I was built for" is more useful than one that guesses.

Aggregation: winner-takes-all or additive

fz.FuzzyClassifier(aggregation="max")   # the single most confident rule decides
fz.FuzzyClassifier(aggregation="sum")   # weak rules that agree add up

"max" is Ishibuchi's classic single-winner model and is more robust to a badly estimated certainty factor. "sum" lets a group of agreeing weak rules outvote one strong rule, which helps when the partition is fine-grained.

Explaining a prediction

clf.explain(x0=7.0, x1=4.0)
{'output': 'high',
 'fired_rules': [
   {'index': 5, 'rule': 'IF (x0 is hi and x1 is mid) THEN high',
    'firing': 0.48, 'share': 0.71},
   {'index': 2, 'rule': 'IF (x0 is mid and x1 is mid) THEN low',
    'firing': 0.20, 'share': 0.29}]}

That is the whole model's reasoning for that one input: which rules spoke, how loudly, and how much of the decision each accounts for.

In a scikit-learn pipeline

from fuzzytool.integrations.sklearn import ChiClassifier
from sklearn.model_selection import cross_val_score

cross_val_score(ChiClassifier(inputs), X, y, cv=5)

ChiClassifier learns the rule base in fit and exposes the learned system as clf.system_, so a cross-validated model is still one you can print, audit and save. FuzzySystemClassifier does the same for a classifier you wrote by hand.

See also: Auditing a rule base, Rule learning.