Aller au contenu

É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.

En classification binaire, chaque patient est positif (malade) ou négatif (sain), et le classifieur f^\hat{f} 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égatifPrédit positif
Vrai négatifTN (true negative)FP (false positive)
Vrai positifFN (false negative)TP (true positive)

Sur un échantillon de test de nn patients, le taux d’erreur est la proportion de cases hors diagonale :

R^(f^)=FP+FNn=1n∑i=1n1{Yi≠f^(Xi)}\hat{R}(\hat{f}) = \frac{FP + FN}{n} = \frac{1}{n} \sum_{i=1}^{n} \mathbf{1}\big\lbrace Y_i \neq \hat{f}(X_i) \big\rbrace

où 1{⋅}\mathbf{1}\lbrace \cdot \rbrace 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 : A^=1−R^=(TP+TN)/n\hat{A} = 1 - \hat{R} = (TP + TN)/n.

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 :

Preˊcision(f^)=P(Y=pos∣f^(X)=pos)≈TPTP+FP\text{Précision}(\hat{f}) = P\big(Y = \text{pos} \mid \hat{f}(X) = \text{pos}\big) \approx \frac{TP}{TP + FP}

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 :

Rappel(f^)=P(f^(X)=pos∣Y=pos)≈TPTP+FN\text{Rappel}(\hat{f}) = P\big(\hat{f}(X) = \text{pos} \mid Y = \text{pos}\big) \approx \frac{TP}{TP + FN}

C’est la couverture : quelle part des malades l’outil retrouve-t-il ?

Sur notre exemple : précision =40/135≈0,30= 40/135 \approx 0{,}30 et rappel =40/50=0,80= 40/50 = 0{,}80. L’outil retrouve 80 % des malades, mais sept alertes sur dix sont de fausses alertes.

TN = 855sains déclarés sainsFP = 95 : sains alertés à tortTP = 40FN = 10950 patients sains50maladesrappel = 40 / 50 = 0,80précision = 40 / (40 + 95) ≈ 0,30préditsainpréditmalade
Les aires sont proportionnelles aux effectifs. Le rappel se lit dans la colonne étroite des malades (40 sur 50) ; la précision compare les deux bandes d'alerte, et les 95 fausses alertes des sains écrasent les 40 vraies.

Les deux probabilités conditionnent « dans des sens opposés », et la formule de Bayes les relie. Notons π=P(Y=pos)\pi = P(Y = \text{pos}) la prévalence (la proportion de malades) et TFP=P(f^(X)=pos∣Y=neˊg)\text{TFP} = P(\hat{f}(X) = \text{pos} \mid Y = \text{nég}) le taux de faux positifs. Alors :

Preˊcision=Rappel×πRappel×π+TFP×(1−π)\text{Précision} = \frac{\text{Rappel} \times \pi}{\text{Rappel} \times \pi + \text{TFP} \times (1 - \pi)}

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 π\pi5 %50 %
Précision0,300,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.

0 %20 %40 %60 %80 %100 %prévalence π (part de malades)0,00,20,40,60,81,0probabilitédépistage (π = 5 %) :précision 0,30service spécialisé (π = 50 %) :précision 0,89précision (rappel 0,8, faux positifs 0,1)rappel : ne dépend pas de π
Le même outil, deux populations : la précision monte avec la prévalence, alors que le rappel ne bouge pas.

Pour résumer précision et rappel en un seul nombre, on utilise le score F1, leur moyenne harmonique : F1=2×preˊcision×rappelpreˊcision+rappelF_1 = \frac{2 \times \text{précision} \times \text{rappel}}{\text{précision} + \text{rappel}}. Ici, F1=2×0,296×0,8/1,096≈0,43F_1 = 2 \times 0{,}296 \times 0{,}8 / 1{,}096 \approx 0{,}43. Le cours d’Apprentissage automatique détaille les autres mesures (spécificité, FβF_\beta) 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.

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é qq, en tirant à pile ou face (biaisé), sans regarder ses données.

  • Parmi les malades, il en déclare positifs une proportion qq : son rappel vaut qq.
  • Parmi les sains, il en déclare aussi une proportion qq : son taux de faux positifs vaut qq.

En faisant varier qq de 0 à 1, on obtient les points (q,q)(q, q) : 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 qq, 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 π\pi, 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 π\pi. 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.

