# %% Εργαστήριο – Ταξινόμηση Ι
# ### Μηχανική Μάθηση και Εξόρυξη Γνώσης · ΗΜΜΥ ΕΛΜΕΠΑ · 9ο εξάμηνο
#
# Στόχος του εργαστηρίου είναι να γράψετε μόνοι σας τις βασικές εντολές για:
#
# - train / test split
# - δυαδική ταξινόμηση
# - cross-validation
# - confusion matrix
# - precision, recall και F1
# - threshold
# - ROC / AUC
# - multiclass classification
# - kNN
# - Naive Bayes
# - σύγκριση ταξινομητών
#
# Οι βιβλιοθήκες και τα δεδομένα δίνονται έτοιμα.  
# Στα σημεία **Άσκηση** γράφετε εσείς τον κώδικα.

# %% 0. Βιβλιοθήκες
#
# Τρέξτε το κελί χωρίς αλλαγές.

# %%
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split, cross_val_score, cross_val_predict
from sklearn.linear_model import SGDClassifier, LogisticRegression
from sklearn.dummy import DummyClassifier
from sklearn.metrics import (
    confusion_matrix,
    precision_score,
    recall_score,
    f1_score,
    precision_recall_curve,
    roc_curve,
    roc_auc_score
)
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB

np.random.seed(42)

# %% Μέρος Α – Τα δεδομένα
#
# Χρησιμοποιούμε το `digits` του scikit-learn.
#
# Κάθε δείγμα είναι μία εικόνα 8×8 pixels και η ετικέτα είναι ένα ψηφίο από 0 έως 9.

# %%
digits = load_digits()

X = digits.data
y = digits.target

print("X:", X.shape)
print("y:", y.shape)

fig, axs = plt.subplots(1, 10, figsize=(12, 1.6))

for ax, img, label in zip(axs, digits.images, y):
    ax.imshow(img, cmap="gray_r")
    ax.set_title(label)
    ax.axis("off")

plt.show()

# %% Άσκηση Α1 – Train / test split
#
# Χωρίστε τα δεδομένα σε:
#
# - 80% training
# - 20% test
# - `random_state=42`
# - stratified split με βάση το `y`
#
# Χρησιμοποιήστε τη συνάρτηση `train_test_split`.
#
# **Έλεγχος:** πρέπει να προκύψουν 1437 training samples και 360 test samples.

# %%
# Γράψτε εδώ τον κώδικά σας

# %% Άσκηση Α2 – Δυαδικό πρόβλημα «είναι 5;»
#
# Δημιουργήστε δύο νέες μεταβλητές:
#
# - `y_train_5`
# - `y_test_5`
#
# Κάθε τιμή πρέπει να είναι `True` όταν το αντίστοιχο ψηφίο είναι 5 και `False` διαφορετικά.
#
# Στη συνέχεια εμφανίστε το ποσοστό των `5` στο training set.
#
# **Αναμενόμενο:** περίπου 10%.

# %%
# Γράψτε εδώ τον κώδικά σας

# %% Μέρος Β – Accuracy και Cross-Validation
#
# Θα συγκρίνουμε:
#
# 1. έναν `SGDClassifier`
# 2. έναν `DummyClassifier` που προβλέπει πάντα την πιο συχνή κλάση

# %% Άσκηση Β1 – Δημιουργία classifiers
#
# Δημιουργήστε:
#
# - `sgd_clf` με `random_state=42`
# - `dummy` με `strategy="most_frequent"`
#
# Μην κάνετε ακόμη `fit`.

# %%
# Γράψτε εδώ τον κώδικά σας

# %% Άσκηση Β2 – 3-fold Cross-Validation
#
# Υπολογίστε την accuracy με `cross_val_score` για τους δύο classifiers.
#
# Χρησιμοποιήστε:
#
# - training data
# - το δυαδικό target `y_train_5`
# - `cv=3`
# - `scoring="accuracy"`
#
# Εμφανίστε τα τρία scores για κάθε classifier.
#
# **Ερώτηση:** γιατί ο Dummy classifier μπορεί να έχει accuracy κοντά στο 90%;

# %%
# Γράψτε εδώ τον κώδικά σας

# %% Μέρος Γ – Confusion Matrix, Precision, Recall, F1
#
# Δεν θα χρησιμοποιήσουμε ακόμη το test set.
#
# Θα πάρουμε out-of-fold predictions από το training set.

