HW4: Scenario1 source code and report added

This commit is contained in:
Christos Choutouridis 2025-10-26 20:36:08 +02:00
parent 8ced0fd58d
commit 613b21e059
32 changed files with 665 additions and 1 deletions

Binary file not shown.

View File

@ -71,8 +71,16 @@
% ===================================================================== % =====================================================================
\section{Εισαγωγή} \section{Εισαγωγή}
Στην παρούσα εργασία Η παρούσα εργασία εστιάζει στην επίλυση προβλημάτων \textbf{ταξινόμησης} μέσω ασαφών συστημάτων τύπου \textbf{TakagiSugenoKang (TSK)}.
Η ταξινόμηση αποτελεί μια από τις βασικότερες εφαρμογές της υπολογιστικής νοημοσύνης, καθώς συνδυάζει στοιχεία λογικής, στατιστικής και μηχανικής μάθησης για τη λήψη αποφάσεων με αβεβαιότητα.
Τα μοντέλα TSK προσφέρουν ένα ευέλικτο πλαίσιο, στο οποίο η γνώση αναπαρίσταται με τη μορφή κανόνων τύπου \emph{IFTHEN}, ενώ η εκπαίδευση των παραμέτρων μπορεί να γίνει με υβριδικούς αλγορίθμους (Least Squares + Gradient Descent) όπως ο \textsc{anfis}.
Η εργασία χωρίζεται σε δύο πειραματικά σενάρια:
\begin{itemize}
\item \textbf{Σενάριο~1}: Εφαρμογή σε ένα απλό, χαμηλής διαστασιμότητας dataset (Haberman), με στόχο τη σύγκριση των προσεγγίσεων \textit{class-independent} και \textit{class-dependent Subtractive Clustering}.
\item \textbf{Σενάριο~2}: Εφαρμογή σε πιο σύνθετο πρόβλημα ταξινόμησης με δεδομένα υψηλής διαστασιμότητας, όπου αξιολογούνται τεχνικές προ-επεξεργασίας και επιλογής χαρακτηριστικών.
\end{itemize}
Μέσω των δύο σεναρίων εξετάζεται η διαδικασία εκπαίδευσης, αξιολόγησης και βελτιστοποίησης ενός ασαφούς συστήματος, καθώς και ο τρόπος με τον οποίο η παραμετροποίηση επηρεάζει την απόδοση, την ερμηνευσιμότητα και την υπολογιστική πολυπλοκότητα.
\subsection{Παραδοτέα} \subsection{Παραδοτέα}
@ -84,9 +92,167 @@
\end{itemize} \end{itemize}
% ===================================================================== % =====================================================================
\section{Υλοποίηση} \section{Υλοποίηση}
Για την υλοποίηση ακολουθήθηκε μια κοινή ροή επεξεργασίας και ανάλυσης δεδομένων, με στόχο τη μέγιστη επαναχρησιμοποίηση κώδικα μεταξύ των δύο σεναρίων.
Συγκεκριμένα, δημιουργήθηκαν ανεξάρτητες συναρτήσεις για:
\begin{enumerate}
\item Τον διαχωρισμό των δεδομένων (\textit{split\_data}).
\item Την προ-επεξεργασία (\textit{preprocess\_data}).
\item Την αξιολόγηση των αποτελεσμάτων (\textit{evaluate\_classification}).
\item Και τη συστηματική εμφάνιση/αποθήκευση γραφημάτων (\textit{plot\_results1, plot\_results2}).
\end{enumerate}
Έτσι, η εκτέλεση κάθε σεναρίου πραγματοποιείται αυτόνομα μέσω των scripts \texttt{scenario1.m} και \texttt{scenario2.m}, τα οποία συντονίζουν τα παραπάνω στάδια.
Η δομή αυτή εξασφαλίζει καθαρότερο κώδικα, ευκολότερη συντήρηση και πλήρη αναπαραγωγιμότητα των αποτελεσμάτων.
\subsection{Διαχωρισμός δεδομένων — \textit{split\_data()}}
Η συνάρτηση \textit{split\_data()} αναλαμβάνει να διαχωρίσει το αρχικό dataset σε τρία υποσύνολα: εκπαίδευσης, ελέγχου (validation) και δοκιμής (test).
Η διαδικασία βασίζεται σε τυχαία δειγματοληψία με καθορισμένο seed, ώστε τα ίδια σύνολα να μπορούν να αναπαραχθούν σε επόμενες εκτελέσεις.
Ο διαχωρισμός είναι στρωματοποιημένος, ώστε η αναλογία των κλάσεων να παραμένει ίδια σε όλα τα υποσύνολα.
\subsection{Προ-επεξεργασία δεδομένων — \textit{preprocess\_data()}}
Η προ-επεξεργασία εφαρμόζεται για να βελτιωθεί η σταθερότητα της εκπαίδευσης και να εξισορροπηθούν οι τιμές των εισόδων.
Στο πλαίσιο της εργασίας επιλέχθηκε κανονικοποίηση τύπου \textbf{z-score}, δηλαδή:
\[
x_i' = \frac{x_i - \mu_i}{\sigma_i}
\]
όπου $\mu_i$ και $\sigma_i$ είναι ο μέσος και η τυπική απόκλιση κάθε χαρακτηριστικού.
Η συνάρτηση \textit{preprocess\_data()} εφαρμόζει αυτόματα την κανονικοποίηση στα train/validation/test σύνολα και επιστρέφει τα απαραίτητα στατιστικά για τυχόν επαναφορά στις αρχικές κλίμακες.
\subsection{Αξιολόγηση αποτελεσμάτων — \textit{evaluate\_classification()}}
Η συνάρτηση \textit{evaluate\_classification()} συγκρίνει τις προβλέψεις του μοντέλου με τις πραγματικές ετικέτες και υπολογίζει τις μετρικές αξιολόγησης (OA, PA, UA, $\kappa$).
Επιπλέον, παράγει και επιστρέφει τη μήτρα σύγχυσης.
Η υλοποίηση έγινε ώστε να υποστηρίζει οποιονδήποτε αριθμό κλάσεων, ενώ παρέχει και normalized εκδόσεις των μετρικών, διευκολύνοντας τη συγκριτική ανάλυση μεταξύ μοντέλων.
\subsection{Απεικόνιση αποτελεσμάτων}
Για τη γραφική παρουσίαση των αποτελεσμάτων δημιουργήθηκαν οι συναρτήσεις \textit{plot\_results1()} και \textit{plot\_results2()}, οι οποίες παράγουν και αποθηκεύουν αυτόματα όλα τα ζητούμενα γραφήματα κάθε σεναρίου.
Κάθε γράφημα αποθηκεύεται σε υποκατάλογο (\texttt{figures\_scn1}, \texttt{figures\_scn2}) με συνεπή ονοματολογία, ώστε να διευκολύνεται η ενσωμάτωσή τους στην αναφορά.
Η σχεδίαση περιλαμβάνει μήτρες σύγχυσης, καμπύλες εκπαίδευσης, συναρτήσεις συμμετοχής πριν και μετά την εκπαίδευση, καθώς και συγκεντρωτικά διαγράμματα OA$\kappa$ και RulesAccuracy.
Το επόμενο τμήμα παρουσιάζει το πρώτο σενάριο και τα αποτελέσματά του, εφαρμόζοντας τη μεθοδολογία που περιγράφηκε παραπάνω.
\section{Σενάριο 1 — Εφαρμογή σε απλό Dataset}
Το πρώτο σενάριο αφορά την επίλυση ενός προβλήματος \textbf{δυαδικής ταξινόμησης} χρησιμοποιώντας μοντέλα TSK με σταθερές (singleton) εξόδους.
Ως dataset χρησιμοποιήθηκε το \textit{Habermans Survival Dataset}, το οποίο περιλαμβάνει 306 δείγματα με τρεις αριθμητικές εισόδους (ηλικία, έτος εγχείρησης, αριθμός λεμφαδένων) και μία δυαδική ετικέτα που δηλώνει αν ο ασθενής επέζησε για τουλάχιστον πέντε έτη μετά την εγχείρηση.
Ο στόχος είναι η εκπαίδευση και αξιολόγηση μοντέλων TSK που προκύπτουν μέσω \textbf{Subtractive Clustering (SC)} τόσο στην \textit{class-independent} όσο και στην \textit{class-dependent} παραλλαγή, καθώς και η διερεύνηση της επίδρασης του παραμέτρου ακτίνας $r_a$ στην πολυπλοκότητα και την απόδοση του συστήματος.
\subsection{Πειραματική διαδικασία}
Το σύνολο δεδομένων χωρίστηκε στρωματοποιημένα σε τρία υποσύνολα (60 \% train, 20 \% validation, 20 \% test).
Η κανονικοποίηση των εισόδων έγινε με \textbf{z-score}, ενώ οι ετικέτες παρέμειναν ακέραιες.
Για κάθε mode (class-independent και class-dependent) δοκιμάστηκαν δύο τιμές ακτίνας, $r_a{=}\{0.20,0.80\}$, ώστε να προκύψουν τέσσερα μοντέλα συνολικά.
Η εκπαίδευση πραγματοποιήθηκε με \textbf{anfis} για 100 εποχές και με validation έλεγχο για αποφυγή υπερεκπαίδευσης.
Οι μετρικές αξιολόγησης που χρησιμοποιήθηκαν ήταν:
\begin{itemize}
\item \textbf{Overall Accuracy (OA)},
\item \textbf{Producers Accuracy (PA)} και \textbf{Users Accuracy (UA)} ανά κλάση,
\item και ο \textbf{συντελεστής συμφωνίας $\kappa$ του Cohen}.
\end{itemize}
\subsection{Αποτελέσματα}
\paragraph{Μήτρες σύγχυσης.}
Το σχήμα~\ref{fig:conf_mats} παρουσιάζει τις μήτρες σύγχυσης και για τα τέσσερα μοντέλα.
Και στις δύο προσεγγίσεις, η απόδοση για την κλάση 1 είναι σταθερά υψηλότερη, γεγονός που σχετίζεται με την ανισορροπία του dataset (\textasciitilde 73 \% δείγματα κλάσης 1).
Η αύξηση της ακτίνας μειώνει τον αριθμό κανόνων και οδηγεί σε απλούστερα μοντέλα χωρίς σημαντική απώλεια ακρίβειας.
\begin{figure}[H]
\centering
\includegraphics[width=0.47\textwidth]{../source/figures_scn1/cm_run01_class-independent_r0.20_rules32}
\includegraphics[width=0.47\textwidth]{../source/figures_scn1/cm_run02_class-independent_r0.80_rules3}\\[4pt]
\includegraphics[width=0.47\textwidth]{../source/figures_scn1/cm_run03_class-dependent_r0.20_rules51}
\includegraphics[width=0.47\textwidth]{../source/figures_scn1/cm_run04_class-dependent_r0.80_rules4}
\caption{Μήτρες σύγχυσης για όλα τα μοντέλα (\textit{class-independent} και \textit{class-dependent}, $r_a{=}0.20,0.80$).}
\label{fig:conf_mats}
\end{figure}
\paragraph{Μετρικές PA/UA.}
Το σχήμα~\ref{fig:pa_ua_all} απεικονίζει την ακρίβεια παραγωγού (PA) και χρήστη (UA) ανά κλάση για κάθε μοντέλο.
Η ανισορροπία των δεδομένων οδηγεί σε χαμηλότερες τιμές PA/UA για τη μειοψηφούσα κλάση 2, ωστόσο η συνολική τάση παραμένει σταθερή μεταξύ των modes.
\begin{figure}[H]
\centering
\includegraphics[width=0.47\textwidth]{../source/figures_scn1/pa_ua_run01_class-independent_r0.20_rules32}
\includegraphics[width=0.47\textwidth]{../source/figures_scn1/pa_ua_run02_class-independent_r0.80_rules3}\\[4pt]
\includegraphics[width=0.47\textwidth]{../source/figures_scn1/pa_ua_run03_class-dependent_r0.20_rules51}
\includegraphics[width=0.47\textwidth]{../source/figures_scn1/pa_ua_run04_class-dependent_r0.80_rules4}
\caption{PA (recall) και UA (precision) ανά κλάση για όλα τα μοντέλα.}
\label{fig:pa_ua_all}
\end{figure}
\paragraph{Καμπύλες εκπαίδευσης.}
Οι καμπύλες εκπαίδευσης της Εικόνας~\ref{fig:learning_curves_all} δείχνουν τη σύγκλιση του ANFIS για κάθε περίπτωση.
Για μικρό $r_a$, το training error μηδενίζεται γρήγορα (υπερεκπαίδευση), ενώ για μεγάλο $r_a$ η εκπαίδευση είναι πιο ομαλή και το validation error σταθεροποιείται χαμηλότερα, δείχνοντας καλύτερη γενίκευση.
\begin{figure}[H]
\centering
\includegraphics[width=0.47\textwidth]{../source/figures_scn1/learning_run01_class-independent_r0.20_rules32}
\includegraphics[width=0.47\textwidth]{../source/figures_scn1/learning_run02_class-independent_r0.80_rules3}\\[4pt]
\includegraphics[width=0.47\textwidth]{../source/figures_scn1/learning_run03_class-dependent_r0.20_rules51}
\includegraphics[width=0.47\textwidth]{../source/figures_scn1/learning_run04_class-dependent_r0.80_rules4}
\caption{Καμπύλες εκπαίδευσης (training / validation error vs epoch) για όλα τα μοντέλα.}
\label{fig:learning_curves_all}
\end{figure}
\paragraph{Συναρτήσεις συμμετοχής.}
Τα σχήματα~\ref{fig:mfs_indep} και \ref{fig:mfs_dep} απεικονίζει τις MFs πριν και μετά την εκπαίδευση για όλα τα μοντέλα.
Για μικρό $r_a$, παρατηρείται υπερβολική επικάλυψη και πλήθος κανόνων· για μεγάλο $r_a$ λιγότερες MFs και πιο ομαλές μεταβάσεις.
Η μεταβολή των MFs επιβεβαιώνει τη σωστή προσαρμογή των παραμέτρων στο σύνολο εκπαίδευσης.
\begin{figure}[H]
\centering
\includegraphics[width=0.85\textwidth]{../source/figures_scn1/mfs_run01_class-independent_r0.20_rules32}\\[4pt]
\includegraphics[width=0.85\textwidth]{../source/figures_scn1/mfs_run02_class-independent_r0.80_rules3}
\caption{Συναρτήσεις συμμετοχής (before/after) για τα class-independent μοντέλα .}
\label{fig:mfs_indep}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[width=0.85\textwidth]{../source/figures_scn1/mfs_run03_class-dependent_r0.20_rules51}\\[4pt]
\includegraphics[width=0.85\textwidth]{../source/figures_scn1/mfs_run04_class-dependent_r0.80_rules4}
\caption{Συναρτήσεις συμμετοχής (before/after) για τα class-dependent μοντέλα.}
\label{fig:mfs_dep}
\end{figure}
\paragraph{Συνολικές μετρικές και συσχετίσεις.}
Το σχήμα~\ref{fig:summary_metrics} παρουσιάζει την OA και $\kappa$ για κάθε μοντέλο, ενώ το σχήμα~\ref{fig:rules_accuracy} δείχνει τη σχέση αριθμού κανόνων -- ακρίβειας.
Παρατηρείται ότι η μέγιστη OA ($\approx73\%$) και ο υψηλότερος $\kappa$ προκύπτουν στα μοντέλα με $r_a{=}0.80$ (34 κανόνες), επιβεβαιώνοντας ότι η απλούστευση δεν μειώνει την απόδοση.
\begin{figure}[H]
\centering
\includegraphics[width=0.48\textwidth]{../source/figures_scn1/overall_accuracy_across_models}
\includegraphics[width=0.48\textwidth]{../source/figures_scn1/kappa_across_models}
\caption{Συνολική OA και συντελεστής $\kappa$ για όλα τα μοντέλα.}
\label{fig:summary_metrics}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[width=0.65\textwidth]{../source/figures_scn1/rules_vs_accuracy}
\caption{Σχέση αριθμού κανόνων και Overall Accuracy.}
\label{fig:rules_accuracy}
\end{figure}
\subsection{Συμπεράσματα}
Συνοψίζοντας:
\begin{itemize}
\item Οι TSK μοντελοποιήσεις μέσω SC παρείχαν σταθερή ταξινόμηση ακόμη και σε ανισόρροπα δεδομένα.
\item Η αύξηση του αριθμού κανόνων δεν βελτίωσε σημαντικά την απόδοση αλλά μείωσε την ερμηνευσιμότητα.
\item Η class-dependent εκδοχή δεν υπερείχε σημαντικά, υποδεικνύοντας μικρή διακριτότητα των κλάσεων.
\item Οι καμπύλες εκμάθησης και οι MFs δείχνουν ομαλή εκπαίδευση χωρίς αστάθειες.
\item Το \textbf{class-independent μοντέλο με $r_a{=}0.80$} παρουσιάζει την καλύτερη ισορροπία μεταξύ ακρίβειας, απλότητας και γενίκευσης.
\end{itemize}
% ===================================================================== % =====================================================================
\subsection{Επίλογος} \subsection{Επίλογος}

