Classification supervisée

Dans ce TP, on va aborder la classification supervisée en la mettant en œuvre sur le jeu de données olives. Les notions essentielles de classification supervisée ont été vues en L2. Il s'agît donc ici de rappel et surtout de mise en pratique. Si nécessaire, on pourra consulter mes notes de cours de fouille de données pour une présentation de la classification supervisée.
Les 2 TPs précédents (tableaux de données et graphiques) doivent impérativement avoir été faits.

À l'issue de ce TP, vous m'envoyez par email un compte-rendu (format odt ou pdf) indiquant la réponse aux questions qui sont posées. Vous m'envoyez également un fichier python réalisant toutes les manipulations de ce TP : je dois pouvoir exécuter ce fichier en tapant python3 nom-de-votre-fichier.py et reproduire vos résultats. Cette exécution ne doit pas provoquer d'erreur de python. Remarque : un notebook ne convient pas.

Introduction

Les olives du jeu de données proviennent chacune d'une région particulière d'Italie : sud, Sardaigne, nord. Celles-ci sont numérotées et la valeur associée à chaque olive est dans l'attribut 1 dénommé region. Chaque région est découpée en sous-régions ; cette sous-région est dans les attributs 0 et 2 ; l'attribut 2 est juste le numéro de la sous-région dont le nom est donné par l'attribut 0. La carte ci-dessous indique ces régions et certaines sous-régions.

carte des zones géographiques d'origine des olvies du jeu de données (Cette figure est issue du livre sur ggobi.)

Chaque olive étant par ailleurs caractérisée par sa teneur en certains acides, la question que nous étudions dans ce TP est : à partir de cette composition chimique, peut-on déterminer la provenance géographique de l'olive ?
Si on arrive à déterminer la provenance à partir de la composition chimique de l'olive, on peut garantir que la provenance indiquée est la bonne et pourquoi pas, détecter ainsi des tentatives de fraudes si ce n'est pas le cas.

On va pratiquer en deux phases. Durant la première, on va explorer visuellement le jeu de données : la réponse à la question posée est-elle visible sur un ou des graphiques ? Durant la seconde, on va utiliser un algorithme d'apprentissage automatique pour essayer de répondre à la question. Les deux approches ne sont pas exclusives, bien au contraire : la première guide le choix d'une méthode à appliquer lors de la seconde.

Premiers pas : approche graphique (exploration visuelle des données)

Face à un jeu de données, la première chose à faire et de l'explorer visuellement. Dans le cas où on souhaite prédire la valeur d'un attribut, on se pose la question : la réponse à la question qui est posée saute-t-elle aux yeux ?
Donc, on commence par faire des graphiques très simples pour voir s'il est possible de prédire la région de provenance de l'olive. On commence par la région car il y a 3 régions (attribut region) qui se découpent en 8 zones (attributs et Area Name et area). Il est donc probablement plus facile de déterminer la région que la zone.
Cette exploration visuelle permet également d'orienter le choix d'un modèle pour réaliser la tâche de prédiction : si on voit des relations simples, on sait que des modèles simples pourront réaliser les prédictions voulues.

Visualisation univariée : répartition des valeurs de chaque attribut en fonction de sa classe

Par graphique très simple, on entend la visualisation de la répartition de la valeur d'un attribut (= univarié) en fonction de sa classe. Pour chaque acide, on réalise un graphique comme celui-ci pour l'attribut palmitic :

Taux d'acide palmatique dans les olives du jeu de données

D'un coup d'œil, on voit que cet attribut ne permet pas de distinguer les olives des 3 classes. Quand on considère une valeur de l'acide palmitique supérieure à 1400, on voit que l'on peut en déduire que l'olive provient de la zone 1 : il n'y a que des points bleus pour ces abscisses : tout va bien. Par contre, si cette valeur est comprise entre 1000 et 1200, les 3 classes sont possibles : pour ces abscisses, on voit des points des 3 couleurs. Donc, si la teneur en acide palmitique est 1100, on ne peut pas déterminer la zone d'origine de l'olive.
En conclusion, l'attribut palmitic ne permet pas de déterminer la région d'origine de l'olive.

