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
|
||||