View File

@ -0,0 +1,50 @@
function results = evaluate_classification(yTrue, yPred, classLabels)
% EVALUATE_CLASSIFICATION Compute OA, PA, UA, Kappa and confusion matrix
%
% results = evaluate_classification(yTrue, yPred, classLabels)
%
% yTrue, yPred : true & predicted class labels
% classLabels : vector with all class IDs (optional)
%
% results struct:
% .confMat
% .OA
% .PA
% .UA
% .Kappa
if nargin < 3
classLabels = unique([yTrue; yPred]);
end
% Ensure column vectors
yTrue = yTrue(:);
yPred = yPred(:);
% Build confusion matrix
confMat = confusionmat(yTrue, yPred, 'Order', classLabels);
% Overall Accuracy
OA = trace(confMat) / sum(confMat(:));
% Producer's & User's Accuracy (per class)
PA = diag(confMat) ./ sum(confMat,2); % Recall per class
UA = diag(confMat) ./ sum(confMat,1)'; % Precision per class
% Cohen's Kappa
n = sum(confMat(:));
po = OA;
pe = sum(sum(confMat,1) .* sum(confMat,2)) / n^2;
Kappa = (po - pe) / (1 - pe);
% Pack results
results.confMat = confMat;
results.OA = OA;
results.PA = PA;
results.UA = UA;
results.Kappa = Kappa;
% Optional display
fprintf('Overall Accuracy: %.2f%%\n', 100*OA);
fprintf('Cohen''s Kappa : %.3f\n', Kappa);
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -0,0 +1,5 @@
Run,Mode,Radius,Rules,OA,Kappa
1,class-independent,0.2,32,0.688524590163934,0.412569690826153
2,class-independent,0.8,3,0.737704918032787,0.616502946954813
3,class-dependent,0.2,51,0.60655737704918,0.296492071119654
4,class-dependent,0.8,4,0.721311475409836,0.604349484929416
1 Run Mode Radius Rules OA Kappa
2 1 class-independent 0.2 32 0.688524590163934 0.412569690826153
3 2 class-independent 0.8 3 0.737704918032787 0.616502946954813
4 3 class-dependent 0.2 51 0.60655737704918 0.296492071119654
5 4 class-dependent 0.8 4 0.721311475409836 0.604349484929416

