cross validation - Validation croisée : Introduction

cross-validation

Une des anecdotes comiques que j'ai au sujet de la cross-validation c'est : Comment, je me suis fait canarder par ma directrice de Thèse pour un simple quiproquo !  

C'était une réunion avec ma directrice de thèse et deux de mes encadrants ! Ou bien entendu je devais passer à la trappe en présentant mon travail sur l'année ! 

Sauf que, si vous travaillez déjà vous devez très probablement le savoir, sans données  il n'est quasiment possible de rien faire ! Et il se trouve que j'avais des données, mais vraiment très peu ! Genre max, un exemple par classe, si tu vois ce que je veux dire  ! car c'était un problème de classification que je devais résoudre à cette époque la !

Madame X, ma DdT pour ne pas le dire en entier de peur que de me faire dénoncer ! était vénère ce jour la  et m'a pris de court en me disant oui, mais si vous avez peu d'exemples pourquoi n'avez vous pas fait de validation croisée ! Ce qui m'emmène à la petite leçon du jour! Qu'est-ce que la validation croisée. 

La validation croisée est une astuce très maline qui a été inventée pour pouvoir à la fois contrer les problèmes liés au manque d'exemples, mais également pouvoir tester les algo qu'on a choisi de tester sur tout le dataset plutôt que sur une petite base de test qui classiquement correspond à environ 1/3 des exemples à disposition.

Du coup classiquement avec la validation croisée, on se retrouve avec 3 fois plus d'exemples pour tester.

Dans cet article, nous verrons

 ce qu'est la cross-validation ou validation croisée en français

Comment utiliser la bibliothèque scikit-learn pour faire de la cross-validation

Les différentes méthodes existantes de cross-validation 


Qu'est-ce qu'est la cross-validation ? 

Classiquement, quand l'on entraine un modèle, l'on définit un jeu d'apprentissage sur lequel l'on entraînera le problème, éventuellement un jeu de validation qui nous permettra de valider que le modèle est bien entraîné et de trouver les hyperparamètre optimaux du classifieur, et enfin un jeu de test qui nous permettra de nous faire une idée de la réelle performance du modèle.

La cross-validation, pousse le schéma précédent un peu à l'extrême dans l'idée qu'on va le faire de nombreuses fois, idéalement 5 ou 10 fois si je jeu de données est suffisamment grand pour le permettre. 

L'idée est la suivante : la cross validation consiste à scinder un jeu de données en k différents jeux de test, avec pour règle qu'aucun exemple du jeu de données initial ne peut appartenir à deux bases de test différentes.  Par conséquent, nous nous retrouvons avec k base de test toute ayant k/nombre total d'exemples comme nombre d'exemples pour tester.

Le reste des exemples obtenu pour chaque base de test est utilisé comme base d'apprentissage. Le schéma suivant  résume bien cette idée. 

                                    Schéma                                                    

Cette stratégie a un quadruple effet : 

Si auparavant, l'on manquait d'exemple pour le test, si par exemple nous avions choisi initialement d'utiliser 1/5 des exemples pour les tests et 4/5 des exemples pour l'apprentissage, nous nous retrouvons grâce à la cross-validation à cinq fois plus d'exemples puisque tout le jeu de données passe à la trappe. 

D'un autre côté, le fait que le modèle entraîné soit testé sur plusieurs bases d'apprentissage différentes et qu'il malgré tout obtienne de bons résultats sur différentes  bases de test nous assure qu'il est robuste et permet de nous assurer quelque soit la base d'apprentissage, les résultats devraient plus ou moins resté les mêmes. L'on s'assure donc que le modèle a une bonne capacité de généralisation.

Enfin, cela permet d'avoir une idée approximative de comment réagirait le modèle testé en cas réel meilleur que si l'on était resté dans le schéma classique apprentissage-test. 

 

L'astuce est la suivante, il s'agit de faire un tirage sans remise des exemples de la base de données. 

