3.1 Métriques et proportions

À partir du moment où la classification ne se fait pas sans erreurs, nous avons la présence de faux positifs et de faux négatifs. Le fait d’être un faux positif ou un faux négatif dépend essentiellement du point de vue, c’est-à-dire, de la classe d’intérêt. Cependant ces erreurs restent des erreurs, quel que soit le point de vue (sauf si nous fusionnons les classes confondues, bien évidemment). Un élément important à considérer est que le taux d’erreur, qu’il soit global ou local tel que mesuré, par exemple, par le rappel ou la précision pour une classe cible dépend énormément des proportions d’occurrences observées dans les différentes classes, et ce, aussi bien dans la phase d’apprentissage que de test ou de déploiement.

Ce problème nous ramène, en réalité à des calculs basiques de probabilités et au théorème de Bayes que nous avons abordés dans le module 7 du premier cours de science des données biologiques. Rappelez-vous, nous avions traité le cas du dépistage d’une maladie, dont le résultat dépendait du fait que la maladie est fréquente ou rare dans la population (sa prévalence). Si vous ne vous souvenez plus de quoi il s’agit, il peut être utile de relire maintenant les sections 7.1.1 à 7.1.3 de ce premier cours, car ce contenu s’applique parfaitement ici.

Pour illustrer une nouvelle fois ce phénomène, prenons un cas extrême. Admettons que notre classifieur soit capable de discerner sans aucune erreur une classe parmi deux possibles, disons la classe A. Quel sera le rappel ? Ça, c’est facile, comme tous les individus A seront classés comme A par notre classifieur, nous n’aurons aucun faux négatif. Donc, le rappel qui est \(TP / (TP + FN)\) vaudra toujours 1 ou 100%, et ce, quelles que soient les proportions relatives de A et de B dans notre échantillon. En absence d’erreur, il n’y a pas d’ambiguïté ni de dépendance aux proportions relatives.

Par contre, calculons maintenant la précision pour A, si nous savons que notre même classifieur a tendance à classer 10% des individus B comme des A (faux positifs FP). La précision étant le rapport \(TP / (TP + FP)\), interviennent ici ces faux positifs qui dépendent eux de ce que notre classifieur est capable de faire par rapport à la classe B. Dix pour cent de faux positifs, oui, mais sur combien d’items ? Prenons deux cas :

  1. La classe A est prédominante dans l’échantillon, disons qu’elle représente 80% de l’ensemble. Pour 100 individus, nous aurons donc 80 A, tous vrais positifs, et 20 B, dont 10%, soit deux sont faussement classés comme A. La précision est donc de \(80 / (80 + 2)\), soit un peu moins de 98%. C’est un très bon résultat.

  2. Dans notre second cas, les proportions sont inversées. Nous avons 20% de A et 80% de B. Donc, les vrais positifs pour 100 individus seront de 20 (tous les A) et les faux positifs seront 10% de 80, soit 8. La précision devient donc \(20 / (20 + 8)\), soit un tout petit peu plus de 71%.

Dans le second cas, la précision pour la classe A a diminué de manière très nette, rien qu’en changeant les proportions relatives de A et de B dans notre échantillon, les performances de notre classifieur n’ayant absolument pas été modifiées entre les deux situations. Nous venons de démontrer que les métriques, dès qu’il y a la moindre erreur de classification possible, sont très sensibles aux proportions relatives des individus dans les classes. Notons que la diminution de la précision dans le cas (2) est liée à la fois à la diminution des vrais positifs (puisqu’il y a moins de A dans notre set), et à l’augmentation des faux positifs, qui dépendent eux de la quantité de B en augmentation. Notons que le raisonnement est symétrique. Donc, le rappel sera également impacté dès que le taux de classification correcte pour A devient inférieur à 100%.

À l’extrême, il devient très difficile de classer correctement des items appartenant à des classes rares à cause de ce phénomène. En effet, si A ne représente plus que 1% de l’échantillon, nous aurons un seul vrai positif, et 10% de 99, soit pratiquement 10 faux positifs pour un lot de 100 individus. Donc, la précision pour A devient \(1 / (1 + 10)\), soit 9% seulement. Nous verrons alors notre classifieur comme très mauvais à l’examen des items qu’il prétend être des A, et pour lesquels la grosse majorité ne le sera pas. Pourtant, il classe A sans aucune erreur et ne fait que 10% d’erreur pour B, ce qui présenté de la sorte, passe pour un bon classifieur.

