Classification#

Tout l’art de la classification repose sur le tracé d’une frontière au bon endroit.

Vladimir Vapnik

La classification est le problème fondamental de l’apprentissage supervisé dans lequel la variable cible \(y\) est catégorielle (ou discrète). Contrairement à la régression, où l’on prédit une valeur continue, le classifieur attribue chaque observation à une classe parmi un ensemble fini. Ce chapitre développe le modèle de base de la classification — la régression logistique — et introduit les outils d’évaluation indispensables pour juger de la qualité d’un classifieur.

Le problème de classification#

Définition 66 (Problème de classification)

Soit un ensemble d’entraînement \(\mathcal{D} = \{(\mathbf{x}_i, y_i)\}_{i=1}^n\)\(\mathbf{x}_i \in \mathbb{R}^p\) est un vecteur de features et \(y_i \in \{1, 2, \ldots, K\}\) est l’étiquette de classe. Le problème de classification consiste à trouver une fonction \(f : \mathbb{R}^p \to \{1, \ldots, K\}\) qui minimise une mesure d’erreur sur des données non vues.

On distingue deux cas fondamentaux :

  • Classification binaire (\(K = 2\)) : on cherche à séparer deux classes, souvent notées \(y \in \{0, 1\}\) (négatif / positif). Exemples : spam / non-spam, tumeur maligne / bénigne, fraude / légitime.

  • Classification multiclasse (\(K \geq 3\)) : on attribue l’observation à l’une de \(K\) classes. Exemples : reconnaissance de chiffres manuscrits (\(K = 10\)), classification d’espèces (\(K = 3\) pour Iris), diagnostic médical parmi plusieurs pathologies.

Remarque 60

La classification multilabel (une observation peut appartenir à plusieurs classes simultanément) est un problème distinct que nous n’abordons pas ici. De même, la classification ordinale (les classes sont ordonnées, comme « faible / moyen / élevé ») nécessite des traitements spécifiques.

Pourquoi la régression linéaire ne convient pas#

On pourrait être tenté de traiter la classification binaire comme une régression en codant \(y \in \{0, 1\}\) et en ajustant un modèle linéaire \(\hat{y} = \mathbf{x}^\top \boldsymbol{\beta}\). Ce choix pose plusieurs problèmes :

  1. Les prédictions ne sont pas bornées : \(\hat{y}\) peut prendre des valeurs négatives ou supérieures à 1, ce qui n’a pas de sens comme probabilité.

  2. La frontière de décision est linéaire mais la fonction de coût (MSE) n’est pas adaptée à une cible binaire.

  3. Les résidus ne sont pas normaux : la distribution de \(y \mid \mathbf{x}\) est une Bernoulli, pas une gaussienne.

Il faut donc un modèle qui produit des probabilités \(\hat{p} = P(y = 1 \mid \mathbf{x}) \in [0, 1]\), puis assigne la classe par seuillage. C’est exactement le rôle de la régression logistique.

Régression logistique#

La fonction sigmoïde#

Définition 67 (Fonction sigmoïde (logistique))

La fonction sigmoïde (ou logistique) est définie par

\[\sigma(z) = \frac{1}{1 + e^{-z}}\]

Elle transforme tout réel \(z \in \mathbb{R}\) en une valeur dans l’intervalle \(]0, 1[\).

Proposition 13 (Propriétés de la sigmoïde)

