Skip to content

Classifier API

The ANFISClassifier offers a scikit-learn inspired interface for multi-class classification tasks, wrapping membership-function management, model construction, and training into a single estimator.

anfis_toolbox.classifier.ANFISClassifier

ANFISClassifier(
    *,
    n_classes: int | None = None,
    n_mfs: int = 3,
    mf_type: str = "gaussian",
    init: str | None = "grid",
    overlap: float = 0.5,
    margin: float = 0.1,
    inputs_config: Mapping[Any, Any] | None = None,
    random_state: int | None = None,
    optimizer: str
    | BaseTrainer
    | type[BaseTrainer]
    | None = "adam",
    optimizer_params: Mapping[str, Any] | None = None,
    learning_rate: float | None = None,
    epochs: int | None = None,
    batch_size: int | None = None,
    shuffle: bool | None = None,
    verbose: bool = False,
    loss: LossFunction | str | None = None,
    rules: Sequence[Sequence[int]] | None = None,
)

Bases: BaseEstimatorLike, FittedMixin, ClassifierMixinLike

Adaptive Neuro-Fuzzy classifier with a scikit-learn style API.

The estimator manages membership-function synthesis, rule construction, and trainer selection so you can focus on calling :meth:fit, :meth:predict, :meth:predict_proba, and :meth:evaluate with familiar NumPy-like data structures.

Examples:

clf = ANFISClassifier() clf.fit(X, y) ANFISClassifier(...) clf.predict([[0.1, -0.2]]) array([...])

Parameters

n_classes : int, optional Number of target classes. Must be >= 2 when provided. If omitted, the classifier infers the class count during the first call to fit. n_mfs : int, default=3 Default number of membership functions per input. mf_type : str, default="gaussian" Default membership function family applied when membership functions are inferred from data. init : {"grid", "fcm", "random", None}, default="grid" Strategy used when inferring membership functions from data. None falls back to "grid". overlap : float, default=0.5 Controls overlap when generating membership functions automatically. margin : float, default=0.10 Margin added around observed data ranges during grid initialization. inputs_config : Mapping, optional Per-input overrides. Keys may be feature names (when X is a :class:pandas.DataFrame) or integer indices. Values may be:

* ``dict`` with keys among ``{"n_mfs", "mf_type", "init", "overlap",
  "margin", "range", "membership_functions", "mfs"}``.
* A list or tuple of membership function objects for full control.
* ``None`` for defaults.

random_state : int, optional Random state forwarded to initialization routines and stochastic optimizers. optimizer : str, BaseTrainer, type[BaseTrainer], or None, default="adam" Trainer identifier or instance used for fitting. Strings map to entries in :data:TRAINER_REGISTRY. None defaults to "adam". optimizer_params : Mapping, optional Additional keyword arguments forwarded to the trainer constructor. learning_rate, epochs, batch_size, shuffle, verbose : optional scalars Common trainer hyper-parameters provided for convenience. When the selected trainer supports the parameter it is included automatically. loss : str or LossFunction, optional Custom loss forwarded to trainers that expose a loss parameter. rules : Sequence[Sequence[int]] | None, optional Explicit fuzzy rule indices to use instead of the full Cartesian product. Each rule lists the membership-function index per input. None keeps the default exhaustive rule set.

Parameters