Les différentes métriques qui mesurent les performances de nos classifieurs sont très sensibles aux proportions relatives des différentes classes. Comme l’optimisation des classifieurs se fait sur base de ces métriques, elle est elle-même dépendante des proportions relatives des classes dans le set d’apprentissage.

Enfin, si les proportions relatives des items dans les classes diffèrent entre le set de test et les échantillons à classer lors du déploiement du classifieur, les valeurs calculées lors de la phase de test seront biaisées et ne reflèterons pas du tout les performances réelles du classifeur, une fois déployé.

3.1.1 Proportions en apprentissage

Une première conséquence intéressante de ce que nous venons d’observer est que les proportions relatives des items dans le set d’apprentissage vont conditionner le comportement de notre classifieur. Si nous voulons augmenter le rappel pour une classe, nous pouvons augmenter ses proportions de manière relative aux autres classes dans le set d’apprentissage. Mais si nous ne voulons pas perdre en précision, nous éviterons d’avoir des proportions trop déséquilibrées entre les classes. C’est pour cette raison qu’il est souvent conseillé de procéder à un ré-échantillonnage dans le but d’obtenir un effectif à peu près égal entre les différentes classes dans le set d’apprentissage. Par contre, changer les proportions de la sorte nous mène à une erreur globale plus grande en déploiement. Donc, à nous à définir au préalable quelle(s) métrique(s) –et quelle(s) classe(s)– sont les plus importantes par rapport à nos objectifs. Dans le challenge, vous aurez à réfléchir sur cette question !

Reprenons l’exemple de nos Amérindiens de la tribu Pima confrontés au diabète (en n’utilisant que les cas complets).

SciViews::R("ml")
pima <- read("PimaIndiansDiabetes2", package = "mlbench")
pima1 %<-% drop_na(pima)
table(pima1$diabetes)
# 
# neg pos 
# 262 130

Comme nous l’avions déjà signalé, nous avons deux fois plus de cas négatifs que positifs. Revenons sur notre classifieur à forêt aléatoire avec 500 arbres :

set.seed(3631)
pima1_rf <- mlRforest(data = pima1, diabetes ~ ., ntree = 500)
pima1_rf_conf <- confusion(cvpredict(pima1_rf, cv.k = 10), pima1$diabetes)
summary(pima1_rf_conf)
# 392 items classified with 309 true positives (error = 21.2%)
# 
# Global statistics on reweighted data:
# Error rate: 21.2%, F(micro-average): 0.753, F(macro-average): 0.75
# 
# # A data.frame: 2 x 24
#   ` `    Fscore Recall Precision Specificity   NPV   FPR   FNR   FDR   FOR  LRPT
#   <rown>  <dbl>  <dbl>     <dbl>       <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
# 1 neg     0.848  0.882     0.816       0.6   0.716 0.4   0.118 0.184 0.284  2.20
# 2 pos     0.653  0.6       0.716       0.882 0.816 0.118 0.4   0.284 0.184  5.07
# # … with 14 more variables: LRNT <dbl>, LRPS <dbl>, LRNS <dbl>, BalAcc <dbl>,
# #   MCC <dbl>, Chisq <dbl>, Bray <dbl>, Auto <dbl>, Manu <dbl>, A_M <dbl>,
# #   TP <int>, FP <dbl>, FN <dbl>, TN <dbl>

Notez que le rappel (Recall) est plus faible pour les cas positifs (60%) que pour les cas négatifs (88%). Ce n’est pas obligé, mais relativement normal, car notre classifieur est optimisé pour réduire l’erreur globale. Dans cette situation, ayant plus de cas négatifs, il vaut mieux déclarer un cas douteux comme négatif puisque le risque de se tromper est plus faible que de le déclarer positif. La précision pour pos est de 71.5%. Admettons que nous souhaitons maintenant étudier un maximum de ces Indiennes diabétiques. Le rappel pour la classe pos est notre métrique importante, or c’est la valeur la plus faible actuellement. Comme faire pour l’augmenter sans changer d’algorithme ? Et bien, une des façons de procéder consiste à changer délibérément les proportions des classes dans le set d’apprentissage. Si nous prenons le même nombre d’individus positifs que négatifs, cela donne ceci :

# Rééchantillonnage du set
pima1 %>.%
  group_by(., diabetes) |> sample_n(130L, replace = FALSE) %->%
  pima1b