# %% Άσκηση Γ1 – Προβλέψεις με Cross-Validation
#
# Χρησιμοποιήστε `cross_val_predict` με:
#
# - `sgd_clf`
# - `X_train`
# - `y_train_5`
# - `cv=3`
#
# Αποθηκεύστε το αποτέλεσμα στη μεταβλητή:
#
# `y_train_pred`

# %%
# Γράψτε εδώ τον κώδικά σας

# %% Άσκηση Γ2 – Confusion Matrix
#
# Υπολογίστε τον πίνακα σύγχυσης με `confusion_matrix`.
#
# Αποθηκεύστε τον στη μεταβλητή `cm` και εμφανίστε τον.
#
# Στη συνέχεια εκτελέστε:
#
# 
# TN, FP, FN, TP = cm.ravel()
# print("TN:", TN, "FP:", FP, "FN:", FN, "TP:", TP)
# 

# %%
# Γράψτε εδώ τον κώδικά σας

# %% Άσκηση Γ3 – Precision, Recall και F1
#
# Υπολογίστε για το `y_train_pred`:
#
# - precision
# - recall
# - F1
#
# Χρησιμοποιήστε τις συναρτήσεις:
#
# - `precision_score`
# - `recall_score`
# - `f1_score`
#
# Εμφανίστε τα τρία αποτελέσματα.

# %%
# Γράψτε εδώ τον κώδικά σας

# %% Μέρος Δ – Decision score και κατώφλι
#
# Ο classifier δεν παράγει μόνο `True/False`.  
# Μπορούμε να πάρουμε ένα **score απόφασης** και να επιλέξουμε διαφορετικό threshold.

# %% Άσκηση Δ1 – Decision scores
#
# Χρησιμοποιήστε `cross_val_predict` ξανά, αλλά αυτή τη φορά προσθέστε:
#
# `method="decision_function"`
#
# Αποθηκεύστε τα scores στο:
#
# `y_scores`

# %%
# Γράψτε εδώ τον κώδικά σας

# %% Άσκηση Δ2 – Precision–Recall curve
#
# Χρησιμοποιήστε:
#
# `precision_recall_curve`
#
# για να πάρετε:
#
# - `precisions`
# - `recalls`
# - `thresholds`
#
# Στη συνέχεια εκτελέστε το παρακάτω κελί για το διάγραμμα.

# %%
# Γράψτε εδώ τον κώδικά σας

# %%
plt.plot(thresholds, precisions[:-1], label="Precision")
plt.plot(thresholds, recalls[:-1], label="Recall")

plt.xlabel("Threshold")
plt.legend()
plt.show()

# %% Άσκηση Δ3 – Threshold για precision ≥ 90%
#
# Βρείτε το **πρώτο** σημείο στο οποίο:
#
# `precision >= 0.90`
#
# και βρείτε το αντίστοιχο threshold.
#
# Στη συνέχεια δημιουργήστε νέες προβλέψεις με:
#
# `y_scores >= threshold`
#
# και υπολογίστε ξανά precision και recall.
#
# **Ερώτηση:** τι συνέβη στο recall;

# %%
# Γράψτε εδώ τον κώδικά σας

# %% Μέρος Ε – ROC και AUC
#
# Χρησιμοποιούμε τα ίδια `y_scores`.

# %% Άσκηση Ε1 – ROC curve
#
# Χρησιμοποιήστε `roc_curve` για να πάρετε:
#
# - `fpr`
# - `tpr`
# - `thresholds_roc`
#
# Στη συνέχεια υπολογίστε το AUC με `roc_auc_score`.

# %%
# Γράψτε εδώ τον κώδικά σας

# %% Άσκηση Ε2 – Διάγραμμα ROC
#
# Χρησιμοποιήστε τα `fpr` και `tpr` που υπολογίσατε.

# %%
plt.plot(fpr, tpr)
plt.plot([0, 1], [0, 1], "--")

plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.show()

# %% Μέρος ΣΤ – Multiclass Classification
#
# Επιστρέφουμε τώρα στο αρχικό πρόβλημα των 10 ψηφίων.

# %% Άσκηση ΣΤ1 – Multiclass SGD
#
# Δημιουργήστε έναν νέο:
#
# `SGDClassifier(random_state=42)`
#
# και εκπαιδεύστε τον με:
#
# - `X_train`
# - `y_train`
#
# Στη συνέχεια προβλέψτε την κλάση του `X_test[0]`.
#
# Εμφανίστε:
#
# - την πραγματική κλάση
# - την πρόβλεψη

# %%
# Γράψτε εδώ τον κώδικά σας