n_classes : int, optional Number of output classes. Must be at least two when provided. If omitted, the value is inferred from the training targets during the first fit call. n_mfs : int, default=3 Default number of membership functions to allocate per input when inferred from data. mf_type : str, default="gaussian" Membership function family used for automatically generated membership functions. init : {"grid", "fcm", "random", None}, default="grid" Initialization strategy applied when synthesizing membership functions from the training data. None falls back to "grid". overlap : float, default=0.5 Desired overlap between adjacent membership functions during automatic generation. margin : float, default=0.10 Additional range padding applied around observed feature minima and maxima for grid initialization. inputs_config : Mapping, optional Per-feature overrides for the generated membership functions. Keys may be feature names (when X is a :class:pandas.DataFrame), integer indices, or "x{i}" aliases. Values may include dictionaries with membership-generation arguments, explicit membership function sequences, or None to retain defaults. random_state : int, optional Seed forwarded to stochastic initializers and optimizers. optimizer : str | BaseTrainer | type[BaseTrainer] | None, default="adam" Training algorithm identifier or instance. String aliases are looked up in :data:TRAINER_REGISTRY. None defaults to "adam". Hybrid variants that depend on least-squares refinements are limited to regression and raise ValueError when supplied here. optimizer_params : Mapping, optional Additional keyword arguments provided to the trainer constructor when a string alias or trainer class is supplied. learning_rate, epochs, batch_size, shuffle, verbose : optional Convenience hyper-parameters injected into the trainer whenever the chosen implementation accepts them. shuffle supports False to disable random shuffling. loss : str | LossFunction, optional Custom loss specification forwarded to trainers that expose a loss parameter. None resolves to cross-entropy. rules : Sequence[Sequence[int]] | None, optional Optional explicit fuzzy rule definitions. Each rule lists the membership-function index for each input. None uses the full Cartesian product of configured membership functions.

Source code in anfis_toolbox/classifier.py
def __init__(
    self,
    *,
    n_classes: int | None = None,
    n_mfs: int = 3,
    mf_type: str = "gaussian",
    init: str | None = "grid",
    overlap: float = 0.5,
    margin: float = 0.10,
    inputs_config: Mapping[Any, Any] | None = None,
    random_state: int | None = None,
    optimizer: str | BaseTrainer | type[BaseTrainer] | None = "adam",
    optimizer_params: Mapping[str, Any] | None = None,
    learning_rate: float | None = None,
    epochs: int | None = None,
    batch_size: int | None = None,
    shuffle: bool | None = None,
    verbose: bool = False,
    loss: LossFunction | str | None = None,
    rules: Sequence[Sequence[int]] | None = None,
) -> None:
    """Configure an :class:`ANFISClassifier` with the supplied hyper-parameters.

    Parameters
    ----------
    n_classes : int, optional
        Number of output classes. Must be at least two when provided. If
        omitted, the value is inferred from the training targets during
        the first ``fit`` call.
    n_mfs : int, default=3
        Default number of membership functions to allocate per input when
        inferred from data.
    mf_type : str, default="gaussian"
        Membership function family used for automatically generated
        membership functions.
    init : {"grid", "fcm", "random", None}, default="grid"
        Initialization strategy applied when synthesizing membership
        functions from the training data. ``None`` falls back to ``"grid"``.
    overlap : float, default=0.5
        Desired overlap between adjacent membership functions during
        automatic generation.
    margin : float, default=0.10
        Additional range padding applied around observed feature minima
        and maxima for grid initialization.
    inputs_config : Mapping, optional
        Per-feature overrides for the generated membership functions.
        Keys may be feature names (when ``X`` is a :class:`pandas.DataFrame`),
        integer indices, or ``"x{i}"`` aliases. Values may include dictionaries
        with membership-generation arguments, explicit membership function
        sequences, or ``None`` to retain defaults.
    random_state : int, optional
        Seed forwarded to stochastic initializers and optimizers.
    optimizer : str | BaseTrainer | type[BaseTrainer] | None, default="adam"
        Training algorithm identifier or instance. String aliases are looked
        up in :data:`TRAINER_REGISTRY`. ``None`` defaults to ``"adam"``.
        Hybrid variants that depend on least-squares refinements are limited
        to regression and raise ``ValueError`` when supplied here.
    optimizer_params : Mapping, optional
        Additional keyword arguments provided to the trainer constructor
        when a string alias or trainer class is supplied.
    learning_rate, epochs, batch_size, shuffle, verbose : optional
        Convenience hyper-parameters injected into the trainer whenever the
        chosen implementation accepts them. ``shuffle`` supports ``False``
        to disable random shuffling.
    loss : str | LossFunction, optional
        Custom loss specification forwarded to trainers that expose a
        ``loss`` parameter. ``None`` resolves to cross-entropy.
    rules : Sequence[Sequence[int]] | None, optional
        Optional explicit fuzzy rule definitions. Each rule lists the
        membership-function index for each input. ``None`` uses the full
        Cartesian product of configured membership functions.
    """
    if n_classes is not None and int(n_classes) < 2:
        raise ValueError("n_classes must be >= 2")
    self.n_classes: int | None = int(n_classes) if n_classes is not None else None
    self.n_mfs = int(n_mfs)
    self.mf_type = str(mf_type)
    self.init = None if init is None else str(init)
    self.overlap = float(overlap)
    self.margin = float(margin)
    self.inputs_config: dict[Any, InputConfigValue] | None = (
        dict(inputs_config) if inputs_config is not None else None
    )
    self.random_state = random_state
    self.optimizer = optimizer
    self.optimizer_params = dict(optimizer_params) if optimizer_params is not None else None
    self.learning_rate = learning_rate
    self.epochs = epochs
    self.batch_size = batch_size
    self.shuffle = shuffle
    self.verbose = verbose
    self.loss = loss
    self.rules = None if rules is None else tuple(tuple(int(idx) for idx in rule) for rule in rules)

    # Fitted attributes (initialised during fit)
    self.model_: TSKANFISClassifier | None = None
    self.optimizer_: BaseTrainer | None = None
    self.feature_names_in_: list[str] | None = None
    self.n_features_in_: int | None = None
    self.training_history_: TrainingHistory | None = None
    self.input_specs_: list[NormalizedInputSpec] | None = None
    self.classes_: np.ndarray | None = None
    self._class_to_index_: dict[Any, int] | None = None
    self.rules_: list[tuple[int, ...]] | None = None

