HW4: Scenario1 source code and report added
| @ -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{Επίλογος} | ||||
|  | ||||
							
								
								
									
										50
									
								
								Work 4/source/evaluate_classification.m
									
									
									
									
									
										Normal 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 | ||||
| After Width: | Height: | Size: 36 KiB | 
| After Width: | Height: | Size: 36 KiB | 
| After Width: | Height: | Size: 35 KiB | 
| After Width: | Height: | Size: 35 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Work 4/source/figures_scn1/kappa_across_models.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 44 KiB | 
| After Width: | Height: | Size: 66 KiB | 
| After Width: | Height: | Size: 82 KiB | 
| After Width: | Height: | Size: 68 KiB | 
| After Width: | Height: | Size: 82 KiB | 
| After Width: | Height: | Size: 411 KiB | 
| After Width: | Height: | Size: 146 KiB | 
| After Width: | Height: | Size: 566 KiB | 
| After Width: | Height: | Size: 174 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Work 4/source/figures_scn1/overall_accuracy_across_models.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 52 KiB | 
| After Width: | Height: | Size: 56 KiB | 
| After Width: | Height: | Size: 56 KiB | 
| After Width: | Height: | Size: 56 KiB | 
| After Width: | Height: | Size: 56 KiB | 
| After Width: | Height: | Size: 134 KiB | 
| After Width: | Height: | Size: 122 KiB | 
| After Width: | Height: | Size: 133 KiB | 
| After Width: | Height: | Size: 117 KiB | 
							
								
								
									
										
											BIN
										
									
								
								Work 4/source/figures_scn1/rules_vs_accuracy.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 46 KiB | 
							
								
								
									
										5
									
								
								Work 4/source/figures_scn1/summary_scn1.csv
									
									
									
									
									
										Normal 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 | ||||
| 
 | 
							
								
								
									
										165
									
								
								Work 4/source/plot_results1.m
									
									
									
									
									
										Normal 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 | ||||
							
								
								
									
										17
									
								
								Work 4/source/preprocess_data.m
									
									
									
									
									
										Normal 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 | ||||
							
								
								
									
										
											BIN
										
									
								
								Work 4/source/results_scn1.mat
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										222
									
								
								Work 4/source/scenario1.m
									
									
									
									
									
										Normal 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 | ||||
| 
 | ||||
							
								
								
									
										39
									
								
								Work 4/source/split_data.m
									
									
									
									
									
										Normal 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 | ||||