Le Machine Learning avec Spark grâce à HDInsight – 2nde partie


Dans la première partie de ce billet, nous avons vu comment déployer un cluster Apache Spark sur Azure HDInsight.

Ce billet s'intéresse sur cette base à l'étudie d'un exemple d'application d'apprentissage automatique (Machine Learning). Ce sera alors l'occasion de découvrir les principales commandes d'Apache Spark à travers PySpark, le Framework de programmation Spark en Python.

J'en profite pour remercier très sincèrement Anaig Maréchal actuellement en stage au sein de l'équipe pour cette contribution 🙂

Nos données

Pour cet exemple, nous avons choisi d'utiliser un jeu de données issu de la galerie Cortana Intelligence sur le revenu d'adultes américains. Il s'agit ici de prédire si un individu gagne plus ou moins que 50K par an. Nous sommes donc face à un problème de classification. A cet effet, 14 prédicteurs et 32561 individus sont à notre disposition.

La première étape est d'importer le fichier de données dans le stockage de type blob du compte de stockage lié à notre cluster Spark. On peut par exemple utiliser l'outil Microsoft Azure Storage Explorer pour administrer facilement les ressources de ce compte comme suggéré dans la première partie de ce billet.

Nous avons pour notre part stocké le fichier CSV dans un dossier « data ». Notez que ce répertoire est « virtuel » car nous ne sommes pas dans un système de gestion de fichier : un blob n'est pas structuré et tout est physiquement stocké à la racine. Cela permet donc seulement de se repérer plus simplement dans les fichiers d'un point de vue utilisateur.

Afin d'exécuter le code, nous utilisons un blocs-notes Jupyter, dont l'utilisation est directement mise à disposition depuis le cluster sur Azure. Des exemples d'application en Scala et en Python sont déjà disponibles ainsi que de la documentation à l'image des articles Noyaux disponibles sur les blocs-notes Jupyter avec clusters Spark HDInsight et Utilisation de packages externes avec les blocs-notes Jupyter dans des clusters Apache Spark sur HDInsight. Dans notre cas, nous créons un nouveau notebook.

Importation des bibliothèques

Passage obligatoire, on commence par importer les bibliothèques PySpark nécessaires à la mise en œuvre de notre exemple. La bibliothèque pyspark.sql va nous permettre de manipuler les données et la bibliothèque pyspark.ml sera utilisée pour la partie Machine Learning à proprement parlé.

Importation des données

La fonction spark.read.csv nous permet d'importer les données depuis un CSV vers un data frame. Nous lui passons en paramètre le chemin vers le fichier. Pour accéder à l'interface WASB (Windows Azure Blob Storage), le modèle de chemin à utiliser est :

« wasb://nom_du_conteneur@compte_de_stockage.blob.core.windows.net/nom_du_repertoire/nom_du_fichier.csv »

Comme nous travaillons sur Azure et que nous n'avons qu'un seul conteneur, nous utilisons le raccourci « wasb:///nom_du_repertoire/nom_du_fichier.csv ». Le paramètre header nous permet d'indiquer que le fichier contient des en-têtes et sep de définir le séparateur utilisé par le csv.

Si la structure des données est connue, la fonction StructType est conseillée pour la définir explicitement. Dans ce cas, on utiliserait le paramètre schema=[schema] dans la fonction spark.read.csv. Nous avons ici préféré utiliser le paramètre inferSchema qui permet de déduire automatiquement la structure des données.

La fonction printSchema nous permet de vérifier que la structure déduite est bien correcte.

On retrouve bien les 15 colonnes du csv dont la variable « income » qui est à prédire.

Visualisation des données

On peut alors afficher les données pour en avoir un aperçu. La fonction show permet d'afficher par défaut les 20 premières lignes du data frame, ou le nombre de lignes passé en paramètre.

La fonction describe affiche un résumé statistique des données numériques d'un data frame : le nombre de valeurs, leur moyenne, leur déviation standard, leur minimum et leur maximum. Ainsi, l'âge moyen dans ce jeu de données est 38.6 ans et la population va de 17 à 90 ans.