__repr__

__repr__() -> str

Return a formatted representation summarising configuration and fitted artefacts.

Source code in anfis_toolbox/classifier.py
def __repr__(self) -> str:
    """Return a formatted representation summarising configuration and fitted artefacts."""
    return format_estimator_repr(
        type(self).__name__,
        self._repr_config_pairs(),
        self._repr_children_entries(),
    )

evaluate

evaluate(
    X: ArrayLike,
    y: ArrayLike,
    *,
    return_dict: bool = True,
    print_results: bool = True,
) -> Mapping[str, MetricValue] | None

Evaluate predictive performance on a labelled dataset.

Parameters

X : array-like Evaluation inputs. y : array-like Ground-truth labels. Accepts integer labels or one-hot encodings. return_dict : bool, default=True When True return the computed metric dictionary; when False return None after optional printing. print_results : bool, default=True Emit a formatted summary to stdout. Set to False to suppress printing.

Returns:

Mapping[str, MetricValue] | None Dictionary containing accuracy, balanced accuracy, macro/micro precision/recall/F1 scores, and the confusion matrix when return_dict is True; otherwise None.

Raises:

RuntimeError If called before the estimator has been fitted. ValueError When X and y disagree on sample count or labels are incompatible with the configured class count.

Source code in anfis_toolbox/classifier.py
def evaluate(
    self,
    X: npt.ArrayLike,
    y: npt.ArrayLike,
    *,
    return_dict: bool = True,
    print_results: bool = True,
) -> Mapping[str, MetricValue] | None:
    """Evaluate predictive performance on a labelled dataset.

    Parameters
    ----------
    X : array-like
        Evaluation inputs.
    y : array-like
        Ground-truth labels. Accepts integer labels or one-hot encodings.
    return_dict : bool, default=True
        When ``True`` return the computed metric dictionary; when ``False``
        return ``None`` after optional printing.
    print_results : bool, default=True
        Emit a formatted summary to stdout. Set to ``False`` to suppress
        printing.

    Returns:
    -------
    Mapping[str, MetricValue] | None
        Dictionary containing accuracy, balanced accuracy, macro/micro
        precision/recall/F1 scores, and the confusion matrix when
        ``return_dict`` is ``True``; otherwise ``None``.

    Raises:
    ------
    RuntimeError
        If called before the estimator has been fitted.
    ValueError
        When ``X`` and ``y`` disagree on sample count or labels are
        incompatible with the configured class count.
    """
    check_is_fitted(self, attributes=["model_"])
    X_arr, _ = ensure_2d_array(X)
    encoded_targets, _ = self._encode_targets(y, X_arr.shape[0], allow_partial_classes=True)
    proba = self.predict_proba(X_arr)
    metrics: dict[str, MetricValue] = ANFISMetrics.classification_metrics(encoded_targets, proba)
    metrics.pop("log_loss", None)
    if print_results:

        def _is_effectively_nan(value: Any) -> bool:
            if value is None:
                return True
            if isinstance(value, (float, np.floating)):
                return bool(np.isnan(value))
            if isinstance(value, (int, np.integer)):
                return False
            if isinstance(value, np.ndarray):
                if value.size == 0:
                    return False
                if np.issubdtype(value.dtype, np.number):
                    return bool(np.isnan(value.astype(float)).all())
                return False
            return False

        print("ANFISClassifier evaluation:")  # noqa: T201
        for key, value in metrics.items():
            if _is_effectively_nan(value):
                continue
            if isinstance(value, (float, np.floating)):
                display_value = f"{float(value):.6f}"
                print(f"  {key}: {display_value}")  # noqa: T201
            elif isinstance(value, (int, np.integer)):
                print(f"  {key}: {int(value)}")  # noqa: T201
            elif isinstance(value, np.ndarray):
                array_repr = np.array2string(value, precision=6, suppress_small=True)
                if "\n" in array_repr:
                    indented = "\n    ".join(array_repr.splitlines())
                    print(f"  {key}:\n    {indented}")  # noqa: T201
                else:
                    print(f"  {key}: {array_repr}")  # noqa: T201
            else:
                print(f"  {key}: {value}")  # noqa: T201
    return metrics if return_dict else None