La fonction sigmoïde possède les propriétés suivantes :

  1. \(\sigma(0) = 1/2\)

  2. \(\lim_{z \to +\infty} \sigma(z) = 1\) et \(\lim_{z \to -\infty} \sigma(z) = 0\)

  3. \(\sigma(-z) = 1 - \sigma(z)\) (symétrie)

  4. \(\sigma'(z) = \sigma(z)(1 - \sigma(z))\) (dérivée)

  5. La fonction inverse est le logit : \(\sigma^{-1}(p) = \ln\!\left(\frac{p}{1-p}\right)\)

Proof. Pour la propriété 4, on pose \(\sigma(z) = (1 + e^{-z})^{-1}\) et on dérive :

\[\sigma'(z) = \frac{e^{-z}}{(1 + e^{-z})^2} = \frac{1}{1 + e^{-z}} \cdot \frac{e^{-z}}{1 + e^{-z}} = \sigma(z) \cdot (1 - \sigma(z))\]

Pour la propriété 3 :

\[\sigma(-z) = \frac{1}{1 + e^{z}} = \frac{e^{-z}}{e^{-z} + 1} = 1 - \frac{1}{1 + e^{-z}} = 1 - \sigma(z)\]

Hide code cell source

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

def sigmoid(z):
    return 1 / (1 + np.exp(-z))

z = np.linspace(-7, 7, 300)

fig, axes = plt.subplots(2, 1, figsize=(9, 8))

# Sigmoïde
axes[0].plot(z, sigmoid(z), linewidth=2.5, color="steelblue")
axes[0].axhline(0.5, color="gray", linestyle="--", linewidth=0.8)
axes[0].axvline(0, color="gray", linestyle="--", linewidth=0.8)
axes[0].set_xlabel("$z$")
axes[0].set_ylabel(r"$\sigma(z)$")
axes[0].set_title("Fonction sigmoïde")
axes[0].set_ylim(-0.05, 1.05)

# Dérivée de la sigmoïde
s = sigmoid(z)
axes[1].plot(z, s * (1 - s), linewidth=2.5, color="coral")
axes[1].set_xlabel("$z$")
axes[1].set_ylabel(r"$\sigma'(z)$")
axes[1].set_title("Dérivée de la sigmoïde")

plt.tight_layout()
plt.show()
_images/72a8ca13807a9e1f63cd1bfef1fb7cae060b373bdc5bf7b10d8315539d80d556.png

Modèle de régression logistique#

Définition 68 (Régression logistique)

Le modèle de régression logistique pour la classification binaire modélise la probabilité de la classe positive comme

\[P(y = 1 \mid \mathbf{x}) = \sigma(\mathbf{x}^\top \boldsymbol{\beta} + \beta_0) = \frac{1}{1 + \exp\!\left(-(\mathbf{x}^\top \boldsymbol{\beta} + \beta_0)\right)}\]

\(\boldsymbol{\beta} \in \mathbb{R}^p\) est le vecteur de poids et \(\beta_0 \in \mathbb{R}\) le biais (intercept).

La prédiction est obtenue par seuillage :

\[\begin{split}\hat{y} = \begin{cases} 1 & \text{si } P(y = 1 \mid \mathbf{x}) \geq 0.5 \\ 0 & \text{sinon} \end{cases}\end{split}\]

Remarque 61

Malgré son nom, la régression logistique est bien un classifieur, pas un régresseur. Le terme « régression » est historique et fait référence à la modélisation d’une probabilité continue \(P(y = 1 \mid \mathbf{x})\).

Interprétation probabiliste#

La régression logistique peut être interprétée comme un modèle linéaire généralisé (GLM) avec un lien logit. En notant \(p = P(y = 1 \mid \mathbf{x})\), le modèle suppose que le log-odds (logarithme du rapport de cotes) est une fonction linéaire des features :

\[\ln\!\left(\frac{p}{1-p}\right) = \mathbf{x}^\top \boldsymbol{\beta} + \beta_0\]

Définition 69 (Rapport de cotes (odds ratio))

Le rapport de cotes (odds) est le ratio de la probabilité de la classe positive à celle de la classe négative :

\[\text{odds} = \frac{p}{1-p} = e^{\mathbf{x}^\top \boldsymbol{\beta} + \beta_0}\]

Pour une feature \(x_j\), une augmentation d’une unité multiplie les odds par \(e^{\beta_j}\). Si \(\beta_j > 0\), la probabilité de la classe positive augmente ; si \(\beta_j < 0\), elle diminue.

Exemple 6

Considérons un modèle de prédiction de spam avec une feature \(x_1\) = « nombre d’occurrences du mot gratuit ». Si \(\beta_1 = 0.8\), alors chaque occurrence supplémentaire multiplie les odds de spam par \(e^{0.8} \approx 2.23\) : le message devient 2.23 fois plus susceptible d’être un spam (en termes de cotes).

Frontière de décision#

Définition 70 (Frontière de décision)

La frontière de décision (decision boundary) de la régression logistique est l’ensemble des points \(\mathbf{x}\) pour lesquels

\[P(y = 1 \mid \mathbf{x}) = 0.5 \iff \mathbf{x}^\top \boldsymbol{\beta} + \beta_0 = 0\]

C’est un hyperplan dans \(\mathbb{R}^p\). En dimension 2, c’est une droite.

Remarque 62

La frontière de décision de la régression logistique est toujours linéaire. Pour obtenir des frontières non linéaires, on peut ajouter des features polynomiales (comme en régression polynomiale) ou utiliser d’autres modèles (SVM à noyau, arbres de décision, réseaux de neurones).

Fonction de coût : log-loss (entropie croisée binaire)#

Les paramètres \((\boldsymbol{\beta}, \beta_0)\) sont estimés par maximum de vraisemblance. Chaque observation suit une loi de Bernoulli :

\[P(y_i \mid \mathbf{x}_i) = p_i^{y_i} (1 - p_i)^{1 - y_i}\]

\(p_i = \sigma(\mathbf{x}_i^\top \boldsymbol{\beta} + \beta_0)\). La log-vraisemblance est

\[\ell(\boldsymbol{\beta}, \beta_0) = \sum_{i=1}^n \left[ y_i \ln p_i + (1 - y_i) \ln(1 - p_i) \right]\]

Maximiser la log-vraisemblance équivaut à minimiser la log-loss (ou entropie croisée binaire).

Définition 71 (Log-loss (entropie croisée binaire))

La log-loss (binary cross-entropy) est la fonction de coût de la régression logistique :

\[\mathcal{L}(\boldsymbol{\beta}, \beta_0) = -\frac{1}{n} \sum_{i=1}^n \left[ y_i \ln(\hat{p}_i) + (1 - y_i) \ln(1 - \hat{p}_i) \right]\]

\(\hat{p}_i = \sigma(\mathbf{x}_i^\top \boldsymbol{\beta} + \beta_0)\).

Remarque 63

La log-loss pénalise fortement les prédictions confiantes mais erronées : si \(y_i = 1\) et \(\hat{p}_i \approx 0\), le terme \(-\ln(\hat{p}_i) \to +\infty\). Cela force le modèle à produire des probabilités calibrées.

Hide code cell source

p = np.linspace(0.001, 0.999, 500)

fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(p, -np.log(p), linewidth=2.5, label=r"$y = 1$ : $-\ln(\hat{p})$", color="steelblue")
ax.plot(p, -np.log(1 - p), linewidth=2.5, label=r"$y = 0$ : $-\ln(1 - \hat{p})$", color="coral")
ax.set_xlabel(r"Probabilité prédite $\hat{p}$")
ax.set_ylabel("Coût (log-loss)")
ax.set_title("Fonction de coût log-loss")
ax.legend(fontsize=12)
ax.set_ylim(0, 6)
plt.tight_layout()
plt.show()
_images/85f72d6e82345e9717a1b17701e7e7546b73af681c4d79cd817bbc08bab27278.png

Optimisation#

La log-loss est une fonction convexe par rapport à \((\boldsymbol{\beta}, \beta_0)\), ce qui garantit l’existence d’un minimum global. Il n’existe pas de solution analytique, mais les algorithmes d’optimisation itératifs convergent de manière fiable :

  • Descente de gradient : \(\boldsymbol{\beta} \leftarrow \boldsymbol{\beta} - \eta \nabla_{\boldsymbol{\beta}} \mathcal{L}\)

  • Newton-Raphson (IRLS) : convergence quadratique, utilisé par défaut dans Scikit-learn (solver='lbfgs')

  • Descente de gradient stochastique (SGD) : pour les très grands jeux de données

Proposition 14 (Gradient de la log-loss)

Le gradient de la log-loss par rapport à \(\boldsymbol{\beta}\) est

\[\nabla_{\boldsymbol{\beta}} \mathcal{L} = \frac{1}{n} \sum_{i=1}^n (\hat{p}_i - y_i) \mathbf{x}_i = \frac{1}{n} \mathbf{X}^\top (\hat{\mathbf{p}} - \mathbf{y})\]

Ce gradient a la même forme que celui de la régression linéaire avec le MSE, ce qui n’est pas une coïncidence mais une propriété des GLM avec lien canonique.

Régularisation#

En pratique, on ajoute une pénalité de régularisation pour éviter le surapprentissage. Scikit-learn utilise par défaut une régularisation L2 :

\[\mathcal{L}_{\text{reg}} = \mathcal{L} + \frac{1}{2C} \|\boldsymbol{\beta}\|_2^2\]

Le paramètre \(C > 0\) contrôle la force de la régularisation : plus \(C\) est petit, plus la régularisation est forte.

Remarque 64

Attention à la convention de Scikit-learn : le paramètre C est l”inverse de la force de régularisation \(\lambda\), soit \(C = 1/\lambda\). Cela signifie que C=0.01 correspond à une régularisation forte et C=100 à une régularisation faible.

Extension multiclasse#

Approche One-vs-Rest (OvR)#

Définition 72 (One-vs-Rest (OvR))

La stratégie One-vs-Rest (ou One-vs-All) décompose un problème à \(K\) classes en \(K\) problèmes binaires. Pour chaque classe \(k \in \{1, \ldots, K\}\) :

  1. On entraîne un classifieur binaire \(f_k\) qui distingue la classe \(k\) (positive) de toutes les autres (négatives).

  2. Chaque classifieur produit un score \(s_k(\mathbf{x}) = \mathbf{x}^\top \boldsymbol{\beta}_k + \beta_{0,k}\).

  3. La classe prédite est celle avec le score le plus élevé :

\[\hat{y} = \arg\max_{k \in \{1, \ldots, K\}} s_k(\mathbf{x})\]

Remarque 65

L’approche OvR est simple et parallélisable, mais les classifieurs binaires sont entraînés sur des jeux déséquilibrés (une classe contre toutes les autres). De plus, les scores \(s_k(\mathbf{x})\) ne sont pas directement comparables car ils proviennent de modèles distincts.

Fonction softmax et régression logistique multinomiale#

Définition 73 (Fonction softmax)

La fonction softmax généralise la sigmoïde au cas multiclasse. Pour un vecteur de scores \(\mathbf{z} = (z_1, \ldots, z_K) \in \mathbb{R}^K\) :

\[\text{softmax}(\mathbf{z})_k = \frac{e^{z_k}}{\sum_{j=1}^K e^{z_j}}, \quad k = 1, \ldots, K\]

Elle produit un vecteur de probabilités : \(\text{softmax}(\mathbf{z})_k \geq 0\) et \(\sum_{k=1}^K \text{softmax}(\mathbf{z})_k = 1\).

Définition 74 (Régression logistique multinomiale)

La régression logistique multinomiale modélise directement les probabilités des \(K\) classes :

\[P(y = k \mid \mathbf{x}) = \frac{\exp(\mathbf{x}^\top \boldsymbol{\beta}_k + \beta_{0,k})}{\sum_{j=1}^K \exp(\mathbf{x}^\top \boldsymbol{\beta}_j + \beta_{0,j})}\]

La fonction de coût est l”entropie croisée catégorielle :

\[\mathcal{L} = -\frac{1}{n} \sum_{i=1}^n \sum_{k=1}^K \mathbb{1}_{[y_i = k]} \ln P(y_i = k \mid \mathbf{x}_i)\]

Remarque 66

Dans Scikit-learn, LogisticRegression utilise par défaut l’approche softmax (multinomiale) pour les solveurs qui la supportent (lbfgs, newton-cg, sag, saga). Le paramètre multi_class ayant été retiré dans les versions récentes, le choix est désormais automatique.

Hide code cell source

def softmax(z):
    exp_z = np.exp(z - np.max(z, axis=1, keepdims=True))  # stabilité numérique
    return exp_z / exp_z.sum(axis=1, keepdims=True)

# Exemple avec 3 classes
scores = np.array([[2.0, 1.0, 0.1],
                    [0.5, 2.5, 0.3],
                    [0.2, 0.1, 3.0]])

probs = softmax(scores)
print("Scores bruts :")
print(scores)
print("\nProbabilités après softmax :")
print(probs.round(4))
print("\nSomme par ligne :", probs.sum(axis=1).round(6))
print("Classes prédites :", probs.argmax(axis=1))
Scores bruts :
[[2.  1.  0.1]
 [0.5 2.5 0.3]
 [0.2 0.1 3. ]]

Probabilités après softmax :
[[0.659  0.2424 0.0986]
 [0.1086 0.8025 0.0889]
 [0.0545 0.0493 0.8962]]

Somme par ligne : [1. 1. 1.]
Classes prédites : [0 1 2]

Frontières de décision#

La visualisation des frontières de décision en 2D est un outil pédagogique essentiel pour comprendre le comportement d’un classifieur. On fixe deux features et on colore chaque point de l’espace selon la classe prédite.

Hide code cell source

from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler, PolynomialFeatures
from sklearn.pipeline import make_pipeline
from sklearn.datasets import make_classification, make_moons

def plot_decision_boundary(model, X, y, ax, title="", resolution=200):
    """Trace la frontière de décision d'un classifieur 2D."""
    x_min, x_max = X[:, 0].min() - 0.5, X[:, 0].max() + 0.5
    y_min, y_max = X[:, 1].min() - 0.5, X[:, 1].max() + 0.5
    xx, yy = np.meshgrid(np.linspace(x_min, x_max, resolution),
                          np.linspace(y_min, y_max, resolution))
    Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)

    ax.contourf(xx, yy, Z, alpha=0.3, cmap="Set2")
    ax.contour(xx, yy, Z, colors="black", linewidths=0.5, alpha=0.5)
    scatter = ax.scatter(X[:, 0], X[:, 1], c=y, cmap="Set2",
                          edgecolors="black", s=30, linewidth=0.5)
    ax.set_title(title)
    ax.set_xlabel("$x_1$")
    ax.set_ylabel("$x_2$")
    return scatter

Hide code cell source

# Données linéairement séparables
X_lin, y_lin = make_classification(n_samples=200, n_features=2, n_redundant=0,
                                     n_informative=2, n_clusters_per_class=1,
                                     random_state=42)

# Données non linéairement séparables (deux lunes)
X_moon, y_moon = make_moons(n_samples=200, noise=0.25, random_state=42)

fig, axes = plt.subplots(3, 1, figsize=(9, 14))

# Régression logistique linéaire sur données linéaires
lr_lin = LogisticRegression(random_state=42)
lr_lin.fit(X_lin, y_lin)
plot_decision_boundary(lr_lin, X_lin, y_lin, axes[0],
                        "Logistique linéaire\n(données séparables)")

# Régression logistique linéaire sur données non linéaires
lr_moon = LogisticRegression(random_state=42)
lr_moon.fit(X_moon, y_moon)
plot_decision_boundary(lr_moon, X_moon, y_moon, axes[1],
                        "Logistique linéaire\n(données non séparables)")

# Régression logistique avec features polynomiales
lr_poly = make_pipeline(PolynomialFeatures(degree=3), LogisticRegression(C=10, random_state=42))
lr_poly.fit(X_moon, y_moon)
plot_decision_boundary(lr_poly, X_moon, y_moon, axes[2],
                        "Logistique polynomiale (degré 3)\n(données non séparables)")

plt.tight_layout()
plt.show()
_images/1485e7374a0259818755818b7afd7d2871e3996af953010e2e2cf98c6170a115.png

Remarque 67

L’ajout de features polynomiales permet à la régression logistique de capturer des frontières non linéaires. C’est un compromis entre la simplicité du modèle linéaire et la flexibilité des modèles plus complexes. Le degré du polynôme et le paramètre de régularisation \(C\) contrôlent conjointement la complexité de la frontière.

Frontières multiclasses#

Hide code cell source

from sklearn.datasets import load_iris

iris = load_iris()
X_iris = iris.data[:, 2:4]  # petal length et petal width
y_iris = iris.target

fig, axes = plt.subplots(2, 1, figsize=(9, 9))

# OvR
lr_ovr = LogisticRegression(max_iter=1000, random_state=42)
lr_ovr.fit(X_iris, y_iris)
plot_decision_boundary(lr_ovr, X_iris, y_iris, axes[0],
                        "Régression logistique — OvR")

# Multinomiale (softmax)
lr_multi = LogisticRegression(max_iter=1000, random_state=42)
lr_multi.fit(X_iris, y_iris)
plot_decision_boundary(lr_multi, X_iris, y_iris, axes[1],
                        "Régression logistique — Multinomiale (softmax)")

plt.tight_layout()
plt.show()
_images/4a8adf5a67b27b0497f0fa33c5b5cc75e7603b736d189e7c410de60180994483.png

Remarque 68

Sur le jeu Iris (features petal length et petal width), les approches OvR et multinomiale produisent des frontières similaires car les classes sont relativement bien séparées. Les différences apparaissent surtout lorsque les classes se chevauchent fortement ou lorsque les problèmes binaires OvR sont très déséquilibrés.

Métriques de classification#

L’évaluation d’un classifieur ne se réduit pas à un seul chiffre. Selon le contexte (diagnostic médical, détection de fraude, filtrage de spam), les erreurs n’ont pas le même coût. Cette section présente les métriques essentielles.

Matrice de confusion#

Définition 75 (Matrice de confusion)

Pour un problème binaire, la matrice de confusion est un tableau \(2 \times 2\) qui croise les classes réelles et les classes prédites :

Prédit positif

Prédit négatif

Réel positif

VP (Vrai Positif)

FN (Faux Négatif)

Réel négatif

FP (Faux Positif)

VN (Vrai Négatif)

  • VP (True Positive) : positif correctement identifié

  • VN (True Negative) : négatif correctement identifié

  • FP (False Positive, erreur de type I) : négatif classé à tort comme positif

  • FN (False Negative, erreur de type II) : positif classé à tort comme négatif

Exemple 7

Dans un test de dépistage médical :

  • VP : patient malade correctement diagnostiqué

  • FN : patient malade non détecté (le plus dangereux)

  • FP : patient sain faussement diagnostiqué (anxiété inutile, examens supplémentaires)

  • VN : patient sain correctement identifié

Dans ce contexte, on cherche à minimiser les FN (haute sensibilité), quitte à accepter davantage de FP.

Accuracy, précision, rappel, F1-score#

Définition 76 (Accuracy)

L”accuracy (exactitude) est la proportion de prédictions correctes :

\[\text{Accuracy} = \frac{VP + VN}{VP + VN + FP + FN}\]

Remarque 69

L’accuracy est trompeuse en présence de déséquilibre de classes. Si 95 % des observations sont négatives, un modèle qui prédit toujours « négatif » obtient une accuracy de 95 %, alors qu’il ne détecte aucun positif.

Définition 77 (Précision et rappel)

La précision (precision) est la proportion de vrais positifs parmi les prédictions positives :

\[\text{Precision} = \frac{VP}{VP + FP}\]

Le rappel (recall, ou sensibilité, ou taux de vrais positifs) est la proportion de positifs correctement détectés :

\[\text{Recall} = \frac{VP}{VP + FN}\]

Définition 78 (F1-score)

Le F1-score est la moyenne harmonique de la précision et du rappel :

\[F_1 = 2 \cdot \frac{\text{Precision} \times \text{Recall}}{\text{Precision} + \text{Recall}} = \frac{2 \, VP}{2 \, VP + FP + FN}\]

Plus généralement, le \(F_\beta\)-score pondère le rappel \(\beta^2\) fois plus que la précision :

\[F_\beta = (1 + \beta^2) \cdot \frac{\text{Precision} \times \text{Recall}}{\beta^2 \cdot \text{Precision} + \text{Recall}}\]

Remarque 70

Le choix entre précision et rappel dépend du coût des erreurs :

  • Haute précision souhaitée : quand un faux positif est coûteux (par exemple, un système judiciaire : condamner un innocent est grave).

  • Haut rappel souhaité : quand un faux négatif est coûteux (par exemple, dépistage d’une maladie grave : manquer un patient malade est dangereux).

Le F1-score est un compromis ; le \(F_2\)-score favorise le rappel, le \(F_{0.5}\)-score favorise la précision.

Courbe ROC et AUC#

Définition 79 (Courbe ROC)

La courbe ROC (Receiver Operating Characteristic) représente le taux de vrais positifs (rappel) en fonction du taux de faux positifs pour différents seuils de décision \(\tau\) :

\[\text{TVP}(\tau) = \frac{VP(\tau)}{VP(\tau) + FN(\tau)}, \quad \text{TFP}(\tau) = \frac{FP(\tau)}{FP(\tau) + VN(\tau)}\]

En faisant varier \(\tau\) de 0 à 1, on obtient une courbe dans le carré \([0,1]^2\).

Définition 80 (AUC (Area Under the Curve))

L”AUC est l’aire sous la courbe ROC :

\[\text{AUC} = \int_0^1 \text{TVP}(\text{TFP}^{-1}(t)) \, dt\]
  • \(\text{AUC} = 1\) : classifieur parfait

  • \(\text{AUC} = 0.5\) : classifieur aléatoire (diagonale)

  • \(\text{AUC} < 0.5\) : classifieur pire que le hasard (inverser les prédictions)

Proposition 15 (Interprétation probabiliste de l’AUC)

L’AUC est égale à la probabilité qu’un exemple positif tiré au hasard reçoive un score plus élevé qu’un exemple négatif tiré au hasard :

\[\text{AUC} = P(\hat{p}(\mathbf{x}^+) > \hat{p}(\mathbf{x}^-))\]

\(\mathbf{x}^+\) est de classe positive et \(\mathbf{x}^-\) de classe négative.

Courbe précision-rappel#

Définition 81 (Courbe précision-rappel)

La courbe précision-rappel (precision-recall curve) représente la précision en fonction du rappel pour différents seuils de décision. Elle est particulièrement informative lorsque les classes sont déséquilibrées, car elle ne prend pas en compte les vrais négatifs (contrairement à la courbe ROC).

L”AP (Average Precision) est l’aire sous la courbe précision-rappel.

Remarque 71

Pour un jeu de données très déséquilibré (par exemple 1 % de positifs), la courbe ROC peut sembler optimiste car l’abscisse (TFP) est diluée par le grand nombre de vrais négatifs. La courbe précision-rappel est alors plus discriminante : un classifieur aléatoire y apparaît comme une ligne horizontale à \(\text{Precision} = \text{prévalence}\) (1 %), et non comme une diagonale à 50 %.

Exemple complet avec Scikit-learn#

Mettons en pratique l’ensemble de ces concepts sur le jeu de données Breast Cancer Wisconsin, un problème de classification binaire classique (tumeur maligne ou bénigne).

Hide code cell source

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
import pandas as pd

# Chargement des données
data = load_breast_cancer()
X, y = data.data, data.target
feature_names = data.feature_names
target_names = data.target_names

print(f"Dimensions : {X.shape}")
print(f"Classes : {target_names} (0 = {target_names[0]}, 1 = {target_names[1]})")
print(f"Répartition : {dict(zip(*np.unique(y, return_counts=True)))}")
Dimensions : (569, 30)
Classes : ['malignant' 'benign'] (0 = malignant, 1 = benign)
Répartition : {np.int64(0): np.int64(212), np.int64(1): np.int64(357)}

Hide code cell source

# Séparation entraînement / test
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

# Pipeline : standardisation + régression logistique
model = make_pipeline(
    StandardScaler(),
    LogisticRegression(C=1.0, max_iter=5000, random_state=42)
)
model.fit(X_train, y_train)

print(f"Accuracy entraînement : {model.score(X_train, y_train):.4f}")
print(f"Accuracy test         : {model.score(X_test, y_test):.4f}")
Accuracy entraînement : 0.9890
Accuracy test         : 0.9825

Matrice de confusion#

Hide code cell source

from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay

y_pred = model.predict(X_test)

fig, ax = plt.subplots(figsize=(6, 5))
cm = confusion_matrix(y_test, y_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=target_names)
disp.plot(cmap="Blues", ax=ax, colorbar=False)
ax.set_title("Matrice de confusion — Breast Cancer")
plt.tight_layout()
plt.show()

print(f"VP = {cm[1,1]}, VN = {cm[0,0]}, FP = {cm[0,1]}, FN = {cm[1,0]}")
_images/aed8469042d87f3bd056cf843de23bc5feb87c83d07a923d99e647621decf07f.png
VP = 71, VN = 41, FP = 1, FN = 1

Rapport de classification#

Hide code cell source

from sklearn.metrics import classification_report

print(classification_report(y_test, y_pred, target_names=target_names))
              precision    recall  f1-score   support

   malignant       0.98      0.98      0.98        42
      benign       0.99      0.99      0.99        72

    accuracy                           0.98       114
   macro avg       0.98      0.98      0.98       114
weighted avg       0.98      0.98      0.98       114

Courbe ROC et AUC#

Hide code cell source

from sklearn.metrics import roc_curve, auc, RocCurveDisplay

y_scores = model.predict_proba(X_test)[:, 1]
fpr, tpr, thresholds = roc_curve(y_test, y_scores)
roc_auc = auc(fpr, tpr)

fig, ax = plt.subplots(figsize=(7, 6))
ax.plot(fpr, tpr, linewidth=2.5, color="steelblue",
        label=f"Régression logistique (AUC = {roc_auc:.3f})")
ax.plot([0, 1], [0, 1], linestyle="--", color="gray", label="Classifieur aléatoire")
ax.fill_between(fpr, tpr, alpha=0.15, color="steelblue")
ax.set_xlabel("Taux de faux positifs (TFP)")
ax.set_ylabel("Taux de vrais positifs (TVP)")
ax.set_title("Courbe ROC — Breast Cancer")
ax.legend(loc="lower right", fontsize=11)
ax.set_xlim([-0.02, 1.02])
ax.set_ylim([-0.02, 1.02])
plt.tight_layout()
plt.show()
_images/6a349bc8d28493ee967dd54efa91740d4a5476bbf8013e0e0e287d087945920b.png

Courbe précision-rappel#

Hide code cell source

from sklearn.metrics import precision_recall_curve, average_precision_score

precision, recall, thresholds_pr = precision_recall_curve(y_test, y_scores)
ap = average_precision_score(y_test, y_scores)

fig, ax = plt.subplots(figsize=(7, 6))
ax.plot(recall, precision, linewidth=2.5, color="coral",
        label=f"Régression logistique (AP = {ap:.3f})")
ax.fill_between(recall, precision, alpha=0.15, color="coral")
ax.set_xlabel("Rappel")
ax.set_ylabel("Précision")
ax.set_title("Courbe Précision-Rappel — Breast Cancer")
ax.legend(loc="lower left", fontsize=11)
ax.set_xlim([-0.02, 1.02])
ax.set_ylim([-0.02, 1.02])
plt.tight_layout()
plt.show()
_images/b3a131dea2defac65c240040fa026209afd11ccd2ec228e80b30f6127ff00df8.png

Influence du seuil de décision#

Hide code cell source

from sklearn.metrics import f1_score, precision_score, recall_score

seuils = np.linspace(0.05, 0.95, 100)
precisions = []
recalls = []
f1s = []

for s in seuils:
    y_pred_s = (y_scores >= s).astype(int)
    precisions.append(precision_score(y_test, y_pred_s, zero_division=0))
    recalls.append(recall_score(y_test, y_pred_s))
    f1s.append(f1_score(y_test, y_pred_s))

fig, ax = plt.subplots(figsize=(9, 5))
ax.plot(seuils, precisions, label="Précision", linewidth=2, color="steelblue")
ax.plot(seuils, recalls, label="Rappel", linewidth=2, color="coral")
ax.plot(seuils, f1s, label="F1-score", linewidth=2, color="seagreen")
ax.axvline(0.5, color="gray", linestyle="--", linewidth=0.8, label="Seuil = 0.5")

best_f1_idx = np.argmax(f1s)
ax.axvline(seuils[best_f1_idx], color="seagreen", linestyle=":", linewidth=1.2,
            label=f"Meilleur F1 (seuil = {seuils[best_f1_idx]:.2f})")

ax.set_xlabel("Seuil de décision")
ax.set_ylabel("Score")
ax.set_title("Précision, Rappel et F1 en fonction du seuil")
ax.legend(fontsize=10)
ax.set_xlim([0.05, 0.95])
ax.set_ylim([0, 1.05])
plt.tight_layout()
plt.show()
_images/1e891f8a06f995b2709fb1fb8e4b1de1aa5d0d1efe0e279341a8fc15930113ac.png

Remarque 72

Le seuil par défaut de \(0.5\) n’est pas toujours optimal. En ajustant le seuil, on peut déplacer le compromis précision-rappel selon les besoins du problème. Le graphique ci-dessus montre clairement cette relation inverse : augmenter le seuil améliore la précision mais réduit le rappel, et inversement.

Exemple multiclasse : Iris#

Hide code cell source

from sklearn.datasets import load_iris
from sklearn.metrics import ConfusionMatrixDisplay

iris = load_iris()
X_iris_full = iris.data
y_iris_full = iris.target

X_tr, X_te, y_tr, y_te = train_test_split(
    X_iris_full, y_iris_full, test_size=0.3, random_state=42, stratify=y_iris_full
)

model_iris = make_pipeline(
    StandardScaler(),
    LogisticRegression(max_iter=5000, random_state=42)
)
model_iris.fit(X_tr, y_tr)

y_pred_iris = model_iris.predict(X_te)

print(f"Accuracy test : {model_iris.score(X_te, y_te):.4f}\n")
print(classification_report(y_te, y_pred_iris, target_names=iris.target_names))

fig, ax = plt.subplots(figsize=(6, 5))
ConfusionMatrixDisplay.from_predictions(
    y_te, y_pred_iris, display_labels=iris.target_names, cmap="Blues", ax=ax
)
ax.set_title("Matrice de confusion — Iris (multiclasse)")
plt.tight_layout()
plt.show()
Accuracy test : 0.9111

              precision    recall  f1-score   support

      setosa       1.00      1.00      1.00        15
  versicolor       0.82      0.93      0.88        15
   virginica       0.92      0.80      0.86        15

    accuracy                           0.91        45
   macro avg       0.92      0.91      0.91        45
weighted avg       0.92      0.91      0.91        45
_images/eef620e2f52bea6053c24bdd0ced556dfee6c92030a3cd14da29430ac845c410.png

Courbes ROC multiclasses (One-vs-Rest)#

Hide code cell source

from sklearn.metrics import roc_curve, auc
from sklearn.preprocessing import label_binarize

# Binarisation des labels pour le calcul ROC multiclasse
y_te_bin = label_binarize(y_te, classes=[0, 1, 2])
y_scores_iris = model_iris.predict_proba(X_te)

fig, ax = plt.subplots(figsize=(8, 6))
colors = ["steelblue", "coral", "seagreen"]

for i, (name, color) in enumerate(zip(iris.target_names, colors)):
    fpr_i, tpr_i, _ = roc_curve(y_te_bin[:, i], y_scores_iris[:, i])
    auc_i = auc(fpr_i, tpr_i)
    ax.plot(fpr_i, tpr_i, color=color, linewidth=2.5,
            label=f"{name} (AUC = {auc_i:.3f})")

ax.plot([0, 1], [0, 1], linestyle="--", color="gray")
ax.set_xlabel("Taux de faux positifs")
ax.set_ylabel("Taux de vrais positifs")
ax.set_title("Courbes ROC multiclasses (OvR) — Iris")
ax.legend(loc="lower right", fontsize=11)
ax.set_xlim([-0.02, 1.02])
ax.set_ylim([-0.02, 1.02])
plt.tight_layout()
plt.show()
_images/c86e602d66e78bd02afec788d20300326d3ab546cc6373b90d8af9fe4bd1045e.png

Résumé#

La régression logistique est le point d’entrée de la classification en apprentissage automatique. Ses atouts sont nombreux : interprétabilité (les coefficients ont une signification en termes d’odds ratio), efficacité computationnelle, et capacité à produire des probabilités calibrées. Ses limites — la linéarité de la frontière de décision — peuvent être partiellement contournées par l’ajout de features polynomiales.

Concept

Résumé

Sigmoïde

\(\sigma(z) = 1/(1 + e^{-z})\), transforme \(\mathbb{R} \to ]0, 1[\)

Modèle logistique

\(P(y=1 \mid \mathbf{x}) = \sigma(\mathbf{x}^\top \boldsymbol{\beta} + \beta_0)\)

Frontière de décision

Hyperplan \(\mathbf{x}^\top \boldsymbol{\beta} + \beta_0 = 0\)

Log-loss

\(-\frac{1}{n}\sum [y_i \ln \hat{p}_i + (1-y_i)\ln(1-\hat{p}_i)]\)

Softmax

Généralisation multiclasse de la sigmoïde

Accuracy

Proportion de prédictions correctes

Précision

VP / (VP + FP)

Rappel

VP / (VP + FN)

F1-score

Moyenne harmonique de précision et rappel

AUC

Aire sous la courbe ROC, mesure globale de discrimination

Remarque 73

La régression logistique est rarement le modèle le plus performant, mais c’est presque toujours un excellent modèle de référence (baseline). Avant de recourir à des méthodes plus complexes (SVM, forêts aléatoires, réseaux de neurones), il est indispensable de vérifier qu’elles surpassent significativement ce modèle simple. Les chapitres suivants présenteront des alternatives plus flexibles, mais les métriques et les outils d’évaluation introduits ici resteront valables pour tous les classifieurs.