Avec par exemple une validation croisée ou k-cross validation en anglais avec k=3, c'est-à-dire que l'on divise la base de données en 3 bases de test, toute avec des exemples différents, aucun exemple ne peut se retrouver simultanément dans 2 bases test ! Les 2 tiers restant de chaque base après le retranchement des exemples de test son utilisé pour l'entraînement du modèle. 

Comment utiliser la bibliothèque scikit-learn pour faire de la cross-validation ? 

Si tu connais pas scikit-learn, ben c'est peut être le moment de t'y intéréssé car actuellement c'est l'une des meilleurs librairie python pour faire du machine learning. Et comme il se doit la cross-validation y est implémenté. 

Il existe plusieurs manières d'utiliser la validation croisée avec la bibliothèque sklearn. Il y a tout d'abord une fonction particulièrement cool, que personnellement j'utilise tout le temps qui s'appelle cross_val_score()

Il s'agit d'une fonction qui va te permettre de directement avoir l'accuracy ou le taux de bonne prédiction du modèle que tu entraînes s'il s'agit d'un classifieur de la manière la plus simple qui soit. Ci-dessous, tu peux voir le prototype de la fonction.   

accuracy_pour_tout_les_folds=model_selection.cross_val_score(modèle, base, labels, cv=k)


Comme tu peux le voir ci-dessus, cette méthode  prend a 3 paramètres principaux, qui sont le modèle, la base d'exemple, la base de labels des exemples et enfin le nombre split que tu souhaites pour effectuer ta cross-validation. En sortie tu auras le taux de prédiction que la fonction a obtenu pour chacun des folds qu'elle a créés. 

En clair, ça donne un truc comme ça :

[accuracy_fold1, accuracy_fold 2, ...., accuracy_fold_k]

[0.9,0.8.0.7,......,0.92]

Pour avoir une idée du résultat global, il est possible d'utiliser la moyenne de ce dernier : 

moyenne_accuracy=accuracy_pour_tout_les_folds.mean()

La standard déviation est également utilisée pour évaluer la stabilité du modèle d'une base app à l'autre :

std_accuracy = accuracy_pour_tout_les_folds.std()

On va illustrer tout cela avec un petit exemple avec le jeu de données Iris. Je t'explique rapidement le délire : 

Le jeu de données Iris est un Jeu de données  qui a été constitué à partir de mesures prélevées des fleurs appartenant à 3 espèces différentes (Iris-setosa, Iris-versicolor et Iris-virginica). Il est constitué au total de 150 exemples avec 50 exemples pour chaque espèce. Les quatre mesures qui ont été prélevées sont : la longueur de sépale, la largeur de sépale, la longueur de pétale et la largeur de pétale.

Extrait du jeu de données :

5.1,3.5,1.4,0.2,Iris-setosa
4.9,3.0,1.4,0.2,Iris-setosa
6.0,2.9,4.5,1.5,Iris-versicolor
5.7,2.6,3.5,1.0,Iris-versicolor
7.9,3.8,6.4,2.0,Iris-virginica
6.4,2.8,5.6,2.2,Iris-virginica


from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score
#importation du dataset Iris
iris = load_iris()
#déclarationd d'un arbre de décision
arbre_decision = DecisionTreeClassifier()
#calcul du score en cross-validation obtenu par l'abre de décision sur le jeu de données Iris
scores = cross_val_score(arbre_decision, iris.data, iris.target, cv=5)
#Affichage des résultats
print ("Le score obtenu pour chacun des 5 fold créé est : ",scores)
print ("Cela fait une moyenne de : ",scores.mean())
print ("Et un écart-type de  : ",scores.std())
print ("---------------------------------------------")
scores = cross_val_score(arbre_decision, iris.data, iris.target, cv=10)
print ("Le score obtenu pour chacun des 10 folds créé est : ",scores)
print ("Cela fait une moyenne de : ",scores.mean())
print ("Et un écart-type de  : ",scores.std())


Résultat :