fit

fit(
    X: ArrayLike,
    y: ArrayLike,
    *,
    validation_data: tuple[ndarray, ndarray] | None = None,
    validation_frequency: int = 1,
    verbose: bool | None = None,
    **fit_params: Any,
) -> ANFISClassifier

Fit the classifier on labelled data.

Parameters

X : array-like Training inputs with shape (n_samples, n_features). y : array-like Target labels. Accepts integer or string labels as well as one-hot matrices with shape (n_samples, n_classes). validation_data : tuple[np.ndarray, np.ndarray], optional Optional validation split supplied to the underlying trainer. Inputs and targets must already be numeric and share the same row count. validation_frequency : int, default=1 Frequency (in epochs) at which validation metrics are computed when validation_data is provided. verbose : bool, optional Override the estimator's verbose flag for this fit call. When provided, the value is stored on the estimator and forwarded to the trainer configuration. **fit_params : Any Additional keyword arguments forwarded directly to the trainer fit method.

Returns:

ANFISClassifier Reference to self to enable fluent-style chaining.

Raises:

ValueError If the input arrays disagree on the number of samples or the label encoding is incompatible with the configured n_classes. TypeError If the trainer fit implementation does not return a dictionary-style training history.

Source code in anfis_toolbox/classifier.py
def fit(
    self,
    X: npt.ArrayLike,
    y: npt.ArrayLike,
    *,
    validation_data: tuple[np.ndarray, np.ndarray] | None = None,
    validation_frequency: int = 1,
    verbose: bool | None = None,
    **fit_params: Any,
) -> ANFISClassifier:
    """Fit the classifier on labelled data.

    Parameters
    ----------
    X : array-like
        Training inputs with shape ``(n_samples, n_features)``.
    y : array-like
        Target labels. Accepts integer or string labels as well as one-hot
        matrices with shape ``(n_samples, n_classes)``.
    validation_data : tuple[np.ndarray, np.ndarray], optional
        Optional validation split supplied to the underlying trainer.
        Inputs and targets must already be numeric and share the same row
        count.
    validation_frequency : int, default=1
        Frequency (in epochs) at which validation metrics are computed when
        ``validation_data`` is provided.
    verbose : bool, optional
        Override the estimator's ``verbose`` flag for this fit call. When
        provided, the value is stored on the estimator and forwarded to the
        trainer configuration.
    **fit_params : Any
        Additional keyword arguments forwarded directly to the trainer
        ``fit`` method.

    Returns:
    -------
    ANFISClassifier
        Reference to ``self`` to enable fluent-style chaining.

    Raises:
    ------
    ValueError
        If the input arrays disagree on the number of samples or the label
        encoding is incompatible with the configured ``n_classes``.
    TypeError
        If the trainer ``fit`` implementation does not return a
        dictionary-style training history.
    """
    X_arr, feature_names = ensure_2d_array(X)
    n_samples = X_arr.shape[0]
    y_encoded, classes = self._encode_targets(y, n_samples)

    self.classes_ = classes
    self._class_to_index_ = {self._normalize_class_key(cls): idx for idx, cls in enumerate(classes.tolist())}

    self.feature_names_in_ = feature_names
    self.n_features_in_ = X_arr.shape[1]
    self.input_specs_ = self._resolve_input_specs(feature_names)

    if verbose is not None:
        self.verbose = bool(verbose)

    _ensure_training_logging(self.verbose)
    if self.n_classes is None:
        raise RuntimeError("n_classes could not be inferred from the provided targets")
    self.model_ = self._build_model(X_arr, feature_names)
    trainer = self._instantiate_trainer()
    self.optimizer_ = trainer
    trainer_kwargs: dict[str, Any] = dict(fit_params)
    if validation_data is not None:
        trainer_kwargs.setdefault("validation_data", validation_data)
    if validation_data is not None or validation_frequency != 1:
        trainer_kwargs.setdefault("validation_frequency", validation_frequency)

    history = trainer.fit(self.model_, X_arr, y_encoded, **trainer_kwargs)
    if not isinstance(history, dict):
        raise TypeError("Trainer.fit must return a TrainingHistory dictionary")
    self.training_history_ = history
    self.rules_ = self.model_.rules

    self._mark_fitted()
    return self