0,000,250,500,751,00taux de faux positifs0,00,20,40,60,81,0rappel (taux de vrais positifs)tirage au hasardavec probabilité q :point (q, q)seuil 0,80seuil 0,60seuil 0,35courbe ROCclassifieur (AUC = 0,875)hasard (AUC = 0,5)0,000,250,500,751,00rappel0,00,20,40,60,81,0précisioncourbe précision-rappelclassifieurhasard : prévalence 4/10
Les dix patients de l'exemple. En ROC, tout tirage au hasard tombe sur la diagonale ; en précision-rappel, le hasard est l'horizontale à la prévalence, ici 4/10.

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 cFNc_{FN} pour un faux négatif et cFPc_{FP} pour un faux positif. Pour un patient dont la probabilité d’être malade est p=P(Y=pos∣X=x)p = P(Y = \text{pos} \mid X = x) :

  • le déclarer sain coûte en moyenne cFN×pc_{FN} \times p (on se trompe s’il est malade) ;
  • le déclarer malade coûte en moyenne cFP×(1−p)c_{FP} \times (1 - p) (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 cFN p>cFP (1−p)c_{FN}\,p > c_{FP}\,(1 - p), c’est-à-dire

p>cFPcFP+cFNp > \frac{c_{FP}}{c_{FP} + c_{FN}}

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 (cFN=10c_{FN} = 10, cFP=1c_{FP} = 1), le seuil descend à 1/11≈0,091/11 \approx 0{,}09 : on déclare malade tout patient qui a plus de 9 % de chances de l’être.

0,00,20,40,60,81,0probabilité p d’être malade0,00,20,40,60,81,01,2coût moyen de la décisionseuil 0,50coûts égauxdéclarer sain : pdéclarer malade : 1 − p0,00,20,40,60,81,0probabilité p d’être malade0,00,20,40,60,81,01,2seuil 1/11 ≈ 0,09on déclare maladedès p > 0,09faux négatif 10 fois plus cherdéclarer sain : 10 × pdéclarer malade : 1 − p
La règle de Bayes choisit la décision la moins coûteuse, en bas des deux droites. Quand le faux négatif coûte dix fois plus, la droite « déclarer sain » devient raide et le seuil descend de 0,5 à 0,09.

En pratique, on procède ainsi :

  1. fixer avec le métier les coûts cFNc_{FN} et cFPc_{FP}, ou une contrainte (« rappel d’au moins 95 % ») ;
  2. choisir le seuil sur un échantillon de validation ;
  3. mesurer une seule fois, sur l’échantillon de test, le rappel, la précision et le coût obtenus.
import numpy as np
from 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))
  • Le taux d’erreur (FP+FN)/n(FP + FN)/n 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 P(Y=pos∣f^(X)=pos)P(Y = \text{pos} \mid \hat{f}(X) = \text{pos}), la fiabilité d’une alerte ; le rappel est P(f^(X)=pos∣Y=pos)P(\hat{f}(X) = \text{pos} \mid Y = \text{pos}), 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 cFNc_{FN} et cFPc_{FP}, la règle de Bayes déclare positif quand p>cFP/(cFP+cFN)p > c_{FP}/(c_{FP} + c_{FN}) ; à 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.

Exercice · Lire une matrice de confusion

facile

Sur 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 : (50+20)/1000=0,07(50 + 20)/1000 = 0{,}07 ; exactitude : 0,930{,}93.
  • Précision : 30/(30+50)=0,37530/(30 + 50) = 0{,}375.
  • Rappel : 30/(30+20)=0,6030/(30 + 20) = 0{,}60.
  • F1=2×0,375×0,6/(0,375+0,6)=0,45/0,975≈0,46F_1 = 2 \times 0{,}375 \times 0{,}6 / (0{,}375 + 0{,}6) = 0{,}45/0{,}975 \approx 0{,}46.

Il y a 20+30=5020 + 30 = 50 malades, donc « tout le monde sain » obtient une exactitude de 950/1000=0,95950/1000 = 0{,}95, 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, 30
n = tn + fp + fn + tp
taux_erreur = (fp + fn) / n
precision = tp / (tp + fp)
rappel = tp / (tp + fn)
f1 = 2 * precision * rappel / (precision + rappel)
print(taux_erreur, round(1 - taux_erreur, 2)) # 0.07 0.93
print(precision, rappel, round(f1, 2)) # 0.375 0.6 0.46
# Classifieur trivial « tout le monde sain » : il a raison sur tous les sains
print((tn + fp) / n) # 0.95

Exercice · Le même test, deux populations

moyen

Un test a un rappel de 0,9 et un taux de faux positifs de 0,05. Calculez sa précision :

  1. dans un dépistage de masse, où la prévalence est de 1 % ;
  2. 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 =0,9 π0,9 π+0,05 (1−π)= \frac{0{,}9\,\pi}{0{,}9\,\pi + 0{,}05\,(1 - \pi)}.

  1. π=0,01\pi = 0{,}01 : 0,0090,009+0,0495=0,0090,0585≈0,15\frac{0{,}009}{0{,}009 + 0{,}0495} = \frac{0{,}009}{0{,}0585} \approx 0{,}15.
  2. π=0,2\pi = 0{,}2 : 0,180,18+0,04=0,180,22≈0,82\frac{0{,}18}{0{,}18 + 0{,}04} = \frac{0{,}18}{0{,}22} \approx 0{,}82.

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 masse
rng = np.random.default_rng(0)
malade = rng.uniform(size=100_000) < 0.01
alerte = 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 alertes

Exercice · Choisir un seuil par les coûts

difficile

Un 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.

  1. Quel est le seuil optimal ?
  2. Trois patients ont des scores de 0,03, 0,06 et 0,30. Lesquels envoyez-vous en examen de confirmation ?
  3. Montrez que, pour le patient à 0,06, déclarer « malade » coûte moins cher en moyenne que déclarer « sain ».
Voir la solution
  1. Seuil =cFPcFP+cFN=11+20=121≈0,048= \frac{c_{FP}}{c_{FP} + c_{FN}} = \frac{1}{1 + 20} = \frac{1}{21} \approx 0{,}048.

  2. On déclare malade si p>0,048p > 0{,}048 : les patients à 0,06 et 0,30 passent l’examen, pas celui à 0,03.

  3. Prenons cFP=1c_{FP} = 1 et cFN=20c_{FN} = 20. Pour p=0,06p = 0{,}06 :

    • déclarer sain coûte en moyenne 20×0,06=1,220 \times 0{,}06 = 1{,}2 ;
    • déclarer malade coûte en moyenne 1×0,94=0,941 \times 0{,}94 = 0{,}94.

    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, 20
seuil = 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 malade
cout_malade = c_fp * (1 - scores) # déclarer malade : coûte si sain
print(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,30