À faire : réalisez ce graphique pour chacun des 8 acides et concluez (vérifiez que vous obtenez bien le même graphique que moi pour l'acide palmitique). Y a-t-il un, ou des attributs, qui permet de déterminer la région d'origine ? ou qui peut aider ?
Remarque : pour réaliser ce graphique, j'ai ajouté une colonne numero au tableau de données olives. Cette colonne contient tout simplement le numéro de la donnée.

À faire : sans dévoiler la solution, vous devez trouver un attribut qui sépare l'une des classes des deux autres. Pour celui-ci, vous obtenez un graphique qui ressemble à celui-ci :

Allure générale du graphique recherché.

Ce graphique montre que la classe 1 (points bleus) est facilement identifiée grace à cet attribut.
Quand vous l'avez trouvé, vous pouvez vous concentrer sur les 2 classes qui sont encore mélangées (jaune et rouge) et recommencer le même raisonnement en vous occupant uniquement des données de ces deux classes. Vous devez trouver un attribut qui sépare bien les 2 classes restantes.
Quels sont ces deux attributs et comment pouvez-vous déterminer la classe à partir de ces deux attributs ?

Visualisation bivariée

« Bivariée » signifie que l'on considère deux attributs. L'idée est ici la même que précédemment, mais au lieu de chercher une relation entre un attribut et la classe, on cherche une relation entre un couple d'attributs et la classe.
Reprenons le schéma réalisé à la fin du TP précédent :

Les olives du jeu de données dans le plan des attributs oleic/palmitic.

On voit que ces deux attributs ne permettent pas de déterminer la classe de manière simple : les points des 3 couleurs sont mélangés.
On peut quand même voir que pour une valeur de l'acide oléique inférieure à (environ) 7200, l'acide palmitique permet à peu près de bien prédire la classe 1 ou 3. Cela ne répond pas à notre question, mais c'est un élément d'information.

Je pourrais vous demander de chercher s'il existe une paire d'attributs qui permettent de prédire la région d'origine des olives mais je sais que cette recherche sera vaine et qu'elle vous prendra du temps. Donc vous pouvez passer à la suite, ou quand même vérifier qu'il n'y a rien à voir de simple sur aucun de ces graphiques.

Approche automatique

Il arrive que l'approche visuelle ne permette pas de voir quelque chose (c'est rare). Quoiqu'il en soit, en complément de l'exploration visuelle, nous allons essayer de prédire la région d'origine de l'olive à l'aide de méthodes qui ont pour objectif d'apprendre un modèle réalisant ce type de prédiction.

Face à un jeu de données de petite taille comme celui-ci (quelques centaines de lignes, une dizaine de colonnes, quelques classes), on commence par essayer une méthode qui généralement donne de très bons résultats, les arbres de décision. L'induction d'un arbre de décision est très souvent une méthode très efficace, très peu coûteuse en temps de calcul, qui donne de très bonnes performances pour la prédiction de la classe.

Pour cela, on va utiliser la bibliotèque scikit_learn. Pour pouvoir créer des arbres de décision, on fera : from sklearn import tree.
La documentation est .

Induction d'un arbre de décision

Pour induire un modèle, on fait comme suit : le jeu de données est découpé en deux parties, l'une utilisée pour induire le modèle, la seconde pour estimer la probabilité qu'il commette une erreur lors d'une prédiction.
La première partie constitue les données d'entraînement, la seconde les données de test.
Il est classique d'utiliser 80% des données disponibles comme données d'entraînement, les 20% restant constituant les données de test.
La répartition entre les deux parties est faite aléatoirement. Il est important de vérifier que les données sont stratifiées, c'est-à-dire que la proportion des données de chaque classe est à peu près la même dans les deux parties.

Pour découper le jeu de données en ces deux parties, on fait comme suit :

  1. on crée un vecteur de N nombres aléatoires uniformément répartis entre 0 et 1, où N est le nombre total de données. Pour cela, on utilise la fonction random.rand (N) du paquetage numpy. Appelons ce vecteur v.
  2. À partir de v, on construit un vecteur booléen qui contient 80% de valeurs True. Appelons ce vecteur b.
  3. On utilise b pour construire les deux jeux de données, l'un pour les données d'entraînement, l'autre pour les données de test. Appelons-les respectivement olives_train et olives_test.

Nous pouvons maintenant induire l'arbre de décision.

À faire :

  1. créer les deux jeux de données olives_train et olives_test.
  2. Vérifier que la propotion des données de chacun des 3 classes est à peu près la même dans les deux jeux de données.
  3. Induire un arbre de décision à partir de olives_train. Il y a beaucoup de paramètres qui peuvent être réglés mais nous nous en tiendrons à leur valeur par défaut dans un premier temps.