Le score obtenu pour chacun des 5 fold créé est :  [0.96666667 0.96666667 0.9        1.         1.        ]
Cela fait une moyenne de :  0.9666666666666668
Et un écart-type de  :  0.036514837167011066
---------------------------------------------
Le score obtenu pour chacun des 10 folds créé est :  [1.         0.93333333 1.         0.93333333 0.93333333 0.86666667
 0.93333333 1.         1.         1.        ]
Cela fait une moyenne de :  0.96
Et un écart-type de  :  0.044221663871405324


Globalement on remarque dans le résultat précédent que le score obtenu en 5-fold n'est pas très différent du score obtenu en 10 fold ce qui est plutôt rassurant et indique que l'on est une présence d'une base d'apprentissage avec de bons exemples. De plus le modèle fonction est bon pour le cas du dataset Iris car en moyenne, on a obtenu un score de 0.966 pour le 5-fold et de 0.96 pour la 10 folds ce qu'il laisse environ 4 % de marge d'erreur. Le modèle choisi risque donc de se tromper de 4 cas sur 100. Enfin l'écart-type de 0.0422, nous indique qu'il n'y a pas une très grande variation de résultat entre les différents fold, on est donc en présence d'un modèle stable. 

Il est possible de réitérer l'expérience avec le perceptron multicouche pour voir. Normalement il devrait obtenir un meilleur résultat si je ne me trompe pas. Pour cela, nous allons juste remplacer la ligne ou  l'on déclare l'arbre de décision par  cette ligne-là.

mlp = MLPClassifier(solver='lbfgs', alpha=1e-5, hidden_layer_sizes=(8, 2), random_state=1)


Il s'agit d'un perceptron multicouche avec 2 couches cachées de 8 neurones et entrainé via la rétropropagation du gradient. En gros, le code est le suivant :

from sklearn.datasets import load_iris
from sklearn.neural_network import MLPClassifier
from sklearn.model_selection import cross_val_score
#importation du dataset Iris
iris = load_iris()
#déclaration d'un perceptron multicouches
mlp = MLPClassifier(solver='lbfgs', alpha=1e-5, hidden_layer_sizes=(8, 2), random_state=1)
#calcul du score en cross-validation obtenu par l'abre de décision sur le jeu de données Iris
scores = cross_val_score(mlp, iris.data, iris.target, cv=5)
#Affichage des résultats
print ("Le score obtenu pour chacun des 5 fold créé est : ",scores)

print ("Cela fait une moyenne de : ",scores.mean())

print ("Et un écart-type de  : ",scores.std())

print ("---------------------------------------------")

scores = cross_val_score(mlp, iris.data, iris.target, cv=10)
print ("Le score obtenu pour chacun des 10 folds créé est : ",scores)

print ("Cela fait une moyenne de : ",scores.mean())

print ("Et un écart-type de  : ",scores.std())


Et le résultat :

Le score obtenu pour chacun des 5 folds créés est :  [1.         0.96666667 0.96666667 0.93333333 1.        ]
Cela fait une moyenne de :  0.9733333333333334
Et un écart-type de  :  0.02494438257849294
---------------------------------------------
Le score obtenu pour chacun des 10 folds créés est :  [1.         1.         1.         0.93333333 0.93333333 1.
 0.93333333 1.         1.         1.        ]
Cela fait une moyenne de :  0.9800000000000001
Et un écart-type de  :  0.030550504633038926

On remarque qu'il est de 97.3 pour le 5 fold et de 98 pour le 10 fold. Il est bien meilleur comme pouvait me laisser pensé ma première intuition. 

Dernière chose. Il peut arriver que dans certains cas, vous vouliez utiliser la cross-validation sans passer par la fonction cross_val_score comme moi je le montre plus haut dans ce tuto. C'est d'ailleurs une situation qui m'arrive souvent, car bien souvent j'utilise des modèles venant d'autre bibliothèque plus performante pour résoudre mon problème. C'est pourquoi je finis cette section sur scikit-learn en te présentant la fonction Kfold. 

