Fuzzy Regression#

This guide covers regression with Ex-Fuzzy: predicting a continuous target with rules you can read. It assumes you have met Core Concepts.

Introduction#

ex_fuzzy.BaseFuzzyRulesRegressor learns Type-1 fuzzy rules for a numeric target using a genetic algorithm. The input partitions are fixed before the search starts, so every membership can be precomputed once and each candidate rule base can be scored through a vectorized NumPy or PyTorch path.

The default backend="pymoo" runs that search on the CPU. With the optional backend="evox", population evolution and batched regression fitness run in PyTorch on CUDA when a compatible GPU is available. EvoX automatically uses the same PyTorch implementation on the CPU when CUDA is unavailable.

The estimator follows the scikit-learn API, so it works with cross_val_score, Pipeline and GridSearchCV.

Basic Workflow#

  1. Prepare X (samples x features) and a one-dimensional numeric y

  2. Create a BaseFuzzyRulesRegressor

  3. Call fit with a generation and population budget

  4. Call predict, score and print_rules

Quick Start Example#

import numpy as np
from sklearn.datasets import make_friedman1
from sklearn.model_selection import train_test_split
from ex_fuzzy import BaseFuzzyRulesRegressor

X, y = make_friedman1(n_samples=500, n_features=5, noise=0.5, random_state=0)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

regressor = BaseFuzzyRulesRegressor(nRules=20, nAnts=3, n_linguistic_variables=3)
regressor.fit(X_train, y_train, n_gen=50, pop_size=50)

print(regressor.score(X_test, y_test))
regressor.print_rules()

Consequent Types#

Crisp Consequents (default)#

Zero-order Takagi-Sugeno. Each rule carries a number, and a prediction is the firing-strength-weighted average of the numbers of the rules that fired:

Rule 1: IF x0 IS Low AND x2 IS High THEN output = 41.8203

When to use: the default. It gives the best numeric resolution, because a consequent is a value rather than a label.

regressor = BaseFuzzyRulesRegressor(
    nRules=20, nAnts=3, consequent_type="crisp"
)

Fuzzy Consequents (Mamdani Inference)#

Each rule names an output fuzzy set. Inference clips every consequent set by its rule’s firing strength, aggregates the clipped sets with max, and defuzzifies by taking the centroid over a discretized universe:

Rule 1: IF x0 IS Low AND x2 IS High THEN output IS Output_3

The output sets are not fixed in advance – their trapezoids are evolved alongside the rules. Each set’s four breakpoints are sorted on decoding, so every chromosome yields a well-formed trapezoid inside the target range.

When to use: when a rule should read as a complete linguistic statement, and you can trade some numeric resolution for it.

regressor = BaseFuzzyRulesRegressor(
    nRules=20,
    nAnts=3,
    consequent_type="fuzzy",
    n_output_lvs=4,          # number of output sets to evolve
    n_universe_points=101,   # centroid integration grid
)

Rule Modes#

Additive Mode (default)#

All rules contribute to every prediction, weighted by how strongly they fire.

regressor = BaseFuzzyRulesRegressor(nRules=10, nAnts=2, rule_mode="additive")

Sufficient Mode#

Only each sample’s strongest rule fires – winner-takes-all. If even that rule fires at or below tolerance, the sample falls back to the training-target mean.

regressor = BaseFuzzyRulesRegressor(
    nRules=10, nAnts=2, rule_mode="sufficient", tolerance=0.05
)

When to use: when you want each prediction attributable to exactly one rule. With crisp consequents the model becomes piecewise constant, since every prediction is then one rule’s consequent.

Reading the Fitted Model#

regressor.print_rules()                     # IF-THEN text
text = regressor.print_rules(return_rules=True)
rulebase = regressor.get_rulebase()         # the rule base object
firing = rulebase.compute_rule_antecedent_memberships(X_test)

firing has shape (n_samples, n_rules) and tells you which rule drove each prediction.

Precomputed Linguistic Variables#

Pass your own partitions to keep the input labels fixed and comparable across models:

from ex_fuzzy import utils, FUZZY_SETS

partitions = utils.construct_partitions(X_train, FUZZY_SETS.t1, n_partitions=3)
regressor = BaseFuzzyRulesRegressor(nRules=20, linguistic_variables=partitions)

Practical Notes#

  • The search maximizes training \(R^2\). Estimate generalization with cross_val_score, refitting inside each fold.

  • n_gen matters more than pop_size for final quality; budget it first.

  • nAnts is capped at the number of features. Rules may end up with fewer effective antecedents when the search selects the same feature twice.

  • Only Type-1 fuzzy sets are supported so far.