from sklearn import datasets
from sklearn.model_selection import train_test_split, KFold
from sklearn.neighbors import KNeighborsClassifier
import matplotlib.pyplot as plt
import numpy as np
from random import seed
from graphs import bars_plot2, bars_plot
import time

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

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

# Extraction des images de la base de données
images = mnist.data.reshape((-1, 28, 28))

# Extraction des classes des images de la base de données
targets = mnist.target

# Génération d'échantillon de données avec 5000 exemples
indexes = np.random.randint(70000, size=5000)
data = images[indexes,:]
data_targets = targets[indexes]

# Division de la base de données en lots d'entraînement et de test
def k_10_classifier_and_score():
    pourcentage_donnees = 0.8
    xtrain, xtest, ytrain, ytest = train_test_split(data, data_targets, train_size=pourcentage_donnees)
    # Mise en conformité des dimensions de xtrain et xtest aux dimensions attendues par le classifeur
    xtrain = xtrain.reshape((-1, 784))
    xtest = xtest.reshape((-1, 784))
    # Instanciation et entraînement du classifieur
    k = 10
    clf = KNeighborsClassifier(n_neighbors=k)
    clf.fit(X=xtrain, y=ytrain)
    predictions = clf.predict(X=xtest)
    # Classe d'image 4 et sa classe prédite
    print("VERDICT IMAGE N°4\nClasse réelle: %s\nClasse prédite: %s"%(ytest[3], predictions[3]))
    # Affichage du score sur l'échantillon de test
    score_xtest = clf.score(X=xtest, y=ytest)
    print("Score xtest = %.2f%%"%(score_xtest*100))
    # Taux d'erreur sur les données d'apprentissage
    score_xtrain = clf.score(X=xtrain, y=ytrain)
    print("Score d'erreur xtrain = %.2f%%"%(100*(1-score_xtrain)))

def k_variation_and_score():
    # Variation du nombre k de voisins et score résultant
    n_folds = 10
    kf = KFold(n_splits=n_folds, shuffle=True)
    k_score = {}
    for k in range(2,16):
        sum_score = 0
        for train_index, test_index in kf.split(X=data):
            x_train, x_test = data[train_index], data[test_index]
            y_train, y_test = data_targets[train_index], data_targets[test_index]
            x_train = x_train.reshape((-1, 784))
            x_test = x_test.reshape((-1, 784))
            clf = KNeighborsClassifier(n_neighbors=k)
            clf.fit(X=x_train, y=y_train)
            sum_score += clf.score(X=x_test, y=y_test)
        k_score[k] = (sum_score/n_folds)*100
    bars_plot2(bar_labels=list(k_score.keys()), bar_heights=list(k_score.values()), xlabel="Valeur de k",
               ylabel="Score", fig_title="Score par valeur de k (nombre de voisins)", show=True, percent_mark=True,
               output="tp1Output/mnist_k_variation_scores.png")

def train_test_percent_variation_and_score():
    # Variation du pourcentage des échantillons (training et test) et affichage des scores résultants
    k_score_percent = {}
    for pourcentage in [0.05*i for i in range(1,20)]:
        xtrain_percent, xtest_percent, ytrain_percent, ytest_percent = train_test_split(data, data_targets, train_size=pourcentage)
        xtrain_percent = xtrain_percent.reshape((-1, 784))
        xtest_percent = xtest_percent.reshape((-1, 784))
        clf = KNeighborsClassifier(n_neighbors=10)
        clf.fit(X=xtrain_percent, y=ytrain_percent)
        k_score_percent[pourcentage] = clf.score(X=xtest_percent, y=ytest_percent)*100
    bars_plot2(bar_labels=["%.2f" % (percent) for percent in list(k_score_percent.keys())],
               bar_heights=list(k_score_percent.values()), xlabel="Pourcentage d'échantillons train",
               ylabel="Score",
               fig_title="Score par pourcentage de découpage de DATA (5000 images) en échantillons train et test",
               show=True, percent_mark=True, bar_colors="red",
               output="tp1Output/mnist_split_data_percentage_scores.png")

