Travaux pratiques - Echantillonnage. Analyse en composantes principales¶
(la nouvelle version de cette séance, utilisant l’API DataFrame)
Références externes utiles :
Les exemples ci-dessous reprennent, en partie, ceux des documentations en ligne.
Echantillonnage¶
Nous travaillerons lors de cette séance sur des données Spambase Data Set issues de l’archive de l’UCI. Vous trouverez sur le site de l’UCI des explications plus détaillées concernant ces données, regardez-les, notamment la signification des variables et les classes auxquelles appartiennent les données. Pour charger ces données, ouvrez une fenêtre terminal et entrez les commandes suivantes :
[cloudera@quickstart ~]$ mkdir -p tpacp/data [cloudera@quickstart ~]$ cd tpacp/data [cloudera@quickstart data]$ wget https://archive.ics.uci.edu/ml/ machine-learning-databases/spambase/spambase.data --no-check-certificate
Dans le fichier spambase.data
, chaque ligne contient un vecteur de 57 valeurs float
et ensuite une étiquette de classe (0 ou 1). Pour lire ce fichier et créer la structure de données qui sera utilisée dans la suite, lancez spark-shell
dans une nouvelle fenêtre terminal. Entrez
[cloudera@quickstart ~]$ export PATH="$PATH:/usr/lib/spark/bin" [cloudera@quickstart ~]$ cd tpacp [cloudera@quickstart tpacp]$ spark-shell
et ensuite, dans l’interpréteur de commandes Spark :
scala> import org.apache.spark.mllib.util.MLUtils scala> import org.apache.spark.mllib.linalg.Vectors scala> import org.apache.spark.rdd.RDD scala> val spambase = sc.textFile("file:///home/cloudera/tpacp/data/spambase.data") scala> val lignes = spambase.map(l => l.split(",").map(_.toDouble)).map(l => (l.last, Vectors.dense(l.take(57)))).cache() scala> lignes.count() // pour vérifier la création de ce RDD de 4601 lignes
lignes
est un RDD de type RDD[K,V]
qui contiendra les données dont nous nous servirons dans la suite, nous le rendons donc persistant avec cache()
.
Question :
Expliquez comment le RDD lignes
est obtenu à partir du RDD spambase
.
Echantillonnage simple¶
Nous obtiendrons d’abord deux RDD, lignesEch1
et lignesEch2
, qui sont des échantillons du RDD lignes
avec deux valeurs très différentes pour fraction
(taux de sélection) :
scala> val lignesEch1 = lignes.sample(false, 0.5) scala> lignesEch1.count() scala> val lignesEch2 = lignes.sample(false, 0.1) scala> lignesEch2.count()
Question :
Que pouvez-vous constater en comparant le nombre de lignes de lignesEch1
ou lignesEch2
et celui de lignes
multiplié par la valeur de fraction
correspondante ? Expliquez.
Nous examinerons maintenant des statistiques simples calculées sur les colonnes des 3 RDD :
scala> import org.apache.spark.mllib.linalg.Vector scala> import org.apache.spark.mllib.stat.{MultivariateStatisticalSummary, Statistics} scala> val summary = Statistics.colStats(lignes.values) scala> val summary1 = Statistics.colStats(lignesEch1.values) scala> val summary2 = Statistics.colStats(lignesEch2.values) scala> println(summary.mean) scala> println(summary1.mean) scala> println(summary2.mean)
Question :
Quel constat faites-vous en comparant les moyennes calculées pour une même variable (colonne) sur lignes
et sur les deux échantillons ? Expliquez.
Echantillonnage stratifié¶
Pour un RDD de type (clé,valeur), l’échantillonnage stratifié permet de fixer le taux d’échantillonnage pour chaque valeur de la clé. La méthode sampleByKey
(voir PairRDDFunctions
dans la documentation API Spark en Scala) respecte de façon approximative les taux d’échantillonnage indiqués. La méthode sampleByKeyExact
respecte de façon plus stricte ces taux de sélection mais est plus coûteuse.
scala> import org.apache.spark.rdd.PairRDDFunctions scala> val fractions = Map(0.0 -> 0.5, 1.0 -> 0.5) // Map[K, Double] scala> val lignesEchStratApprox = lignes.sampleByKey(false, fractions) scala> val lignesEchStratExact = lignes.sampleByKeyExact(false, fractions) scala> lignes.countByKey() scala> lignesEch1.countByKey() scala> lignesEchStratApprox.countByKey() scala> lignesEchStratExact.countByKey()
Question :
Que constatez-vous concernant le rapport entre les nombres de valeurs pour chaque clé (chaque « strate ») ? Expliquez.
Analyse en composantes principales (ACP)¶
MLlib donne la possibilité de calculer directement les k premières composantes principales pour un ensemble d’observations. Les N observations sont représentées par une matrice distribuée de type RowMatrix
dont le nombre de lignes est égal au nombre d’observations (N) et le nombre colonnes au nombre de variables (noté ici par n). Ces k premières composantes principales sont les vecteurs propres d’une ACP centrée mais non réduite appliquée aux observations. Les composantes principales sont retournées dans une matrice locale DenseMatrix
de n lignes (le nombre de variables initiales) et k colonnes (le nombre de composantes principales demandées). Les composantes principales sont triées en ordre décroissant des valeurs propres correspondantes mais ces valeurs propres ne sont pas retournées.
Dans la suite, nous réaliserons des ACP sur les données Spambase Data Set, ainsi que sur un échantillon de ces données et sur des données aléatoires générées suivant une loi normale multidimensionnelle isotrope (même variance dans toutes les directions). Nous visualiserons les résultats.
Réaliser l’ACP¶
L’ACP centrée et normée sur les données lues dans le RDD lignes
:
scala> import org.apache.spark.mllib.linalg.distributed.RowMatrix scala> import org.apache.spark.mllib.feature.StandardScaler // Obtenir un RDD avec les colonnes centrées (moyenne=0) et réduites (variance=1) scala> val centRed = new StandardScaler(withMean = true, withStd = true).fit(lignes.values) scala> val lignesCR = centRed.transform(lignes.values) // Obtenir la RowMatrix à partir du RDD lignesCR scala> val matLignes: RowMatrix = new RowMatrix(lignesCR) // Calculer les 3 premières composantes principales scala> val matCompPrincipales = matLignes.computePrincipalComponents(3) // Projeter les données sur les 3 premières composantes principales scala> val projections: RowMatrix = matLignes.multiply(matCompPrincipales) // Déterminer (a posteriori) les valeurs propres correspondantes scala> val matSummary = projections.computeColumnSummaryStatistics()
Question :
Quelles sont les trois plus grandes valeurs propres ? Appliquez une ACP centrée mais non normée sur les données de lignes
, comparez les trois plus grandes valeurs propres.
L’ACP centrée et normée sur l’échantillon simple lignesEch2
de lignes
:
scala> val centRedEch2 = new StandardScaler(withMean = true, withStd = true).fit(lignesEch2.values) scala> val lignesCREch2 = centRedEch2.transform(lignesEch2.values) scala> val matLignesEch2 = new RowMatrix(lignesCREch2) scala> val matCompPrincEch2 = matLignesEch2.computePrincipalComponents(3) scala> val projectionsEch2 = matLignesEch2.multiply(matCompPrincEch2) scala> val matSummaryEch2 = projectionsEch2.computeColumnSummaryStatistics()
L’ACP sur des données tridimensionnelles obtenues par tirage aléatoire suivant une loi normale isotrope :
scala> import org.apache.spark.mllib.random.RandomRDDs scala> import org.apache.spark.mllib.random.RandomRDDs scala> val normalRnd = RandomRDDs.normalVectorRDD(sc, 4601L, 3) scala> val matRnd = new RowMatrix(normalRnd) scala> val matCompPrincRnd = matRnd.computePrincipalComponents(3) scala> val projectionsRnd = matRnd.multiply(matCompPrincRnd) scala> val matSummaryRnd = projectionsRnd.computeColumnSummaryStatistics()
Question :
Comparez entre elles les trois premières valeurs propres pour chacune des analyses effectuées. Commentez le résultat obtenu.
Visualiser les résultats¶
Dans cette séance de travaux pratiques nous visualiserons les données à l’aide d’un outil simple qu’est gnuplot
. Vous pouvez vous servir d’autres outils (comme matplotlib
ou ggplot2
) si vous les maîtrisez déjà ; il vous faudra toutefois les installer.
Vérifiez si gnuplot
est présent : entrez gnuplot
dans une fenêtre terminal, si le programme est installé alors vous aurez un prompt gnuplot>
, quittez en entrant quit
. Si gnuplot
n’est pas présent sur le système il est nécessaire de l’installer. Pour cela, entrez la commande suivante dans une fenêtre terminal :
[cloudera@quickstart ~]$ sudo yum install gnuplot
Gnuplot peut être utilisé avec des commandes en ligne ou avec des scripts. Pour écrire le script de base qui permettra la visualisation, ouvrez un éditeur (par ex. KWrite
) et copiez dans la fenêtre de l’éditeur le texte suivant :
set datafile separator ',' set term x11 0 set title "Projections donnees spambase" set xlabel "CP 1" set ylabel "CP 2" set zlabel "CP 3" splot "/home/cloudera/tpacp/data/acpspam/part-00000" using 1:2:3 pause -1 # set term x11 1 set title "Projections donnees echantillon spambase" splot "/home/cloudera/tpacp/data/acpech2/part-00000" using 1:2:3 pause -1 # set term x11 2 set title "Projections donnees aleatoires" splot "/home/cloudera/tpacp/data/acprnd/part-00000" using 1:2:3 pause -1
Enregistrez-le dans un fichier visualisation.gp
dans le répertoire /home/cloudera/tpacp
Il est maintenant nécessaire d’enregistrer sur disque les données projetées, dans un format adapté à la lecture par gnuplot
: un vecteur par lignes, composantes séparées par un espace (ou une tabulation). Pour cela, entrez les commandes suivantes :
// Enregistrement des projections des données spambase sur les 3 premières CP scala> val projectionsTxt = projections.rows.map(l => l.toString.filter(c => c != '[' & c != ']')) scala> projectionsTxt.saveAsTextFile("file:///home/cloudera/tpacp/data/acpspam") // Enregistrement des projections des données de l'échantillon scala> val projectionsEch2Txt = projectionsEch2.rows.map(l => l.toString.filter(c => c != '[' & c != ']')) scala> projectionsEch2Txt.saveAsTextFile("file:///home/cloudera/tpacp/data/acpech2") // Enregistrement des projections des données aléatoires scala> val projectionsRndTxt = projectionsRnd.rows.map(l => l.toString.filter(c => c != '[' & c != ']')) scala> projectionsRndTxt.saveAsTextFile("file:///home/cloudera/tpacp/data/acprnd")
Ces commandes ont créé des sous-répertoires acpspam
, acpech2
et acprnd
dans le répertoire /home/cloudera/tpacp/data
. Chacun de ces sous-répertoires contient un fichier avec les données correspondantes, portant le nom part-00000
.
Si on supprime la ligne set datafile separator ','
du fichier visualisation.gp
ci-dessus, alors il faut ajouter .replace(',', ' ')
comme dernière opération du map
pour obtenir le bon format dans projectionsTxt
, projectionsEch2Txt
et projectionsRndTxt
.
Pour visualiser les résultats, entrez les commandes suivantes dans une fenêtre terminal :
[cloudera@quickstart ~]$ gnuplot gnuplot> load "/home/cloudera/tpacp/visualisation.gp"
En appuyant sur Entrée, vous ouvrez successivement chacun des trois fichiers, avec la possibilité de changer de point de vue avec la souris (clic gauche).