View File

@ -0,0 +1,165 @@
function plot_results1(results, classLabels, cfg)
% PLOT_RESULTS1 Scenario 1 plotting suite (Classification)
% Generates and saves:
% (A) Confusion matrix per model
% (B) PA/UA bars per model
% (C) Membership functions BEFORE/AFTER training per model
% (D) Learning curves (train/val error) per model
% (E) Predictions vs Truth (test set) per model
% (F) OA and Kappa across models (bars)
% (G) Rules vs Accuracy scatter
%
% All PNGs saved under cfg.outDir.
outDir = cfg.outDir;
if ~exist(outDir,'dir'), mkdir(outDir); end
nRuns = numel(results);
OA = zeros(nRuns,1);
Kap = zeros(nRuns,1);
nRules = zeros(nRuns,1);
modes = strings(nRuns,1);
radii = zeros(nRuns,1);
for i = 1:nRuns
OA(i) = results(i).metrics.OA;
Kap(i) = results(i).metrics.Kappa;
nRules(i) = results(i).nRules;
modes(i) = string(results(i).mode);
radii(i) = results(i).radius;
end
% -------- 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);
% (A) Confusion matrix
fig = figure('Color','w');
confusionchart(results(i).metrics.confMat, string(classLabels), ...
'Title', sprintf('Confusion %s (r=%.2f, rules=%d)', ...
results(i).mode, results(i).radius, results(i).nRules));
exportgraphics(fig, fullfile(outDir, ['cm_' tag '.png']), 'Resolution', 200);
close(fig);
% (B) PA / UA bars
fig = figure('Color','w');
t = tiledlayout(2,1,'TileSpacing','compact','Padding','compact');
nexttile;
bar(results(i).metrics.PA); ylim([0 1]);
xticks(1:numel(classLabels)); xticklabels(string(classLabels));
ylabel('PA (Recall)');
title(sprintf('Producer''s Accuracy %s (r=%.2f)', results(i).mode, results(i).radius));
nexttile;
bar(results(i).metrics.UA); ylim([0 1]);
xticks(1:numel(classLabels)); xticklabels(string(classLabels));
ylabel('UA (Precision)');
title(sprintf('User''s Accuracy %s (r=%.2f)', results(i).mode, results(i).radius));
exportgraphics(fig, fullfile(outDir, ['pa_ua_' tag '.png']), 'Resolution', 200);
close(fig);
% (C) Membership functions BEFORE/AFTER
% Layout: 2 rows (Before/After) x D columns (inputs)
try
plot_mfs_before_after(results(i).initFis, results(i).fis, ...
sprintf('%s (r=%.2f, %s)', results(i).mode, results(i).radius, tag), ...
fullfile(outDir, ['mfs_' tag '.png']));
catch ME
warning('MF plot failed for %s: %s', tag, ME.message);
end
% (D) Learning curves (ANFIS)
trErr = results(i).trError; % may be vector
ckErr = results(i).ckError; % may be vector
if ~isempty(trErr)
fig = figure('Color','w');
plot(1:numel(trErr), trErr, 'LineWidth', 1.2); hold on;
if ~isempty(ckErr)
plot(1:numel(ckErr), ckErr, '--', '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 %s (r=%.2f, rules=%d)', ...
results(i).mode, results(i).radius, results(i).nRules));
exportgraphics(fig, fullfile(outDir, ['learning_' tag '.png']), 'Resolution', 200);
close(fig);
end
% (E) Predictions vs Truth (test set) like report example
if isfield(results(i),'yhat') && isfield(results(i),'ytrue')
yhat = results(i).yhat(:);
ytrue = results(i).ytrue(:);
fig = figure('Color','w');
plot(ytrue, 'LineWidth', 1.0); hold on;
plot(yhat, '--', 'LineWidth', 1.0);
xlabel('Test sample index'); ylabel('Class label');
title(sprintf('Truth vs Prediction %s (r=%.2f)', results(i).mode, results(i).radius));
legend('Truth','Prediction','Location','best'); grid on;
exportgraphics(fig, fullfile(outDir, ['pred_vs_truth_' tag '.png']), 'Resolution', 200);
close(fig);
end
end
% -------- Across-model summaries --------
[~, idxSort] = sortrows([double(modes=='class-independent'), radii], [1 2]);
OA_s = OA(idxSort);
Kap_s = Kap(idxSort);
modes_s = modes(idxSort);
radii_s = radii(idxSort);
labels = arrayfun(@(j) sprintf('%s\nr=%.2f', modes_s(j), radii_s(j)), 1:nRuns, 'uni', 0);
fig = figure('Color','w');
bar(OA_s*100); xticks(1:nRuns); xticklabels(labels); xtickangle(30);
ylabel('Overall Accuracy (%)'); title('Overall Accuracy across models'); grid on;
exportgraphics(fig, fullfile(outDir, 'overall_accuracy_across_models.png'), 'Resolution', 200);
close(fig);
fig = figure('Color','w');
bar(Kap_s); xticks(1:nRuns); xticklabels(labels); xtickangle(30);
ylabel('Cohen''s \kappa'); title('Kappa across models'); grid on;
exportgraphics(fig, fullfile(outDir, 'kappa_across_models.png'), 'Resolution', 200);
close(fig);
fig = figure('Color','w');
gscatter(nRules, OA*100, modes, [], [], 8);
xlabel('#Rules'); ylabel('OA (%)'); title('Rules vs Accuracy'); grid on; legend('Location','best');
exportgraphics(fig, fullfile(outDir, 'rules_vs_accuracy.png'), 'Resolution', 200);
close(fig);
% CSV summary
T = table((1:nRuns)', modes, radii, nRules, OA, Kap, ...
'VariableNames', {'Run','Mode','Radius','Rules','OA','Kappa'});
writetable(T, fullfile(outDir, 'summary_scn1.csv'));
end
% ------------------ helpers ------------------
function plot_mfs_before_after(fisBefore, fisAfter, suptitleStr, outPng)
% Plot input MFs before/after in a 2xD tiled layout.
D = numel(fisAfter.Inputs); % assume same D
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
% fallback: skip before if not available
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(['MFs ' suptitleStr]);
exportgraphics(fig, outPng, 'Resolution', 200);
close(fig);
end

