from sklearn import datasets
from sklearn.model_selection import train_test_split, KFold
from sklearn.neural_network import MLPClassifier
from sklearn.svm import SVC
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import precision_score, recall_score, plot_confusion_matrix, confusion_matrix, ConfusionMatrixDisplay
import numpy as np
import matplotlib.pyplot as plt
from random import seed, randint
from graphs import bars_plot2, curve_plot
from statistics import mean
import time


np.random.seed(0) # initialise le generateur aletoire avec seed=0
seed(0)

#######################
###  TP3 PROGRAMME  ###
#######################
# Chargement de la base de données MNIST
mnist = datasets.fetch_openml('mnist_784')

# Division de la base de données
x_train = mnist.data[:3500,:] ## training
y_train = mnist.target[:3500]
x_test = mnist.data[3500:5001,:]  ## test
y_test = mnist.target[3500:5001]

# print(x_train.shape,x_test.shape) #

# Mise en conformité des dimensions de xtrain et xtest aux dimensions attendues par le classifeur
x_train = x_train.reshape((-1, 784))
x_test = x_test.reshape((-1, 784))

def kernel_variation():
    kernels = ["linear","poly","rbf","sigmoid"]
    scores = {}
    for kernel in kernels:
        clsvm = SVC(kernel=kernel)
        clsvm.fit(X=x_train, y=y_train)
        scores[kernel] = clsvm.score(X=x_test, y=y_test) * 100
        print("Score avec kernel=%s: %.2f"%(kernel, scores[kernel]))
    bars_plot2(bar_labels=kernels, bar_heights=list(scores.values()), xlabel="Kernels utilisés",
              ylabel="Score", fig_title="Score par kernel utilisé", show=True, percent_mark=True,
               output="tp3Output/scores_for_kernel_variation.png")


def kernel_and_c_variation():
    kernels = ["linear", "poly", "rbf", "sigmoid"]
    scores = {}
    c_values = [0.1 + 0.18 * x for x in range(6)]
    for kernel in kernels:
        scores[kernel] = {}
        for c_value in c_values:
            clsvm = SVC(kernel=kernel, C=c_value)
            clsvm.fit(X=x_train, y=y_train)
            scores[kernel][c_value] = clsvm.score(X=x_test, y=y_test) * 100
            print("Score avec kernel=%s: %.2f" % (kernel, clsvm.score(X=x_test, y=y_test) * 100))
    plt.figure(figsize=(12.8, 9.6))
    plt.plot(c_values, list(scores["linear"].values()), label="linear")
    plt.plot(c_values, list(scores["poly"].values()), label="poly")
    plt.plot(c_values, list(scores["rbf"].values()), label="rbf")
    plt.plot(c_values, list(scores["sigmoid"].values()), label="sigmoid")
    plt.xlabel("Valeurs de C")
    plt.xlim(left=0.1)
    plt.ylabel("Score (précision)")
    plt.title("Courbe d'évolution de la précision en fonction de C")
    plt.legend()
    plt.savefig("tp3Output/scores_all_kernel_with_c_variation.png", dpi="figure")
    plt.show()
    plt.close()

def train_test_error_with_c_variation():
    kernels = ["linear", "poly", "rbf", "sigmoid"]
    scores = {}
    c_values = [0.1 + 0.18 * x for x in range(6)]
    data_types = ["train", "test"]
    for data_type in data_types:
        scores[data_type] = {}
        for kernel in kernels:
            scores[data_type][kernel] = {}
            for c_value in c_values:
                clsvm = SVC(kernel=kernel, C=c_value)
                clsvm.fit(X=x_train, y=y_train)
                if data_type=="train":
                    scores[data_type][kernel][c_value] = (1 - clsvm.score(X=x_train, y=y_train)) * 100
                else:
                    scores[data_type][kernel][c_value] = (1 - clsvm.score(X=x_test, y=y_test)) * 100
                print("Score d'erreur données=%s avec kernel=%s: %.2f" % (data_type, kernel, scores[data_type][kernel][c_value]))
    plt.figure(figsize=(12.8, 9.6))
    plt.plot(c_values, list(scores["train"]["linear"].values()), label="train linear", marker="o")
    plt.plot(c_values, list(scores["train"]["poly"].values()), label="train poly", marker="o")
    plt.plot(c_values, list(scores["train"]["rbf"].values()), label="train rbf", marker="o")
    plt.plot(c_values, list(scores["train"]["sigmoid"].values()), label="train sigmoid", marker="o")
    plt.plot(c_values, list(scores["test"]["linear"].values()), label="test linear", marker="*")
    plt.plot(c_values, list(scores["test"]["poly"].values()), label="test poly", marker="*")
    plt.plot(c_values, list(scores["test"]["rbf"].values()), label="test rbf", marker="*")
    plt.plot(c_values, list(scores["test"]["sigmoid"].values()), label="test sigmoid", marker="*")
    plt.xlabel("Valeurs de C")
    plt.xlim(left=0.1)
    plt.ylabel("Error score")
    plt.title("Courbe d'évolution du pourcentage d'erreur de précision en fonction de C")
    plt.legend()
    plt.savefig("tp3Output/error_scores_train_test_with_kernel_and_c_variation.png", dpi="figure")
    plt.show()
    plt.close()