Pour être court, car j'en suis sur que tu meurs d'envie de copier-coller direct le code pour le mettre dans ton programme si tu t'y connais un peu, et sinon j'en suis persuadé que tout ce qu'on a vu jusqu'à présent même mon anecdote avec la DdT t'aura au moins appris quelque chose et mis en appétit.  Quoi qu'il en soit. 

La classe Kfold est une classe qui te permet de splitter ton jeu de donnée dans le nombre de fold que tu souhaites avoir. Son constructeur est le suivant : 

kf = KFold(n_splits=k)



from sklearn.datasets import load_iris
from sklearn.neural_network import MLPClassifier
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import KFold
import numpy as np
#importation du dataset Iris
iris = load_iris()
#déclaration d'un perceptron multicouches
mlp = MLPClassifier(solver='lbfgs', alpha=1e-5, hidden_layer_sizes=(8, 2), random_state=1)
#Déclarration de l'objet qui va splitter la base de données en 10
#Shuffle à true de tels sorte que la base soit globalement équilibré en terme de classe
kf = KFold(n_splits=10,shuffle=True, random_state=1)
scores=[]
#Pour toutes les bases d'apprentissage et de tests obtenues après le split du jeu Iris
for train_index, test_index in kf.split(iris.data):
    #La base d'exemples courante d'apprentissage et celle de de test sont définit
    X_train, X_test = iris.data[train_index], iris.data[test_index]
    #la bases de labels courante d'apprentissage et celle de test sont également définis
    y_train, y_test = iris.target[train_index], iris.target[test_index]
    #Le modèle est entraîné avec la base d'exemple et de labels d'apprentissage courante
    mlp.fit (X_train,y_train)
    #Le score du fold courant est évalué avec base app courant
    score_fold_courant= mlp.score (X_test,y_test)
    #j'ai mis courant mais je me demande si c'est pas courante?
    #On dit quoi déjà ? Un fold ou une fold?
    #le score courant est rajouté a la liste des scores
    scores.append( score_fold_courant)
#la liste finale est tranformée en numpy array pour pouvoir utiliser les méthodes mean et std
scores=np.array(scores)
#Affichage des résultats
print ("Le score obtenu pour chacun des 10 fold créé est : ",scores)
print ("Cela fait une moyenne de : ",scores.mean())
print ("Et un écart-type de  : ",scores.std())
print ("---------------------------------------------")


Résultat :

Le score obtenu pour chacun des 10 fold créé est :  [1.         1.         1.         1.         1.         0.93333333
 1.         0.93333333 0.93333333 0.86666667]
Cela fait une moyenne de :  0.9666666666666668
Et un écart-type de  :  0.04472135954999579
---------------------------------------------



Les différentes méthodes de cross-validation

Leave One out 

Leave P out 

stratified

  

Quoi qu'il en soit, pour retourner à ma petite histoire,  je lui ai dit : la validation croisée, c'est quoi ça ? 

La DdT a explosé de colère, vous ne connaissez pas la validation croisée ? dit-elle 

Non, madame dis je avec mon regard innocent ! 

Non, mais ces étudiants il oublie même ce qu'on leur enseigne, rétorque-t-elle ! 

Sauf que moi dans ma p’tite tête, je connaissais la validation croisée, mais sous un autre nom ! Car moi je dis tout le temps la k-fold validation ou encore la cross validation! M'enfin bref, je ne vais pas m'attarder là-dessus.

Pour finir, l'argument de la DdT était complètement bidon, car la validation croisée implique qu'on ais au moins un exemple  de test et un exemple d'App. Or dans mon cas, j'avais tellement peu de données que j'avais littéralement un seul exemple pour l'apprentissage et pour le test.

Voilà c'est tout pour la leçon d'aujourd'hui, tu auras appris ce qu'est la cross-validation !


Source :

https://scikit-learn.org/stable/modules/cross_validation.html
https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.KFold.html

 

  

.