diff --git a/Cours/ML-05-Reseaux_de_neurones.pdf b/Cours/ML-05-Reseaux_de_neurones.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..775736fcf124a8aa4ede5acfcf438eec830e84de
Binary files /dev/null and b/Cours/ML-05-Reseaux_de_neurones.pdf differ
diff --git a/TD/TD3/regression_logistique-complet.py b/TD/TD3/regression_logistique-complet.py
new file mode 100644
index 0000000000000000000000000000000000000000..eb5adf7b079e2a8f5dcd7da03d25e2b0b86da233
--- /dev/null
+++ b/TD/TD3/regression_logistique-complet.py
@@ -0,0 +1,262 @@
+import matplotlib.pyplot as plt
+import numpy as np
+
+def lecture_donnees(nom_fichier, delimiteur=','):
+    """ Lit le fichier contenant les données et renvoiee les matrices correspondant
+
+    Parametres
+    ----------
+    nom_fichier : nom du fichier contenant les données
+    delimiteur : caratère délimitant les colonne dans le fichier ("," par défaut)
+
+    Retour
+    -------
+    X : matrice des données de dimension [N, nb_var]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    N : nombre d'éléments
+    nb_var : nombre de variables prédictives
+
+    """
+    
+    data = np.loadtxt(nom_fichier, delimiter=delimiteur)
+    nb_var = data.shape[1] - 1
+    N = data.shape[0]
+
+    X = data[:, :-1]
+    Y = data[:, -1].reshape(N,1)
+    
+    return X, Y, N, nb_var
+
+def normalisation(X):
+    """ Normalise les données par un centrage-réduction des variables prédictives
+    
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var]
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+    Retour
+    -------
+    X_norm : matrice des données centrées-réduites de dimension [N, nb_var]
+    mu : moyenne des variables de dimension [1,nb_var]
+    sigma : écart-type des variables de dimension [1,nb_var]
+    
+    """
+    
+    mu = np.mean(X, 0)
+    sigma = np.std(X, 0)
+    X_norm = (X - mu) / sigma
+
+    return X_norm, mu, sigma
+
+def sigmoide(z):
+    """ Calcule la valeur de la fonction sigmoide appliquée à z
+    
+    Parametres
+    ----------
+    z : peut être un scalaire ou une matrice
+
+    Return
+    -------
+    s : valeur de sigmoide de z. Même dimension que z
+
+    """
+
+    s = 1 / (1 + np.exp(-z))
+
+    return s
+
+def calcule_cout(X, Y, theta):
+    """ Calcule la valeur de la fonction cout ( - log vraisemblance)
+    
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var+1]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    theta : matrices contenant les paramètres theta du modèle linéaire de dimension [1, nb_var+1]
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+    Return
+    -------
+    cout : nombre correspondant à la valeur de la fonction cout (moyenne des différences au carré)
+
+    """
+
+    N = X.shape[0]
+    
+    cout = - (Y*np.log(sigmoide(X.dot(theta.T))) + (1-Y)*np.log(1-sigmoide(X.dot(theta.T)))).sum() / N
+
+    return cout
+
+def descente_gradient(X, Y, theta, alpha, nb_iters):
+    """ Apprentissage des parametres de regression logistique par descente du gradient
+    
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var+1]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    theta : matrices contenant les paramètres theta du modèle linéaire de dimension [1, nb_var+1]
+    alpha : taux d'apprentissage
+    nb_iters : nombre d'itérations
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+
+    Retour
+    -------
+    theta : matrices contenant les paramètres theta appris par descente du gradient de dimension [1, nb_var+1]
+    J_history : tableau contenant les valeurs de la fonction cout pour chaque iteration de dimension nb_iters
+
+
+    """
+    
+    # Initialisation de variables utiles
+    N = X.shape[0]
+    J_history = np.zeros(nb_iters)
+
+    for i in range(0, nb_iters):
+
+        error = sigmoide(X.dot(theta.T)) - Y
+        theta -= (alpha/N)*np.sum(X*error, 0)
+
+        J_history[i] = calcule_cout(X, Y, theta)
+        
+
+    return theta, J_history
+
+def prediction(X,theta):
+    """ Predit la classe de chaque élement de X
+    
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var+1]
+    theta : matrices contenant les paramètres theta du modèle linéaire de dimension [1, nb_var+1]
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+
+    Retour
+    -------
+    p : matrices de dimension [N,1] indiquant la classe de chaque élement de X (soit 0, soit 1)
+
+    """
+
+    p = sigmoide(X.dot(theta.T))
+    pos = np.where(p >= 0.5)
+    neg = np.where(p < 0.5)
+
+    p[pos] = 1
+    p[neg] = 0
+
+    return p
+
+def taux_classification(Ypred,Y):
+    """ Calcule le taux de classification (proportion d'éléments bien classés)
+    
+    Parametres
+    ----------
+    Ypred : matrice contenant les valeurs de classe prédites de dimension [N, 1]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    
+    avec N : nombre d'éléments 
+
+
+    Retour
+    -------
+    t : taux de classification
+
+    """
+
+    N = Ypred.size
+    nb_erreurs = np.sum(np.abs(Ypred-Y))
+    
+    t = (N-nb_erreurs) / N
+
+    return t
+
+def affichage(X, Y):
+    """ Affichage en 2 dimensions des données (2 dimensions de X) et représentation de la 
+        classe (indiquée par Y) par une couleur
+    
+
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var+1]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+    Retour
+    -------
+    None
+
+    """
+
+    pos = np.where(Y == 1)[0]
+    neg = np.where(Y == 0)[0]
+    plt.scatter(X[pos, 1], X[pos, 2], marker="+", c='b')
+    plt.scatter(X[neg, 1], X[neg, 2], marker="o", c='r')
+
+
+if __name__ == "__main__":
+    # ===================== Partie 1: Lecture et normalisation des données=====================
+    print("Lecture des données ...")
+
+    X, Y, N, nb_var = lecture_donnees("notes.txt")
+
+    # Affichage des 10 premiers exemples du dataset
+    print("Affichage des 10 premiers exemples du dataset : ")
+    for i in range(0, 10):
+        print(f"x = {X[i,:]}, y = {Y[i]}")
+        
+    # Normalisation des variables (centrage-réduction)
+    print("Normalisation des variables ...")
+
+    X, mu, sigma = normalisation(X)
+
+    # Ajout d'une colonne de 1 à X (pour theta0)
+    X = np.hstack((np.ones((N,1)), X)) 
+
+    # Affichage des points en 2D et représentation de leur classe réelle par une couleur
+    if nb_var == 2 :
+        plt.figure(0)
+        plt.title("Disposition des points en 2D - Réalité")
+        affichage(X,Y)
+
+    # ===================== Partie 2: Descente du gradient =====================
+    print("Apprentissage par descente du gradient ...")
+
+    # Choix du taux d'apprentissage et du nombre d'itérations
+    alpha = 0.01
+    nb_iters = 50000
+
+    # Initialisation de theta et réalisation de la descente du gradient
+    theta = np.zeros((1,nb_var+1))
+    theta, J_history = descente_gradient(X, Y, theta, alpha, nb_iters)
+
+    # Affichage de l'évolution de la fonction de cout lors de la descente du gradient
+    plt.figure(1)
+    plt.title("Evolution de le fonction de cout lors de la descente du gradient")
+    plt.plot(np.arange(J_history.size), J_history)
+    plt.xlabel("Nombre d'iterations")
+    plt.ylabel("Cout J")
+
+    # Affichage de la valeur de theta
+    print(f"Theta calculé par la descente du gradient : {theta}")
+
+    # Evaluation du modèle
+    Ypred = prediction(X,theta)
+
+    print("Taux de classification : ", taux_classification(Ypred,Y))
+
+    # Affichage des points en 2D et représentation de leur classe prédite par une couleur
+    if nb_var == 2 :
+        plt.figure(2)
+        plt.title("Disposition des points en 2D - Prédiction")
+        affichage(X,Ypred)
+        
+    plt.show()
+
+    print("Regression logistique Terminée.")
diff --git a/TD/TD3/regression_logistique-etendu-1-complet.py b/TD/TD3/regression_logistique-etendu-1-complet.py
new file mode 100644
index 0000000000000000000000000000000000000000..010ea2ca43862317903497d257e169da0f3c7c72
--- /dev/null
+++ b/TD/TD3/regression_logistique-etendu-1-complet.py
@@ -0,0 +1,369 @@
+# Ajout de différentes stratégies de descente du gradient
+
+import matplotlib.pyplot as plt
+import numpy as np
+
+def lecture_donnees(nom_fichier, delimiteur=','):
+    """ Lit le fichier contenant les données et renvoiee les matrices correspondant
+
+    Parametres
+    ----------
+    nom_fichier : nom du fichier contenant les données
+    delimiteur : caratère délimitant les colonne dans le fichier ("," par défaut)
+
+    Retour
+    -------
+    X : matrice des données de dimension [N, nb_var]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    N : nombre d'éléments
+    nb_var : nombre de variables prédictives
+
+    """
+    
+    data = np.loadtxt(nom_fichier, delimiter=delimiteur)
+    nb_var = data.shape[1] - 1
+    N = data.shape[0]
+
+    X = data[:, :-1]
+    Y = data[:, -1].reshape(N,1)
+    
+    return X, Y, N, nb_var
+
+def normalisation(X):
+    """ Normalise les données par un centrage-réduction des variables prédictives
+    
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var]
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+    Retour
+    -------
+    X_norm : matrice des données centrées-réduites de dimension [N, nb_var]
+    mu : moyenne des variables de dimension [1,nb_var]
+    sigma : écart-type des variables de dimension [1,nb_var]
+    
+    """
+    
+    mu = np.mean(X, 0)
+    sigma = np.std(X, 0)
+    X_norm = (X - mu) / sigma
+
+    return X_norm, mu, sigma
+
+def sigmoide(z):
+    """ Calcule la valeur de la fonction sigmoide appliquée à z
+    
+    Parametres
+    ----------
+    z : peut être un scalaire ou une matrice
+
+    Return
+    -------
+    s : valeur de sigmoide de z. Même dimension que z
+
+    """
+
+    s = 1 / (1 + np.exp(-z))
+
+    return s
+
+def calcule_cout(X, Y, theta):
+    """ Calcule la valeur de la fonction cout ( - log vraisemblance)
+    
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var+1]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    theta : matrices contenant les paramètres theta du modèle linéaire de dimension [1, nb_var+1]
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+    Return
+    -------
+    cout : nombre correspondant à la valeur de la fonction cout (moyenne des différences au carré)
+
+    """
+
+    N = X.shape[0]
+    
+    cout = - (Y*np.log(sigmoide(X.dot(theta.T))) + (1-Y)*np.log(1-sigmoide(X.dot(theta.T)))).sum() / N
+
+    return cout
+
+def descente_gradient_lot(X, Y, theta, alpha, nb_iters):
+    """ Apprentissage des parametres de regression logistique par descente du gradient
+    avec la stratégie par lot
+    
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var+1]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    theta : matrices contenant les paramètres theta du modèle linéaire de dimension [1, nb_var+1]
+    alpha : taux d'apprentissage
+    nb_iters : nombre d'itérations
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+
+    Retour
+    -------
+    theta : matrices contenant les paramètres theta appris par descente du gradient de dimension [1, nb_var+1]
+    J_history : tableau contenant les valeurs de la fonction cout pour chaque iteration de dimension nb_iters
+
+
+    """
+    
+    # Initialisation de variables utiles
+    N = X.shape[0]
+    J_history = np.zeros(nb_iters)
+
+    for i in range(0, nb_iters):
+
+        error = sigmoide(X.dot(theta.T)) - Y
+        theta -= (alpha/N)*np.sum(X*error, 0)
+
+        J_history[i] = calcule_cout(X, Y, theta)
+        
+
+    return theta, J_history
+
+def descente_gradient_stochastique(X, Y, theta, alpha, nb_iters):
+    """ Apprentissage des parametres de regression logistique par descente du gradient
+    avec la stratégie stochastique
+    
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var+1]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    theta : matrices contenant les paramètres theta du modèle linéaire de dimension [1, nb_var+1]
+    alpha : taux d'apprentissage
+    nb_iters : nombre d'itérations
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+
+    Retour
+    -------
+    theta : matrices contenant les paramètres theta appris par descente du gradient de dimension [1, nb_var+1]
+    J_history : tableau contenant les valeurs de la fonction cout pour chaque iteration de dimension nb_iters
+
+
+    """
+    
+    # Initialisation de variables utiles
+    N = X.shape[0]
+    J_history = np.zeros(nb_iters)
+
+    # Création d'un vecteur d'indices pour parcourir X élément par élément, dans un ordre aléatoire
+    indices = np.arange(N)
+    np.random.shuffle(indices)
+
+    for i in range(0, nb_iters):
+
+        for j in range(N):
+
+            error = sigmoide(X[indices[j],:].dot(theta.T)) - Y[indices[j]]
+            theta -= alpha*X[indices[j],:]*error
+
+        J_history[i] = calcule_cout(X, Y, theta)
+        
+
+    return theta, J_history
+
+def descente_gradient_mini_lot(X, Y, theta, alpha, nb_iters, taille_lots=16):
+    """ Apprentissage des parametres de regression logistique par descente du gradient
+    avec la stratégie par mini-lots
+    
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var+1]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    theta : matrices contenant les paramètres theta du modèle linéaire de dimension [1, nb_var+1]
+    alpha : taux d'apprentissage
+    nb_iters : nombre d'itérations
+    taille_lots = nombre d'éléments dans chaque mini-lot
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+
+    Retour
+    -------
+    theta : matrices contenant les paramètres theta appris par descente du gradient de dimension [1, nb_var+1]
+    J_history : tableau contenant les valeurs de la fonction cout pour chaque iteration de dimension nb_iters
+
+
+    """
+    
+    # Initialisation de variables utiles
+    N = X.shape[0]
+    J_history = np.zeros(nb_iters)
+
+    # Création d'un vecteur d'indices pour parcourir X élément par élément, dans un ordre aléatoire
+    indices = np.arange(N)
+    np.random.shuffle(indices)
+    nb_lots = N // taille_lots
+    indices_lots = []
+    for i in range(nb_lots):
+        ind_debut = i*taille_lots
+        indices_lots.append(indices[ind_debut:ind_debut+taille_lots])
+
+    for i in range(0, nb_iters):
+
+        for j in range(nb_lots):
+
+            error = sigmoide(X[indices_lots[j],:].dot(theta.T)) - Y[indices_lots[j]]
+            theta -= (alpha/taille_lots)*np.sum(X[indices_lots[j],:]*error, 0)
+
+        J_history[i] = calcule_cout(X, Y, theta)
+        
+
+    return theta, J_history
+
+
+def prediction(X,theta):
+    """ Predit la classe de chaque élement de X
+    
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var+1]
+    theta : matrices contenant les paramètres theta du modèle linéaire de dimension [1, nb_var+1]
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+
+    Retour
+    -------
+    p : matrices de dimension [N,1] indiquant la classe de chaque élement de X (soit 0, soit 1)
+
+    """
+
+    p = sigmoide(X.dot(theta.T))
+    pos = np.where(p >= 0.5)
+    neg = np.where(p < 0.5)
+
+    p[pos] = 1
+    p[neg] = 0
+
+    return p
+
+def taux_classification(Ypred,Y):
+    """ Calcule le taux de classification (proportion d'éléments bien classés)
+    
+    Parametres
+    ----------
+    Ypred : matrice contenant les valeurs de classe prédites de dimension [N, 1]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    
+    avec N : nombre d'éléments 
+
+
+    Retour
+    -------
+    t : taux de classification
+
+    """
+
+    N = Ypred.size
+    nb_erreurs = np.sum(np.abs(Ypred-Y))
+    
+    t = (N-nb_erreurs) / N
+
+    return t
+
+def affichage(X, Y):
+    """ Affichage en 2 dimensions des données (2 dimensions de X) et représentation de la 
+        classe (indiquée par Y) par une couleur
+    
+
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var+1]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+    Retour
+    -------
+    None
+
+    """
+
+    pos = np.where(Y == 1)[0]
+    neg = np.where(Y == 0)[0]
+    plt.scatter(X[pos, 1], X[pos, 2], marker="+", c='b')
+    plt.scatter(X[neg, 1], X[neg, 2], marker="o", c='r')
+
+
+if __name__ == "__main__":
+    # ===================== Partie 1: Lecture et normalisation des données=====================
+    print("Lecture des données ...")
+
+    X, Y, N, nb_var = lecture_donnees("notes.txt")
+
+    # Affichage des 10 premiers exemples du dataset
+    print("Affichage des 10 premiers exemples du dataset : ")
+    for i in range(0, 10):
+        print(f"x = {X[i,:]}, y = {Y[i]}")
+        
+    # Normalisation des variables (centrage-réduction)
+    print("Normalisation des variables ...")
+
+    X, mu, sigma = normalisation(X)
+
+    # Ajout d'une colonne de 1 à X (pour theta0)
+    X = np.hstack((np.ones((N,1)), X)) 
+
+    # Affichage des points en 2D et représentation de leur classe réelle par une couleur
+    if nb_var == 2 :
+        plt.figure(0)
+        plt.title("Disposition des points en 2D - Réalité")
+        affichage(X,Y)
+
+    # ===================== Partie 2: Descente du gradient =====================
+    print("Apprentissage par descente du gradient ...")
+
+    # Choix du taux d'apprentissage et du nombre d'itérations
+    alpha = 0.01
+    nb_iters = 1000
+
+    J_histories = []
+    # Initialisation de theta et réalisation de la descente du gradient
+    theta = np.zeros((1,nb_var+1))
+    theta, J_history = descente_gradient_lot(X, Y, theta, alpha, nb_iters)
+    J_histories.append(J_history)
+    theta = np.zeros((1,nb_var+1))
+    theta, J_history = descente_gradient_stochastique(X, Y, theta, alpha, nb_iters)
+    J_histories.append(J_history)
+    theta = np.zeros((1,nb_var+1))
+    theta, J_history = descente_gradient_mini_lot(X, Y, theta, alpha, nb_iters, taille_lots=16)
+    J_histories.append(J_history)
+    J_histories = np.array(J_histories).T
+    
+
+    # Affichage de l'évolution de la fonction de cout lors de la descente du gradient
+    plt.figure(1)
+    plt.title("Evolution de le fonction de cout lors de la descente du gradient")
+    plt.plot(np.arange(J_histories.shape[0]), J_histories, label = ["lot", "stochastique", "mini-lot"])
+    plt.xlabel("Nombre d'iterations")
+    plt.ylabel("Cout J")
+    plt.legend()
+
+    # Affichage de la valeur de theta
+    print(f"Theta calculé par la descente du gradient : {theta}")
+
+    # Evaluation du modèle
+    Ypred = prediction(X,theta)
+
+    print("Taux de classification : ", taux_classification(Ypred,Y))
+
+    # Affichage des points en 2D et représentation de leur classe prédite par une couleur
+    if nb_var == 2 :
+        plt.figure(2)
+        plt.title("Disposition des points en 2D - Prédiction")
+        affichage(X,Ypred)
+        
+    plt.show()
+
+    print("Regression logistique Terminée.")
diff --git a/TD/TD3/regression_logistique-etendu-2-complet.py b/TD/TD3/regression_logistique-etendu-2-complet.py
new file mode 100644
index 0000000000000000000000000000000000000000..ccaf71cef082f76c6a5803458061355be002a4c8
--- /dev/null
+++ b/TD/TD3/regression_logistique-etendu-2-complet.py
@@ -0,0 +1,420 @@
+# Ajout des ensembles d'apprentissage et de test
+
+import matplotlib.pyplot as plt
+import numpy as np
+
+def lecture_donnees(nom_fichier, delimiteur=','):
+    """ Lit le fichier contenant les données et renvoiee les matrices correspondant
+
+    Parametres
+    ----------
+    nom_fichier : nom du fichier contenant les données
+    delimiteur : caratère délimitant les colonne dans le fichier ("," par défaut)
+
+    Retour
+    -------
+    X : matrice des données de dimension [N, nb_var]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    N : nombre d'éléments
+    nb_var : nombre de variables prédictives
+
+    """
+    
+    data = np.loadtxt(nom_fichier, delimiter=delimiteur)
+    nb_var = data.shape[1] - 1
+    N = data.shape[0]
+
+    X = data[:, :-1]
+    Y = data[:, -1].reshape(N,1)
+    
+    return X, Y, N, nb_var
+
+def normalisation(X):
+    """ Normalise les données par un centrage-réduction des variables prédictives
+    
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var]
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+    Retour
+    -------
+    X_norm : matrice des données centrées-réduites de dimension [N, nb_var]
+    mu : moyenne des variables de dimension [1,nb_var]
+    sigma : écart-type des variables de dimension [1,nb_var]
+    
+    """
+    
+    mu = np.mean(X, 0)
+    sigma = np.std(X, 0)
+    X_norm = (X - mu) / sigma
+
+    return X_norm, mu, sigma
+
+def decoupage_donnees(X,Y,ratio_apprentissage):
+    """ Découpe les données en deux ensembles : apprentissage et test
+
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    ratio_apprentissage : ratio de données d’apprentissage par rapport aux données de test (entre 0 et 1)
+
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+    Retour
+    -------
+    X_app : matrice des données d'apprentissage
+    Y_app : matrice contenant les valeurs de la variable cible pour les données d'apprentissage
+    X_test : matrice des données de test
+    Y_test : matrice contenant les valeurs de la variable cible pour les données de test
+
+    """
+
+    # Création d'un vecteur d'indices dans un ordre aléatoire
+    N = X.shape[0]
+    indices = np.arange(N)
+    np.random.shuffle(indices)
+
+    indice_split = int(N*ratio_apprentissage)
+    
+    indices_app = indices[:indice_split]
+    indices_test = indices[indice_split:]
+
+    X_app = X[indices_app,:]
+    Y_app = Y[indices_app]
+    X_test = X[indices_test,:]
+    Y_test = Y[indices_test]
+
+    return X_app, Y_app, X_test, Y_test
+
+
+def sigmoide(z):
+    """ Calcule la valeur de la fonction sigmoide appliquée à z
+    
+    Parametres
+    ----------
+    z : peut être un scalaire ou une matrice
+
+    Return
+    -------
+    s : valeur de sigmoide de z. Même dimension que z
+
+    """
+
+    s = 1 / (1 + np.exp(-z))
+
+    return s
+
+def calcule_cout(X, Y, theta):
+    """ Calcule la valeur de la fonction cout ( - log vraisemblance)
+    
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var+1]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    theta : matrices contenant les paramètres theta du modèle linéaire de dimension [1, nb_var+1]
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+    Return
+    -------
+    cout : nombre correspondant à la valeur de la fonction cout (moyenne des différences au carré)
+
+    """
+
+    N = X.shape[0]
+    
+    cout = - (Y*np.log(sigmoide(X.dot(theta.T))) + (1-Y)*np.log(1-sigmoide(X.dot(theta.T)))).sum() / N
+
+    return cout
+
+def descente_gradient_lot(X, Y, theta, alpha, nb_iters):
+    """ Apprentissage des parametres de regression logistique par descente du gradient
+    avec la stratégie par lot
+    
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var+1]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    theta : matrices contenant les paramètres theta du modèle linéaire de dimension [1, nb_var+1]
+    alpha : taux d'apprentissage
+    nb_iters : nombre d'itérations
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+
+    Retour
+    -------
+    theta : matrices contenant les paramètres theta appris par descente du gradient de dimension [1, nb_var+1]
+    J_history : tableau contenant les valeurs de la fonction cout pour chaque iteration de dimension nb_iters
+
+
+    """
+    
+    # Initialisation de variables utiles
+    N = X.shape[0]
+    J_history = np.zeros(nb_iters)
+
+    for i in range(0, nb_iters):
+
+        error = sigmoide(X.dot(theta.T)) - Y
+        theta -= (alpha/N)*np.sum(X*error, 0)
+
+        J_history[i] = calcule_cout(X, Y, theta)
+        
+
+    return theta, J_history
+
+def descente_gradient_stochastique(X, Y, theta, alpha, nb_iters):
+    """ Apprentissage des parametres de regression logistique par descente du gradient
+    avec la stratégie stochastique
+    
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var+1]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    theta : matrices contenant les paramètres theta du modèle linéaire de dimension [1, nb_var+1]
+    alpha : taux d'apprentissage
+    nb_iters : nombre d'itérations
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+
+    Retour
+    -------
+    theta : matrices contenant les paramètres theta appris par descente du gradient de dimension [1, nb_var+1]
+    J_history : tableau contenant les valeurs de la fonction cout pour chaque iteration de dimension nb_iters
+
+
+    """
+    
+    # Initialisation de variables utiles
+    N = X.shape[0]
+    J_history = np.zeros(nb_iters)
+
+    # Création d'un vecteur d'indices pour parcourir X élément par élément, dans un ordre aléatoire
+    indices = np.arange(N)
+    np.random.shuffle(indices)
+
+    for i in range(0, nb_iters):
+
+        for j in range(N):
+
+            error = sigmoide(X[indices[j],:].dot(theta.T)) - Y[indices[j]]
+            theta -= alpha*X[indices[j],:]*error
+
+        J_history[i] = calcule_cout(X, Y, theta)
+        
+
+    return theta, J_history
+
+def descente_gradient_mini_lot(X, Y, theta, alpha, nb_iters, taille_lots=16):
+    """ Apprentissage des parametres de regression logistique par descente du gradient
+    avec la stratégie par mini-lots
+    
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var+1]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    theta : matrices contenant les paramètres theta du modèle linéaire de dimension [1, nb_var+1]
+    alpha : taux d'apprentissage
+    nb_iters : nombre d'itérations
+    taille_lots = nombre d'éléments dans chaque mini-lot
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+
+    Retour
+    -------
+    theta : matrices contenant les paramètres theta appris par descente du gradient de dimension [1, nb_var+1]
+    J_history : tableau contenant les valeurs de la fonction cout pour chaque iteration de dimension nb_iters
+
+
+    """
+    
+    # Initialisation de variables utiles
+    N = X.shape[0]
+    J_history = np.zeros(nb_iters)
+
+    # Création d'un vecteur d'indices pour parcourir X élément par élément, dans un ordre aléatoire
+    indices = np.arange(N)
+    np.random.shuffle(indices)
+    nb_lots = N // taille_lots
+    indices_lots = []
+    for i in range(nb_lots):
+        ind_debut = i*taille_lots
+        indices_lots.append(indices[ind_debut:ind_debut+taille_lots])
+
+    for i in range(0, nb_iters):
+
+        for j in range(nb_lots):
+
+            error = sigmoide(X[indices_lots[j],:].dot(theta.T)) - Y[indices_lots[j]]
+            theta -= (alpha/taille_lots)*np.sum(X[indices_lots[j],:]*error, 0)
+
+        J_history[i] = calcule_cout(X, Y, theta)
+        
+
+    return theta, J_history
+
+
+def prediction(X,theta):
+    """ Predit la classe de chaque élement de X
+    
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var+1]
+    theta : matrices contenant les paramètres theta du modèle linéaire de dimension [1, nb_var+1]
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+
+    Retour
+    -------
+    p : matrices de dimension [N,1] indiquant la classe de chaque élement de X (soit 0, soit 1)
+
+    """
+
+    p = sigmoide(X.dot(theta.T))
+    pos = np.where(p >= 0.5)
+    neg = np.where(p < 0.5)
+
+    p[pos] = 1
+    p[neg] = 0
+
+    return p
+
+def taux_classification(Ypred,Y):
+    """ Calcule le taux de classification (proportion d'éléments bien classés)
+    
+    Parametres
+    ----------
+    Ypred : matrice contenant les valeurs de classe prédites de dimension [N, 1]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    
+    avec N : nombre d'éléments 
+
+
+    Retour
+    -------
+    t : taux de classification
+
+    """
+
+    N = Ypred.size
+    nb_erreurs = np.sum(np.abs(Ypred-Y))
+    
+    t = (N-nb_erreurs) / N
+
+    return t
+
+def affichage(X, Y):
+    """ Affichage en 2 dimensions des données (2 dimensions de X) et représentation de la 
+        classe (indiquée par Y) par une couleur
+    
+
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var+1]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+    Retour
+    -------
+    None
+
+    """
+
+    pos = np.where(Y == 1)[0]
+    neg = np.where(Y == 0)[0]
+    plt.scatter(X[pos, 1], X[pos, 2], marker="+", c='b')
+    plt.scatter(X[neg, 1], X[neg, 2], marker="o", c='r')
+
+
+if __name__ == "__main__":
+    # ===================== Partie 1: Lecture et normalisation des données=====================
+    print("Lecture des données ...")
+
+    X, Y, N, nb_var = lecture_donnees("notes.txt")
+
+    # Affichage des 10 premiers exemples du dataset
+    print("Affichage des 10 premiers exemples du dataset : ")
+    for i in range(0, 10):
+        print(f"x = {X[i,:]}, y = {Y[i]}")
+        
+    # Normalisation des variables (centrage-réduction)
+    print("Normalisation des variables ...")
+
+    X, mu, sigma = normalisation(X)
+
+    # Ajout d'une colonne de 1 à X (pour theta0)
+    X = np.hstack((np.ones((N,1)), X)) 
+
+    # Découpage des données en un ensemble d'apprentissage et un ensemble de test
+    X_app, Y_app, X_test, Y_test = decoupage_donnees(X,Y,0.8)
+
+    # Affichage des points d'apprentissage en 2D et représentation de leur classe réelle par une couleur
+    if nb_var == 2 :
+        plt.figure()
+        plt.title("Disposition des points en 2D - Réalité (apprentissage)")
+        affichage(X_app,Y_app)
+
+    # Affichage des points de test en 2D et représentation de leur classe réelle par une couleur
+    if nb_var == 2 :
+        plt.figure()
+        plt.title("Disposition des points en 2D - Réalité (test)")
+        affichage(X_test,Y_test)
+
+    # ===================== Partie 2: Descente du gradient =====================
+    print("Apprentissage par descente du gradient ...")
+
+    # Choix du taux d'apprentissage et du nombre d'itérations
+    alpha = 0.01
+    nb_iters = 50
+
+    # Initialisation de theta et réalisation de la descente du gradient
+    theta = np.zeros((1,nb_var+1))
+    # theta, J_history = descente_gradient_lot(X_app, Y_app, theta, alpha, nb_iters)
+    # theta, J_history = descente_gradient_stochastique(X_app, Y_app, theta, alpha, nb_iters)
+    theta, J_history = descente_gradient_mini_lot(X_app, Y_app, theta, alpha, nb_iters, taille_lots=16)
+    
+
+    # Affichage de l'évolution de la fonction de cout lors de la descente du gradient
+    plt.figure()
+    plt.title("Evolution de le fonction de cout lors de la descente du gradient")
+    plt.plot(np.arange(J_history.size), J_history)
+    plt.xlabel("Nombre d'iterations")
+    plt.ylabel("Cout J")
+
+    # Affichage de la valeur de theta
+    print(f"Theta calculé par la descente du gradient : {theta}")
+
+    # Evaluation du modèle sur les données d'apprentissage
+    Ypred_app = prediction(X_app,theta)
+
+    print("Taux de classification sur l'ensemble d'apprentissage : ", taux_classification(Ypred_app,Y_app))
+
+    # Evaluation du modèle sur les données de test
+    Ypred_test = prediction(X_test,theta)
+
+    print("Taux de classification sur l'ensemble de test : ", taux_classification(Ypred_test,Y_test))
+
+    # Affichage des points d'apprentissage en 2D et représentation de leur classe prédite par une couleur
+    if nb_var == 2 :
+        plt.figure()
+        plt.title("Disposition des points en 2D - Prédiction (apprentissage)")
+        affichage(X_app,Ypred_app)
+
+    # Affichage des points de test en 2D et représentation de leur classe prédite par une couleur
+    if nb_var == 2 :
+        plt.figure()
+        plt.title("Disposition des points en 2D - Prédiction (test)")
+        affichage(X_test,Ypred_test)
+
+
+    plt.show()
+
+    print("Regression logistique Terminée.")
diff --git a/TD/TD3/regression_logistique-etendu-3-complet.py b/TD/TD3/regression_logistique-etendu-3-complet.py
new file mode 100644
index 0000000000000000000000000000000000000000..b6a07efb075b9eaa8975bb04335205682f480b1f
--- /dev/null
+++ b/TD/TD3/regression_logistique-etendu-3-complet.py
@@ -0,0 +1,436 @@
+# Ajout de la classification multi-classes
+
+import matplotlib.pyplot as plt
+import numpy as np
+
+def lecture_donnees(nom_fichier, delimiteur=','):
+    """ Lit le fichier contenant les données et renvoiee les matrices correspondant
+
+    Parametres
+    ----------
+    nom_fichier : nom du fichier contenant les données
+    delimiteur : caratère délimitant les colonne dans le fichier ("," par défaut)
+
+    Retour
+    -------
+    X : matrice des données de dimension [N, nb_var]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    N : nombre d'éléments
+    nb_var : nombre de variables prédictives
+    nb_classes : nombre de classes (max de la valeur de la variable cible +1)
+
+
+    """
+    
+    data = np.loadtxt(nom_fichier, delimiter=delimiteur)
+    nb_var = data.shape[1] - 1
+    N = data.shape[0]
+
+    X = data[:, :-1]
+    Y = data[:, -1].reshape(N,1)
+    
+    nb_classes = int(np.max(Y)+1)
+
+    return X, Y, N, nb_var, nb_classes
+
+def normalisation(X):
+    """ Normalise les données par un centrage-réduction des variables prédictives
+    
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var]
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+    Retour
+    -------
+    X_norm : matrice des données centrées-réduites de dimension [N, nb_var]
+    mu : moyenne des variables de dimension [1,nb_var]
+    sigma : écart-type des variables de dimension [1,nb_var]
+    
+    """
+    
+    mu = np.mean(X, 0)
+    sigma = np.std(X, 0)
+    X_norm = (X - mu) / sigma
+
+    return X_norm, mu, sigma
+
+def decoupage_donnees(X,Y,ratio_apprentissage):
+    """ Découpe les données en deux ensembles : apprentissage et test
+
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    ratio_apprentissage : ratio de données d’apprentissage par rapport aux données de test (entre 0 et 1)
+
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+    Retour
+    -------
+    X_app : matrice des données d'apprentissage
+    Y_app : matrice contenant les valeurs de la variable cible pour les données d'apprentissage
+    X_test : matrice des données de test
+    Y_test : matrice contenant les valeurs de la variable cible pour les données de test
+
+    """
+
+    # Création d'un vecteur d'indices dans un ordre aléatoire
+    N = X.shape[0]
+    indices = np.arange(N)
+    np.random.shuffle(indices)
+
+    indice_split = int(N*ratio_apprentissage)
+    
+    indices_app = indices[:indice_split]
+    indices_test = indices[indice_split:]
+
+    X_app = X[indices_app,:]
+    Y_app = Y[indices_app]
+    X_test = X[indices_test,:]
+    Y_test = Y[indices_test]
+
+    return X_app, Y_app, X_test, Y_test
+
+
+def sigmoide(z):
+    """ Calcule la valeur de la fonction sigmoide appliquée à z
+    
+    Parametres
+    ----------
+    z : peut être un scalaire ou une matrice
+
+    Return
+    -------
+    s : valeur de sigmoide de z. Même dimension que z
+
+    """
+
+    s = 1 / (1 + np.exp(-z))
+
+    return s
+
+def calcule_cout(X, Y, theta):
+    """ Calcule la valeur de la fonction cout ( - log vraisemblance)
+    
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var+1]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    theta : matrices contenant les paramètres theta du modèle linéaire de dimension [1, nb_var+1]
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+    Return
+    -------
+    cout : nombre correspondant à la valeur de la fonction cout (moyenne des différences au carré)
+
+    """
+
+    N = X.shape[0]
+    
+    cout = - (Y*np.log(sigmoide(X.dot(theta.T))) + (1-Y)*np.log(1-sigmoide(X.dot(theta.T)))).sum() / N
+
+    return cout
+
+def descente_gradient_lot(X, Y, theta, alpha, nb_iters):
+    """ Apprentissage des parametres de regression logistique par descente du gradient
+    avec la stratégie par lot
+    
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var+1]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    theta : matrices contenant les paramètres theta du modèle linéaire de dimension [1, nb_var+1]
+    alpha : taux d'apprentissage
+    nb_iters : nombre d'itérations
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+
+    Retour
+    -------
+    theta : matrices contenant les paramètres theta appris par descente du gradient de dimension [1, nb_var+1]
+    J_history : tableau contenant les valeurs de la fonction cout pour chaque iteration de dimension nb_iters
+
+
+    """
+    
+    # Initialisation de variables utiles
+    N = X.shape[0]
+    J_history = np.zeros(nb_iters)
+
+    for i in range(0, nb_iters):
+
+        error = sigmoide(X.dot(theta.T)) - Y
+        theta -= (alpha/N)*np.sum(X*error, 0)
+
+        J_history[i] = calcule_cout(X, Y, theta)
+        
+
+    return theta, J_history
+
+def descente_gradient_stochastique(X, Y, theta, alpha, nb_iters):
+    """ Apprentissage des parametres de regression logistique par descente du gradient
+    avec la stratégie stochastique
+    
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var+1]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    theta : matrices contenant les paramètres theta du modèle linéaire de dimension [1, nb_var+1]
+    alpha : taux d'apprentissage
+    nb_iters : nombre d'itérations
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+
+    Retour
+    -------
+    theta : matrices contenant les paramètres theta appris par descente du gradient de dimension [1, nb_var+1]
+    J_history : tableau contenant les valeurs de la fonction cout pour chaque iteration de dimension nb_iters
+
+
+    """
+    
+    # Initialisation de variables utiles
+    N = X.shape[0]
+    J_history = np.zeros(nb_iters)
+
+    # Création d'un vecteur d'indices pour parcourir X élément par élément, dans un ordre aléatoire
+    indices = np.arange(N)
+    np.random.shuffle(indices)
+
+    for i in range(0, nb_iters):
+
+        for j in range(N):
+
+            error = sigmoide(X[indices[j],:].dot(theta.T)) - Y[indices[j]]
+            theta -= alpha*X[indices[j],:]*error
+
+        J_history[i] = calcule_cout(X, Y, theta)
+        
+
+    return theta, J_history
+
+def descente_gradient_mini_lot(X, Y, theta, alpha, nb_iters, taille_lots=16):
+    """ Apprentissage des parametres de regression logistique par descente du gradient
+    avec la stratégie par mini-lots
+    
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var+1]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    theta : matrices contenant les paramètres theta du modèle linéaire de dimension [1, nb_var+1]
+    alpha : taux d'apprentissage
+    nb_iters : nombre d'itérations
+    taille_lots = nombre d'éléments dans chaque mini-lot
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+
+    Retour
+    -------
+    theta : matrices contenant les paramètres theta appris par descente du gradient de dimension [1, nb_var+1]
+    J_history : tableau contenant les valeurs de la fonction cout pour chaque iteration de dimension nb_iters
+
+
+    """
+    
+    # Initialisation de variables utiles
+    N = X.shape[0]
+    J_history = np.zeros(nb_iters)
+
+    # Création d'un vecteur d'indices pour parcourir X élément par élément, dans un ordre aléatoire
+    indices = np.arange(N)
+    np.random.shuffle(indices)
+    nb_lots = N // taille_lots
+    indices_lots = []
+    for i in range(nb_lots):
+        ind_debut = i*taille_lots
+        indices_lots.append(indices[ind_debut:ind_debut+taille_lots])
+
+    for i in range(0, nb_iters):
+
+        for j in range(nb_lots):
+
+            error = sigmoide(X[indices_lots[j],:].dot(theta.T)) - Y[indices_lots[j]]
+            theta -= (alpha/taille_lots)*np.sum(X[indices_lots[j],:]*error, 0)
+
+        J_history[i] = calcule_cout(X, Y, theta)
+        
+
+    return theta, J_history
+
+
+def prediction(X,thetas):
+    """ Predit la classe de chaque élement de X
+    
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var+1]
+    thetas : liste contenant les paramètres theta des modèle logistiques de dimension [1, nb_var+1] pour chaque classe
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+
+    Retour
+    -------
+    p : matrices de dimension [N,1] indiquant la classe de chaque élement de X 
+
+    """
+
+    N = X.shape[0]
+
+    preds = sigmoide(X.dot(thetas[0].T)).reshape(N,1)
+    
+    for i in range(1,len(thetas)):
+        pred = sigmoide(X.dot(thetas[i].T)).reshape(N,1)
+        preds = np.concatenate((preds, pred), axis=1)
+
+    p = np.argmax(preds,1)
+    
+    return p
+
+def taux_classification(Ypred,Y):
+    """ Calcule le taux de classification (proportion d'éléments bien classés)
+    
+    Parametres
+    ----------
+    Ypred : matrice contenant les valeurs de classe prédites de dimension [N, 1]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    
+    avec N : nombre d'éléments 
+
+
+    Retour
+    -------
+    t : taux de classification
+
+    """
+
+    t = np.mean(Ypred==Y)
+    return t
+
+def affichage(X, Y):
+    """ Affichage en 2 dimensions des données (2 dimensions de X) et représentation de la 
+        classe (indiquée par Y) par une couleur
+    
+
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var+1]
+    Y : matrice contenant les valeurs de la variable cible de dimension [N, 1]
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+    Retour
+    -------
+    None
+
+    """
+
+    plt.scatter(X[:, 1], X[:, 2], c=Y)
+
+
+if __name__ == "__main__":
+    # ===================== Partie 1: Lecture et normalisation des données=====================
+    print("Lecture des données ...")
+
+    X, Y, N, nb_var, nb_classes = lecture_donnees("iris.txt")
+
+    # Affichage des 10 premiers exemples du dataset
+    print("Affichage des 10 premiers exemples du dataset : ")
+    for i in range(0, 10):
+        print(f"x = {X[i,:]}, y = {Y[i]}")
+        
+    # Normalisation des variables (centrage-réduction)
+    print("Normalisation des variables ...")
+
+    X, mu, sigma = normalisation(X)
+
+    # Ajout d'une colonne de 1 à X (pour theta0)
+    X = np.hstack((np.ones((N,1)), X)) 
+
+    # Découpage des données en un ensemble d'apprentissage et un ensemble de test
+    X_app, Y_app, X_test, Y_test = decoupage_donnees(X,Y,0.8)
+
+    # Affichage des points d'apprentissage en 2D et représentation de leur classe réelle par une couleur
+
+    plt.figure()
+    plt.title("Disposition des points en 2D - Réalité (apprentissage)")
+    affichage(X_app,Y_app)
+
+    # Affichage des points de test en 2D et représentation de leur classe réelle par une couleur
+
+    plt.figure()
+    plt.title("Disposition des points en 2D - Réalité (test)")
+    affichage(X_test,Y_test)
+
+    # ===================== Partie 2: Descente du gradient =====================
+    print("Apprentissage par descente du gradient ...")
+
+    # Choix du taux d'apprentissage et du nombre d'itérations
+    alpha = 0.01
+    nb_iters = 5000
+
+    # Initialisation de theta et réalisation de la descente du gradient pour chaque modèle
+    thetas = []
+    J_histories = []
+    for ind_classe in range(nb_classes):
+        theta = np.zeros((1,nb_var+1))
+
+        pos = np.where(Y_app == ind_classe)
+        neg = np.where(Y_app != ind_classe)
+
+        Y_app_modif = np.copy(Y_app)
+        Y_app_modif[pos] = 1
+        Y_app_modif[neg] = 0
+
+
+        theta, J_history = descente_gradient_lot(X_app, Y_app_modif, theta, alpha, nb_iters)
+        # theta, J_history = descente_gradient_stochastique(X_app, Y_app_modif, theta, alpha, nb_iters)
+        # theta, J_history = descente_gradient_mini_lot(X_app, Y_app_modif, theta, alpha, nb_iters, taille_lots=16)
+    
+        thetas.append(theta)
+        J_histories.append(J_history)
+
+
+    # Affichage de l'évolution de la fonction de cout lors de la descente du gradient
+    plt.figure()
+    plt.title(f"Evolution de le fonction de cout lors de la descente du gradient")
+    for i in range(len(J_histories)):
+        plt.plot(np.arange(J_histories[i].size), J_histories[i], label = f"Class {i}")
+    plt.xlabel("Nombre d'iterations")
+    plt.ylabel("Cout J")
+    plt.legend()
+
+    # Affichage de la valeur de thetas
+    print(f"Thetas calculés par la descente du gradient : {thetas}")
+
+    # Evaluation du modèle sur les données d'apprentissage
+    Ypred_app = prediction(X_app,thetas).reshape(-1,1)
+    print("Taux de classification sur l'ensemble d'apprentissage : ", taux_classification(Ypred_app,Y_app))
+
+    # Evaluation du modèle sur les données de test
+    Ypred_test = prediction(X_test,thetas).reshape(-1,1)
+
+    print("Taux de classification sur l'ensemble de test : ", taux_classification(Ypred_test,Y_test))
+
+    # Affichage des points d'apprentissage en 2D et représentation de leur classe prédite par une couleur
+
+    plt.figure()
+    plt.title("Disposition des points en 2D - Prédiction (apprentissage)")
+    affichage(X_app,Ypred_app)
+
+    # Affichage des points de test en 2D et représentation de leur classe prédite par une couleur
+
+    plt.figure()
+    plt.title("Disposition des points en 2D - Prédiction (test)")
+    affichage(X_test,Ypred_test)
+
+
+    plt.show()
+
+    print("Regression logistique Terminée.")
diff --git a/TD/TD4/ML-TD4.pdf b/TD/TD4/ML-TD4.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..0d3763f3b4f17ed25c7675d631385796edc38395
Binary files /dev/null and b/TD/TD4/ML-TD4.pdf differ
diff --git a/TD/TD4/food_truck.txt b/TD/TD4/food_truck.txt
new file mode 100644
index 0000000000000000000000000000000000000000..0f88ccb611f840ba9283e0de2a26b6cb9b8fde02
--- /dev/null
+++ b/TD/TD4/food_truck.txt
@@ -0,0 +1,97 @@
+6.1101,17.592
+5.5277,9.1302
+8.5186,13.662
+7.0032,11.854
+5.8598,6.8233
+8.3829,11.886
+7.4764,4.3483
+8.5781,12
+6.4862,6.5987
+5.0546,3.8166
+5.7107,3.2522
+14.164,15.505
+5.734,3.1551
+8.4084,7.2258
+5.6407,0.71618
+5.3794,3.5129
+6.3654,5.3048
+5.1301,0.56077
+6.4296,3.6518
+7.0708,5.3893
+6.1891,3.1386
+20.27,21.767
+5.4901,4.263
+6.3261,5.1875
+5.5649,3.0825
+18.945,22.638
+12.828,13.501
+10.957,7.0467
+13.176,14.692
+22.203,24.147
+5.2524,-1.22
+6.5894,5.9966
+9.2482,12.134
+5.8918,1.8495
+8.2111,6.5426
+7.9334,4.5623
+8.0959,4.1164
+5.6063,3.3928
+12.836,10.117
+6.3534,5.4974
+5.4069,0.55657
+6.8825,3.9115
+11.708,5.3854
+5.7737,2.4406
+7.8247,6.7318
+7.0931,1.0463
+5.0702,5.1337
+5.8014,1.844
+11.7,8.0043
+5.5416,1.0179
+7.5402,6.7504
+5.3077,1.8396
+7.4239,4.2885
+7.6031,4.9981
+6.3328,1.4233
+6.3589,-1.4211
+6.2742,2.4756
+5.6397,4.6042
+9.3102,3.9624
+9.4536,5.4141
+8.8254,5.1694
+5.1793,-0.74279
+21.279,17.929
+14.908,12.054
+18.959,17.054
+7.2182,4.8852
+8.2951,5.7442
+10.236,7.7754
+5.4994,1.0173
+20.341,20.992
+10.136,6.6799
+7.3345,4.0259
+6.0062,1.2784
+7.2259,3.3411
+5.0269,-2.6807
+6.5479,0.29678
+7.5386,3.8845
+5.0365,5.7014
+10.274,6.7526
+5.1077,2.0576
+5.7292,0.47953
+5.1884,0.20421
+6.3557,0.67861
+9.7687,7.5435
+6.5159,5.3436
+8.5172,4.2415
+9.1802,6.7981
+6.002,0.92695
+5.5204,0.152
+5.0594,2.8214
+5.7077,1.8451
+7.6366,4.2959
+5.8707,7.2029
+5.3054,1.9869
+8.2934,0.14454
+13.394,9.0551
+5.4369,0.61705
diff --git a/TD/TD4/houses.txt b/TD/TD4/houses.txt
new file mode 100644
index 0000000000000000000000000000000000000000..79e9a807edd86632d58aa2ec832e190d997f43e7
--- /dev/null
+++ b/TD/TD4/houses.txt
@@ -0,0 +1,47 @@
+2104,3,399900
+1600,3,329900
+2400,3,369000
+1416,2,232000
+3000,4,539900
+1985,4,299900
+1534,3,314900
+1427,3,198999
+1380,3,212000
+1494,3,242500
+1940,4,239999
+2000,3,347000
+1890,3,329999
+4478,5,699900
+1268,3,259900
+2300,4,449900
+1320,2,299900
+1236,3,199900
+2609,4,499998
+3031,4,599000
+1767,3,252900
+1888,2,255000
+1604,3,242900
+1962,4,259900
+3890,3,573900
+1100,3,249900
+1458,3,464500
+2526,3,469000
+2200,3,475000
+2637,3,299900
+1839,2,349900
+1000,1,169900
+2040,4,314900
+3137,3,579900
+1811,4,285900
+1437,3,249900
+1239,3,229900
+2132,4,345000
+4215,4,549000
+2162,4,287000
+1664,2,368500
+2238,3,329900
+2567,4,314000
+1200,3,299000
+852,2,179900
+1852,4,299900
+1203,3,239500
diff --git a/TD/TD4/iris.txt b/TD/TD4/iris.txt
new file mode 100644
index 0000000000000000000000000000000000000000..1de4bbac2d427f72603868801f7647f04cb281a1
--- /dev/null
+++ b/TD/TD4/iris.txt
@@ -0,0 +1,150 @@
+5.1,3.5,1.4,0.2,0
+4.9,3.0,1.4,0.2,0
+4.7,3.2,1.3,0.2,0
+4.6,3.1,1.5,0.2,0
+5.0,3.6,1.4,0.2,0
+5.4,3.9,1.7,0.4,0
+4.6,3.4,1.4,0.3,0
+5.0,3.4,1.5,0.2,0
+4.4,2.9,1.4,0.2,0
+4.9,3.1,1.5,0.1,0
+5.4,3.7,1.5,0.2,0
+4.8,3.4,1.6,0.2,0
+4.8,3.0,1.4,0.1,0
+4.3,3.0,1.1,0.1,0
+5.8,4.0,1.2,0.2,0
+5.7,4.4,1.5,0.4,0
+5.4,3.9,1.3,0.4,0
+5.1,3.5,1.4,0.3,0
+5.7,3.8,1.7,0.3,0
+5.1,3.8,1.5,0.3,0
+5.4,3.4,1.7,0.2,0
+5.1,3.7,1.5,0.4,0
+4.6,3.6,1.0,0.2,0
+5.1,3.3,1.7,0.5,0
+4.8,3.4,1.9,0.2,0
+5.0,3.0,1.6,0.2,0
+5.0,3.4,1.6,0.4,0
+5.2,3.5,1.5,0.2,0
+5.2,3.4,1.4,0.2,0
+4.7,3.2,1.6,0.2,0
+4.8,3.1,1.6,0.2,0
+5.4,3.4,1.5,0.4,0
+5.2,4.1,1.5,0.1,0
+5.5,4.2,1.4,0.2,0
+4.9,3.1,1.5,0.1,0
+5.0,3.2,1.2,0.2,0
+5.5,3.5,1.3,0.2,0
+4.9,3.1,1.5,0.1,0
+4.4,3.0,1.3,0.2,0
+5.1,3.4,1.5,0.2,0
+5.0,3.5,1.3,0.3,0
+4.5,2.3,1.3,0.3,0
+4.4,3.2,1.3,0.2,0
+5.0,3.5,1.6,0.6,0
+5.1,3.8,1.9,0.4,0
+4.8,3.0,1.4,0.3,0
+5.1,3.8,1.6,0.2,0
+4.6,3.2,1.4,0.2,0
+5.3,3.7,1.5,0.2,0
+5.0,3.3,1.4,0.2,0
+7.0,3.2,4.7,1.4,1
+6.4,3.2,4.5,1.5,1
+6.9,3.1,4.9,1.5,1
+5.5,2.3,4.0,1.3,1
+6.5,2.8,4.6,1.5,1
+5.7,2.8,4.5,1.3,1
+6.3,3.3,4.7,1.6,1
+4.9,2.4,3.3,1.0,1
+6.6,2.9,4.6,1.3,1
+5.2,2.7,3.9,1.4,1
+5.0,2.0,3.5,1.0,1
+5.9,3.0,4.2,1.5,1
+6.0,2.2,4.0,1.0,1
+6.1,2.9,4.7,1.4,1
+5.6,2.9,3.6,1.3,1
+6.7,3.1,4.4,1.4,1
+5.6,3.0,4.5,1.5,1
+5.8,2.7,4.1,1.0,1
+6.2,2.2,4.5,1.5,1
+5.6,2.5,3.9,1.1,1
+5.9,3.2,4.8,1.8,1
+6.1,2.8,4.0,1.3,1
+6.3,2.5,4.9,1.5,1
+6.1,2.8,4.7,1.2,1
+6.4,2.9,4.3,1.3,1
+6.6,3.0,4.4,1.4,1
+6.8,2.8,4.8,1.4,1
+6.7,3.0,5.0,1.7,1
+6.0,2.9,4.5,1.5,1
+5.7,2.6,3.5,1.0,1
+5.5,2.4,3.8,1.1,1
+5.5,2.4,3.7,1.0,1
+5.8,2.7,3.9,1.2,1
+6.0,2.7,5.1,1.6,1
+5.4,3.0,4.5,1.5,1
+6.0,3.4,4.5,1.6,1
+6.7,3.1,4.7,1.5,1
+6.3,2.3,4.4,1.3,1
+5.6,3.0,4.1,1.3,1
+5.5,2.5,4.0,1.3,1
+5.5,2.6,4.4,1.2,1
+6.1,3.0,4.6,1.4,1
+5.8,2.6,4.0,1.2,1
+5.0,2.3,3.3,1.0,1
+5.6,2.7,4.2,1.3,1
+5.7,3.0,4.2,1.2,1
+5.7,2.9,4.2,1.3,1
+6.2,2.9,4.3,1.3,1
+5.1,2.5,3.0,1.1,1
+5.7,2.8,4.1,1.3,1
+6.3,3.3,6.0,2.5,2
+5.8,2.7,5.1,1.9,2
+7.1,3.0,5.9,2.1,2
+6.3,2.9,5.6,1.8,2
+6.5,3.0,5.8,2.2,2
+7.6,3.0,6.6,2.1,2
+4.9,2.5,4.5,1.7,2
+7.3,2.9,6.3,1.8,2
+6.7,2.5,5.8,1.8,2
+7.2,3.6,6.1,2.5,2
+6.5,3.2,5.1,2.0,2
+6.4,2.7,5.3,1.9,2
+6.8,3.0,5.5,2.1,2
+5.7,2.5,5.0,2.0,2
+5.8,2.8,5.1,2.4,2
+6.4,3.2,5.3,2.3,2
+6.5,3.0,5.5,1.8,2
+7.7,3.8,6.7,2.2,2
+7.7,2.6,6.9,2.3,2
+6.0,2.2,5.0,1.5,2
+6.9,3.2,5.7,2.3,2
+5.6,2.8,4.9,2.0,2
+7.7,2.8,6.7,2.0,2
+6.3,2.7,4.9,1.8,2
+6.7,3.3,5.7,2.1,2
+7.2,3.2,6.0,1.8,2
+6.2,2.8,4.8,1.8,2
+6.1,3.0,4.9,1.8,2
+6.4,2.8,5.6,2.1,2
+7.2,3.0,5.8,1.6,2
+7.4,2.8,6.1,1.9,2
+7.9,3.8,6.4,2.0,2
+6.4,2.8,5.6,2.2,2
+6.3,2.8,5.1,1.5,2
+6.1,2.6,5.6,1.4,2
+7.7,3.0,6.1,2.3,2
+6.3,3.4,5.6,2.4,2
+6.4,3.1,5.5,1.8,2
+6.0,3.0,4.8,1.8,2
+6.9,3.1,5.4,2.1,2
+6.7,3.1,5.6,2.4,2
+6.9,3.1,5.1,2.3,2
+5.8,2.7,5.1,1.9,2
+6.8,3.2,5.9,2.3,2
+6.7,3.3,5.7,2.5,2
+6.7,3.0,5.2,2.3,2
+6.3,2.5,5.0,1.9,2
+6.5,3.0,5.2,2.0,2
+6.2,3.4,5.4,2.3,2
+5.9,3.0,5.1,1.8,2
\ No newline at end of file
diff --git a/TD/TD4/rn_regression.py b/TD/TD4/rn_regression.py
new file mode 100644
index 0000000000000000000000000000000000000000..3c6e7727fd4b858354374acd392f88b46ce3f4c6
--- /dev/null
+++ b/TD/TD4/rn_regression.py
@@ -0,0 +1,321 @@
+import matplotlib.pyplot as plt
+import numpy as np
+
+def lecture_donnees(nom_fichier, delimiteur=','):
+    """ Lit le fichier contenant les données et renvoiee les matrices correspondant
+
+    Parametres
+    ----------
+    nom_fichier : nom du fichier contenant les données
+    delimiteur : caratère délimitant les colonne dans le fichier ("," par défaut)
+
+    Retour
+    -------
+    x : matrice des données de dimension [N, nb_var]
+    d : matrice contenant les valeurs de la variable cible de dimension [N, nb_cible]
+    N : nombre d'éléments
+    nb_var : nombre de variables prédictives
+    nb_cible : nombre de variables cibles
+
+    """
+    
+    data = np.loadtxt(nom_fichier, delimiter=delimiteur)
+    
+    #########################################################
+    ##### A compléter (et supprimer l'instruction pass) ##### 
+    #########################################################
+    pass
+
+    # return x, d, N, nb_var, nb_cible
+
+def normalisation(x):
+    """ Normalise les données par un centrage-réduction des variables prédictives
+    
+    Parametres
+    ----------
+    X : matrice des données de dimension [N, nb_var]
+    
+    avec N : nombre d'éléments et nb_var : nombre de variables prédictives
+
+    Retour
+    -------
+    X_norm : matrice des données centrées-réduites de dimension [N, nb_var]
+    mu : moyenne des variables de dimension [1,nb_var]
+    sigma : écart-type des variables de dimension [1,nb_var]
+    
+    """
+    
+    #########################################################
+    ##### A compléter (et supprimer l'instruction pass) ##### 
+    #########################################################
+    pass
+
+    # return x_norm, mu, sigma
+
+def decoupage_donnees(x,d,prop_val=0.2, prop_test=0.2):
+    """ Découpe les données initiales en trois sous-ensembles distincts d'apprentissage, de validation et de test
+    
+    Parametres
+    ----------
+    x : matrice des données de dimension [N, nb_var]
+    d : matrice des valeurs cibles [N, nb_cible]
+    prop_val : proportion des données de validation sur l'ensemble des données (entre 0 et 1)
+    prop_test : proportion des données de test sur l'ensemble des données (entre 0 et 1)
+    
+    avec N : nombre d'éléments, nb_var : nombre de variables prédictives, nb_cible : nombre de variables cibles
+
+    Retour
+    -------
+    x_app : matrice des données d'apprentissage
+    d_app : matrice des valeurs cibles d'apprentissage
+    x_val : matrice des données d'apprentissage
+    d_val : matrice des valeurs cibles d'apprentissage
+    x_test : matrice des données d'apprentissage
+    d_test : matrice des valeurs cibles d'apprentissage
+
+    """
+    #########################################################
+    ##### A compléter (et supprimer l'instruction pass) ##### 
+    #########################################################
+    pass
+
+    # return x_app, d_app, x_val, d_val, x_test, d_test
+
+def calcule_cout_mse(y, d):
+    """ Calcule la valeur de la fonction cout MSE (moyenne des différences au carré)
+    
+    Parametres
+    ----------
+    y : matrice des données prédites 
+    d : matrice des données réelles 
+    
+    Return
+    -------
+    cout : nombre correspondant à la valeur de la fonction cout (moyenne des différences au carré)
+
+    """
+    #########################################################
+    ##### A compléter (et supprimer l'instruction pass) ##### 
+    #########################################################
+    pass
+
+    # return cout
+
+def passe_avant(x, W, b, activation):
+    """ Réalise une passe avant dans le réseau de neurones
+    
+    Parametres
+    ----------
+    x : matrice des entrées, de dimension nb_var x N
+    W : liste contenant les matrices des poids du réseau
+    b : liste contenant les matrices des biais du réseau
+    activation : liste contenant les fonctions d'activation des couches du réseau
+
+    avec N : nombre d'éléments, nb_var : nombre de variables prédictives 
+
+    Return
+    -------
+    a : liste contenant les potentiels d'entrée des couches du réseau
+    h : liste contenant les sorties des couches du réseau
+
+    """
+    #########################################################
+    ##### A compléter (et supprimer l'instruction pass) ##### 
+    #########################################################
+    pass
+
+    # return a, h
+
+def passe_arriere(delta_h, a, h, W, activation):
+    """ Réalise une passe arrière dans le réseau de neurones (rétropropagation)
+    
+    Parametres
+    ----------
+    delta_h : matrice contenant les valeurs du gradient du coût par rapport à la sortie du réseau
+    a : liste contenant les potentiels d'entrée des couches du réseau
+    h : liste contenant les sorties des couches du réseau
+    W : liste contenant les matrices des poids du réseau
+    activation : liste contenant les fonctions d'activation des couches du réseau
+
+    Return
+    -------
+    delta_W : liste contenant les matrice des gradients des poids des couches du réseau
+    delta_b : liste contenant les matrice des gradients des biais des couches du réseau
+
+    """
+
+    #########################################################
+    ##### A compléter (et supprimer l'instruction pass) ##### 
+    #########################################################
+    pass
+
+    # return delta_W, delta_b
+
+def sigmoide(z, deriv=False):
+    """ Calcule la valeur de la fonction sigmoide ou de sa dérivée appliquée à z
+    
+    Parametres
+    ----------
+    z : peut être un scalaire ou une matrice
+    deriv : booléen. Si False renvoie la valeur de la fonction sigmoide, si True renvoie sa dérivée
+
+    Return
+    -------
+    s : valeur de la fonction sigmoide appliquée à z ou de sa dérivée. Même dimension que z
+
+    """
+
+    #########################################################
+    ##### A compléter (et supprimer l'instruction pass) ##### 
+    #########################################################
+    pass
+
+    # return s
+
+def lineaire(z, deriv=False):
+    """ Calcule la valeur de la fonction linéaire ou de sa dérivée appliquée à z
+    
+    Parametres
+    ----------
+    z : peut être un scalaire ou une matrice
+    deriv : booléen. Si False renvoie la valeur de la fonction linéire, si True renvoie sa dérivée
+
+
+    Return
+    -------
+    s : valeur de la fonction linéaire appliquée à z ou de sa dérivée. Même dimension que z
+
+    """
+    #########################################################
+    ##### A compléter (et supprimer l'instruction pass) ##### 
+    #########################################################
+    pass
+
+    # return s
+
+def relu(z, deriv=False):
+    """ Calcule la valeur de la fonction relu ou de sa dérivée appliquée à z
+    
+    Parametres
+    ----------
+    z : peut être un scalaire ou une matrice
+    deriv : booléen. Si False renvoie la valeur de la fonction relu, si True renvoie sa dérivée
+
+    Return
+    -------
+    s : valeur de la fonction relu appliquée à z ou de sa dérivée. Même dimension que z
+
+    """
+
+    #########################################################
+    ##### A compléter (et supprimer l'instruction pass) ##### 
+    #########################################################
+    pass
+
+    # return s
+
+
+# ===================== Partie 1: Lecture et normalisation des données =====================
+print("Lecture des données ...")
+
+x, d, N, nb_var, nb_cible = lecture_donnees("food_truck.txt")
+# x, d, N, nb_var, nb_cible = lecture_donnees("houses.txt")
+
+# Affichage des 10 premiers exemples du dataset
+print("Affichage des 10 premiers exemples du dataset : ")
+for i in range(0, 10):
+    print(f"x = {x[i,:]}, d = {d[i]}")
+    
+# Normalisation des variables (centrage-réduction)
+print("Normalisation des variables ...")
+x, mu, sigma = normalisation(x)
+dmax = d.max()
+d = d / dmax
+
+# Découpage des données en sous-ensemble d'apprentissage, de validation et de test
+x_app, d_app, x_val, d_val, x_test, d_test =  decoupage_donnees(x,d)
+
+# ===================== Partie 2: Apprentissage =====================
+
+# Choix du taux d'apprentissage et du nombre d'itérations
+alpha = 0.001
+nb_iters = 500
+couts_apprentissage = np.zeros(nb_iters)
+couts_validation = np.zeros(nb_iters)
+
+# Dimensions du réseau
+D_c = [nb_var, 5, 10, nb_cible] # liste contenant le nombre de neurones pour chaque couche 
+activation = [relu, sigmoide, lineaire] # liste contenant les fonctions d'activation des couches cachées et de la couche de sortie 
+
+# Initialisation aléatoire des poids du réseau
+W = []
+b = []
+for i in range(len(D_c)-1):    
+    W.append(2 * np.random.random((D_c[i+1], D_c[i])) - 1)
+    b.append(np.zeros((D_c[i+1],1)))
+
+x_app = x_app.T # Les données sont présentées en entrée du réseau comme des vecteurs colonnes
+d_app = d_app.T 
+
+x_val = x_val.T # Les données sont présentées en entrée du réseau comme des vecteurs colonnes
+d_val = d_val.T 
+
+x_test = x_test.T # Les données sont présentées en entrée du réseau comme des vecteurs colonnes
+d_test = d_test.T 
+
+for t in range(nb_iters):
+
+    #############################################################################
+    # Passe avant : calcul de la sortie prédite y sur les données de validation #
+    #############################################################################
+    a, h = passe_avant(x_val, W, b, activation)
+    y_val = h[-1] # Sortie prédite
+
+    ###############################################################################
+    # Passe avant : calcul de la sortie prédite y sur les données d'apprentissage #
+    ###############################################################################
+    a, h = passe_avant(x_app, W, b, activation)
+    y_app = h[-1] # Sortie prédite
+
+    ###########################################
+    # Calcul de la fonction perte de type MSE #
+    ###########################################
+    couts_apprentissage[t] = calcule_cout_mse(y_app,d_app)
+    couts_validation[t] = calcule_cout_mse(y_val,d_val)
+
+    ####################################
+    # Passe arrière : rétropropagation #
+    ####################################
+    delta_h = (y_app-d_app) # Pour la dernière couche 
+    delta_W, delta_b = passe_arriere(delta_h, a, h, W, activation)
+  
+    #############################################
+    # Mise à jour des poids et des biais  ##### #
+    ############################################# 
+    for i in range(len(b)-1,-1,-1):
+        b[i] -= alpha * delta_b[i]
+        W[i] -= alpha * delta_W[i]
+
+print("Coût final sur l'ensemble d'apprentissage : ", couts_apprentissage[-1])
+print("Coût final sur l'ensemble de validation : ", couts_validation[-1])
+
+# Affichage de l'évolution de la fonction de cout lors de la rétropropagation
+plt.figure(0)
+plt.title("Evolution de le fonction de coût lors de la retropropagation")
+plt.plot(np.arange(couts_apprentissage.size), couts_apprentissage, label="Apprentissage")
+plt.plot(np.arange(couts_validation.size), couts_validation, label="Validation")
+plt.legend(loc="upper left")
+plt.xlabel("Nombre d'iterations")
+plt.ylabel("Coût")
+plt.show()
+
+# ===================== Partie 3: Evaluation sur l'ensemble de test =====================
+
+#######################################################################
+# Passe avant : calcul de la sortie prédite y sur les données de test #
+#######################################################################
+a, h = passe_avant(x_test, W, b, activation)
+y_test = h[-1] # Sortie prédite
+
+cout = calcule_cout_mse(y_test,d_test)
+print("Coût sur l'ensemble de test : ", cout)
diff --git a/TD/TD4/scores.txt b/TD/TD4/scores.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3a5f95245719c6f7f08ece4a7785c0f0467c610e
--- /dev/null
+++ b/TD/TD4/scores.txt
@@ -0,0 +1,100 @@
+34.62365962451697,78.0246928153624,0
+30.28671076822607,43.89499752400101,0
+35.84740876993872,72.90219802708364,0
+60.18259938620976,86.30855209546826,1
+79.0327360507101,75.3443764369103,1
+45.08327747668339,56.3163717815305,0
+61.10666453684766,96.51142588489624,1
+75.02474556738889,46.55401354116538,1
+76.09878670226257,87.42056971926803,1
+84.43281996120035,43.53339331072109,1
+95.86155507093572,38.22527805795094,0
+75.01365838958247,30.60326323428011,0
+82.30705337399482,76.48196330235604,1
+69.36458875970939,97.71869196188608,1
+39.53833914367223,76.03681085115882,0
+53.9710521485623,89.20735013750205,1
+69.07014406283025,52.74046973016765,1
+67.94685547711617,46.67857410673128,0
+70.66150955499435,92.92713789364831,1
+76.97878372747498,47.57596364975532,1
+67.37202754570876,42.83843832029179,0
+89.67677575072079,65.79936592745237,1
+50.534788289883,48.85581152764205,0
+34.21206097786789,44.20952859866288,0
+77.9240914545704,68.9723599933059,1
+62.27101367004632,69.95445795447587,1
+80.1901807509566,44.82162893218353,1
+93.114388797442,38.80067033713209,0
+61.83020602312595,50.25610789244621,0
+38.78580379679423,64.99568095539578,0
+61.379289447425,72.80788731317097,1
+85.40451939411645,57.05198397627122,1
+52.10797973193984,63.12762376881715,0
+52.04540476831827,69.43286012045222,1
+40.23689373545111,71.16774802184875,0
+54.63510555424817,52.21388588061123,0
+33.91550010906887,98.86943574220611,0
+64.17698887494485,80.90806058670817,1
+74.78925295941542,41.57341522824434,0
+34.1836400264419,75.2377203360134,0
+83.90239366249155,56.30804621605327,1
+51.54772026906181,46.85629026349976,0
+94.44336776917852,65.56892160559052,1
+82.36875375713919,40.61825515970618,0
+51.04775177128865,45.82270145776001,0
+62.22267576120188,52.06099194836679,0
+77.19303492601364,70.45820000180959,1
+97.77159928000232,86.7278223300282,1
+62.07306379667647,96.76882412413983,1
+91.56497449807442,88.69629254546599,1
+79.94481794066932,74.16311935043758,1
+99.2725269292572,60.99903099844988,1
+90.54671411399852,43.39060180650027,1
+34.52451385320009,60.39634245837173,0
+50.2864961189907,49.80453881323059,0
+49.58667721632031,59.80895099453265,0
+97.64563396007767,68.86157272420604,1
+32.57720016809309,95.59854761387875,0
+74.24869136721598,69.82457122657193,1
+71.79646205863379,78.45356224515052,1
+75.3956114656803,85.75993667331619,1
+35.28611281526193,47.02051394723416,0
+56.25381749711624,39.26147251058019,0
+30.05882244669796,49.59297386723685,0
+44.66826172480893,66.45008614558913,0
+66.56089447242954,41.09209807936973,0
+40.45755098375164,97.53518548909936,1
+49.07256321908844,51.88321182073966,0
+80.27957401466998,92.11606081344084,1
+66.74671856944039,60.99139402740988,1
+32.72283304060323,43.30717306430063,0
+64.0393204150601,78.03168802018232,1
+72.34649422579923,96.22759296761404,1
+60.45788573918959,73.09499809758037,1
+58.84095621726802,75.85844831279042,1
+99.82785779692128,72.36925193383885,1
+47.26426910848174,88.47586499559782,1
+50.45815980285988,75.80985952982456,1
+60.45555629271532,42.50840943572217,0
+82.22666157785568,42.71987853716458,0
+88.9138964166533,69.80378889835472,1
+94.83450672430196,45.69430680250754,1
+67.31925746917527,66.58935317747915,1
+57.23870631569862,59.51428198012956,1
+80.36675600171273,90.96014789746954,1
+68.46852178591112,85.59430710452014,1
+42.0754545384731,78.84478600148043,0
+75.47770200533905,90.42453899753964,1
+78.63542434898018,96.64742716885644,1
+52.34800398794107,60.76950525602592,0
+94.09433112516793,77.15910509073893,1
+90.44855097096364,87.50879176484702,1
+55.48216114069585,35.57070347228866,0
+74.49269241843041,84.84513684930135,1
+89.84580670720979,45.35828361091658,1
+83.48916274498238,48.38028579728175,1
+42.2617008099817,87.10385094025457,1
+99.31500880510394,68.77540947206617,1
+55.34001756003703,64.9319380069486,1
+74.77589300092767,89.52981289513276,1