from sklearn import datasets
from sklearn.model_selection import train_test_split, KFold
from sklearn.neural_network import MLPClassifier
from sklearn.metrics import precision_score, zero_one_loss, recall_score
import numpy as np
import matplotlib.pyplot as plt
from random import seed, randint
from graphs import bars_plot2, bars_plot
import time


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

#######################
###  TP2 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[:4900,:] ## training
y_train = mnist.target[:4900]
x_test = mnist.data[4900:7000,:]  ## test
y_test = mnist.target[4900:7000]

# 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 modele_hidden_layer_sizes_50():
    # Création du modèle, entraînement et génération de précession
    modele = MLPClassifier(hidden_layer_sizes=(50))
    modele.fit(X=x_train, y=y_train)
    score = modele.score(X=x_test, y=y_test)
    print("MLPClassifier[hidden_layer_sizes=(50)] score = %.2f"%(100*score)) # après compilation 95.19%
    # Classe d'image 4 et sa classe prédite
    y_pred = modele.predict(X=x_test)
    print("VERDICT IMAGE N°4\nClasse réelle: %s\nClasse prédite: %s"%(y_test[3], y_pred[3]))
    # Calcul de la précession à travers le package precision_score
    score_package = precision_score(y_true=y_test, y_pred=y_pred, average="micro")
    print("MLPClassifier[hidden_layer_sizes=(50)] score[using precision_score] = %.2f"%(100*score_package)) # après compilation %

def layers_number_variation_and_scores():
    # Variation du nombre de couches et affichage de scores résultants
    scores_dictionary = {}
    layers = []
    for iteration in range(1,11):
        layers.append(50)
        hidden_layers = tuple(layers)
        print(hidden_layers)
        modele_iter = MLPClassifier(hidden_layer_sizes=hidden_layers)
        modele_iter.fit(X=x_train, y=y_train)
        scores_dictionary["%s couches"%(iteration)] = modele_iter.score(X=x_test, y=y_test) * 100
    # Génération sous forme diagramme à barre
    print(scores_dictionary)
    plt.figure(figsize=(12.8, 9.6))
    plt.plot(range(1,11), list(scores_dictionary.values()))
    plt.xlabel("Nombre de couches de 50 neurones")
    plt.xlim(left=1)
    plt.ylabel("Score")
    plt.title("Courbe d'évolution du score de précession en fonction du nombre de couches cachées")
    plt.savefig("tp2Output/scores_with_hidden_layers_variation.png", dpi="figure")
    plt.show(); plt.close()

def five_models_with_different_layers():
    scores = {}
    times = {}
    layers = {}
    for counter in range(5):
        hl = tuple([randint(10, 300) for i in range(counter*2 + 2)])
        modele = MLPClassifier(hidden_layer_sizes=hl)
        tic = time.process_time()
        modele.fit(X=x_train, y=y_train)
        toc = time.process_time()
        times[counter] = toc - tic
        scores[counter] = modele.score(X=x_test, y=y_test) * 100
        layers[counter] = hl
    barWidth = 0.4
    y1 = [times[c] for c in range(5)]
    y2 = [scores[c] for c in range(5)]
    r1 = [0, 4, 8, 12, 16]
    r2 = [x + barWidth for x in r1]
    plt.figure(figsize=(12.8, 9.6))
    bar1 = plt.bar(r1, y1, width=barWidth, color=['red' for i in y1], linewidth=2, label="temps (sec)")
    bar2 = plt.bar(r2, y2, width=barWidth, color=['green' for i in y2], linewidth=4, label="precision (%)")
    plt.xticks([r + barWidth for r in r1], ['%s couches\n%s'%(i*2+2, layers[i]) for i in range(5)], rotation=10)
    plt.xlabel("Modèles")
    plt.ylabel("Valeur")
    plt.title("Evaluation du temps d'apprentissage et la précession de cinq modèles de différentes couches")
    for rect in bar1 + bar2:
        height = rect.get_height()
        plt.text(rect.get_x() + rect.get_width() / 2.0, height, '%.2f' % height, ha='center', va='bottom')
    plt.legend()
    plt.savefig("tp2Output/comparison_times_scores_of_methods_different_layers", dpi="figure")
    plt.show()

def optimization_algorithms_variation():
    solvers = ["lbfgs", "sgd", "adam"]
    scores = {}
    scores["lbfgs"] = {}
    scores["sgd"] = {}
    scores["adam"] = {}
    for counter in range(1, 11):
        hl = tuple([randint(10, 300) for i in range(counter)])
        for solver in solvers:
            modele = MLPClassifier(hidden_layer_sizes=hl, solver=solver)
            modele.fit(X=x_train, y=y_train)
            scores[solver][counter] = modele.score(X=x_test, y=y_test) * 100
    print(scores)
    plt.figure(figsize=(12.8, 9.6))
    plt.plot(range(1,11), list(scores["lbfgs"].values()), label="L-BFGS")
    plt.plot(range(1,11), list(scores["sgd"].values()), label="SGD")
    plt.plot(range(1,11), list(scores["adam"].values()), label="ADAM")
    plt.xlabel("Nombre de couches cachées")
    plt.xlim(left=1)
    plt.ylabel("Score de précession")
    plt.title("Courbe d'évolution du score de précession en fonction du nombre de couches cachées")
    plt.legend()
    plt.savefig("tp2Output/scores_with_hidden_layers_variation_different_solvers.png", dpi="figure")
    plt.show(); plt.close()

