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.

Repeated Antecedents#

The genetic search may place several rules with the same antecedents (and different consequents) in the rule base. In additive mode they behave as one rule with the mean consequent and double weight; in sufficient mode the later ones never speak. Either way they use rule slots without describing a new region. unique_antecedents=True keeps only the first of them, in the objective and in the fitted rule base:

regressor = BaseFuzzyRulesRegressor(nRules=30, nAnts=2, unique_antecedents=True)

The default is False, which leaves the objective unchanged. benchmarks/benchmark_regression_unique_antecedents.py compares both settings.

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.