def generate_confusion_matrix():
    kernels = ["linear", "poly", "rbf", "sigmoid"]
    for kernel in kernels:
        clsvm = SVC(kernel=kernel)
        clsvm.fit(X=x_train, y=y_train)
        plot_confusion_matrix(estimator=clsvm, X=x_test, y_true=y_test, values_format=".1f")
        plt.show()

def time_precision_recall_error():
    kernels = ["linear", "poly", "rbf", "sigmoid"]
    times = {}
    scores = {}
    recalls = {}
    errors = {}
    c_values = [0.1 + 0.18 * x for x in range(6)]
    for kernel in kernels:
        times[kernel] = {}
        scores[kernel] = {}
        recalls[kernel] = {}
        errors[kernel] = {}
        for c_value in c_values:
            clsvm = SVC(kernel=kernel, C=c_value)
            tic = time.process_time()
            clsvm.fit(X=x_train, y=y_train)
            toc = time.process_time()
            y_pred = clsvm.predict(X=x_test)
            times[kernel][c_value] = toc - tic
            scores[kernel][c_value] = clsvm.score(X=x_test, y=y_test) * 100
            recalls[kernel][c_value] = recall_score(y_true=y_test, y_pred=y_pred, average="micro")
            errors[kernel][c_value] = 100 - scores[kernel][c_value]
            print("Score avec kernel=%s: %.2f" % (kernel, clsvm.score(X=x_test, y=y_test) * 100))
    plt.figure(figsize=(12.8, 9.6))
    plt.plot(c_values, list(times["linear"].values()), label="linear")
    plt.plot(c_values, list(times["poly"].values()), label="poly")
    plt.plot(c_values, list(times["rbf"].values()), label="rbf")
    plt.plot(c_values, list(times["sigmoid"].values()), label="sigmoid")
    plt.xlabel("Valeurs de C")
    plt.xlim(left=0.1)
    plt.ylabel("Temps d'apprentissage (secondes)")
    plt.title("Courbe d'évolution du temps d'apprentissage en fonction de C")
    plt.legend()
    plt.savefig("tp3Output/times_all_kernel_with_c_variation.png", dpi="figure")
    plt.show(); plt.close()
    ###
    plt.figure(figsize=(12.8, 9.6))
    plt.plot(c_values, list(scores["linear"].values()), label="linear precision")
    plt.plot(c_values, list(scores["poly"].values()), label="poly precision")
    plt.plot(c_values, list(scores["rbf"].values()), label="rbf precision")
    plt.plot(c_values, list(scores["sigmoid"].values()), label="sigmoid precision")
    plt.plot(c_values, list(errors["linear"].values()), label="linear error", marker="*", linestyle="-.")
    plt.plot(c_values, list(errors["poly"].values()), label="poly error", marker="*", linestyle="-.")
    plt.plot(c_values, list(errors["rbf"].values()), label="rbf error", marker="*", linestyle="-.")
    plt.plot(c_values, list(errors["sigmoid"].values()), label="sigmoid error", marker="*", linestyle="-.")
    plt.xlabel("Valeurs de C")
    plt.xlim(left=0.1)
    plt.ylabel("Score")
    plt.title("Courbe d'évolution de la précision et de l'erreur de classification en fonction de C")
    plt.legend()
    plt.savefig("tp3Output/scores_and_errors_all_kernel_with_c_variation.png", dpi="figure")
    plt.show(); plt.close()
    ###
    plt.figure(figsize=(12.8, 9.6))
    plt.plot(c_values, list(recalls["linear"].values()), label="linear")
    plt.plot(c_values, list(recalls["poly"].values()), label="poly")
    plt.plot(c_values, list(recalls["rbf"].values()), label="rbf")
    plt.plot(c_values, list(recalls["sigmoid"].values()), label="sigmoid")
    plt.xlabel("Valeurs de C")
    plt.xlim(left=0.1)
    plt.ylabel("Recall score")
    plt.title("Courbe d'évolution du recall en fonction de C")
    plt.legend()
    plt.savefig("tp3Output/recalls_all_kernel_with_c_variation.png", dpi="figure")
    plt.show(); plt.close()

