%% scenario2.m — Assignment 4 (Classification), Scenario 2 (Epileptic Seizure) % TSK classification on a high-dimensional dataset with feature selection. % Verbose version with progress printing. % % Uses: split_data, preprocess_data, evaluate_classification, plot_results2 % Dataset path: ./Datasets/epileptic_seizure_data.csv % % Assignment 4 in Fuzzy systems % % author: % Christos Choutouridis ΑΕΜ 8997 % cchoutou@ece.auth.gr close all; clear; clc; %parpool('threads',6); fprintf('\nScenario 2 - Epileptic Seizure Classification'); fprintf('\n================================================\n\n'); % CONFIGURATION cfg = struct(); rng(42,'twister'); % reproducibility % Data handling cfg.split = [0.6 0.2 0.2]; cfg.standardize = true; % Feature selection + SC hyper-params % Debug configuration cfg.feature_grid = [5 8]; %[5 8 11 15]; cfg.radii_grid = [0.5 0.75]; %[0.25 0.50 0.75 1.00]; cfg.kfold = 3; cfg.maxEpochs = 20; % ANFIS options cfg.displayANFIS = 0; % Default configuraion %cfg.feature_grid = [5 8 11 15]; %cfg.radii_grid = [0.25 0.50 0.75 1.00]; %cfg.kfold = 5; %cfg.maxEpochs = 100; % ANFIS options %cfg.displayANFIS = 0; % Output directory cfg.outDir = 'figures_scn2'; if ~exist(cfg.outDir,'dir'), mkdir(cfg.outDir); end fprintf('Configuration loaded: %d folds, %d feature options, %d radius options.\n', ... cfg.kfold, numel(cfg.feature_grid), numel(cfg.radii_grid)); % DATA dataPath = './Datasets/epileptic_seizure_data.csv'; fprintf('Loading dataset from %s ...\n', dataPath); assert(isfile(dataPath), 'Dataset not found!'); raw = importdata(dataPath); if isstruct(raw) && isfield(raw,'data') A = raw.data; else A = readmatrix(dataPath); end X = A(:,1:end-1); Y = A(:,end); Y = double(Y(:)); classLabels = unique(Y); num_classes = numel(classLabels); fprintf('Dataset loaded: %d samples, %d features, %d classes.\n', ... size(X,1), size(X,2), num_classes); % SPLIT & PREPROCESS fprintf('\nSplitting data into train/val/test (%.0f/%.0f/%.0f%%)...\n', cfg.split*100); [trainX, valX, testX, trainY, valY, testY] = split_data(X, Y, cfg.split); fprintf('-> train: %d val: %d test: %d\n', size(trainX,1), size(valX,1), size(testX,1)); if cfg.standardize fprintf('Applying z-score normalization...\n'); [trainX, mu, sigma] = preprocess_data(trainX); valX = preprocess_data(valX, mu, sigma); testX = preprocess_data(testX, mu, sigma); else mu = []; sigma = []; end fullTrainX = [trainX; valX]; fullTrainY = [trainY; valY]; % GRID SEARCH fprintf('\nGRID SEARCH (features × radius) using %d-fold CV\n', cfg.kfold); cvp = cvpartition(trainY, 'KFold', cfg.kfold, 'Stratify', true); nF = numel(cfg.feature_grid); nR = numel(cfg.radii_grid); cvScores = zeros(nF, nR); cvRules = zeros(nF, nR); for fi = 1:nF featKeep = cfg.feature_grid(fi); for ri = 1:nR radius = cfg.radii_grid(ri); fprintf('\n[GRID] features=%2d, radius=%.2f ... ', featKeep, radius); kappas = zeros(cvp.NumTestSets,1); rulesK = zeros(cvp.NumTestSets,1); for k = 1:cvp.NumTestSets fprintf('\n-> Fold %d/%d ... ', k, cfg.kfold); trIdx = training(cvp, k); teIdx = test(cvp, k); Xtr = trainX(trIdx,:); Ytr = trainY(trIdx); Xva = trainX(teIdx,:); Yva = trainY(teIdx); % Relief feature selection [idxFeat, ~] = relief_select(Xtr, Ytr); sel = idxFeat(1:min(featKeep, numel(idxFeat))); Xtr = Xtr(:, sel); Xva = Xva(:, sel); % Build FIS inRanges = [min(Xtr,[],1); max(Xtr,[],1)]; initFis = build_classdep_fis(Xtr, Ytr, classLabels, radius, inRanges); % Train trData = [Xtr double(Ytr)]; vaData = [Xva double(Yva)]; anfisOpts = anfisOptions('InitialFis', initFis, ... 'EpochNumber', cfg.maxEpochs, ... 'ValidationData', vaData, ... 'OptimizationMethod', 1, ... 'DisplayErrorValues', 0, ... 'DisplayStepSize', 0); [~, ~, ~, bestFis, ~] = anfis(trData, anfisOpts); % Evaluate fold yhat = evalfis(bestFis, Xva); yhat = round(yhat); yhat(yhat < min(classLabels)) = min(classLabels); yhat(yhat > max(classLabels)) = max(classLabels); R = evaluate_classification(Yva, yhat, classLabels); kappas(k) = R.Kappa; rulesK(k) = numel(bestFis.rule); fprintf('kappa=%.3f rules=%d\n', R.Kappa, rulesK(k)); end cvScores(fi,ri) = mean(kappas); cvRules(fi,ri) = round(mean(rulesK)); fprintf('\n-> mean Kappa=%.3f mean rules=%d\n', cvScores(fi,ri), cvRules(fi,ri)); end end [maxPerRow, idxR] = max(cvScores, [], 2); [bestKappa, idxF] = max(maxPerRow); idxR = idxR(idxF); bestFeatures = cfg.feature_grid(idxF); bestRadius = cfg.radii_grid(idxR); bestRulesEst = cvRules(idxF, idxR); fprintf('\nBEST HYPERPARAMS\nfeatures=%d radius=%.2f CV Kappa=%.3f mean rules=%d\n', ... bestFeatures, bestRadius, bestKappa, bestRulesEst); % FINAL TRAIN fprintf('\nTraining final model on train+val with best params ...\n'); [idxAll, weightsAll] = relief_select(fullTrainX, fullTrainY); sel = idxAll(1:min(bestFeatures, numel(idxAll))); Xtr = fullTrainX(:, sel); Xte = testX(:, sel); inRanges = [min(Xtr,[],1); max(Xtr,[],1)]; initFis = build_classdep_fis(Xtr, fullTrainY, classLabels, bestRadius, inRanges); trData = [Xtr double(fullTrainY)]; teData = [Xte double(testY)]; anfisOpts = anfisOptions('InitialFis', initFis, ... 'EpochNumber', cfg.maxEpochs, ... 'ValidationData', teData, ... 'OptimizationMethod', 1, ... 'DisplayErrorValues', 0, ... 'DisplayStepSize', 0); [fisTrained, trError, ~, bestFis, vaError] = anfis(trData, anfisOpts); fprintf('Final training complete: %d rules.\n', numel(bestFis.rule)); % TEST EVAL fprintf('\nEvaluating on TEST set ...\n'); yhat_test = evalfis(bestFis, Xte); yhat_test = round(yhat_test); yhat_test(yhat_test < min(classLabels)) = min(classLabels); yhat_test(yhat_test > max(classLabels)) = max(classLabels); Rtest = evaluate_classification(testY, yhat_test, classLabels); fprintf('\n[TEST RESULTS]\n'); fprintf(' OA = %.2f %%\n', 100*Rtest.OA); fprintf(' Kappa= %.3f\n', Rtest.Kappa); fprintf(' Rules= %d\n', numel(bestFis.rule)); % PLOTTING fprintf('\nGenerating figures ...\n'); results = struct(); results.cvScores = cvScores; results.cvRules = cvRules; results.fGrid = cfg.feature_grid; results.rGrid = cfg.radii_grid; results.bestF = numel(sel); results.bestR = bestRadius; results.bestFis = bestFis; results.initFis = initFis; results.trError = trError; results.vaError = vaError; results.ytrue = testY; results.yhat = yhat_test; results.metrics = Rtest; results.selIdx = sel; results.reliefW = weightsAll; plot_results2(results, cfg, classLabels); save('results_scn2.mat','results','cfg','classLabels','mu','sigma'); fprintf('\nDone. Figures saved in: %s\n', cfg.outDir); % LOCAL FUNCTIONS % ================================================== function fis = build_classdep_fis(X, Y, classLabels, radius, inRanges) % BUILD_CLASSDEP_FIS — class-dependent SC Sugeno FIS (ANFIS-ready) % Creates ONE constant output MF PER RULE (ANFIS requirement). % Runs subclust on FEATURES ONLY per class. D = size(X,2); fis = sugfis('Name','TSK_CD'); % Inputs for d = 1:D fis = addInput(fis, [inRanges(1,d) inRanges(2,d)], 'Name', sprintf('x%d', d)); end % Output (range spans label space) outRange = [min(classLabels) max(classLabels)]; fis = addOutput(fis, outRange, 'Name', 'y'); ruleList = []; for k = 1:numel(classLabels) c = classLabels(k); Xi = X(Y==c, :); if isempty(Xi), continue; end [centers, sigmas] = subclust(Xi, radius); nCl = size(centers,1); % Robust sigma broadcasting to M×D if isscalar(sigmas) S = repmat(sigmas, nCl, D); elseif size(sigmas,1)==1 && size(sigmas,2)==D S = repmat(sigmas, nCl, 1); elseif all(size(sigmas)==[nCl D]) S = sigmas; else S = repmat(0.5*(inRanges(2,:)-inRanges(1,:)), nCl, 1); end for i = 1:nCl antIdx = zeros(1,D); for d = 1:D mfName = sprintf('c%d_r%d_x%d', c, i, d); params = [S(i,d) centers(i,d)]; % [sigma center] fis = addMF(fis, sprintf('x%d', d), 'gaussmf', params, 'Name', mfName); antIdx(d) = numel(fis.Inputs(d).MembershipFunctions); end % ONE constant output MF per rule outName = sprintf('const_c%d_r%d', c, i); fis = addMF(fis, 'y', 'constant', double(c), 'Name', outName); outIdx = numel(fis.Outputs(1).MembershipFunctions); ruleList = [ruleList; [antIdx, outIdx, 1, 1]]; %#ok end end if ~isempty(ruleList) fis = addRule(fis, ruleList); end % Standard TSK ops fis.AndMethod = 'prod'; fis.OrMethod = 'probor'; fis.ImplicationMethod = 'prod'; fis.AggregationMethod = 'sum'; fis.DefuzzificationMethod = 'wtaver'; end function [idx, w] = relief_select(X, y) % RELIEF_SELECT — wraps relieff and returns ranked indices + weights. try [idx, w] = relieff(X, y, 10); % k=10 neighbors catch % Fallback: simple variance ranking if Statistics Toolbox missing w = var(X, 0, 1); [~, idx] = sort(w, 'descend'); end end