TP 1 — Régression Logistique (Classification binaire)¶

Pr El Alami — Python & NumPy très détaillés¶

Objectif principal Ce TP vise autant à comprendre la régression logistique qu’à maîtriser la syntaxe Python utilisée en Machine Learning.

Comment travailler ce TP

  • Lire chaque commentaire
  • Comprendre chaque instruction Python
  • Faire le lien avec le cours (Introduction ML & Classification)

Ce TP est guidé et commenté ligne par ligne.

In [1]:
# ============================================================
# 1) IMPORTS — comprendre la syntaxe Python
# ============================================================

# import <bibliothèque> as <alias>
# → permet d'utiliser un nom court pour appeler la bibliothèque
import numpy as np          # NumPy : calcul numérique, tableaux, matrices
import matplotlib.pyplot as plt  # matplotlib : affichage de graphiques

2) Création des données (syntaxe NumPy expliquée)¶

Nous allons créer artificiellement :

  • une matrice X (features)
  • un vecteur y (labels 0 ou 1)
In [2]:
# np.random.seed(0)
# → fixe le générateur aléatoire
# → garantit les mêmes résultats à chaque exécution
np.random.seed(0)

# np.random.randn(200, 2)
# → crée une matrice NumPy de taille (200 lignes, 2 colonnes)
# → chaque valeur suit une loi normale centrée réduite
X = np.random.randn(200, 2)

# X[:, 0] : toutes les lignes, colonne 0
# X[:, 1] : toutes les lignes, colonne 1
#
# (X[:,0] + X[:,1] > 0) :
# → comparaison élément par élément
# → retourne un tableau de booléens (True / False)
#
# .astype(int) :
# → convertit True → 1 et False → 0
y = (X[:, 0] + X[:, 1] > 0).astype(int)

# Vérification des dimensions
print("Dimensions de X :", X.shape)   # (200, 2)
print("Dimensions de y :", y.shape)   # (200,)
print("10 premières valeurs de y :", y[:10])
Dimensions de X : (200, 2)
Dimensions de y : (200,)
10 premières valeurs de y : [1 1 1 1 1 1 1 1 1 0]
In [3]:
# Visualisation du dataset
# plt.scatter(x, y) : nuage de points
# c=y : couleur dépend de la classe
plt.scatter(X[:, 0], X[:, 1], c=y, cmap="bwr")
plt.xlabel("x1")
plt.ylabel("x2")
plt.title("Données : classification binaire")
plt.show()

3) Fonction sigmoïde — fonction Python expliquée¶

La sigmoïde transforme une valeur réelle en une probabilité entre 0 et 1.

In [4]:
def sigmoid(z):
    """
    Fonction sigmoïde :
    σ(z) = 1 / (1 + exp(-z))

    z peut être :
    - un nombre (float)
    - un tableau NumPy (vectorisation automatique)

    NumPy applique exp() élément par élément sans boucle.
    """
    return 1 / (1 + np.exp(-z))
In [5]:
# Test visuel de la sigmoïde
# np.linspace(a, b, n) → n valeurs régulièrement espacées entre a et b
z = np.linspace(-10, 10, 200)

plt.plot(z, sigmoid(z))
plt.xlabel("z")
plt.ylabel("σ(z)")
plt.title("Fonction sigmoïde")
plt.grid(True)
plt.show()

4) Modèle logistique — produit matriciel expliqué¶

In [8]:
def model(X, w, b):
    """
    X : matrice (n_samples, n_features)
    w : vecteur des poids (n_features,)
    b : biais (scalaire)

    np.dot(X, w) :
    - effectue le produit matriciel
    - résultat : vecteur de taille (n_samples,)

    + b :
    - broadcasting NumPy
    - b est ajouté à chaque élément du vecteur
    """
    z = np.dot(X, w) + b
    return sigmoid(z)

5) Fonction coût (Log-Loss) — opérations vectorisées¶

In [11]:
def log_loss(y, y_pred):
    """
    y      : vraies classes (0 ou 1)
    y_pred : probabilités prédites

    np.clip :
    - empêche les valeurs 0 ou 1
    - évite log(0)
    """
    epsilon = 1e-15
    y_pred = np.clip(y_pred, epsilon, 1 - epsilon)

    # Toutes les opérations sont vectorisées :
    # aucune boucle for n'est utilisée
    loss = -np.mean(
        y * np.log(y_pred) +
        (1 - y) * np.log(1 - y_pred)
    )
    return loss

6) Gradients — retour multiple en Python¶

In [12]:
def gradients(X, y, y_pred):
    """
    Calcul des dérivées partielles :

    X.T : transposée de la matrice X
    len(y) : nombre d'exemples

    La fonction retourne deux valeurs :
    - dw (vecteur)
    - db (scalaire)
    """
    n = len(y)
    dw = np.dot(X.T, (y_pred - y)) / n
    db = np.mean(y_pred - y)
    return dw, db

7) Entraînement — boucle for très détaillée¶

In [13]:
# Initialisation des paramètres
# np.zeros(X.shape[1]) :
# → crée un vecteur de zéros de taille = nombre de colonnes de X
w = np.zeros(X.shape[1])
b = 0.0

learning_rate = 0.1
epochs = 1000

losses = []

for i in range(epochs):
    # 1) Calcul des probabilités
    y_pred = model(X, w, b)

    # 2) Calcul du coût
    loss = log_loss(y, y_pred)
    losses.append(loss)

    # 3) Calcul des gradients
    dw, db = gradients(X, y, y_pred)

    # 4) Mise à jour (syntaxe raccourcie -=)
    w -= learning_rate * dw
    b -= learning_rate * db

# Affichage final
print("Poids w :", w)
print("Biais b :", b)
print("Dernière valeur de la loss :", losses[-1])
Poids w : [3.77977079 3.87593655]
Biais b : -0.004352079320227879
Dernière valeur de la loss : 0.1111669520011031
In [14]:
# Courbe de convergence
plt.plot(losses)
plt.xlabel("Itérations")
plt.ylabel("Log-Loss")
plt.title("Convergence de l'entraînement")
plt.grid(True)
plt.show()

8) Prédiction en classes — comparaison + cast¶

In [15]:
def predict_class(X, w, b, threshold=0.5):
    """
    threshold : seuil de décision

    (proba >= threshold) :
    - retourne True / False
    .astype(int) :
    - convertit en 1 / 0
    """
    proba = model(X, w, b)
    return (proba >= threshold).astype(int)

y_pred_class = predict_class(X, w, b)
accuracy = np.mean(y_pred_class == y)
print("Accuracy =", accuracy)
Accuracy = 0.995

9) Frontière de décision — géométrie + Python¶

In [16]:
# Génération des valeurs x1
x1 = np.linspace(X[:, 0].min(), X[:, 0].max(), 200)

# Calcul de x2 à partir de l'équation de la droite
x2 = - (w[0] * x1 + b) / w[1]

plt.scatter(X[:, 0], X[:, 1], c=y, cmap="bwr")
plt.plot(x1, x2, "k--", label="Frontière de décision")
plt.xlabel("x1")
plt.ylabel("x2")
plt.legend()
plt.title("Régression Logistique — Frontière de décision")
plt.show()