def train_percent_variation_and_score():
    # Fixation de la taille de l'échantillon test (25% de DATA) et variation de la taille de l'échantillon train avec affichage de score
    k_score_train_variation = {}
    xtrain_global, xtest_25_percent, ytrain_global, ytest_25_percent = train_test_split(data, data_targets, train_size=0.75)
    xtest_25_percent = xtest_25_percent.reshape((-1, 784))
    for pourcentage in [(10*j)/100.0 for j in range(1,10)]:
        xtrain_echantillon = xtrain_global[:int(pourcentage*len(xtrain_global))+1]
        xtrain_echantillon = xtrain_echantillon.reshape((-1, 784))
        ytrain_echantillon = ytrain_global[:int(pourcentage*len(xtrain_global))+1]
        clf = KNeighborsClassifier(n_neighbors=10)
        clf.fit(X=xtrain_echantillon, y=ytrain_echantillon)
        k_score_train_variation[pourcentage] = clf.score(X=xtest_25_percent, y=ytest_25_percent)*100
    bars_plot2(bar_labels=["%.2f" % (percent) for percent in list(k_score_train_variation.keys())],
               bar_heights=list(k_score_train_variation.values()),
               xlabel="Pourcentage d'échantillons train", ylabel="Score",
               fig_title="Score par pourcentage de découpage de train (%s images initialement) et %s images comme test (25%% de DATA)" % (
               len(xtrain_global), len(xtest_25_percent)),
               show=True, percent_mark=True, bar_colors="magenta",
               output="tp1Output/mnist_split_various_train_percentage_scores_with_fixed_test.png")

def distance_type_variation_and_score():
    # Variation du type de distance avec train=75% de DATA et test=25% de DATA puis affichage de score
    xtrain_distance, xtest_distance, ytrain_distance, ytest_distance = train_test_split(data, data_targets, train_size=0.75)
    xtrain_distance = xtrain_distance.reshape((-1, 784))
    xtest_distance = xtest_distance.reshape((-1, 784))
    distances = ["minkowski", "euclidean", "hamming", "canberra", "braycurtis"]
    distance_score ={}
    for distance in distances:
        clf = KNeighborsClassifier(n_neighbors=10, metric=distance)
        clf.fit(X=xtrain_distance, y=ytrain_distance)
        distance_score[distance] = clf.score(X=xtest_distance, y=ytest_distance)*100
    bars_plot2(bar_labels=list(distance_score.keys()), bar_heights=list(distance_score.values()),
               xlabel="Types de distance", ylabel="Score",
               fig_title="Score pour différent type de distance (métrique) avec train=75% de DATA et test=25% de DATA",
               show=True, percent_mark=True, bar_colors="green",
               output="tp1Output/mnist_various_distance_metric_scores.png")


def parameter_njobs_variation_and_score():
    xtrain_distance, xtest_distance, ytrain_distance, ytest_distance = train_test_split(data, data_targets, train_size=0.75)
    xtrain_distance = xtrain_distance.reshape((-1, 784))
    xtest_distance = xtest_distance.reshape((-1, 784))
    distances = ["minkowski", "euclidean", "hamming", "canberra", "braycurtis"]
    for n_jobs in [6, -1]:
        distance_based_training_time = {}
        for distance in distances:
            clf = KNeighborsClassifier(n_neighbors=10, metric=distance, n_jobs=n_jobs)
            tic = time.process_time()
            clf.fit(X=xtrain_distance, y=ytrain_distance)
            toc = time.process_time()
            distance_based_training_time[distance] = (toc-tic)
        bars_plot2(bar_labels=list(distance_based_training_time.keys()),
                   bar_heights=list(distance_based_training_time.values()),
                   xlabel="Types de distance", ylabel="Temps d'entraînement (seconde)", show=True, bar_colors="pink",
                   fig_title="Temps d'entraînement pour différent type de distance avec train=75%% de DATA et test=25%% de DATA et\nN_JOBS=%s" % (
                       n_jobs))


k_10_classifier_and_score()
k_variation_and_score()
train_test_percent_variation_and_score()
train_percent_variation_and_score()
distance_type_variation_and_score()
parameter_njobs_variation_and_score()