get_rules

get_rules() -> tuple[tuple[int, ...], ...]

Return the fuzzy rule index combinations used by the fitted model.

Returns:

tuple[tuple[int, ...], ...] Immutable tuple describing each fuzzy rule as a per-input membership index.

Raises:

RuntimeError If invoked before fit completes.

Source code in anfis_toolbox/classifier.py
def get_rules(self) -> tuple[tuple[int, ...], ...]:
    """Return the fuzzy rule index combinations used by the fitted model.

    Returns:
    -------
    tuple[tuple[int, ...], ...]
        Immutable tuple describing each fuzzy rule as a per-input
        membership index.

    Raises:
    ------
    RuntimeError
        If invoked before ``fit`` completes.
    """
    check_is_fitted(self, attributes=["rules_"])
    if not self.rules_:
        return ()
    return tuple(tuple(rule) for rule in self.rules_)

load classmethod

load(filepath: str | Path) -> ANFISClassifier

Load a pickled ANFISClassifier from filepath and validate its type.

Source code in anfis_toolbox/classifier.py
@classmethod
def load(cls, filepath: str | Path) -> ANFISClassifier:
    """Load a pickled ``ANFISClassifier`` from ``filepath`` and validate its type."""
    path = Path(filepath)
    with path.open("rb") as stream:
        estimator = pickle.load(stream)  # nosec B301
    if not isinstance(estimator, cls):
        raise TypeError(f"Expected pickled {cls.__name__} instance, got {type(estimator).__name__}.")
    return estimator

predict