View File

@ -0,0 +1,17 @@
function [Xn, mu, sigma] = preprocess_data(X, mu, sigma)
% PREPROCESS Normalize feature matrix using z-score scaling
%
% [Xn, mu, sigma] = preprocess(X)
% [Xn, mu, sigma] = preprocess(X, mu, sigma)
%
% Applies feature-wise standardization (zero mean, unit variance)
% to the predictors only labels Y remain unchanged.
if nargin < 2 || isempty(mu)
mu = mean(X,1);
sigma = std(X,[],1);
end
sigma(sigma==0) = 1; % avoid division by zero
Xn = (X - mu) ./ sigma;
end

Binary file not shown.

222
Work 4/source/scenario1.m Normal file
View File

@ -0,0 +1,222 @@
% scenario1.m Assignment 4 (Classification), Scenario 1 (Haberman)
% TSK classification with Subtractive Clustering (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}
close all; clear; clc;
% ============================ CONFIGURATION ================================
cfg = struct();
rng(42, 'twister'); % reproducibility
% Data handling
cfg.split = [0.6 0.2 0.2]; % train / val / test (stratified in split_data)
cfg.standardize = true; % z-score features
% SC radii sweep
cfg.radii = [0.20 0.80];
% ANFIS options
cfg.maxEpochs = 100;
cfg.errorGoal = 0;
cfg.initialStep = 0.01;
cfg.stepDecrease = 0.9;
cfg.stepIncrease = 1.1;
cfg.displayANFIS = 0; % quiet
% Modes
cfg.modes = {'class-independent','class-dependent'};
% Output
cfg.outDir = 'figures_scn1';
if ~exist(cfg.outDir,'dir'), mkdir(cfg.outDir); end
% =============================== DATA =====================================
dataPath = './Datasets/haberman.data';
assert(isfile(dataPath), 'Dataset not found at: %s', dataPath);
%
raw = load(dataPath);
assert(size(raw,2) == 4, 'Expected 4 columns in haberman.data');
X = raw(:,1:3);
Y = raw(:,4);
Y = Y(:);
classLabels = unique(Y);
minLabel = min(classLabels); maxLabel = max(classLabels);
% =========================== SPLIT & PREPROCESS ===========================
[trainX, valX, testX, trainY, valY, testY] = split_data(X, Y, cfg.split);
if cfg.standardize
[trainX, mu, sigma] = preprocess_data(trainX);
valX = preprocess_data(valX, mu, sigma);
testX = preprocess_data(testX, mu, sigma);
else
mu = []; sigma = [];
end
% For manual sugfis construction
inRanges = [min(trainX,[],1); max(trainX,[],1)];
% ============================== TRAINING ==================================
results = []; runId = 0;
for m = 1:numel(cfg.modes)
modeName = cfg.modes{m};
for r = 1:numel(cfg.radii)
radius = cfg.radii(r);
runId = runId + 1;
fprintf('\n=== Run %d: mode=%s, radius=%.2f ===\n', runId, modeName, radius);
% ----- 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)
initFis = build_classdep_fis(trainX, trainY, classLabels, radius, inRanges);
otherwise
error('Unknown mode: %s', modeName);
end
% ----- ANFIS training -----
trData = [trainX double(trainY)];
ckData = [valX double(valY)];
anfisOpts = [cfg.maxEpochs cfg.errorGoal cfg.initialStep cfg.stepDecrease cfg.stepIncrease];
if cfg.displayANFIS
[fisTrained, trError, ~, ~, ckError] = anfis(trData, initFis, anfisOpts, [], ckData);
else
[fisTrained, trError, ~, ~, ckError] = anfis(trData, initFis, anfisOpts, [0 0 0 0], ckData);
end
% ----- Evaluate on test set -----
yhat_cont = evalfis(testX, fisTrained);
yhat = round(yhat_cont);
% clip into valid label range (important for small rulebases)
yhat(yhat < minLabel) = minLabel;
yhat(yhat > maxLabel) = maxLabel;
% Metrics (note: our evaluate expects (yTrue, yPred))
R = evaluate_classification(testY, yhat, classLabels);
% Collect
res = struct();
res.runId = runId;
res.mode = modeName;
res.radius = radius;
res.fis = fisTrained;
res.nRules = numel(fisTrained.rule);
res.metrics = R;
res.trError = trError;
res.ckError = ckError;
res.mu = mu;
res.sigma = sigma;
res.initFis = initFis;
res.yhat = yhat;
res.ytrue = testY;
results = [results; res];
fprintf('Rules: %d | OA: %.2f%% | Kappa: %.3f\n', res.nRules, 100*R.OA, R.Kappa);
end
end
% ============================== PLOTTING ==================================
plot_results1(results, classLabels, cfg);
% =============================== 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 =============================
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.
D = size(X,2);
fis = sugfis('Name','TSK_ClassDependent');
% Inputs with ranges from training data
for d = 1:D
fis = addInput(fis, [inRanges(1,d) inRanges(2,d)], 'Name', sprintf('x%d', d));
end
% Single scalar output y (range just spans label space)
outRange = [min(classLabels) max(classLabels)];
fis = addOutput(fis, outRange, 'Name', 'y');
ruleList = [];
% Build rules class-by-class
for k = 1:numel(classLabels)
c = classLabels(k);
Xi = X(Y==c, :);
if isempty(Xi), continue; end
% Subtractive clustering on class features
[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 size(sigmas,1) == nCl && size(sigmas,2) == D
S = sigmas;
else
S = repmat(0.5*(inRanges(2,:)-inRanges(1,:)), nCl, 1);
end
% -----------------------------------------
% For each cluster: add one Gaussian MF per input, one constant MF for output,
% and one rule that ties those together (AND=prod).
for i = 1:nCl
antecedentIdx = zeros(1,D);
% 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]
fis = addMF(fis, sprintf('x%d', d), 'gaussmf', params, 'Name', mfName);
antecedentIdx(d) = numel(fis.Inputs(d).MembershipFunctions);
end
% Add ONE output MF (constant) for THIS rule (ANFIS requirement)
outMfName = sprintf('const_c%d_r%d', c, i);
fis = addMF(fis, 'y', 'constant', double(c), 'Name', outMfName);
outIdx = numel(fis.Outputs(1).MembershipFunctions);
% Rule row: [inMFs outMF weight AND=1]
rule = [antecedentIdx, outIdx, 1, 1];
ruleList = [ruleList; rule]; %#ok<AGROW>
end
end
if ~isempty(ruleList)
fis = addRule(fis, ruleList);
end
% Standard TSK operators
fis.AndMethod = 'prod';
fis.OrMethod = 'probor';
fis.ImplicationMethod = 'prod';
fis.AggregationMethod = 'sum';
fis.DefuzzificationMethod = 'wtaver';
end