Manipuler les données

L'utilisation d'un data frame permet la manipulation des données avec Spark SQL, qui fournit une syntaxe très proche du SQL.

La commande la plus basique est le select, qui permet de sélectionner une partie des données. Ici par exemple, on utilise select, when et like pour appliquer un label numérique aux données : 1.0 pour les personnes au revenu supérieur à 50K, 0.0 sinon. Ces fonctions vous rappellent quelque chose, n'est-ce pas ? 😉

Nous verrons plus loin dans ce billet qu'il existe une autre méthode pour labéliser une colonne de façon moins « manuelle ». Néanmoins, cette méthode avec Spark SQL peut toujours servir si on désire plus de flexibilité.

La fonction createOrReplaceTempView va permettre de créer une vue, comme on le ferait en SQL. A partir de cette vue, on peut manipuler les données directement avec des requêtes SQL, et un data frame sera retourné.

Dans la requête ci-dessous, on affiche seulement les lignes des personnes gagnant plus de 50K et dont l'éducation est « HS-grad ».

Le notebook Jupyter permet également d'utiliser directement du SQL grâce à la commande « magique » %%sql, suivie du script.

A noter : des graphiques sont alors automatiquement créés, ce qui est très pratique pour visualiser simplement les données. Par défaut, un tableau est affiché, mais on peut aussi choisir un graphique circulaire, en nuage de point, en ligne, en zone et en barres.

Ici, on affiche par exemple le nombre de personnes gagnant plus de 50K en fonction de l'âge.

Il convient de noter que, d'un point de vue statistique, ce graphique est à interpréter avec précaution car ce dernier dépend aussi du nombre d'individus pour chaque âge. Les seniors sont par exemple sous-représentés ce qui explique en partie le faible nombre de personnes gagnant plus de 50K.

 

Nettoyage des données

La fonction count permet de compter le nombre de lignes du data frame. Nous partons d'un jeu de données de 32561 lignes.

Parmi les fonctions utiles pour le nettoyage des données, on peut retenir dropDuplicates et data.na.drop qui suppriment les doublons et les données manquantes. La valeur « any » du paramètre how de data.na.drop permet de préciser qu'une ligne doit être supprimée si elle contient la moindre valeur nulle. Pour supprimer une ligne dont toutes les colonnes sont nulles, il faut utiliser « all ».

Une autre utilisation de count nous indique qu'il reste 32537 lignes, ce qui signifie qu'on a supprimé 24 lignes en nettoyant les données.

La notion de pipeline

Il existe deux fonctions importantes pour mettre en place un modèle de Machine Learning avec Spark : transform et fit. Transform permet d'appliquer une transformation à nos données, qui vont passer d'une structure à une autre, et fit permet d'appliquer un algorithme au data frame. Ces fonctions sont très fréquemment utilisées, ce qui peut rendre le code redondant. Par exemple, on applique généralement des transformations à un ensemble d'apprentissage, puis à nouveau à ensemble de test.

Un pipeline est un objet permettant d'encapsuler des transformations. Cela permet d'avoir un code plus simple même lorsqu'on applique beaucoup de transformations aux données. Nous allons l'employer dans la suite de notre illustration. Remarquez au passage qu'un pipeline est lui-même une transformation !

Sélection des variables

La fonction dtype nous permet de visualiser le type de nos différentes variables.

Nous sommes ici en présence de plusieurs variables qualitatives, de type string. Or, l'une des particularités de Spark ML est qu'il fonctionne avec des variables numériques. Certains modèles prennent cependant en compte les variables catégoriques, toujours sous forme numériques. Nous allons donc devoir modifier un peu notre data frame pour mettre en place notre exemple.

D'une part, on va pouvoir transformer certains prédicteurs catégoriques nominaux en « dummy variables », une variable numérique associée par classe, tout comme on l'a fait avec la colonne « income ». Pour cela, on utilise la fonction StringIndexer qui le fait automatiquement pour toutes les valeurs différentes rencontrées.