predict(X: ArrayLike) -> np.ndarray

Predict class labels for the provided samples.

Parameters

X : array-like Samples to classify. One-dimensional arrays are treated as a single sample; two-dimensional arrays must have shape (n_samples, n_features).

Returns:

np.ndarray Predicted class labels with shape (n_samples,).

Raises:

RuntimeError If invoked before the estimator is fitted. ValueError When the supplied samples do not match the fitted feature count.

Source code in anfis_toolbox/classifier.py
def predict(self, X: npt.ArrayLike) -> np.ndarray:
    """Predict class labels for the provided samples.

    Parameters
    ----------
    X : array-like
        Samples to classify. One-dimensional arrays are treated as a single
        sample; two-dimensional arrays must have shape ``(n_samples, n_features)``.

    Returns:
    -------
    np.ndarray
        Predicted class labels with shape ``(n_samples,)``.

    Raises:
    ------
    RuntimeError
        If invoked before the estimator is fitted.
    ValueError
        When the supplied samples do not match the fitted feature count.
    """
    check_is_fitted(self, attributes=["model_", "classes_"])
    X_arr = np.asarray(X, dtype=float)
    if X_arr.ndim == 1:
        X_arr = X_arr.reshape(1, -1)
    else:
        X_arr, _ = ensure_2d_array(X)

    if self.n_features_in_ is None:
        raise RuntimeError("Model must be fitted before calling predict.")
    if X_arr.shape[1] != self.n_features_in_:
        raise ValueError(f"Feature mismatch: expected {self.n_features_in_}, got {X_arr.shape[1]}.")
    model = self.model_
    classes = self.classes_
    if model is None or classes is None:
        raise RuntimeError("Model must be fitted before calling predict.")
    encoded = np.asarray(model.predict(X_arr), dtype=int)
    return np.asarray(classes)[encoded]

predict_proba

predict_proba(X: ArrayLike) -> np.ndarray

Predict class probabilities for the provided samples.

Parameters

X : array-like Samples for which to estimate class probabilities.

Returns:

np.ndarray Matrix of shape (n_samples, n_classes) containing class probability estimates.

Raises:

RuntimeError If the estimator has not been fitted. ValueError If sample dimensionality does not match the fitted feature count.

Source code in anfis_toolbox/classifier.py
def predict_proba(self, X: npt.ArrayLike) -> np.ndarray:
    """Predict class probabilities for the provided samples.

    Parameters
    ----------
    X : array-like
        Samples for which to estimate class probabilities.

    Returns:
    -------
    np.ndarray
        Matrix of shape ``(n_samples, n_classes)`` containing class
        probability estimates.

    Raises:
    ------
    RuntimeError
        If the estimator has not been fitted.
    ValueError
        If sample dimensionality does not match the fitted feature count.
    """
    check_is_fitted(self, attributes=["model_"])
    X_arr = np.asarray(X, dtype=float)
    if X_arr.ndim == 1:
        X_arr = X_arr.reshape(1, -1)
    else:
        X_arr, _ = ensure_2d_array(X)

    if self.n_features_in_ is None:
        raise RuntimeError("Model must be fitted before calling predict_proba.")
    if X_arr.shape[1] != self.n_features_in_:
        raise ValueError(f"Feature mismatch: expected {self.n_features_in_}, got {X_arr.shape[1]}.")
    model = self.model_
    if model is None:
        raise RuntimeError("Model must be fitted before calling predict_proba.")
    return np.asarray(model.predict_proba(X_arr), dtype=float)

save

save(filepath: str | Path) -> None

Serialize this estimator (including fitted artefacts) to filepath.

Source code in anfis_toolbox/classifier.py
def save(self, filepath: str | Path) -> None:
    """Serialize this estimator (including fitted artefacts) to ``filepath``."""
    path = Path(filepath)
    path.parent.mkdir(parents=True, exist_ok=True)
    with path.open("wb") as stream:
        pickle.dump(self, stream)  # nosec B301