table(pima1b$diabetes)
# 
# neg pos 
# 130 130
set.seed(854)
pima1b_rf <- mlRforest(data = pima1b, diabetes ~ ., ntree = 500)
pima1b_rf_conf <- confusion(cvpredict(pima1b_rf, cv.k = 10), pima1b$diabetes)
summary(pima1b_rf_conf)
# 260 items classified with 199 true positives (error = 23.5%)
# 
# Global statistics on reweighted data:
# Error rate: 23.5%, F(micro-average): 0.766, F(macro-average): 0.765
# 
# # A data.frame: 2 x 24
#   ` `    Fscore Recall Precision Specificity   NPV   FPR   FNR   FDR   FOR  LRPT
#   <rown>  <dbl>  <dbl>     <dbl>       <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
# 1 pos     0.770  0.785     0.756       0.746 0.776 0.254 0.215 0.244 0.224  3.09
# 2 neg     0.761  0.746     0.776       0.785 0.756 0.215 0.254 0.224 0.244  3.46
# # … with 14 more variables: LRNT <dbl>, LRPS <dbl>, LRNS <dbl>, BalAcc <dbl>,
# #   MCC <dbl>, Chisq <dbl>, Bray <dbl>, Auto <dbl>, Manu <dbl>, A_M <dbl>,
# #   TP <int>, FP <dbl>, FN <dbl>, TN <dbl>

Le rappel pour la classe pos est monté de 60% (pima1) à presque 75% (pima1b). En première approche, nous devrions nous réjouir de ce résultat, d’autant plus que la précision semble être montée en même temps à presque 78%. Notre nouveau classifieur semble nettement plus efficace pour trouver les Indiennes diabétiques dans l’ensemble de la population. Mais attention ! N’oublions jamais que les métriques sont sensibles aux proportions et que justement, nous venons de les “trafiquer”. À titre d’exercice, vous pouvez examiner l’effet d’un changement encore plus radical, par exemple, si vous prenez deux ou trois fois plus de cas positifs que négatifs dans votre set d’apprentissage.

Il existe plusieurs approches pour modifier les proportions relatives dans vos classes pour les sets de test et d’apprentissage. Celle que nous venons d’utiliser consiste à réduire le nombre d’items des classes les plus abondantes. Ici, nous comprenons intuitivement qu’une perte d’information n’est pas une bonne chose.

Nous pouvons aussi manipuler les poids des individus en les augmentant pour les classes rare, si la fonction qui calcule le classifieur supporte cette option, par exemple, classwt= pour mlRforest(). Si ce n’est pas le cas, nous pouvons simuler un poids de 2 ou de 3 en dupliquant ou tripliquant ces individus dans le set.

Enfin, il existe des techniques d’augmentation du nombre d’items par classe via la synthèse de cas artificiels en se basant sur l’information contenue dans le tableau de départ. L’une des techniques les plus utilisées s’appelle “SMOTE”. Ce blog l’explique en même temps qu’il montre que cela peut être dangereux d’augmenter artificiellement le nombre d’items. Nous vous conseillons donc d’utiliser plutôt l’une des deux approches expliquées ci-dessus (réduction des classes abondantes ou surpondération, éventuellement en les dupliquant, des items des classes rares).

Voici comment nous pouvons surpondérer les classes plus rares via la duplication, dans le cas de notre jeu de données pima1 :

# Pondération double pour les cas positifs en les dupliquant
pima1 %>.%
  sfilter(., diabetes == "pos") %>.% # Prendre les items de classe(s) rare(s)
  sbind_rows(., pima1) %->% # Les rajouter au tableau de départ = duplication
  pima1c
table(pima1c$diabetes) # Tableau mieux balancé sans perte
# 
# neg pos 
# 262 260

Bien évidemment, si les classes sont extrêmement inégales en effectifs, nous pouvons réaliser les deux approches simultanément : séparer le tableau de départ en classes rares et classes abondantes. Effectuer un échantillonnage qui limite les items dans les classes abondantes. Dupliquer les items des classes rares ou les tripliquer. Fusionner les deux tableaux ainsi obtenus à l’aide de bind_rows() ou sbind_rows().

3.1.2 Probabilités a priori

Nous avons la possibilité souvent d’estimer les proportions relatives des classes dans les données à classer. Il suffit de réaliser un échantillonnage aléatoire de taille raisonnable (par exemple, un minimum de 100 individus pour exprimer les résultats en pour cent), et de comptabiliser les proportions observées dans chaque classe. Ces proportions seront appelées les probabilités a priori (prior probabilities en anglais). Pour nos Indiennes Pima, les probabilités a priori (si l’échantillonnage de départ est bien aléatoire et réalisé dans les règles de l’art) sont déterminées en fonction de la table de contingence, mais plus loin, nous devrons fournir un vecteur numérique, donc nous convertissons directement ici à partir des proportions calculées sur l’ensemble des données :

