diff --git a/Work 4/report/Work4_report.pdf b/Work 4/report/Work4_report.pdf index 28571e5..0da0e09 100644 Binary files a/Work 4/report/Work4_report.pdf and b/Work 4/report/Work4_report.pdf differ diff --git a/Work 4/report/Work4_report.tex b/Work 4/report/Work4_report.tex index b9ffa27..50cda9d 100644 --- a/Work 4/report/Work4_report.tex +++ b/Work 4/report/Work4_report.tex @@ -71,8 +71,16 @@ % ===================================================================== \section{Εισαγωγή} -Στην παρούσα εργασία +Η παρούσα εργασία εστιάζει στην επίλυση προβλημάτων \textbf{ταξινόμησης} μέσω ασαφών συστημάτων τύπου \textbf{Takagi–Sugeno–Kang (TSK)}. +Η ταξινόμηση αποτελεί μια από τις βασικότερες εφαρμογές της υπολογιστικής νοημοσύνης, καθώς συνδυάζει στοιχεία λογικής, στατιστικής και μηχανικής μάθησης για τη λήψη αποφάσεων με αβεβαιότητα. +Τα μοντέλα TSK προσφέρουν ένα ευέλικτο πλαίσιο, στο οποίο η γνώση αναπαρίσταται με τη μορφή κανόνων τύπου \emph{IF–THEN}, ενώ η εκπαίδευση των παραμέτρων μπορεί να γίνει με υβριδικούς αλγορίθμους (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{Παραδοτέα} @@ -84,9 +92,167 @@ \end{itemize} + % ===================================================================== \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$ και Rules–Accuracy. + +Το επόμενο τμήμα παρουσιάζει το πρώτο σενάριο και τα αποτελέσματά του, εφαρμόζοντας τη μεθοδολογία που περιγράφηκε παραπάνω. + + +\section{Σενάριο 1 — Εφαρμογή σε απλό Dataset} + +Το πρώτο σενάριο αφορά την επίλυση ενός προβλήματος \textbf{δυαδικής ταξινόμησης} χρησιμοποιώντας μοντέλα TSK με σταθερές (singleton) εξόδους. +Ως dataset χρησιμοποιήθηκε το \textit{Haberman’s 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{Producer’s Accuracy (PA)} και \textbf{User’s 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$ (3–4 κανόνες), επιβεβαιώνοντας ότι η απλούστευση δεν μειώνει την απόδοση. + +\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{Επίλογος} diff --git a/Work 4/source/evaluate_classification.m b/Work 4/source/evaluate_classification.m new file mode 100644 index 0000000..3493ceb --- /dev/null +++ b/Work 4/source/evaluate_classification.m @@ -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 diff --git a/Work 4/source/figures_scn1/cm_run01_class-independent_r0.20_rules32.png b/Work 4/source/figures_scn1/cm_run01_class-independent_r0.20_rules32.png new file mode 100644 index 0000000..c771df1 Binary files /dev/null and b/Work 4/source/figures_scn1/cm_run01_class-independent_r0.20_rules32.png differ diff --git a/Work 4/source/figures_scn1/cm_run02_class-independent_r0.80_rules3.png b/Work 4/source/figures_scn1/cm_run02_class-independent_r0.80_rules3.png new file mode 100644 index 0000000..114afcd Binary files /dev/null and b/Work 4/source/figures_scn1/cm_run02_class-independent_r0.80_rules3.png differ diff --git a/Work 4/source/figures_scn1/cm_run03_class-dependent_r0.20_rules51.png b/Work 4/source/figures_scn1/cm_run03_class-dependent_r0.20_rules51.png new file mode 100644 index 0000000..60c5f0f Binary files /dev/null and b/Work 4/source/figures_scn1/cm_run03_class-dependent_r0.20_rules51.png differ diff --git a/Work 4/source/figures_scn1/cm_run04_class-dependent_r0.80_rules4.png b/Work 4/source/figures_scn1/cm_run04_class-dependent_r0.80_rules4.png new file mode 100644 index 0000000..205381d Binary files /dev/null and b/Work 4/source/figures_scn1/cm_run04_class-dependent_r0.80_rules4.png differ diff --git a/Work 4/source/figures_scn1/kappa_across_models.png b/Work 4/source/figures_scn1/kappa_across_models.png new file mode 100644 index 0000000..b51d211 Binary files /dev/null and b/Work 4/source/figures_scn1/kappa_across_models.png differ diff --git a/Work 4/source/figures_scn1/learning_run01_class-independent_r0.20_rules32.png b/Work 4/source/figures_scn1/learning_run01_class-independent_r0.20_rules32.png new file mode 100644 index 0000000..b1038ae Binary files /dev/null and b/Work 4/source/figures_scn1/learning_run01_class-independent_r0.20_rules32.png differ diff --git a/Work 4/source/figures_scn1/learning_run02_class-independent_r0.80_rules3.png b/Work 4/source/figures_scn1/learning_run02_class-independent_r0.80_rules3.png new file mode 100644 index 0000000..cae37df Binary files /dev/null and b/Work 4/source/figures_scn1/learning_run02_class-independent_r0.80_rules3.png differ diff --git a/Work 4/source/figures_scn1/learning_run03_class-dependent_r0.20_rules51.png b/Work 4/source/figures_scn1/learning_run03_class-dependent_r0.20_rules51.png new file mode 100644 index 0000000..bdb5afa Binary files /dev/null and b/Work 4/source/figures_scn1/learning_run03_class-dependent_r0.20_rules51.png differ diff --git a/Work 4/source/figures_scn1/learning_run04_class-dependent_r0.80_rules4.png b/Work 4/source/figures_scn1/learning_run04_class-dependent_r0.80_rules4.png new file mode 100644 index 0000000..76cd0a3 Binary files /dev/null and b/Work 4/source/figures_scn1/learning_run04_class-dependent_r0.80_rules4.png differ diff --git a/Work 4/source/figures_scn1/mfs_run01_class-independent_r0.20_rules32.png b/Work 4/source/figures_scn1/mfs_run01_class-independent_r0.20_rules32.png new file mode 100644 index 0000000..545e6bc Binary files /dev/null and b/Work 4/source/figures_scn1/mfs_run01_class-independent_r0.20_rules32.png differ diff --git a/Work 4/source/figures_scn1/mfs_run02_class-independent_r0.80_rules3.png b/Work 4/source/figures_scn1/mfs_run02_class-independent_r0.80_rules3.png new file mode 100644 index 0000000..4b3334f Binary files /dev/null and b/Work 4/source/figures_scn1/mfs_run02_class-independent_r0.80_rules3.png differ diff --git a/Work 4/source/figures_scn1/mfs_run03_class-dependent_r0.20_rules51.png b/Work 4/source/figures_scn1/mfs_run03_class-dependent_r0.20_rules51.png new file mode 100644 index 0000000..5c206e4 Binary files /dev/null and b/Work 4/source/figures_scn1/mfs_run03_class-dependent_r0.20_rules51.png differ diff --git a/Work 4/source/figures_scn1/mfs_run04_class-dependent_r0.80_rules4.png b/Work 4/source/figures_scn1/mfs_run04_class-dependent_r0.80_rules4.png new file mode 100644 index 0000000..eeb839a Binary files /dev/null and b/Work 4/source/figures_scn1/mfs_run04_class-dependent_r0.80_rules4.png differ diff --git a/Work 4/source/figures_scn1/overall_accuracy_across_models.png b/Work 4/source/figures_scn1/overall_accuracy_across_models.png new file mode 100644 index 0000000..654a2e6 Binary files /dev/null and b/Work 4/source/figures_scn1/overall_accuracy_across_models.png differ diff --git a/Work 4/source/figures_scn1/pa_ua_run01_class-independent_r0.20_rules32.png b/Work 4/source/figures_scn1/pa_ua_run01_class-independent_r0.20_rules32.png new file mode 100644 index 0000000..fea33b4 Binary files /dev/null and b/Work 4/source/figures_scn1/pa_ua_run01_class-independent_r0.20_rules32.png differ diff --git a/Work 4/source/figures_scn1/pa_ua_run02_class-independent_r0.80_rules3.png b/Work 4/source/figures_scn1/pa_ua_run02_class-independent_r0.80_rules3.png new file mode 100644 index 0000000..02d24ad Binary files /dev/null and b/Work 4/source/figures_scn1/pa_ua_run02_class-independent_r0.80_rules3.png differ diff --git a/Work 4/source/figures_scn1/pa_ua_run03_class-dependent_r0.20_rules51.png b/Work 4/source/figures_scn1/pa_ua_run03_class-dependent_r0.20_rules51.png new file mode 100644 index 0000000..e00245c Binary files /dev/null and b/Work 4/source/figures_scn1/pa_ua_run03_class-dependent_r0.20_rules51.png differ diff --git a/Work 4/source/figures_scn1/pa_ua_run04_class-dependent_r0.80_rules4.png b/Work 4/source/figures_scn1/pa_ua_run04_class-dependent_r0.80_rules4.png new file mode 100644 index 0000000..16c4bb4 Binary files /dev/null and b/Work 4/source/figures_scn1/pa_ua_run04_class-dependent_r0.80_rules4.png differ diff --git a/Work 4/source/figures_scn1/pred_vs_truth_run01_class-independent_r0.20_rules32.png b/Work 4/source/figures_scn1/pred_vs_truth_run01_class-independent_r0.20_rules32.png new file mode 100644 index 0000000..d5f1662 Binary files /dev/null and b/Work 4/source/figures_scn1/pred_vs_truth_run01_class-independent_r0.20_rules32.png differ diff --git a/Work 4/source/figures_scn1/pred_vs_truth_run02_class-independent_r0.80_rules3.png b/Work 4/source/figures_scn1/pred_vs_truth_run02_class-independent_r0.80_rules3.png new file mode 100644 index 0000000..820679b Binary files /dev/null and b/Work 4/source/figures_scn1/pred_vs_truth_run02_class-independent_r0.80_rules3.png differ diff --git a/Work 4/source/figures_scn1/pred_vs_truth_run03_class-dependent_r0.20_rules51.png b/Work 4/source/figures_scn1/pred_vs_truth_run03_class-dependent_r0.20_rules51.png new file mode 100644 index 0000000..3f38655 Binary files /dev/null and b/Work 4/source/figures_scn1/pred_vs_truth_run03_class-dependent_r0.20_rules51.png differ diff --git a/Work 4/source/figures_scn1/pred_vs_truth_run04_class-dependent_r0.80_rules4.png b/Work 4/source/figures_scn1/pred_vs_truth_run04_class-dependent_r0.80_rules4.png new file mode 100644 index 0000000..b891f9f Binary files /dev/null and b/Work 4/source/figures_scn1/pred_vs_truth_run04_class-dependent_r0.80_rules4.png differ diff --git a/Work 4/source/figures_scn1/rules_vs_accuracy.png b/Work 4/source/figures_scn1/rules_vs_accuracy.png new file mode 100644 index 0000000..0f8e3c9 Binary files /dev/null and b/Work 4/source/figures_scn1/rules_vs_accuracy.png differ diff --git a/Work 4/source/figures_scn1/summary_scn1.csv b/Work 4/source/figures_scn1/summary_scn1.csv new file mode 100644 index 0000000..f3c48e7 --- /dev/null +++ b/Work 4/source/figures_scn1/summary_scn1.csv @@ -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 diff --git a/Work 4/source/plot_results1.m b/Work 4/source/plot_results1.m new file mode 100644 index 0000000..060ffcc --- /dev/null +++ b/Work 4/source/plot_results1.m @@ -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 diff --git a/Work 4/source/preprocess_data.m b/Work 4/source/preprocess_data.m new file mode 100644 index 0000000..5f556cd --- /dev/null +++ b/Work 4/source/preprocess_data.m @@ -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 diff --git a/Work 4/source/results_scn1.mat b/Work 4/source/results_scn1.mat new file mode 100644 index 0000000..aaf78da Binary files /dev/null and b/Work 4/source/results_scn1.mat differ diff --git a/Work 4/source/scenario1.m b/Work 4/source/scenario1.m new file mode 100644 index 0000000..29d1638 --- /dev/null +++ b/Work 4/source/scenario1.m @@ -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 + 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 + diff --git a/Work 4/source/split_data.m b/Work 4/source/split_data.m new file mode 100644 index 0000000..51f1767 --- /dev/null +++ b/Work 4/source/split_data.m @@ -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