HW4: Scenario 2 source and report added

This commit is contained in:
2025-10-26 22:23:01 +02:00
parent 613b21e059
commit 7f33867398
46 changed files with 873 additions and 27 deletions
+22
View File
@@ -0,0 +1,22 @@
=== Run 1: mode=class-independent, radius=0.20 ===
Overall Accuracy: 68.85%
Cohen's Kappa : 0.413
Rules: 32 | OA: 68.85% | Kappa: 0.413
=== Run 2: mode=class-independent, radius=0.80 ===
Overall Accuracy: 73.77%
Cohen's Kappa : 0.617
Rules: 3 | OA: 73.77% | Kappa: 0.617
=== Run 3: mode=class-dependent, radius=0.20 ===
Overall Accuracy: 60.66%
Cohen's Kappa : 0.296
Rules: 51 | OA: 60.66% | Kappa: 0.296
=== Run 4: mode=class-dependent, radius=0.80 ===
Overall Accuracy: 72.13%
Cohen's Kappa : 0.604
Rules: 4 | OA: 72.13% | Kappa: 0.604
Done. Figures saved in: figures_scn1
+235
View File
@@ -0,0 +1,235 @@
Scenario 2 - Epileptic Seizure Classification
================================================
Configuration loaded: 3 folds, 2 feature options, 2 radius options.
Loading dataset from ./Datasets/epileptic_seizure_data.csv ...
Dataset loaded: 11500 samples, 178 features, 5 classes.
Splitting data into train/val/test (60/20/20%)...
-> train: 6900 val: 2300 test: 2300
Applying z-score normalization...
GRID SEARCH (features × radius) using 3-fold CV
[GRID] features= 5, radius=0.50 ...
-> Fold 1/3 ...
ANFIS info:
Number of nodes: 92
Number of linear parameters: 7
Number of nonlinear parameters: 70
Total number of parameters: 77
Number of training data pairs: 4600
Number of checking data pairs: 2300
Number of fuzzy rules: 7
Minimal training RMSE = 1.13032
Minimal checking RMSE = 1.13236
Overall Accuracy: 30.61%
Cohen's Kappa : 0.169
kappa=0.169 rules=7
-> Fold 2/3 ...
ANFIS info:
Number of nodes: 140
Number of linear parameters: 11
Number of nonlinear parameters: 110
Total number of parameters: 121
Number of training data pairs: 4600
Number of checking data pairs: 2300
Number of fuzzy rules: 11
Minimal training RMSE = 1.12499
Minimal checking RMSE = 1.15105
Overall Accuracy: 39.43%
Cohen's Kappa : 0.289
kappa=0.289 rules=11
-> Fold 3/3 ...
ANFIS info:
Number of nodes: 128
Number of linear parameters: 10
Number of nonlinear parameters: 100
Total number of parameters: 110
Number of training data pairs: 4600
Number of checking data pairs: 2300
Number of fuzzy rules: 10
Minimal training RMSE = 1.13166
Minimal checking RMSE = 1.12551
Overall Accuracy: 34.91%
Cohen's Kappa : 0.225
kappa=0.225 rules=10
-> mean Kappa=0.227 mean rules=9
[GRID] features= 5, radius=0.75 ...
-> Fold 1/3 ...
ANFIS info:
Number of nodes: 68
Number of linear parameters: 5
Number of nonlinear parameters: 50
Total number of parameters: 55
Number of training data pairs: 4600
Number of checking data pairs: 2300
Number of fuzzy rules: 5
Minimal training RMSE = 1.12389
Minimal checking RMSE = 1.12597
Overall Accuracy: 33.17%
Cohen's Kappa : 0.210
kappa=0.210 rules=5
-> Fold 2/3 ...
ANFIS info:
Number of nodes: 68
Number of linear parameters: 5
Number of nonlinear parameters: 50
Total number of parameters: 55
Number of training data pairs: 4600
Number of checking data pairs: 2300
Number of fuzzy rules: 5
Minimal training RMSE = 1.15261
Minimal checking RMSE = 1.16568
Overall Accuracy: 34.30%
Cohen's Kappa : 0.240
kappa=0.240 rules=5
-> Fold 3/3 ...
ANFIS info:
Number of nodes: 68
Number of linear parameters: 5
Number of nonlinear parameters: 50
Total number of parameters: 55
Number of training data pairs: 4600
Number of checking data pairs: 2300
Number of fuzzy rules: 5
Minimal training RMSE = 1.14349
Minimal checking RMSE = 1.13975
Overall Accuracy: 34.65%
Cohen's Kappa : 0.226
kappa=0.226 rules=5
-> mean Kappa=0.225 mean rules=5
[GRID] features= 8, radius=0.50 ...
-> Fold 1/3 ...
ANFIS info:
Number of nodes: 209
Number of linear parameters: 11
Number of nonlinear parameters: 176
Total number of parameters: 187
Number of training data pairs: 4600
Number of checking data pairs: 2300
Number of fuzzy rules: 11
Minimal training RMSE = 1.11568
Minimal checking RMSE = 1.12592
Overall Accuracy: 35.26%
Cohen's Kappa : 0.224
kappa=0.224 rules=11
-> Fold 2/3 ...
ANFIS info:
Number of nodes: 209
Number of linear parameters: 11
Number of nonlinear parameters: 176
Total number of parameters: 187
Number of training data pairs: 4600
Number of checking data pairs: 2300
Number of fuzzy rules: 11
Minimal training RMSE = 1.08705
Minimal checking RMSE = 1.11471
Overall Accuracy: 35.48%
Cohen's Kappa : 0.226
kappa=0.226 rules=11
-> Fold 3/3 ...
ANFIS info:
Number of nodes: 209
Number of linear parameters: 11
Number of nonlinear parameters: 176
Total number of parameters: 187
Number of training data pairs: 4600
Number of checking data pairs: 2300
Number of fuzzy rules: 11
Minimal training RMSE = 1.12397
Minimal checking RMSE = 1.12198
Overall Accuracy: 35.65%
Cohen's Kappa : 0.230
kappa=0.230 rules=11
-> mean Kappa=0.227 mean rules=11
[GRID] features= 8, radius=0.75 ...
-> Fold 1/3 ...
ANFIS info:
Number of nodes: 119
Number of linear parameters: 6
Number of nonlinear parameters: 96
Total number of parameters: 102
Number of training data pairs: 4600
Number of checking data pairs: 2300
Number of fuzzy rules: 6
Minimal training RMSE = 1.12245
Minimal checking RMSE = 1.12803
Overall Accuracy: 32.70%
Cohen's Kappa : 0.196
kappa=0.196 rules=6
-> Fold 2/3 ...
ANFIS info:
Number of nodes: 173
Number of linear parameters: 9
Number of nonlinear parameters: 144
Total number of parameters: 153
Number of training data pairs: 4600
Number of checking data pairs: 2300
Number of fuzzy rules: 9
Minimal training RMSE = 1.10065
Minimal checking RMSE = 1.12512
Overall Accuracy: 37.09%
Cohen's Kappa : 0.251
kappa=0.251 rules=9
-> Fold 3/3 ...
ANFIS info:
Number of nodes: 101
Number of linear parameters: 5
Number of nonlinear parameters: 80
Total number of parameters: 85
Number of training data pairs: 4600
Number of checking data pairs: 2300
Number of fuzzy rules: 5
Minimal training RMSE = 1.13562
Minimal checking RMSE = 1.13557
Overall Accuracy: 33.00%
Cohen's Kappa : 0.196
kappa=0.196 rules=5
-> mean Kappa=0.214 mean rules=7
BEST HYPERPARAMS
features=5 radius=0.50 CV Kappa=0.227 mean rules=9
Training final model on train+val with best params ...
ANFIS info:
Number of nodes: 80
Number of linear parameters: 6
Number of nonlinear parameters: 60
Total number of parameters: 66
Number of training data pairs: 9200
Number of checking data pairs: 2300
Number of fuzzy rules: 6
Minimal training RMSE = 1.14268
Minimal checking RMSE = 1.1463
Final training complete: 6 rules.
Evaluating on TEST set ...
Overall Accuracy: 29.78%
Cohen's Kappa : 0.158
[TEST RESULTS]
OA = 29.78 %
Kappa= 0.158
Rules= 6
Generating figures ...
Done. Figures saved in: figures_scn2
Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 411 KiB