def methods_comparison():
    times = {}
    scores = {}
    recalls = {}
    errors = {}
    matrices = {}
    ### Méthode 1
    knn = KNeighborsClassifier(n_neighbors=10)
    tic = time.process_time()
    knn.fit(X=x_train, y=y_train)
    toc = time.process_time()
    y_pred = knn.predict(X=x_test)
    times["knn"] = toc - tic
    scores["knn"] = knn.score(X=x_test, y=y_pred) * 100
    recalls["knn"] = recall_score(y_true=y_test, y_pred=y_pred, average="micro")
    errors["knn"] = 100 - scores["knn"]
    # matrices["knn"] = confusion_matrix(y_true=y_test,y_pred=y_pred)
    plot_confusion_matrix(estimator=knn, X=x_test, y_true=y_test, values_format=".1f"); plt.show()
    ### Méthode 2
    mlp = MLPClassifier(hidden_layer_sizes=(50))
    tic = time.process_time()
    mlp.fit(X=x_train, y=y_train)
    toc = time.process_time()
    y_pred = knn.predict(X=x_test)
    times["mlp"] = toc - tic
    scores["mlp"] = mlp.score(X=x_test, y=y_pred) * 100
    recalls["mlp"] = recall_score(y_true=y_test, y_pred=y_pred, average="micro")
    errors["mlp"] = 100 - scores["mlp"]
    # matrices["mlp"] = confusion_matrix(y_true=y_test, y_pred=y_pred)
    plot_confusion_matrix(estimator=mlp, X=x_test, y_true=y_test, values_format=".1f"); plt.show()
    ### Méthode 3
    svc = SVC(kernel="rbf", C=1)
    tic = time.process_time()
    svc.fit(X=x_train, y=y_train)
    toc = time.process_time()
    y_pred = knn.predict(X=x_test)
    times["svc"] = toc - tic
    scores["svc"] = svc.score(X=x_test, y=y_pred) * 100
    recalls["svc"] = recall_score(y_true=y_test, y_pred=y_pred, average="micro")
    errors["svc"] = 100 - scores["svc"]
    # matrices["svc"] = confusion_matrix(y_true=y_test, y_pred=y_pred)
    plot_confusion_matrix(estimator=svc, X=x_test, y_true=y_test, values_format=".1f"); plt.show()
    ### Plotting
    barWidth = 0.5
    y1 = [times["knn"], times["mlp"], times["svc"]]
    y2 = [scores["knn"], scores["mlp"], scores["svc"]]
    y3 = [recalls["knn"], recalls["mlp"], recalls["svc"]]
    y4 = [errors["knn"], errors["mlp"], errors["svc"]]
    r1 = [0, 3, 6]
    r2 = [x + barWidth for x in r1]
    r3 = [x + 2 * barWidth for x in r1]
    r4 = [x + 3 * barWidth for x in r1]
    plt.figure(figsize=(12.8, 9.6))
    bar1 = plt.bar(r1, y1, width=barWidth, color=['green' for i in y1], linewidth=2, label="temps (sec)")
    bar2 = plt.bar(r2, y2, width=barWidth, color=['yellow' for i in y2], linewidth=4, label="precision (%)")
    bar3 = plt.bar(r3, y3, width=barWidth, color=['red' for i in y3], linewidth=1, label="recall")
    bar4 = plt.bar(r4, y4, width=barWidth, color=['magenta' for i in y4], linewidth=3, label="error (%)")
    plt.xticks([r + 1.5 * barWidth for r in r1], ['Modèle K-nn', 'Modèle MLP', 'Modèle SVM'])
    plt.xlabel("Méthodes")
    plt.ylabel("Valeur")
    plt.title("Evaluation de différentes métriques de performance pour chacune des méthodes vues")
    for rect in bar1 + bar2 + bar3 + bar4:
        height = rect.get_height()
        plt.text(rect.get_x() + rect.get_width() / 2.0, height, '%.2f' % height, ha='center', va='bottom')
    plt.legend()
    plt.show()
    plt.savefig("tp3Output/global_comparison_of_methods_different_metrics", dpi="figure")


kernel_variation()
kernel_and_c_variation()
train_test_error_with_c_variation()
generate_confusion_matrix()
time_precision_recall_error()
methods_comparison()
