Example 3: Binary Classification on Heart Disease Dataset using SONFIS

This example demonstrates structural adaptation with SONFIS on the Heart Disease dataset, a 13-feature binary classification benchmark. The target variable is binarized — distinguishing the presence from the absence of heart disease — and a rule_reduced_ANFIS model is trained from a small initial rule base that SONFIS then adapts through rule growing, splitting, and pruning.

This example also illustrates the use of lse_for_new_consequents=True, which initializes the consequent parameters of newly created rules using least-squares estimation rather than random initialization, providing a better starting point for subsequent gradient-based updates.

Imports and reproducibility

from ucimlrepo import fetch_ucirepo

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import (
    confusion_matrix, f1_score, precision_score,
    recall_score, accuracy_score, classification_report
)

import torch
import torch.nn as nn
import torch.utils.data as data
import numpy as np
import random

import neuro_fuzzy_toolbox as nft

SEED = 0
random.seed(SEED)
np.random.seed(SEED)
torch.manual_seed(SEED)

Data

Missing values are filled with zero. The original five-class target is binarized: any value greater than 0 is mapped to 1, representing the presence of heart disease. The dataset is split into training (70%), validation (16%), and test (14%) sets using stratified sampling. Features are scaled to [0, 1] and converted to torch.float64 tensors to improve numerical stability in the least-squares estimation steps.

heart_disease = fetch_ucirepo(id=45)

X = heart_disease.data.features
y = heart_disease.data.targets

# Fill missing values
X = X.fillna(value=0)

# Convert to binary classification: 0 = no disease, 1 = disease
y = y.copy()
y.loc[y['num'] > 0, 'num'] = 1

x_train, x_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, stratify=y, random_state=SEED
)
x_train, x_val, y_train, y_val = train_test_split(
    x_train, y_train, test_size=0.2, stratify=y_train, random_state=SEED
)

scaler = MinMaxScaler(feature_range=(0, 1))

x_train = torch.from_numpy(scaler.fit_transform(x_train)).to(torch.float64)
x_val   = torch.from_numpy(scaler.transform(x_val)).to(torch.float64)
x_test  = torch.from_numpy(scaler.transform(x_test)).to(torch.float64)

y_train = torch.from_numpy(y_train.values).squeeze()
y_val   = torch.from_numpy(y_val.values).squeeze()
y_test  = torch.from_numpy(y_test.values).squeeze()

DataLoaders

generator = torch.Generator()
generator.manual_seed(SEED)

train_loader = data.DataLoader(
    data.TensorDataset(x_train, y_train),
    batch_size=16, shuffle=True, generator=generator
)
val_loader = data.DataLoader(
    data.TensorDataset(x_val, y_val),
    batch_size=16, shuffle=False
)

Model

A rule_reduced_ANFIS model is instantiated with 3 initial rules, Gaussian_MF membership functions, and a softmax output layer for binary classification. Premise parameters are initialized from the training data, and consequent parameters are estimated by regularized least squares.

features = heart_disease.variables['name'][:13].tolist()

model = nft.rule_reduced_ANFIS(
    input_size=x_train.shape[1],
    num_mfs=3,
    outputs=2,
    membership_function=nft.Gaussian_MF(),
    output_type='softmax',
    features=features,
    dtype=torch.float64
)

model.init_premises(x_train)
model.init_consequents(x_train, y_train, driver='gelsd', ridge_lambda=1e-3)

Learning algorithm

The parameter update algorithm is defined here and passed to SONFIS as its ANFIStrainer. It will be used internally by SONFIS to update the model parameters at each structural adaptation iteration.

anfis_trainer = nft.Basic_optimizer_training_algorithm(
    epochs=1000,
    loss_function=nn.CrossEntropyLoss(),
    optimizer=torch.optim.AdamW,
    optimizer_params={'lr': 1e-3, 'weight_decay': 1e-2},
    early_stopping=nft.EarlyStopping(patience=80)
)

SONFIS

SONFIS is configured with rule growing, splitting, and pruning thresholds appropriate for this dataset. Enabling lse_for_new_consequents ensures that the consequent parameters of any rule added by GrowNet or SplitSubNet are initialized via least-squares estimation rather than randomly, which tends to reduce the number of gradient updates needed to integrate the new rule into the model. A separate early stopping mechanism is provided at the SONFIS iteration level, independent of the one used by the ANFIStrainer.

sonfis = nft.SONFIS(
    Ngrow=20,
    dGrow=0.8,
    Nsplit=25,
    eSplit=0.35,
    Nvanish=5,
    lVanish=4,
    max_iterations=100,
    ANFIStrainer=anfis_trainer,
    early_stopping=nft.EarlyStopping(patience=25),
    lse_for_new_consequents=True,
    lse_for_new_consequents_lambda=1e-1,
    last_training_iteration=False
)

sonfis(model, train_loader, val_loader)

Evaluation

pred = model.predict(x_test)

acc        = accuracy_score(y_test, pred)
prec       = precision_score(y_test, pred, zero_division=0)
recall     = recall_score(y_test, pred)
f1         = f1_score(y_test, pred, zero_division=0)
conf_matrix = confusion_matrix(y_test, pred)
class_rep  = classification_report(y_test, pred)

print("Accuracy:", acc)
print("Precision:", prec)
print("Recall:", recall)
print("F1 score:", f1, "\n")

print("Confusion Matrix:")
print(conf_matrix, "\n")

print("Classification Report:")
print(class_rep)
Accuracy: 0.8131868131868132
Precision: 0.8378378378378378
Recall: 0.7380952380952381
F1 score: 0.7848101265822784

Confusion Matrix:
[[43  6]
 [11 31]]

Classification Report:
              precision    recall  f1-score   support

           0       0.80      0.88      0.83        49
           1       0.84      0.74      0.78        42

    accuracy                           0.81        91
   macro avg       0.82      0.81      0.81        91
weighted avg       0.82      0.81      0.81        91
print(model.rules)
6

Note

The SONFIS parameters (Ngrow, dGrow, Nsplit, eSplit, Nvanish, lVanish) control the structural adaptation operators and are dataset-dependent. For a detailed description of each parameter, see SONFIS.