D'autre part, on va supprimer les colonnes qui ne nous intéressent pas. La colonne « education » par exemple, déjà représentée numériquement par « education-num », ou la colonne « native-country », qui est « United-States » dans la grande majorité des cas. Deux façons de procéder : en indiquant la colonne à supprimer avec data.drop ou en indiquant les colonnes à garder avec select.

Nous avons maintenant des données nettoyées, entièrement numériques, avec 9 prédicteurs.

Les modèles de Machine Learning Spark ML reçoivent la liste de prédicteurs sous forme de vecteur. On sélectionne donc les variables et on les place dans un vecteur dans une nouvelle colonne « features » grâce à la fonction VectorAssembler.

Découpage des données


On prend 70% de données comme ensemble d'apprentissage avec la fonction randomSplit.

Entraînement et test du modèle

Notre jeu de données compte 3 prédicteurs catégoriques, qu'il est important de différencier des variables continues pour ne pas biaiser le modèle. A cet effet, la fonction VectorIndexer prend en entrée une colonne de vecteurs et détermine automatiquement quelles variables doivent être considérées comme catégoriques. Il est également possible d'ajouter un paramètre maxCategories pour fixer le nombre maximal de valeurs différentes au-dessus duquel les variables sont considérées comme numériques continues.

Grâce à un rapide describe, on trouve facilement que « relationship » est la colonne catégorique avec le plus grand nombre de valeurs différentes, c'est-à-dire 5.


On utilise ensuite la fonction DecisionTreeClassifier en indiquant la colonne à prédire et la colonne contenant le vecteur de prédicteurs.

Tout cela est appliqué une première fois au jeu d'entraînement, puis de test. On peut ensuite afficher les prédictions pour les comparer au label réel, ainsi que la probabilité avec laquelle la classification a été faite entre les deux labels possibles.

On obtient à première vue des résultats plutôt corrects, avec des probabilités plus tranchées que le hasard, qui serait autour de 50%.

Evaluation du modèle

MultiClassClassificationEvaluator est un outil permettant d'évaluer le résultat d'une classification.

Nous avons ici évalué la précision pour notre exemple. Le résultat affiché est « Test Error = 0.155442 », c'est-à-dire que nous obtenons une précision d'environ 85%.

En guise de conclusion

Cette illustration nous a permis de découvrir les principales commandes PySpark et d'aborder le Machine Learning avec Spark en utilisant les arbres de décision pour un problème de classification. Libre à vous maintenant d'essayer d'améliorer la performance obtenue en utilisant un autre modèle, ou d'améliorer le calcul du taux d'erreur en employant de la validation croisée par exemple !

Rétrospectivement, on peut regretter le choix du jeu de données qui ne s'adapte pas très bien à Spark ML, contrairement à l'exemple fréquemment utilisé de l'analyse du retard d'avions, Cf. article Analyse des données sur les retards de vol avec Hive dans HDInsight basé sur Linux (sic). En effet, notre jeu de données comprend plusieurs variables nominales, qui ne peuvent pas être directement employées dans les modèles de Machine Learning. Cela nous a néanmoins permis d'explorer les solutions disponibles pour contourner ce problème, avec les outils de catégorisation notamment. Libre à vous de vous lancer à nouveau avec cet autre jeu de données.

En guise de remarques complémentaires :

  • L'intérêt de Spark est évidemment d'utiliser des jeux de données de volume plus important, pour profiter pleinement de la parallélisation des calculs et de l'architecture distribuée.
  • On aurait aussi pu utiliser Scala pour cet exemple. C'est un langage généralement apprécié avec Spark, qui est lui-même écrit en Scala !
  • Nous avons ici choisi de manipuler les données sous forme de data frames. Une autre possibilité aurait été d'utiliser des RDDs, Resilient Distributed Datasets. Ce sont des objets Spark qui constituent une abstraction de la mémoire distribuée et permettent donc des opérations comme map et reduce par exemple. La performance des RDDs est en revanche généralement moins bonne que celle des data frames et ils peuvent paraître moins familiers.

Ceci conclut cette seconde partie de notre billet sur Apache Spark dans Azure HDInsight.

Comments (0)

Skip to main content