Comprenez pourquoi un modèle doit généraliser
Un étudiant qui apprend par cœur les corrigés des annales obtient 20 sur 20 aux annales… et s’effondre le jour de l’examen, face à des questions nouvelles. Un modèle d’apprentissage peut tomber exactement dans le même piège. Dans ce chapitre, vous allez comprendre pourquoi réussir sur les exemples d’apprentissage ne prouve rien, et comment on cherche au contraire un modèle qui généralise, c’est-à-dire qui fonctionne sur des données qu’il n’a jamais vues.
Notre fil rouge : estimer le prix d’un appartement à Rennes à partir de sa surface.
Erreur empirique et erreur réelle
Section intitulée « Erreur empirique et erreur réelle »Supposons que vous disposiez de 15 ventes récentes : pour chacune, la surface en m² et le prix de vente. Vous construisez un modèle (pour « hypothèse ») qui associe un prix à une surface. Comment savoir s’il est bon ?
Le premier réflexe est de mesurer ses erreurs sur ces 15 ventes. C’est l’erreur empirique (ou erreur apparente, ou risque empirique) :
- est le nombre d’exemples d’apprentissage (ici 15) ;
- est la surface du -ème appartement et son vrai prix ;
- est le prix prédit par le modèle ;
- est une fonction de perte (loss) qui mesure le coût d’une erreur : par exemple en régression, ou 1 si la classe prédite est fausse et 0 sinon en classification.
Mais ce qui vous intéresse vraiment, c’est le prix du prochain appartement mis en vente. C’est l’erreur réelle (ou risque réel) :
Le symbole désigne l’espérance : la moyenne de la perte sur tous les appartements qui pourraient être mis en vente, tirés selon la même loi que vos données. En classification, avec la perte 0-1, est simplement la probabilité que le modèle se trompe sur un nouvel exemple.
Le problème, c’est que est inaccessible : vous ne connaissez pas tous les appartements futurs. Et est un estimateur optimiste de , puisque le modèle a été choisi justement pour bien faire sur ces exemples-là.
En TP, la première évaluation proposée sur les fleurs d’iris tombait dans ce travers : un classifieur des k plus proches voisins, entraîné sur les 150 fleurs, était noté sur ces mêmes 150 fleurs. Le score obtenu ainsi mesure surtout la mémoire du modèle, pas sa capacité à reconnaître une fleur nouvelle. Le chapitre 3 vous montrera comment obtenir une estimation honnête.
Surapprentissage et sous-apprentissage
Section intitulée « Surapprentissage et sous-apprentissage »Revenons aux appartements. Vous pouvez choisir des modèles plus ou moins souples : une droite (prix proportionnel à la surface, plus une constante), une courbe de degré 3, ou un polynôme de degré 9 qui ondule dans tous les sens.
- La droite ne passe pas exactement par les points, mais elle capte la tendance générale.
- Le polynôme de degré 9 peut passer presque exactement par chacune des 15 ventes. Son erreur empirique est quasi nulle. Mais entre deux points, il fait des bonds absurdes : il pourrait prédire qu’un 63 m² vaut moins qu’un studio.
Ce second cas, c’est le surapprentissage (overfitting) : le modèle a appris les particularités de l’échantillon (y compris son bruit : une vente précipitée, un appartement avec vue exceptionnelle) au lieu de la tendance. Son erreur d’apprentissage est très basse, son erreur réelle est élevée.
Le cas inverse existe aussi. Si vous prédisiez le même prix pour tous les appartements (la moyenne), votre modèle serait trop rigide pour capter quoi que ce soit : erreur d’apprentissage et erreur réelle seraient toutes deux élevées. C’est le sous-apprentissage (underfitting).
Le schéma ci-dessous résume la situation. Quand la complexité du modèle augmente, l’erreur d’apprentissage ne fait que baisser, tandis que l’erreur sur de nouvelles données baisse puis remonte.
En pratique, on diagnostique la situation en comparant deux scores : celui obtenu sur les données d’apprentissage et celui obtenu sur des données mises de côté.
| Score apprentissage | Score sur données nouvelles | Diagnostic |
|---|---|---|
| 99 % | 70 % | surapprentissage : le modèle récite |
| 50 % | 49 % | sous-apprentissage : le modèle est trop simple |
| 90 % | 85 % | situation saine, un léger écart est normal |
| 80 % | 95 % | suspect : cherchez une erreur de découpage ou une fuite de données |
Le biais inductif et le rasoir d’Occam
Section intitulée « Le biais inductif et le rasoir d’Occam »Prenez trois ventes : 30 m² à 150 000 €, 50 m² à 250 000 €, 70 m² à 320 000 €. Combien vaut un 60 m² ? Une infinité de courbes passent exactement par ces trois points, et elles donnent toutes des réponses différentes pour 60 m². Les données seules ne suffisent donc jamais à trancher.
Pour généraliser, un algorithme doit faire des hypothèses avant de voir les données : c’est son biais inductif. Supposer que le prix varie de façon linéaire avec la surface est un biais inductif. Supposer que des appartements proches se vendent à des prix proches (l’hypothèse des k plus proches voisins) en est un autre. Un arbre de décision suppose que l’on peut découper l’espace en rectangles par des questions successives. Sans biais inductif, aucune généralisation n’est possible : un modèle qui n’accepte aucune hypothèse ne peut que recopier ce qu’il a vu.
Parmi toutes les hypothèses compatibles avec les données, laquelle préférer ? Le rasoir d’Occam répond : la plus simple. Entre une droite et un polynôme de degré 9 qui expliquent aussi bien les données, choisissez la droite. Ce principe n’est pas une loi de la nature, mais il est payant en pratique : une hypothèse simple a moins de chances de s’être ajustée au hasard de l’échantillon.
Le compromis biais-variance
Section intitulée « Le compromis biais-variance »Pourquoi l’erreur de test remonte-t-elle quand le modèle devient trop complexe ? La décomposition biais-variance donne une réponse précise dans le cas de la régression avec la perte quadratique. Pour un point donné :
Détaillons chaque symbole :
- est le vrai prix, somme d’une tendance inconnue et d’un bruit de moyenne nulle et de variance (la part de hasard dans une vente) ;
- est le modèle appris ; il dépend de l’échantillon d’apprentissage tiré, et l’espérance porte justement sur ce tirage (et sur le bruit) ;
- le biais mesure l’écart entre la prédiction moyenne (si l’on refaisait l’apprentissage sur de nombreux échantillons) et la vérité ;
- la variance mesure à quel point la prédiction change d’un échantillon à l’autre ;
- est le bruit irréductible : aucun modèle ne peut descendre en dessous.
Une image aide beaucoup : pensez à un joueur de fléchettes. Un joueur à fort biais groupe ses fléchettes, mais loin du centre (la droite, face à une relation courbe). Un joueur à forte variance vise en moyenne le centre, mais ses fléchettes sont dispersées (le polynôme de degré 9, qui change complètement de forme si l’on remplace une seule vente).
Quand la complexité augmente, le biais diminue et la variance augmente. L’erreur totale est minimale entre les deux : c’est le compromis biais-variance. Chaque modèle de ce cours possède un « bouton » qui règle ce compromis :
| Modèle | Bouton de complexité | Vers le surapprentissage quand… |
|---|---|---|
| k plus proches voisins | nombre de voisins | diminue |
| Arbre de décision | profondeur, élagage | l’arbre est plus profond |
| Régression régularisée | pénalité | diminue |
| SVM | paramètre | augmente |
| Réseau de neurones | taille, nombre d’époques | le réseau est plus grand ou entraîné plus longtemps |
Mais comment trouver le bon réglage ? En mesurant l’erreur sur des données non vues, pour plusieurs valeurs du bouton, puis en gardant la meilleure. C’est tout l’objet du chapitre suivant.
Voir le surapprentissage en Python
Section intitulée « Voir le surapprentissage en Python »Voici une petite expérience que vous pouvez lancer. On simule des ventes d’appartements dont le prix suit une tendance linéaire plus du bruit, puis on ajuste des polynômes de degrés croissants.
import numpy as npfrom numpy.polynomial import Polynomial
rng = np.random.default_rng(0)
def simuler(n): surface = rng.uniform(20, 120, n) # en m² prix = 3.5 * surface + 40 + rng.normal(0, 25, n) # en milliers d'euros return surface, prix
x_app, y_app = simuler(15) # 15 ventes pour apprendrex_test, y_test = simuler(1000) # 1 000 ventes « futures »
for degre in [0, 1, 3, 9]: modele = Polynomial.fit(x_app, y_app, degre) # moindres carrés mse_app = np.mean((modele(x_app) - y_app) ** 2) mse_test = np.mean((modele(x_test) - y_test) ** 2) print(f"degré {degre} : MSE apprentissage {mse_app:8.1f}, MSE test {mse_test:10.1f}")# degré 0 : MSE apprentissage 15914.5, MSE test 10669.3# degré 1 : MSE apprentissage 274.0, MSE test 669.6# degré 3 : MSE apprentissage 273.6, MSE test 669.0# degré 9 : MSE apprentissage 159.1, MSE test 11718.2Vous devriez constater que l’erreur d’apprentissage diminue à chaque degré supplémentaire, alors que l’erreur de test atteint son plancher dès le degré 1 (la vraie forme de la relation), le degré 3 ne gagnant presque rien, et devient bien plus grande pour le degré 9. Le degré 0 (un prix constant) illustre le sous-apprentissage. Les valeurs exactes dépendent du tirage aléatoire, mais la tendance est robuste.
Comment lutter contre le surapprentissage ? Vous rencontrerez tout au long du cours les mêmes leviers :
- plus de données : la variance diminue quand l’échantillon grandit ;
- un modèle plus simple ou contraint : élagage d’un arbre (chapitre 4), régularisation (chapitre 7), arrêt précoce d’un réseau (chapitre 9) ;
- la moyenne de plusieurs modèles, qui réduit la variance (chapitre 8) ;
- de meilleures caractéristiques, qui permettent à un modèle simple d’être juste.
En résumé
Section intitulée « En résumé »- L’erreur empirique se mesure sur les données d’apprentissage ; l’erreur réelle est l’erreur attendue sur de nouvelles données. La première est optimiste.
- Surapprentissage : très bon en apprentissage, mauvais sur de nouvelles données (le modèle a appris le bruit). Sous-apprentissage : mauvais partout (le modèle est trop simple).
- Tout algorithme repose sur un biais inductif, des hypothèses faites avant de voir les données ; sans elles, aucune généralisation n’est possible.
- Le rasoir d’Occam recommande, à performance égale, l’hypothèse la plus simple.
- L’erreur quadratique se décompose en biais² + variance + bruit irréductible ; augmenter la complexité réduit le biais et augmente la variance.
- Chaque modèle a un bouton de complexité qu’on règle en mesurant l’erreur sur des données non vues.
Exercices
Section intitulée « Exercices »Exercice · Poser un diagnostic
facilePour chacun de ces trois modèles de prédiction des absences, donnez un diagnostic (surapprentissage, sous-apprentissage, situation saine) et proposez une action.
| Modèle | Exactitude en apprentissage | Exactitude sur données nouvelles |
|---|---|---|
| A : arbre de profondeur 30 | 100 % | 71 % |
| B : k plus proches voisins avec | 62 % | 61 % |
| C : forêt aléatoire | 88 % | 84 % |
Voir la solution
- A est en surapprentissage : score parfait en apprentissage, écart de 29 points. Il faut contraindre l’arbre : limiter sa profondeur, imposer un nombre minimal d’exemples par feuille, ou l’élaguer.
- B est en sous-apprentissage : les deux scores sont bas et proches. Avec 2 000 voisins, le vote est si lissé qu’il ne voit plus les différences locales. Il faut diminuer (et le choisir par validation).
- C est dans une situation saine : l’écart de 4 points est normal. On peut chercher à progresser (meilleures caractéristiques, réglages), mais sans problème de fond.
Attention toutefois : ces pourcentages n’ont de sens qu’à côté d’une référence. Si 80 % des patients viennent, prédire « tout le monde vient » donne déjà 80 %, et même le modèle C n’apporte que peu. Nous verrons cela au chapitre 3.
En code
import pandas as pd
scores = pd.DataFrame( {"apprentissage": [1.00, 0.62, 0.88], "nouvelles": [0.71, 0.61, 0.84]}, index=["A arbre prof. 30", "B k-NN k=2000", "C forêt"],)scores["écart"] = scores["apprentissage"] - scores["nouvelles"]
def diagnostic(ligne): if ligne["écart"] > 0.10: # grand écart : le modèle a appris le bruit return "surapprentissage" if ligne["apprentissage"] < 0.70: # mauvais même sur ses propres données return "sous-apprentissage" return "situation saine"
scores["diagnostic"] = scores.apply(diagnostic, axis=1)print(scores)# A arbre prof. 30 : écart 0.29, surapprentissage# B k-NN k=2000 : écart 0.01, sous-apprentissage# C forêt : écart 0.04, situation sainescores <- data.frame( modele = c("A arbre prof. 30", "B k-NN k=2000", "C forêt"), apprentissage = c(1.00, 0.62, 0.88), nouvelles = c(0.71, 0.61, 0.84))scores$ecart <- scores$apprentissage - scores$nouvelles
# Grand écart : surapprentissage ; mauvais même en apprentissage : sous-apprentissagescores$diagnostic <- ifelse(scores$ecart > 0.10, "surapprentissage", ifelse(scores$apprentissage < 0.70, "sous-apprentissage", "situation saine"))print(scores)# A arbre prof. 30 : écart 0.29, surapprentissage# B k-NN k=2000 : écart 0.01, sous-apprentissage# C forêt : écart 0.04, situation saineExercice · Biais ou variance ?
moyenVous entraînez le même type de modèle sur cinq échantillons différents de 50 ventes d’appartements, et vous regardez la prédiction pour un 60 m² (le vrai prix moyen d’un 60 m² est de 250 000 €).
- Modèle 1 : 205, 208, 203, 207, 206 (milliers d’euros).
- Modèle 2 : 190, 310, 240, 285, 225 (milliers d’euros).
Lequel a un fort biais ? Lequel a une forte variance ? Lequel est probablement un polynôme de degré élevé ?
Voir la solution
- Le modèle 1 donne des prédictions très stables (de 203 à 208) mais toutes éloignées de 250 : il a un fort biais et une faible variance. C’est typiquement un modèle trop simple, par exemple un prix constant ou une droite mal adaptée.
- Le modèle 2 a une moyenne de , donc un biais quasi nul, mais ses prédictions varient de 190 à 310 selon l’échantillon : forte variance. C’est le comportement d’un modèle trop souple, comme un polynôme de degré élevé, qui change de forme dès qu’on modifie quelques points.
Aucun des deux n’est satisfaisant : le bon modèle se trouve entre les deux, avec un biais et une variance tous deux modérés.
En code
import numpy as np
vrai_prix = 250 # vrai prix moyen d'un 60 m² (k€)modele_1 = np.array([205, 208, 203, 207, 206])modele_2 = np.array([190, 310, 240, 285, 225])
for nom, pred in [("modèle 1", modele_1), ("modèle 2", modele_2)]: biais = pred.mean() - vrai_prix # écart moyen à la vérité ecart_type = pred.std(ddof=1) # dispersion d'un échantillon à l'autre print(f"{nom} : moyenne {pred.mean():.1f}, biais {biais:.1f}, écart-type {ecart_type:.1f}")# modèle 1 : moyenne 205.8, biais -44.2, écart-type 1.9 (fort biais, faible variance)# modèle 2 : moyenne 250.0, biais 0.0, écart-type 47.8 (biais nul, forte variance)vrai_prix <- 250 # vrai prix moyen d'un 60 m² (k€)predictions <- list(modele_1 = c(205, 208, 203, 207, 206), modele_2 = c(190, 310, 240, 285, 225))
for (nom in names(predictions)) { pred <- predictions[[nom]] cat(nom, ": moyenne", mean(pred), ", biais", mean(pred) - vrai_prix, # écart moyen à la vérité ", écart-type", round(sd(pred), 1), "\n") # dispersion entre échantillons}# modèle 1 : moyenne 205.8, biais -44.2, écart-type 1.9 (fort biais, faible variance)# modèle 2 : moyenne 250.0, biais 0.0, écart-type 47.8 (biais nul, forte variance)Exercice · Pourquoi pas zéro erreur ?
difficileUn collègue affirme : « Avec assez de données et un modèle assez puissant, on peut toujours faire tendre l’erreur réelle vers zéro. » En vous appuyant sur la décomposition biais-variance, expliquez pourquoi c’est faux, avec un exemple tiré de l’immobilier.
Voir la solution
La décomposition fait apparaître trois termes : biais², variance et bruit irréductible . Plus de données et un modèle bien choisi peuvent réduire les deux premiers, mais pas le troisième. Le bruit vient de tout ce que les caractéristiques ne décrivent pas : deux appartements de même surface, dans la même rue, peuvent se vendre à des prix différents parce que l’un a été vendu dans l’urgence, que l’autre a un balcon, ou simplement à cause de la négociation. Tant que le modèle ne connaît que la surface, cette part de hasard reste hors de sa portée. La seule façon de réduire est d’ajouter de l’information (de nouvelles caractéristiques, comme l’étage ou l’état du bien), ce qui change le problème lui-même. En classification, ce plancher porte un nom, l’erreur de Bayes, que vous rencontrerez au chapitre 5.
En code
import numpy as np
rng = np.random.default_rng(0)
def simuler(n): surface = rng.uniform(20, 120, n) prix = 3.5 * surface + 40 + rng.normal(0, 25, n) # bruit d'écart-type 25 return surface, prix
x_test, y_test = simuler(100_000)for n in [15, 150, 1_500, 150_000]: x, y = simuler(n) coef = np.polyfit(x, y, 3) # modèle souple (degré 3) mse = np.mean((np.polyval(coef, x_test) - y_test) ** 2) print(f"n = {n:>7} : MSE test {mse:.1f}")print("bruit irréductible sigma² =", 25 ** 2)# n = 15 : MSE test 790.9# n = 150 : MSE test 644.3# n = 1500 : MSE test 628.9# n = 150000 : MSE test 628.0 (plancher : sigma² = 625, jamais franchi)set.seed(0)simuler <- function(n) { surface <- runif(n, 20, 120) data.frame(surface, prix = 3.5 * surface + 40 + rnorm(n, 0, 25)) # bruit d'écart-type 25}
test <- simuler(1e5)for (n in c(15, 150, 1500, 150000)) { modele <- lm(prix ~ poly(surface, 3), data = simuler(n)) # modèle souple (degré 3) mse <- mean((predict(modele, test) - test$prix)^2) cat("n =", format(n, scientific = FALSE), ": MSE test", round(mse, 1), "\n")}cat("bruit irréductible sigma² =", 25^2, "\n")# n = 15 : MSE test 1033# n = 150 : MSE test 627.7# n = 1500 : MSE test 624.3# n = 150000 : MSE test 622.8 (on plafonne autour de sigma² = 625)