def activation_functions_variation():
    activations = ["identity", "logistic", "tanh", "relu"]
    scores = {}
    scores["identity"] = {}
    scores["logistic"] = {}
    scores["tanh"] = {}
    scores["relu"] = {}
    for counter in range(1, 11):
        hl = tuple([randint(10, 300) for i in range(counter)])
        for activation in activations:
            modele = MLPClassifier(hidden_layer_sizes=hl, activation=activation)
            modele.fit(X=x_train, y=y_train)
            scores[activation][counter] = modele.score(X=x_test, y=y_test) * 100
    plt.figure(figsize=(12.8, 9.6))
    plt.plot(range(1,11), list(scores["identity"].values()), label="identity")
    plt.plot(range(1,11), list(scores["logistic"].values()), label="logistic")
    plt.plot(range(1,11), list(scores["tanh"].values()), label="tanh")
    plt.plot(range(1, 11), list(scores["relu"].values()), label="relu")
    plt.xlabel("Nombre de couches cachées")
    plt.xlim(left=1)
    plt.ylabel("Score de précession")
    plt.title("Courbe d'évolution du score de précession en fonction du nombre de couches cachées")
    plt.legend()
    plt.savefig("tp2Output/scores_with_hidden_layers_variation_different_activations_with_adam.png", dpi="figure")
    plt.show(); plt.close()

def alpha_parameter_variation():
    scores = []
    alphas = [10**(-i) for i in range(1,9)]
    alphas = alphas[::-1]
    for alpha in alphas:
        # hl = tuple([randint(10, 300) for i in range(counter)])
        modele = MLPClassifier(hidden_layer_sizes=(60,47,179,251,296,61,191,232,171,114), alpha=alpha)
        modele.fit(X=x_train, y=y_train)
        scores.append(modele.score(X=x_test, y=y_test) * 100)
    plt.figure(figsize=(12.8, 9.6))
    plt.plot(alphas, scores)
    plt.xlabel("Valeurs d'alpha")
    plt.xlim(left=10e-8)
    plt.ylabel("Score de précession")
    plt.title("Courbe d'évolution du score de précession en fonction du paramètre alpha")
    plt.savefig("tp2Output/scores_with_alpha_variation_same_hidden_layers_sizes.png", dpi="figure")
    plt.show(); plt.close()

def times_scores_recalls_errors_comparison():
    scores = {}
    times = {}
    recalls = {}
    errors = {}
    layers = {}
    for counter in range(5):
        hl = tuple([randint(10, 300) for i in range(counter * 2 + 2)])
        modele = MLPClassifier(hidden_layer_sizes=hl)
        tic = time.process_time()
        modele.fit(X=x_train, y=y_train)
        toc = time.process_time()
        y_pred = modele.predict(X=x_test)
        times[counter] = toc - tic
        scores[counter] = modele.score(X=x_test, y=y_test) * 100
        recalls[counter] = recall_score(y_true=y_test, y_pred=y_pred, average="micro")
        errors[counter] = zero_one_loss(y_true=y_test, y_pred=y_pred) * 100
        layers[counter] = hl
    barWidth = 0.4
    y1 = [times[c] for c in range(5)]
    y2 = [scores[c] for c in range(5)]
    y3 = [recalls[c] for c in range(5)]
    y4 = [errors[c] for c in range(5)]
    r1 = [0, 4, 8, 12, 16]
    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=['red' for i in y1], linewidth=2, label="temps (sec)")
    bar2 = plt.bar(r2, y2, width=barWidth, color=['green' 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], ['%s couches\n%s' % (i * 2 + 2, layers[i]) for i in range(5)], rotation=10)
    plt.xlabel("Modèles")
    plt.ylabel("Valeur")
    plt.title("Evaluation du temps d'apprentissage, la précession, le recall et l'erreur de cinq modèles de différentes couches")
    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.savefig("tp2Output/comparison_times_scores_recalls_errors_of_methods_different_layers", dpi="figure")
    plt.show(); plt.close()


modele_hidden_layer_sizes_50()
layers_number_variation_and_scores()
five_models_with_different_layers()
optimization_algorithms_variation()
activation_functions_variation()
alpha_parameter_variation()
times_scores_recalls_errors_comparison()
