Évaluez un classifieur selon le coût des erreurs
Un hôpital teste un outil de dépistage d’une maladie qui touche 5 % des patients examinés. Sur 1 000 patients, l’outil a raison 895 fois : 89,5 % de bonnes réponses. Faut-il le déployer ? Avant de répondre, sachez qu’un outil qui déclare tout le monde sain, sans rien calculer, a raison 950 fois.
Ce chapitre traite de l’évaluation d’un classifieur, la version classification des étapes 6 et 7 de la roue. Vous y verrez que le taux d’erreur n’est qu’un risque empirique parmi d’autres, que précision et rappel sont des probabilités conditionnelles (ce qui explique bien des surprises), et que le choix final dépend d’une question que seul le métier peut trancher : quelle erreur coûte le plus cher ? Notre fil rouge : cet outil de dépistage.
Le taux d’erreur est un risque empirique
Section intitulée « Le taux d’erreur est un risque empirique »En classification binaire, chaque patient est positif (malade) ou négatif (sain), et le classifieur prédit l’une des deux classes. Les quatre situations possibles se rangent dans la matrice de confusion. Nous suivons ici la convention du cours : les lignes donnent la vraie classe, les colonnes la prédiction, le négatif en premier.
| Prédit négatif | Prédit positif | |
|---|---|---|
| Vrai négatif | TN (true negative) | FP (false positive) |
| Vrai positif | FN (false negative) | TP (true positive) |
Sur un échantillon de test de patients, le taux d’erreur est la proportion de cases hors diagonale :
où vaut 1 si la condition est vraie et 0 sinon. Vous reconnaissez le risque empirique du chapitre 2, avec la perte 0-1. L’exactitude (accuracy) en est le complément : .
Voilà le premier enseignement : avec des classes déséquilibrées, la perte 0-1 traite comme équivalents « rater un malade » et « inquiéter un bien-portant », alors que ces erreurs n’ont rien de comparable. Il faut regarder les deux types d’erreur séparément.
Précision et rappel, deux probabilités conditionnelles
Section intitulée « Précision et rappel, deux probabilités conditionnelles »Le cours définissait précision et rappel comme des probabilités conditionnelles, et c’est la manière la plus éclairante de les comprendre.
La précision (precision) est la probabilité qu’un patient soit vraiment positif sachant que la machine l’a déclaré positif :
C’est la fiabilité d’une alerte : quand l’outil sonne, à quel point vise-t-il juste ?
Le rappel (recall), ou sensibilité, est la probabilité que la machine déclare positif un patient sachant qu’il est positif :
C’est la couverture : quelle part des malades l’outil retrouve-t-il ?
Sur notre exemple : précision et rappel . L’outil retrouve 80 % des malades, mais sept alertes sur dix sont de fausses alertes.
Pourquoi la précision dépend de la prévalence
Section intitulée « Pourquoi la précision dépend de la prévalence »Les deux probabilités conditionnent « dans des sens opposés », et la formule de Bayes les relie. Notons la prévalence (la proportion de malades) et le taux de faux positifs. Alors :
Le numérateur est la proportion de vrais positifs dans la population, le dénominateur la proportion de patients déclarés positifs. Avec un rappel de 0,8 et un taux de faux positifs de 0,1 :
| Prévalence | 5 % | 50 % |
|---|---|---|
| Précision | 0,30 | 0,89 |
Le même outil, avec le même rappel et le même taux de faux positifs, a une précision de 0,30 dans une population de dépistage et de 0,89 dans un service où un patient sur deux est malade.
Pour résumer précision et rappel en un seul nombre, on utilise le score F1, leur moyenne harmonique : . Ici, . Le cours d’Apprentissage automatique détaille les autres mesures (spécificité, ) et leur calcul avec scikit-learn.
Faire varier le seuil : ROC, précision-rappel et hasard
Section intitulée « Faire varier le seuil : ROC, précision-rappel et hasard »La plupart des classifieurs produisent un score (souvent une probabilité estimée d’être malade), puis déclarent positif tout patient dont le score dépasse un seuil. Chaque seuil donne une matrice de confusion différente. En balayant tous les seuils, on trace deux courbes :
- la courbe ROC : le taux de vrais positifs (le rappel) en fonction du taux de faux positifs ;
- la courbe précision-rappel (PR) : la précision en fonction du rappel.
À quoi ressemble le hasard ?
Section intitulée « À quoi ressemble le hasard ? »Pour savoir si une courbe est bonne, il faut la comparer à celle d’un classifieur qui répond au hasard. Imaginons un « classifieur » qui déclare positif chaque patient avec une probabilité , en tirant à pile ou face (biaisé), sans regarder ses données.
- Parmi les malades, il en déclare positifs une proportion : son rappel vaut .
- Parmi les sains, il en déclare aussi une proportion : son taux de faux positifs vaut .
En faisant varier de 0 à 1, on obtient les points : la diagonale du graphique ROC. Les notes du cours disaient qu’une droite correspond à un tirage de Bernoulli de paramètre 1/2 ; c’est vrai, mais incomplet : tout tirage au hasard, quel que soit , tombe sur la diagonale. Une courbe ROC utile doit donc passer au-dessus de la diagonale, et l’aire sous la courbe (AUC) vaut 0,5 pour le hasard.
Sur la courbe PR, le même classifieur aléatoire a une précision égale à la prévalence , quel que soit son rappel (ses alertes tombent sur des malades dans la proportion où il y en a). Le hasard est donc une droite horizontale à la hauteur . Avec 5 % de malades, une précision de 0,30 est six fois mieux que le hasard, alors qu’elle paraît médiocre dans l’absolu.
Faire payer les erreurs à leur prix
Section intitulée « Faire payer les erreurs à leur prix »Le cours posait la question : à l’hôpital, qu’est-ce qui est pire, un faux négatif ou un faux positif ? Un faux positif envoie un patient sain passer un examen de confirmation : du stress, un coût, mais pas de danger. Un faux négatif renvoie chez lui un patient malade, dont la maladie évoluera sans traitement : c’est potentiellement dangereux. Le faux négatif est donc bien pire, et l’on privilégie le rappel.
Mais « privilégier le rappel », concrètement, cela veut dire quel seuil ? La théorie du chapitre 3 donne une réponse précise. Remplaçons la perte 0-1 par une perte qui coûte pour un faux négatif et pour un faux positif. Pour un patient dont la probabilité d’être malade est :
- le déclarer sain coûte en moyenne (on se trompe s’il est malade) ;
- le déclarer malade coûte en moyenne (on se trompe s’il est sain).
La machine de Bayes pour cette perte choisit l’option la moins coûteuse : elle déclare malade dès que , c’est-à-dire
Avec des coûts égaux, on retrouve le seuil 0,5 de la perte 0-1. Si rater un malade coûte dix fois plus cher qu’une fausse alerte (, ), le seuil descend à : on déclare malade tout patient qui a plus de 9 % de chances de l’être.
En pratique, on procède ainsi :
- fixer avec le métier les coûts et , ou une contrainte (« rappel d’au moins 95 % ») ;
- choisir le seuil sur un échantillon de validation ;
- mesurer une seule fois, sur l’échantillon de test, le rappel, la précision et le coût obtenus.
import numpy as npfrom sklearn.metrics import confusion_matrix, precision_score, recall_score
def cout_total(y_vrai, scores, seuil, c_fn=10, c_fp=1): y_pred = (scores >= seuil).astype(int) tn, fp, fn, tp = confusion_matrix(y_vrai, y_pred, labels=[0, 1]).ravel() return c_fn * fn + c_fp * fp
def choisir_seuil(y_val, scores_val, c_fn=10, c_fp=1): seuils = np.linspace(0.01, 0.99, 99) couts = [cout_total(y_val, scores_val, s, c_fn, c_fp) for s in seuils] return seuils[int(np.argmin(couts))] # le seuil de coût minimal sur la validation
# seuil = choisir_seuil(y_val, modele.predict_proba(X_val)[:, 1])# y_test_pred = (modele.predict_proba(X_test)[:, 1] >= seuil).astype(int)# print(recall_score(y_test, y_test_pred), precision_score(y_test, y_test_pred))En résumé
Section intitulée « En résumé »- Le taux d’erreur est le risque empirique pour la perte 0-1 ; avec des classes déséquilibrées, il peut être battu par le classifieur trivial.
- La précision est , la fiabilité d’une alerte ; le rappel est , la couverture des positifs.
- Par la formule de Bayes, la précision dépend de la prévalence, contrairement au rappel et au taux de faux positifs.
- Les courbes ROC et PR balaient tous les seuils. Le hasard est la diagonale en ROC (quel que soit le taux de tirage) et une horizontale à hauteur de la prévalence en PR.
- Avec des coûts et , la règle de Bayes déclare positif quand ; à l’hôpital, le faux négatif coûte plus cher, donc le seuil baisse et le rappel monte.
- Le seuil se choisit sur la validation, et la performance s’annonce sur le test.
Exercices
Section intitulée « Exercices »Exercice · Lire une matrice de confusion
facileSur 1 000 patients, un outil donne TN = 900, FP = 50, FN = 20, TP = 30 (lignes : vraie classe ; colonnes : prédiction). Calculez le taux d’erreur, l’exactitude, la précision, le rappel et le F1. Comparez l’exactitude à celle du classifieur « tout le monde sain ».
Voir la solution
- Taux d’erreur : ; exactitude : .
- Précision : .
- Rappel : .
- .
Il y a malades, donc « tout le monde sain » obtient une exactitude de , supérieure à celle de l’outil. L’exactitude est ici une mauvaise mesure : l’outil retrouve 60 % des malades, le classifieur trivial aucun.
En code
tn, fp, fn, tp = 900, 50, 20, 30n = tn + fp + fn + tp
taux_erreur = (fp + fn) / nprecision = tp / (tp + fp)rappel = tp / (tp + fn)f1 = 2 * precision * rappel / (precision + rappel)print(taux_erreur, round(1 - taux_erreur, 2)) # 0.07 0.93print(precision, rappel, round(f1, 2)) # 0.375 0.6 0.46
# Classifieur trivial « tout le monde sain » : il a raison sur tous les sainsprint((tn + fp) / n) # 0.95tn <- 900; fp <- 50; fn <- 20; tp <- 30n <- tn + fp + fn + tp
taux_erreur <- (fp + fn) / nprecision <- tp / (tp + fp)rappel <- tp / (tp + fn)f1 <- 2 * precision * rappel / (precision + rappel)c(taux_erreur, 1 - taux_erreur) # 0.07 0.93c(precision, rappel, f1) # 0.375 0.6 0.46
# Classifieur trivial « tout le monde sain » : il a raison sur tous les sains(tn + fp) / n # 0.95Exercice · Le même test, deux populations
moyenUn test a un rappel de 0,9 et un taux de faux positifs de 0,05. Calculez sa précision :
- dans un dépistage de masse, où la prévalence est de 1 % ;
- dans une consultation spécialisée, où elle est de 20 %.
Que répondre à un médecin qui s’étonne que « le même test marche beaucoup moins bien » en dépistage ?
Voir la solution
Par la formule de Bayes, précision .
- : .
- : .
Le test ne « marche » pas moins bien : il retrouve toujours 90 % des malades et n’alerte toujours que 5 % des sains. Mais en dépistage de masse, il y a 99 sains pour un malade, donc les 5 % de fausses alertes sur une population énorme dépassent largement les vraies alertes. C’est pourquoi un test positif en dépistage est presque toujours suivi d’un examen de confirmation.
En code
import numpy as np
def precision(rappel, tfp, prevalence): # formule de Bayes vrais_pos = rappel * prevalence return vrais_pos / (vrais_pos + tfp * (1 - prevalence))
for pi in (0.01, 0.20): print(pi, round(precision(0.9, 0.05, pi), 2)) # 0.01 : 0.15 / 0.2 : 0.82
# Vérification par simulation : 100 000 patients en dépistage de masserng = np.random.default_rng(0)malade = rng.uniform(size=100_000) < 0.01alerte = np.where(malade, rng.uniform(size=malade.size) < 0.9, rng.uniform(size=malade.size) < 0.05)print(round(malade[alerte].mean(), 3)) # 0.15 : part de vrais malades parmi les alertesprecision <- function(rappel, tfp, prevalence) { # formule de Bayes vrais_pos <- rappel * prevalence vrais_pos / (vrais_pos + tfp * (1 - prevalence))}precision(0.9, 0.05, c(0.01, 0.20)) # 0.15 0.82
# Vérification par simulation : 100 000 patients en dépistage de masseset.seed(0)malade <- runif(1e5) < 0.01alerte <- ifelse(malade, runif(1e5) < 0.9, runif(1e5) < 0.05)mean(malade[alerte]) # 0.155 : part de vrais malades parmi les alertesExercice · Choisir un seuil par les coûts
difficileUn faux négatif coûte 20 fois plus cher qu’un faux positif. Le modèle fournit des probabilités supposées bien calibrées.
- Quel est le seuil optimal ?
- Trois patients ont des scores de 0,03, 0,06 et 0,30. Lesquels envoyez-vous en examen de confirmation ?
- Montrez que, pour le patient à 0,06, déclarer « malade » coûte moins cher en moyenne que déclarer « sain ».
Voir la solution
-
Seuil .
-
On déclare malade si : les patients à 0,06 et 0,30 passent l’examen, pas celui à 0,03.
-
Prenons et . Pour :
- déclarer sain coûte en moyenne ;
- déclarer malade coûte en moyenne .
Déclarer malade est donc moins coûteux, alors même que le patient a 94 % de chances d’être sain. C’est tout l’intérêt de raisonner en coûts : la décision optimale n’est pas « la classe la plus probable », sauf quand les deux erreurs coûtent la même chose.
En code
import numpy as np
c_fp, c_fn = 1, 20seuil = c_fp / (c_fp + c_fn)print(round(seuil, 3)) # 0.048
scores = np.array([0.03, 0.06, 0.30])cout_sain = c_fn * scores # déclarer sain : coûte si maladecout_malade = c_fp * (1 - scores) # déclarer malade : coûte si sainprint(cout_sain, cout_malade) # [0.6 1.2 6. ] [0.97 0.94 0.7 ]print(scores > seuil) # [False True True] : examen pour 0,06 et 0,30c_fp <- 1; c_fn <- 20seuil <- c_fp / (c_fp + c_fn)seuil # 0.048
scores <- c(0.03, 0.06, 0.30)cout_sain <- c_fn * scores # déclarer sain : coûte si maladecout_malade <- c_fp * (1 - scores) # déclarer malade : coûte si sainrbind(cout_sain, cout_malade) # 0.6 1.2 6.0 / 0.97 0.94 0.70scores > seuil # FALSE TRUE TRUE : examen pour 0,06 et 0,30