After

Width:  |  Height:  |  Size: 411 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 566 KiB

After

Width:  |  Height:  |  Size: 566 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

+2 -2
View File
@@ -29,7 +29,7 @@ function plot_results1(results, classLabels, cfg)
radii(i) = results(i).radius;
end
% -------- Per-model plots --------
% Per-model plots
for i = 1:nRuns
tag = sprintf('run%02d_%s_r%.2f_rules%d', ...
i, results(i).mode, results(i).radius, results(i).nRules);
@@ -102,7 +102,7 @@ function plot_results1(results, classLabels, cfg)
end
end
% -------- Across-model summaries --------
% Across-model summaries
[~, idxSort] = sortrows([double(modes=='class-independent'), radii], [1 2]);
OA_s = OA(idxSort);
Kap_s = Kap(idxSort);
+142
View File
@@ -0,0 +1,142 @@
function plot_results2(results, cfg, classLabels)
% PLOT_RESULTS2 Scenario 2 plotting suite
% Produces and saves:
% (A) CV heatmap of Kappa over (#features × radius)
% (B) CV heatmap of mean #rules over the same grid
% (C) Confusion matrix on TEST for the best model
% (D) PA/UA bars on TEST
% (E) Learning curves (train/validation error)
% (F) MFs before/after for the best model
% (G) Truth vs Prediction (TEST)
% (H) ReliefF feature weights bar chart
%
% All PNGs saved under cfg.outDir.
outDir = cfg.outDir;
% (A) CV heatmap Kappa
fig = figure('Color','w');
imagesc(results.cvScores);
set(gca,'XTick',1:numel(results.rGrid),'XTickLabel',compose('%.2f',results.rGrid));
set(gca,'YTick',1:numel(results.fGrid),'YTickLabel',string(results.fGrid));
xlabel('SC radius r_a'); ylabel('#Features (ReliefF)'); colorbar;
title(sprintf('CV mean Kappa (K=%d folds)', cfg.kfold));
for i = 1:numel(results.fGrid)
for j = 1:numel(results.rGrid)
text(j,i,sprintf('%.2f',results.cvScores(i,j)),...
'HorizontalAlignment','center','Color','w','FontWeight','bold');
end
end
exportgraphics(fig, fullfile(outDir,'cv_kappa_heatmap.png'), 'Resolution', 200);
close(fig);
% (B) CV heatmap mean #rules
fig = figure('Color','w');
imagesc(results.cvRules);
set(gca,'XTick',1:numel(results.rGrid),'XTickLabel',compose('%.2f',results.rGrid));
set(gca,'YTick',1:numel(results.fGrid),'YTickLabel',string(results.fGrid));
xlabel('SC radius r_a'); ylabel('#Features (ReliefF)'); colorbar;
title(sprintf('CV mean #Rules (K=%d folds)', cfg.kfold));
for i = 1:numel(results.fGrid)
for j = 1:numel(results.rGrid)
text(j,i,sprintf('%d',results.cvRules(i,j)),...
'HorizontalAlignment','center','Color','w','FontWeight','bold');
end
end
exportgraphics(fig, fullfile(outDir,'cv_rules_heatmap.png'), 'Resolution', 200);
close(fig);
% (C) Confusion matrix TEST
fig = figure('Color','w');
confusionchart(results.metrics.confMat, string(classLabels), ...
'Title', sprintf('Confusion best model (features=%d, r=%.2f, rules=%d)', ...
results.bestF, results.bestR, numel(results.bestFis.rule)));
exportgraphics(fig, fullfile(outDir,'cm_best_model.png'), 'Resolution', 200);
close(fig);
% (D) PA/UA bars TEST
fig = figure('Color','w');
t = tiledlayout(2,1,'TileSpacing','compact','Padding','compact');
nexttile; bar(results.metrics.PA); ylim([0 1]);
xticks(1:numel(classLabels)); xticklabels(string(classLabels));
ylabel('PA (Recall)'); title('Producer''s Accuracy (TEST)'); grid on;
nexttile; bar(results.metrics.UA); ylim([0 1]);
xticks(1:numel(classLabels)); xticklabels(string(classLabels));
ylabel('UA (Precision)'); title('User''s Accuracy (TEST)'); grid on;
exportgraphics(fig, fullfile(outDir,'pa_ua_best_model.png'), 'Resolution', 200);
close(fig);
% (E) Learning curves
fig = figure('Color','w');
plot(1:numel(results.trError), results.trError, 'LineWidth', 1.2); hold on;
if ~isempty(results.vaError)
plot(1:numel(results.vaError), results.vaError, '--', 'LineWidth', 1.2);
legend('Training Error','Validation Error','Location','best');
else
legend('Training Error','Location','best');
end
xlabel('Epoch'); ylabel('Error'); grid on;
title(sprintf('Learning Curve best model (features=%d, r=%.2f)', ...
results.bestF, results.bestR));
exportgraphics(fig, fullfile(outDir,'learning_best_model.png'), 'Resolution', 200);
close(fig);
% (F) MFs before/after for the best model
try
plot_mfs_before_after(results.initFis, results.bestFis, ...
sprintf('MFs best model (features=%d, r=%.2f)', results.bestF, results.bestR), ...
fullfile(outDir,'mfs_best_model.png'));
catch ME
warning('MF plot failed: %s', ME.message);
end
% (G) Truth vs Prediction TEST
fig = figure('Color','w');
plot(results.ytrue, 'LineWidth', 1.0); hold on;
plot(results.yhat, '--', 'LineWidth', 1.0);
xlabel('Test sample index'); ylabel('Class label'); grid on;
title('Truth vs Prediction (TEST)');
legend('Truth','Prediction','Location','best');
exportgraphics(fig, fullfile(outDir,'pred_vs_truth_best_model.png'), 'Resolution', 200);
close(fig);
% (H) ReliefF feature weights (on full train+val)
fig = figure('Color','w');
w = results.reliefW(:);
idx = results.selIdx(:);
% map back: selected indices first for clarity
bar(1:numel(idx), w(idx)); grid on;
xlabel('Selected feature index'); ylabel('ReliefF weight');
title(sprintf('ReliefF weights (top %d features)', results.bestF));
exportgraphics(fig, fullfile(outDir,'relieff_weights_selected.png'), 'Resolution', 200);
close(fig);
% ===================== local helper =====================
function plot_mfs_before_after(fisBefore, fisAfter, suptitleStr, outPng)
D = numel(fisAfter.Inputs);
fig = figure('Color','w','Position',[100 100 1200 420]);
t = tiledlayout(2, D, 'TileSpacing','compact','Padding','compact');
for d = 1:D
nexttile(d); hold on;
try
[xB, yB] = plotmf(fisBefore, 'input', d);
plot(xB, yB, 'LineWidth', 1.0);
catch
end
title(sprintf('Input %d BEFORE', d));
ylim([0 1]); grid on;
nexttile(D + d); hold on;
[xA, yA] = plotmf(fisAfter, 'input', d);
plot(xA, yA, 'LineWidth', 1.0);
title(sprintf('Input %d AFTER', d));
ylim([0 1]); grid on;
end
sgtitle(suptitleStr);
exportgraphics(fig, outPng, 'Resolution', 200);
close(fig);
end
end
+1 -1
View File
@@ -1,5 +1,5 @@
function [Xn, mu, sigma] = preprocess_data(X, mu, sigma)
% PREPROCESS Normalize feature matrix using z-score scaling
% PREPROCESS_DATA Normalize feature matrix using z-score scaling
%
% [Xn, mu, sigma] = preprocess(X)
% [Xn, mu, sigma] = preprocess(X, mu, sigma)
Binary file not shown.
Binary file not shown.
+29 -21
View File
@@ -1,16 +1,24 @@
% scenario1.m Assignment 4 (Classification), Scenario 1 (Haberman)
%% scenario1.m Assignment 4 (Classification), Scenario 1 (Haberman)
% TSK classification with Subtractive Clustering (SC)
% Modes: (A) class-independent SC, (B) class-dependent SC)
% Modes: (A) class-independent SC,
% (B) class-dependent SC)
% Uses: split_data, preprocess_data, evaluate_classification, plot_results1
%
% Dataset: ./Datasets/haberman.data
% Columns: [age, op_year, axillary_nodes, class] with class in {1,2}
%
% Assignment 4 in Fuzzy systems
%
% author:
% Christos Choutouridis ΑΕΜ 8997
% cchoutou@ece.auth.gr
%
close all; clear; clc;
% ============================ CONFIGURATION ================================
% CONFIGURATION
% ================================
cfg = struct();
rng(42, 'twister'); % reproducibility
rng(42, 'twister'); % reproducibility
% Data handling
cfg.split = [0.6 0.2 0.2]; % train / val / test (stratified in split_data)
@@ -34,7 +42,7 @@ cfg.modes = {'class-independent','class-dependent'};
cfg.outDir = 'figures_scn1';
if ~exist(cfg.outDir,'dir'), mkdir(cfg.outDir); end
% =============================== DATA =====================================
% DATA
dataPath = './Datasets/haberman.data';
assert(isfile(dataPath), 'Dataset not found at: %s', dataPath);
@@ -49,7 +57,7 @@ Y = Y(:);
classLabels = unique(Y);
minLabel = min(classLabels); maxLabel = max(classLabels);
% =========================== SPLIT & PREPROCESS ===========================
% SPLIT & PREPROCESS
[trainX, valX, testX, trainY, valY, testY] = split_data(X, Y, cfg.split);
if cfg.standardize
@@ -63,7 +71,7 @@ end
% For manual sugfis construction
inRanges = [min(trainX,[],1); max(trainX,[],1)];
% ============================== TRAINING ==================================
% TRAINING
results = []; runId = 0;
for m = 1:numel(cfg.modes)
@@ -74,24 +82,23 @@ for m = 1:numel(cfg.modes)
runId = runId + 1;
fprintf('\n=== Run %d: mode=%s, radius=%.2f ===\n', runId, modeName, radius);
% ----- Initial FIS -----
% Initial FIS
switch modeName
case 'class-independent'
% Use new-style API like your colleague
opt = genfisOptions('SubtractiveClustering', ...
'ClusterInfluenceRange', radius);
initFis = genfis(trainX, double(trainY), opt);
% genfis(Subtractive) already builds Sugeno with constant consequents.
case 'class-dependent'
% Our custom builder (fixes colleague's bug: feed only features to subclust)
% Our custom builder
initFis = build_classdep_fis(trainX, trainY, classLabels, radius, inRanges);
otherwise
error('Unknown mode: %s', modeName);
end
% ----- ANFIS training -----
% ANFIS training
trData = [trainX double(trainY)];
ckData = [valX double(valY)];
anfisOpts = [cfg.maxEpochs cfg.errorGoal cfg.initialStep cfg.stepDecrease cfg.stepIncrease];
@@ -102,7 +109,7 @@ for m = 1:numel(cfg.modes)
[fisTrained, trError, ~, ~, ckError] = anfis(trData, initFis, anfisOpts, [0 0 0 0], ckData);
end
% ----- Evaluate on test set -----
% Evaluate on test set
yhat_cont = evalfis(testX, fisTrained);
yhat = round(yhat_cont);
% clip into valid label range (important for small rulebases)
@@ -133,22 +140,23 @@ for m = 1:numel(cfg.modes)
end
end
% ============================== PLOTTING ==================================
% PLOTTING
plot_results1(results, classLabels, cfg);
% =============================== SAVE ALL =================================
% SAVE ALL
save('results_scn1.mat','results','cfg','classLabels','mu','sigma', ...
'trainX','valX','testX','trainY','valY','testY');
fprintf('\nDone. Figures saved in: %s\n', cfg.outDir);
% ============================ LOCAL FUNCTIONS =============================
% LOCAL FUNCTIONS
% ===================================================
function fis = build_classdep_fis(X, Y, classLabels, radius, inRanges)
% BUILD_CLASSDEP_FIS class-dependent SC for Sugeno FIS (ANFIS-ready)
% Creates ONE constant output MF PER RULE (required by ANFIS).
% Runs SUBCLUST on FEATURES ONLY for each class.
% Creates ONE constant output MF PER RULE (required by ANFIS).
% Runs SUBCLUST on FEATURES ONLY for each class.
D = size(X,2);
D = size(X, 2);
fis = sugfis('Name','TSK_ClassDependent');
% Inputs with ranges from training data
@@ -172,7 +180,7 @@ function fis = build_classdep_fis(X, Y, classLabels, radius, inRanges)
[centers, sigmas] = subclust(Xi, radius);
nCl = size(centers,1);
% ---- robust sigma broadcasting to M×D ----
% robust sigma broadcasting to M×D
if isscalar(sigmas)
S = repmat(sigmas, nCl, D);
elseif size(sigmas,1) == 1 && size(sigmas,2) == D
@@ -192,7 +200,7 @@ function fis = build_classdep_fis(X, Y, classLabels, radius, inRanges)
% Add input MFs for this cluster (and remember their indices)
for d = 1:D
mfName = sprintf('c%d_r%d_x%d', c, i, d);
params = [S(i,d) centers(i,d)]; % [sigma center]
params = [S(i, d) centers(i, d)]; % [sigma center]
fis = addMF(fis, sprintf('x%d', d), 'gaussmf', params, 'Name', mfName);
antecedentIdx(d) = numel(fis.Inputs(d).MembershipFunctions);
end
+297
View File
@@ -0,0 +1,297 @@
%% 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<AGROW>
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
+1 -1
View File
@@ -1,5 +1,5 @@
function [trainX, valX, testX, trainY, valY, testY] = split_data(X, Y, ratios)
% SPLIT Split dataset into train/validation/test sets (stratified)
% SPLIT_DATA Split dataset into train/validation/test sets (stratified)
%
% [trainX, valX, testX, trainY, valY, testY] = split(X, Y, ratios)
%