Dans ce TP, nous abordons la réduction de dimension d'un jeu de données, en particulier l'analyse en composantes principales, méthode très connue, très utile et très utilisée.
On pourra consulter mes notes de cours de fouille de données pour une présentation de la réduction de dimension (chapitre 11).
À l'issue de ce TP, vous m'envoyez par email un compte-rendu (format 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.
La réduction de dimension a pour objectif de diminuer le nombre d'attributs d'un jeu de données, en se concentrant sur des attributs réellement pertinents. D'un point de vue géométrique, son objectif est de déterminer le sous-espace (la variété) dans lequel les données vivent.
Là encore, à chaque fois que cela est possible, une exploration visuelle des données est essentielle pour anticiper et comprendre.
Il y a plusieurs manières de réaliser une ACP. On peut utiliser la bibliothèque scikit_learn ou faire les calculs « :à la main » en utilisant simplement la bibliothèque numpy. La première manière n'apporte rien : elle masque des traitements très simples à faire, que j'ai décrit en cours : faire une ACP, c'est juste calculer la décomposition spectrale d'une matrice facile à calculer : pourquoi masquer cette chose si simple ? Pédogiquement, c'est donc une très mauvaise approche. De plus, elle fait appel à une bibliothèque inutile ici (ce qui, comme à chaque fois que l'on utilise un ordinateur inutilement, consomme de l'énergie, donc pollue la planète et, in fine participe au réchauffement climatique et au gaspillage des ressources naturelles). Avec la second méthode, on applique exactement la démarche vue en cours, et cela se fait en quelques appels de fonctions numpy, il n'y a rien de compliqué. Je décris ci-dessous les deux approches. Je recommande fortement la seconde. Remarque : pour le contrôle du 17 février, comme vous avez travaillés en TP avec la méthode que je déconseille, ne vous inquiétez pas, utilisez celle-là quand même si vous le souhaitez !
PCA de scikit_learn
On utilise la méthode PCA () de scikit_learn. Sous sa forme la plus simple, on l'utilise comme suit. On commence par créer un objet acp :
acp = sklearn.decomposition.PCA ()
que l'on applique ensuite sur un jeu de données centrées réduites contenu dans une matrice X :
acp.fit (X)
Remarque concernant le centrage et la réduction : on peut utiliser un objet scaler() mais cette méthode est buggée : le calcul de l'écart-type est faux car la moyenne est estimée et non pas connue, donc le dénominateur doit être N-1 et non N, où N est le nombre de données. Si N est grand cela ne fait pas de grande différence ; si N est petit, il vaut mieux faire le centrage et la réduction « à la main ». Pour obtenir la valeur correcte de la variance, il faut ajouter l'option ddof = 1 à l'appel de la méthode var ().
Après l'appel de la méthode fit (), l'objet acp contient différentes informations :
acp. explained_variance_ contient les valeurs propres dans l'ordre décroissant. Ce sont donc les variances selon les différents axes principaux.acp. components_ : chaque ligne contient un vecteur propre, donc une direction principale. Le vecteur en iè ligne correspond à la iè valeur propre.acp. explained_variance_ratio_ contient la proportion de variance expliquée de chacun des axes principaux.
On va mettre en application l'analyse en composante principale sur un petit jeu de données disponible là. Il contient les caractéristiques de 10 élèves (fictifs) : leur taille, leur poids, leur âge et leur note moyenne.
À faire :
Pour l'instant, on a fait des calculs qui n'ont pas beaucoup d'intérêt en tant que tel.
Nous abordons maintenant la partie vraiment importante : l'interprétation de ces calculs savants.
Commençons par observer et visualiser la proportion de variance expliquée par les composantes principales successives. Faites un graphique comme celui-ci :
Qu'en déduisez-vous ?
Une autre manière de représenter ces mêmes données consiste à visualiser le cumul de proportion de variance expliquée, comme ci-dessous :
On voit bien que le plan principal contient 80% de l'information, donc la projection dans ce plan est informative. Parfois, le plan principal comporte peu d'information (10, 20%). On en verra un exemple dans le travail en autonomie.
On peut ensuite visualiser les données dans les plans principaux. Les coordonnées des données dans l'espace factoriel sont obtenues par acp. transform (X).
À faire : représenter les données dans le plan principal et dans le plan défini par les axes principaux 1 et 3.
J'obtiens cela :
Pensez-vous que le second graphique (plan principal 1x3) soit pertinent ?
Comme on l'a vu en cours, le fait de réduire la dimension déforme le nuage de points. Il faut donc calculer cette déformation pour chaque donnée et écarter ou du moins identifier les données dont la projection dans un plan principal n'est pas fidèle à sa position relative dans l'espace des données. Pour cela, on mesure le cosinus au carré de l'angle entre la demi-droite passant par l'origine et cette donnée d'une part et le plan principal que l'on considère d'autre part. Si ce cosinus au carré est inférieur à 0,3, la déformation est importante (angle > 57°).
À faire : sur le graphique précédent, identifier les points dont la projection est déformée et les affichez avec une couleur particulière (ici en jaune).
J'obtiens cela :
Les axes principaux correspondent à des combinaisons linéaires des attributs originaux. La corrélation entre ces axes et les attributs originaux indique l'information que contient chaque axe principal par rapport aux attributs initiaux. On peut calculer, afficher et analyser ces nombres. On peut aussi réaliser le cercle de corrélation. Le faire.
J'obtiens cela :
À faire : Comment interprêtez-vous ce graphique ?
Appliquer ce qui vient d'être expliqué aux jeux de données suivants :
sklearn.datasets.load_iris ().data. Faites-en une ACP. Quand vous visualisez les iris, il est intéressant d'utiliser une couleur différente pour chacune des classes (disponibles dans sklearn.datasets.load_iris ().target).
read_table() en spécifiant l'argument index_col = 0. En effet, la première colonne du fichier contient chacune des lettres.
https://philippe-preux.codeberg.page/ensg/miashs/datasets/olives.csv pour accéder aux données). Traitez les mêmes questions que pour les iris.On applique exactement la démarche vue en cours :
On suppose que les données sont stockées dans une matrice notée X.
On reprend les 4 points :
moyennes = np.mean (X, axis = 0) écarts_types = np.sqrt (np. var (X, axis = 0, ddof = 1)) Xcr = (X - moyennes) / écarts_types
Xcr contient les données centrées et réduites.N, P = X.shape R = (Xcr.transpose () @ Xcr) / (N-1)
valeurs, vecteurs = np.linalg.eig (R) indices_valeurs_propres_triées = np.argsort (valeurs) [::-1] valeurs_propres = valeurs [indices_valeurs_propres_triées] vecteurs_propres = vecteurs [:, indices_valeurs_propres_triées]Comme on l'a vu en cours, les valeurs propres, et les vecteurs propres associés, doivent être pris en compte dans l'ordre décroissant de leur valeur. Aussi, après avoir calculé la décomposition spectrale, on trie les valeurs propres et les vecteurs propres.
Z = Xcr @ vecteurs_propres
À ce stade, on a calculé tous les objets indiqués plus haut, simplement leur nom n'est pas le même. Voici les équivalences :
acp. explained_variance_ == valeurs_propresacp. components_ == vecteurs_propresacp. explained_variance_ratio_ == valeurs_propres / Pacp. transform (X) == ZOn peut donc effectuer la même analyse que ci-dessus simplement en changeant le nom des objets utilisés.
fig , ax = plt.subplots ()
ax. plot (np.arange (1, P + 1), np.cumsum (valeurs_propres), c = "blue", marker = 'o', ls = ':')
ax. set_ylim (0, 1)
ax. set_xticks (np.arange (1, P + 1))
ax. set_title ("Fraction de variance cumulée pour les élèves.")
fig. show ()fig , ax = plt.subplots ()
ax. plot (np.arange (1, P + 1), np.cumsum (valeurs_propres) / P, c = "blue", marker = 'o', ls = ':')
ax. set_ylim (0, 1)
ax. set_xticks (np.arange (1, P + 1))
ax. set_title ("Fraction de variance cumulée pour les élèves.")
fig. show ()fig , ax = plt.subplots ()
ax. scatter (Z [:,0], Z [:,1])
ax. set_title ("Les élèves dans le plan principal 1x2")
fig. show ()cos2_12 = np.zeros ((N))
for i in range (N):
norme2_x_i = np.sum (Xcr [i,:]**2)
cos2_12 [i] = (Z [i, 0]**2 + Z [i, 1]**2) / norme2_x_i
cos2_13 = np.zeros ((N))
for i in range (N):
norme2_x_i = np.sum (Xcr [i,:]**2)
cos2_13 [i] = (Z [i, 0]**2 + Z [i, 2]**2) / norme2_x_i
couleurs_12 = []
for i in range (N):
couleurs_12. append (0)
for i in (np.arange (N) [cos2_12 < 0.3]):
couleurs_12 [i] = 1
fig , ax = plt.subplots ()
ax. scatter (Z [:,0], Z [:,1], c = couleurs_12)
ax. set_title ("Les élèves dans le plan principal 1x2")
fig. show ()from math import pi
cercle_r = 1
cercle_theta = np.linspace (0, 2 * pi, 360)
cercle_x = cercle_r * np.cos (cercle_theta)
cercle_y = cercle_r * np.sin (cercle_theta)
cor_initial_principal0 = vecteurs_propres [:,0] * np.sqrt (valeurs_propres [0])
cor_initial_principal1 = vecteurs_propres [:,1] * np.sqrt (valeurs_propres [1])
noms = ["Poids", "Taille", "Âge", "Note"]
fig , ax = plt.subplots ()
ax. plot (cercle_x, cercle_y)
ax. set_title ("Cercle de corrélation")
ax. set_aspect (1)
for j in range (P):
ax. text (cor_initial_principal0 [j], cor_initial_principal1 [j], noms [j])
fig.show ()On obtient exactement les mêmes graphiques que plus haut.