# %% Άσκηση ΣΤ2 – Multiclass Cross-Validation
#
# Υπολογίστε την accuracy του multiclass SGD με 5-fold cross-validation.
#
# Χρησιμοποιήστε `cross_val_score`.

# %%
# Γράψτε εδώ τον κώδικά σας

# %% Μέρος Ζ – k-Nearest Neighbors

# %% Άσκηση Ζ1 – kNN
#
# Δημιουργήστε έναν `KNeighborsClassifier` με:
#
# `n_neighbors=3`
#
# Κάντε 5-fold cross-validation στο `X_train`, `y_train`.
#
# Εμφανίστε:
#
# - τα 5 scores
# - τον μέσο όρο

# %%
# Γράψτε εδώ τον κώδικά σας

# %% Άσκηση Ζ2 – Δοκιμή διαφορετικών k
#
# Για κάθε τιμή:
#
# `k = 1, 3, 5, 7, 9`
#
# υπολογίστε τη μέση 5-fold CV accuracy.
#
# Μπορείτε να χρησιμοποιήσετε ένα απλό `for`.
#
# Ποιο `k` δίνει την καλύτερη μέση accuracy;

# %%
# Γράψτε εδώ τον κώδικά σας

# %% Μέρος Η – Naive Bayes
#
# Θα χρησιμοποιήσουμε `GaussianNB`, επειδή τα χαρακτηριστικά των εικόνων είναι αριθμητικά.

# %% Άσκηση Η1 – Gaussian Naive Bayes
#
# Δημιουργήστε έναν `GaussianNB`.
#
# Υπολογίστε τη μέση 5-fold CV accuracy στο `X_train`, `y_train`.
#
# Συγκρίνετέ την με τον kNN.

# %%
# Γράψτε εδώ τον κώδικά σας

# %% Μέρος Θ – Σύγκριση τριών classifiers
#
# Θα συγκρίνουμε:
#
# - Logistic Regression
# - kNN
# - Gaussian Naive Bayes
#
# Το παρακάτω λεξικό είναι έτοιμο.

# %%
models = {
    "Logistic Regression": make_pipeline(
        StandardScaler(),
        LogisticRegression(max_iter=2000)
    ),
    "kNN": make_pipeline(
        StandardScaler(),
        KNeighborsClassifier(n_neighbors=3)
    ),
    "Gaussian NB": GaussianNB()
}

# %% Άσκηση Θ1 – 5-fold Cross-Validation για όλα τα μοντέλα
#
# Για κάθε `(name, model)` του λεξικού `models`:
#
# 1. υπολογίστε 5-fold CV accuracy
# 2. υπολογίστε τον μέσο όρο
# 3. εμφανίστε το όνομα και τη μέση accuracy
#
# Χρησιμοποιήστε ένα `for`.

# %%
# Γράψτε εδώ τον κώδικά σας

# %% Μέρος Ι – Τελική αξιολόγηση στο test set
#
# Μέχρι τώρα χρησιμοποιούσαμε κυρίως cross-validation στο training set.
#
# Τώρα επιλέξτε **ένα** από τα μοντέλα του προηγούμενου μέρους.

# %% Άσκηση Ι1 – Fit και test accuracy
#
# 1. Επιλέξτε το μοντέλο που είχε την καλύτερη CV accuracy.
# 2. Κάντε `fit` σε **όλο το `X_train`, `y_train`**.
# 3. Υπολογίστε την accuracy στο `X_test`, `y_test` με τη μέθοδο `score`.
#
# Αυτή είναι η πρώτη και μοναδική χρήση του test set για τελική αξιολόγηση.

# %%
# Γράψτε εδώ τον κώδικά σας

# %% Προαιρετικές ασκήσεις
#
# Αν υπάρχει χρόνος:
#
# ### 1. Ανιχνευτής «8»
# Επαναλάβετε το δυαδικό πρόβλημα για το ψηφίο 8 αντί για το 5.
#
# ### 2. Threshold για recall ≥ 95%
# Βρείτε threshold που δίνει recall τουλάχιστον 95%.  
# Υπολογίστε το precision σε αυτό το threshold.
#
# ### 3. kNN
# Δοκιμάστε `weights="distance"` και συγκρίνετε με την προεπιλογή.
#
# ### 4. Τελική ερώτηση
# Ποιο από τα accuracy, precision, recall και F1 θα χρησιμοποιούσατε για ανίχνευση μιας πολύ σπάνιας νόσου και γιατί;