pima_prior <- table(pima$diabetes) / nrow(pima)
pima_prior <- structure(as.numeric(pima_prior), names = names(pima_prior))
pima_prior
#       neg       pos 
# 0.6510417 0.3489583
Dans le cas où les probabilités a priori ne sont pas calculée à partir des données, mais sont obtenues depuis la littérature, il suffit de créer un vecteur numerique avec c() en nommant les différentes probabilités du même nom que les niveaux de la variable facteur réponse. Par exemple, ici, notre variable réponse a les niveaux neg et pos. Si nous trouvons l’information que la prévaleuce du diabète dans cette population est de 13.5%, nous écrirons nos probabilités a priori comme pima_prior <- c(neg = 0.865, pos = 0.135). La somme des probabilités doit être de un.

Ensuite, de nombreux algorithmes de classification peuvent nous renvoyer des probabilités d’appartenir à une classe, comme prédit par le classifieur indépendamment des proportions relatives par classe (la prédiction de la classe n’est que celle qui a la probabilité la plus élevée, en réalité). L’arbre de probabilités (résolution graphique), ou un calcul simple de probabilités faisant intervenir le théorème de Bayes (résolution numérique) permet de combiner les probabilités a priori et les probabilités issues du classifieur pour calculer ce que nous appelons les probabilités a posteriori, c’est-à-dire les probabilités d’être A ou B approchant les valeurs que nous observerons en déploiement de notre classifieur. Ce calcul nous l’avions déjà fait dans le module 7 du cours I. Nous ne redéveloppons pas ces calculs ici. Encore une fois, vous êtes invité à relire cette section si vous ne comprenez pas de quoi il s’agit.

Dans {mlearning}, les probabilités a priori peuvent être injectées dans l’objet confusion pour corriger nos métriques en faveur de valeurs plus réalistes relatives aux probabilités a posteriori. Donc, pour nos métriques obtenues à l’aide du set d’apprentissage pima1b aux proportions modifiées, nous rétablissons des valeurs plus plausibles par rapport à la population étudiée, et en même temps, plus comparables avec les métriques sur pima1 en procédant comme suit :

prior(pima1b_rf_conf) <- pima_prior
summary(pima1b_rf_conf)
# 260 items classified with 199 true positives (error = 23.5%)
# 
# Global statistics on reweighted data:
# Error rate: 24%, F(micro-average): 0.755, F(macro-average): 0.748
# 
# # A data.frame: 2 x 24
#   ` `    Fscore Recall Precision Specificity   NPV   FPR   FNR   FDR   FOR  LRPT
#   <rown>  <dbl>  <dbl>     <dbl>       <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
# 1 neg     0.802  0.746     0.866       0.785 0.624 0.215 0.254 0.134 0.376  3.46
# 2 pos     0.695  0.785     0.624       0.746 0.866 0.254 0.215 0.376 0.134  3.09
# # … with 14 more variables: LRNT <dbl>, LRPS <dbl>, LRNS <dbl>, BalAcc <dbl>,
# #   MCC <dbl>, Chisq <dbl>, Bray <dbl>, Auto <dbl>, Manu <dbl>, A_M <dbl>,
# #   TP <dbl>, FP <dbl>, FN <dbl>, TN <dbl>

Nous voyons que les valeurs de rappels ne sont pas modifiées par cette correction. Nous avons toujours 75% pour la classe pos, par contre, la précision a diminué à 62%, et donc le score F a lui aussi diminué. Notre précision pour les cas pos est donc maintenant moins bonne qu’avec pima1 de près de 10%. Ceci est normal. Tout classifieur doit faire un compromis entre rappel et précision. Si nous gagnons pour l’un, nous perdons inévitablement pour l’autre. Notre score F nous indique toutefois un léger gain global.

À retenir

Si vous êtes amené à modifier les proportions des différentes classes dans votre set d’apprentissage (pratique conseillée si les proportions sont trop différentes d’une classe à l’autre), n’oubliez pas de repondérer les calculs des métriques via prior() afin d’avoir des valeurs plus représentatives des performances de votre classifieur en situation.

À vous de jouer !

Réalisez le travail C03Ia_cardiovascular, partie I.

Travail individuel pour les étudiants inscrits au cours de Science des Données Biologiques III à l’UMONS à terminer avant le 2022-11-09 18:00:00.

Initiez votre projet GitHub Classroom

Voyez les explications dans le fichier README.md, partie I.