View File

@ -0,0 +1,39 @@
function [trainX, valX, testX, trainY, valY, testY] = split_data(X, Y, ratios)
% SPLIT Split dataset into train/validation/test sets (stratified)
%
% [trainX, valX, testX, trainY, valY, testY] = split(X, Y, ratios)
%
% ratios : [trainRatio, valRatio, testRatio] (e.g. [0.6 0.2 0.2])
%
% Stratified split ensures class proportions remain consistent.
if nargin < 3
ratios = [0.6 0.2 0.2];
end
assert(abs(sum(ratios) - 1) < 1e-6, 'Ratios must sum to 1.');
n = size(X,1);
classes = unique(Y);
idxTrain = []; idxVal = []; idxTest = [];
for c = classes'
idx = find(Y == c);
idx = idx(randperm(length(idx))); % randomize within class
nTrain = round(ratios(1)*length(idx));
nVal = round(ratios(2)*length(idx));
idxTrain = [idxTrain; idx(1:nTrain)];
idxVal = [idxVal; idx(nTrain+1:nTrain+nVal)];
idxTest = [idxTest; idx(nTrain+nVal+1:end)];
end
% Shuffle within each subset to mix classes
idxTrain = idxTrain(randperm(length(idxTrain)));
idxVal = idxVal(randperm(length(idxVal)));
idxTest = idxTest(randperm(length(idxTest)));
trainX = X(idxTrain,:); trainY = Y(idxTrain);
valX = X(idxVal,:); valY = Y(idxVal);
testX = X(idxTest,:); testY = Y(idxTest);
end