Diagnostic de l'arbre de décision

Avant même de regarder à quoi l'arbre ressemble, il est important de commencer par déterminer si cet arbre est capable de prédire la classe de manière précise. On va donc estimer la probabilité qu'il commette une erreur lorsqu'il prédit la classe d'une donnée. Pour cela, on :

À faire : faites ce qui vient d'être expliqué. Quelle est la valeur de cette estimation ?

Il est intéressant de mesurer ce taux d'erreur pour chaque classe.
À faire : mesurer le taux d'erreur pour chaque classe. Les 3 classes sont-elles toutes les 3 prédites correctement avec la même probabilité ?

Interprétation d'un arbre de décision

On peut obtenir une visualisation de l'arbre de décision en faisant par exemple :

import matplotlib as mpl
import matplotlib.pyplot as plt
fig, ax = plt.subplots ()
tree.plot_tree (arbre)
fig.show ()

qui ouvre une fenêtre avec le graphique suivant :

Arbre de décision

À faire : réalisez ce graphique et le comprendre. En particulier, comprendre ce qui est affiché dans chaque nœud.

Les tests réalisés à chaque nœud sont difficile à comprendre. Pour afficher le nom des attributs, on ajoutera feature_names = list (olives_test) dans l'appel de la méthode plot_tree(). Et on obtient :

Arbre de décision avec le nom des attributs

En consultant la documentation, on peut avoir une représentation plus jolie et informative avec la classe prédite qui est indiquée dans chaque feuille, comme celle-ci :

Joli arbre de décision

Faites-le.

Un arbre de décision peut facilement être transformé en un ensemble de règles de décision. Ici l'arbre est très petit, c'est facile.
Faites-le : quelles sont ces règles ?

Test de l'arbre de décision

Nous avons construit un arbre de décision qui donne de très bons résultats. Est-ce juste de la chance ou est-ce que décidément, cette tâche se résout facilement à l'aide d'un arbre de décision ?

Pour apporter des éléments de réponse à cette question, on refait plusieurs fois la construction de l'arbre de décision en découpant le jeu de données aléatoirement à chaque fois. Par exemple, on peut refaire toute cette procédure 30 fois (ou 100, ...). Pour chaque arbre induit, on estime sa probabilité d'erreur de prédiction que l'on stocke. Une fois les 30 (ou 100, ...) constructions réalisées, on vérifie que la probabilité d'erreur varie, peu, un peu, beaucoup, ... Si elle varie peu, c'est très bon signe : tous les arbres induits ont à peu près la même précision, qui est très bonne pour la prédiction de la région d'origine de l'olive.

À faire : effectuer ce qui vient d'être expliqué. Que pensez-vous du résultat ?

Reproductibilité

Pour construire les jeux de données d'entraînement et de test, nous avons utilisé des nombres générés pseudo-aléatoirement. Si vous effectuez ce qui a été expliqué plus haut pour l'induction d'un arbre deux fois de suite, vous obtiendrez des résultats différents (dans le cas présent, la différence est petite). Il est important de pouvoir reproduire les résultats que vous avez obtenu. Pour cela, il faut que les nombres pseudo-aléatoires qui sont générés soient les mêmes d'une exécution à la suivante. Pour obtenir ce résultat, il faut initialiser la graine du générateur de nombres pseudo-aléatoires. On fait ainsi :

graine = int ("ScienceDesDonnees", base=36)%2**31
rs = np.random.RandomState (graine)

graine est un entier. La première ligne transforme une chaîne de caractères quelconque en un entier (à la place de ce qui est écrit, vous pouvez écrire tout simplement graine = 1234567 par exemple). Ensuite, on initialise le générateur de nombres pseudo-aléatoires.
Puis, lors de l'induction de l'arbre de décision, on passe celui-ci en paramètre :

DecisionTreeClassifier (random_state = rs)

À faire : effectuer l'initialisation de cette graine et refaites toute la procédure d'induction d'arbre. Estimer sa probabiité d'erreur. Si vous effectuez cela plusieurs fois, vous devez obtenir exactement le même résultat à chaque fois.

Pour finir...

Refaites le même travail pour prédire l'attribut area.
Arrive-t-on à bien prédire toutes les classes ? Aussi bien que la région ?