TP : Map-Reduce en Python
Auteurs
- Stéphane Derrode & Lamia Derrode, Centrale Lyon, Dpt Mathématiques & Informatique
Objectifs
Le TP est composé de 3 parties :
- D’abord, une partie tutoriel qui permet de prendre en main les fichiers nécessaires pour développer des programmes map-reduce en Python.
- Ensuite, la seconde partie permet de vérifier votre compréhension des principes de la programmation
map-reducesur un fichier de données réelles. - Enfin, la troisième partie propose des exercices pour aller plus loin.
Dans ce TP, on considère que vous disposez de connaissances avancées en Python et d’une installation Python 3 sur votre ordinateur.
Organisation de la séance
| Partie | Durée conseillée | Contenu |
|---|---|---|
| Partie 1 | ~1h | Prise en main du pipeline map-reduce sur Dracula |
| Partie 2 | ~1h30 | Analyse d’un fichier de ventes réel (4 millions de lignes) |
| Partie 3 | ~1h30 | Exercices avancés au choix |
Table des matières
Partie 1 : Tutoriel wordcount¶
Nous allons ici faire fonctionner l’algorithme map-reduce qui compte les mots d’un fichier texte (vu en cours).
Préparation¶
- Sauvegarder le fichier dracula dans votre espace de travail. Ce fichier contient le livre Dracula, libre de droits.
- Sauvegarder également le fichier wordcount_mapreduce.py dans votre espace de travail.
-
Analyser le script et lancez-le. Observer, dans le répertoire de travail, la création de 3 fichiers texte :
- resultmapper.txt : le résultat du mapper.
- resultmappersorted.txt : le résultat du mapper après tri par ordre alphabétique selon les clés (phase dite de shuffling and sorting, traduction : mélange et tri).
- resultreducer.txt : le résultat du reducer (et donc le résultat final).
Le script met en œuvre le pipeline suivant, qui émule le comportement de Hadoop sur une seule machine :
fichier texte
│
▼
[ mapper ] ──────► resultmapper.txt
│
▼
[ sort ] ──────► resultmappersorted.txt
│
▼
[ reducer ] ──────► resultreducer.txt
- Télécharger d’autres livres au format texte sur textfiles.com et adapter le script pour qu’il fonctionne sur ces fichiers.
Wordcount improvement!¶
En analysant le résultat du précédent algorithme, on constatera que le comptage de mots n’est pas très satisfaisant : les tirets, les points de terminaison, les guillemets ou les virgules viennent corrompre le résultat ! Notez également que le programme précédent distingue les mots en majuscule des mots en minuscule (e.g. ‘youth’ vs ‘Youth’).
À faire : Modifier le programme précédent pour qu’il corrige ces comportements inappropriés.
Piste
Le module string expose string.punctuation (liste de tous les signes de ponctuation). La méthode str.translate() combinée à str.maketrans() permet de les supprimer d’une chaîne sans boucle explicite.
Auto-évaluation — résultat attendu sur Dracula
Après nettoyage (passage en minuscules + suppression de la ponctuation), votre programme doit produire 9 719 mots distincts pour 161 057 occurrences au total.
Les 20 mots les plus fréquents :
| Rang | Mot | Occurrences |
|---|---|---|
| 1 | the | 7 882 |
| 2 | and | 5 902 |
| 3 | i | 4 796 |
| 4 | to | 4 462 |
| 5 | of | 3 610 |
| 6 | a | 2 914 |
| 7 | he | 2 569 |
| 8 | in | 2 498 |
| 9 | that | 2 470 |
| 10 | it | 2 152 |
| 11 | was | 1 878 |
| 12 | as | 1 589 |
| 13 | we | 1 542 |
| 14 | for | 1 532 |
| 15 | is | 1 499 |
| 16 | his | 1 473 |
| 17 | me | 1 452 |
| 18 | not | 1 398 |
| 19 | you | 1 392 |
| 20 | with | 1 285 |
Si vous obtenez Youth et youth séparément, ou si the, apparaît, votre nettoyage est incomplet.
Partie 2 : Fichier de ventes¶
Préambule : Pour répondre à cet exercice, pensez à sauvegarder le script original wordcount_mapreduce.py sous un nouveau nom (ventes.py par exemple) : il vous suffira alors de modifier uniquement la partie algorithmique et les noms des fichiers d’entrée et de sortie.
Présentation du fichier¶
Télécharger le fichier purchases.txt (211 Mo) dans votre espace de travail. Il contient 4 138 476 lignes ! Une version réduite à 10 000 lignes est téléchargeable ici : purchases10000.txt (511 Ko).
Conseil : commencez vos tests avec
purchases10000.txt, puis validez votre solution sur le fichier complet.
Le fichier est organisé en 6 colonnes :
- date (format
YYYY-MM-DD) ; - heure (format
hh:mm) ; - ville d’achat ;
- catégorie de l’achat (parmi Book, Men’s Clothing, DVDs…) ;
- somme dépensée par le client ;
- moyen de paiement (parmi Amex, Cash, MasterCard…).
Les colonnes sont séparées par une tabulation. Ce caractère est codé par \t en Python.
Questionner le fichier de ventes¶
Voici une liste de questions à traiter. Pour chacune, réfléchissez d’abord à quelles colonnes du fichier constitueront la clé et la valeur de votre mapper, avant d’écrire le code.
-
Quelle est la somme totale dépensée pour chaque catégorie d’achat ?
Format de sortie attendu : une ligne par catégorie, triée, avec la somme totale.Auto-évaluation (sur
purchases.txtcomplet, 4 138 476 lignes)18 catégories, totaux remarquablement homogènes (~57 millions chacune — le fichier est manifestement synthétique). Les 5 plus grosses :
Catégorie Total dépensé DVDs 57 649 212,14 € Children’s Clothing 57 624 820,94 € Men’s Clothing 57 621 279,04 € Sporting Goods 57 599 085,89 € Garden 57 539 833,11 € Et les 3 plus petites :
Catégorie Total dépensé Computers 57 315 406,32 € Cameras 57 299 046,64 € Pet Supplies 57 197 250,24 € Sur
purchases10000.txt, l’ordre sera différent (effet d’échantillonnage) mais les sommes par catégorie doivent être proportionnelles. -
Dans quelle ville la catégorie Women’s Clothing a-t-elle permis de générer le plus d’argent Cash ?
Format de sortie attendu : le nom de la ville gagnante avec le montant correspondant.Indice
Cette question nécessite deux passes map-reduce : la première calcule le total par ville (filtré sur la catégorie et le moyen de paiement), la seconde identifie la ville au montant maximum.
Auto-évaluation (sur
purchases.txtcomplet)Ville gagnante : Fresno — 132 743,03 €
Top 5 :
Ville Women’s Clothing payé Cash Fresno 132 743,03 € Las Vegas 124 790,58 € Colorado Springs 123 911,43 € Greensboro 123 411,60 € Richmond 123 302,50 € -
À quelle heure les clients dépensent-ils le plus ?
Format de sortie attendu : une ligne par tranche horaire avec le montant total, e.g. “entre 16h et 17h : 2 345 678 €”.Auto-évaluation (sur
purchases.txtcomplet)Le fichier ne contient que des achats entre 9h et 17h59. L’heure 16h-17h gagne avec 115 228 708,28 €, mais l’écart entre les tranches est faible (~1 % entre la plus haute et la plus basse).
Tranche Total dépensé 09h-10h 114 767 312,45 € 10h-11h 114 997 301,67 € 11h-12h 114 799 964,72 € 12h-13h 114 909 978,09 € 13h-14h 115 061 758,15 € 14h-15h 114 886 427,44 € 15h-16h 114 916 347,74 € 16h-17h 115 228 708,28 € 17h-18h 114 890 154,72 € Si votre top affiche une tranche comme “9h-10h” ou si tous vos totaux sont du même ordre de grandeur que ceux ci-dessus, c’est bon.
Partie 3 : Pour aller plus loin¶
Voici plusieurs sujets à traiter, par ordre de difficulté croissante. Vous n’êtes pas obligés de tous les traiter : privilégiez la qualité d’un exercice bien compris à la quantité.
Anagrammes¶
Étant donné un fichier de mots, écrire un script map-reduce qui détecte les mots ayant exactement les mêmes lettres (mais dans un ordre différent), et le même nombre de lettres (le mot ‘a’ est donc différent du mot ‘aa’).
Ainsi, par exemple, le fichier de mots suivant
melon barre deviner lemon arbre fiable fable vendre devenir faible barbe
donnera en sortie cette liste :
faible, fiable
arbre, barre
devenir, deviner
lemon, melon
La sortie ne devra afficher que les réponses avec au moins 2 mots. Dans cet exemple, les mots fable ou barbe n’apparaissent pas car ce sont les seuls mots de la liste avec ces lettres.
L’algorithme ne devra pas tenir compte de la présence éventuelle de majuscules dans les mots.
Cadrage map-reduce : Quelle représentation d’un mot, indépendante de l’ordre de ses lettres, pourrait servir de clé dans le mapper ? (Il en existe plusieurs : certaines sont plus simples à calculer, d’autres plus efficaces.)
Dictionnaires de mots Pour vos tests intensifs, vous pourrez utiliser :
- le fichier (de langue anglaise) : words_alpha.txt
- ou le fichier (de langue française) : Liste-de-mots-francais-Gutenberg
Faites attention aux voyelles accentuées de la langue française.
Auto-évaluation — quelques anagrammes vérifiables
Sur l’exemple melon barre deviner lemon arbre fiable fable vendre devenir faible barbe, vous devez obtenir exactement 4 groupes :
faible, fiable
arbre, barre
devenir, deviner
lemon, melon
Sur words_alpha.txt (~370 000 mots anglais), votre script doit retrouver des classes connues comme :
algin, align, liang, ligan, linga, gnail, naiglg— variantes autour des lettresa-g-i-l-ncaret, cater, crate, react, recta, trace— variantes autour dea-c-e-r-taster, rates, stare, tares, tears, reast, resat, …— variantes autour dea-e-r-s-t
Astuce de validation : trier les lettres d’un mot pour en faire la clé est l’approche la plus simple. sorted("react") == sorted("crate") doit être vrai.
Aide au Scrabble¶
Étant donné un dictionnaire de mots, écrire un script map-reduce qui donne tous les mots du dictionnaire que l’on peut construire avec une suite de 8 lettres donnée. La liste de mots devra être triée par ordre décroissant du nombre de lettres utilisées (un score entre 0 et 1 pourra être utilisé à cet effet), et les mots anagrammes listés ensemble.
Par exemple, si la suite de 8 lettres est bnaitypi, avec le dictionnaire words_alpha, le programme doit renvoyer :
0.75 [painty]
0.625 [pinta, patin, paint, inapt]
…
Cadrage map-reduce : Cet exercice est une extension du précédent. La difficulté supplémentaire est de vérifier qu’un mot du dictionnaire est constructible à partir des lettres disponibles — sans forcément les utiliser toutes. Réfléchissez à comment encoder cette contrainte dans la clé ou la phase de filtrage du reducer.
Auto-évaluation — patterns attendus
Pour la suite bnaitypi avec words_alpha.txt, l’extrait de l’énoncé donne déjà :
0.75 [painty]
0.625 [pinta, patin, paint, inapt]
…
À score plus bas, on s’attend à voir notamment :
0.5 [pita, naib, tapi, pian, pain, anti, pita, pina, …]
0.5 [bait, pyin, …]
0.375 [bin, nip, pin, tin, ait, pat, …]
Astuce : un mot du dictionnaire est constructible si chaque lettre y apparaît au plus autant de fois que dans la suite donnée. Un compteur (collections.Counter) avec l’opérateur <= fait l’affaire en une ligne — mais difficile à transposer tel quel en map-reduce ; il faut souvent en passer par une comparaison de signatures triées + filtrage côté reducer.
Parseur de fichier de logs¶
Étant donné ce fichier de 100 000 logs provenant d’un serveur web, écrire un script map-reduce qui génère des statistiques mensuelles, comme :
- le nombre d’accès par mois,
- l’adresse IP qui a fait le plus grand nombre d’accès durant chaque mois.
Format d’une ligne de log (format Apache Combined Log) :
Cadrage map-reduce : Quelle information extraire de chaque ligne pour constituer la clé du mapper ? Comment formuler deux questions différentes avec deux scripts map-reduce distincts ?
Auto-évaluation — sortie attendue sur web_access.log (100 000 lignes, année 2023)
Question 1 — nombre d’accès par mois :
| Mois | Accès |
|---|---|
| 2023-01 | 8 558 |
| 2023-02 | 7 666 |
| 2023-03 | 8 519 |
| 2023-04 | 8 224 |
| 2023-05 | 8 450 |
| 2023-06 | 8 162 |
| 2023-07 | 8 489 |
| 2023-08 | 8 481 |
| 2023-09 | 8 091 |
| 2023-10 | 8 587 |
| 2023-11 | 8 222 |
| 2023-12 | 8 551 |
Total : 100 000 accès ; Février est le mois le moins chargé (cohérent avec un mois plus court).
Question 2 — IP la plus active de chaque mois (résultats vérifiables, IPs toutes en 192.168.1.x) :
| Mois | IP gagnante | Hits |
|---|---|---|
| 2023-01 | 192.168.1.206 | 55 |
| 2023-02 | 192.168.1.216 | 49 |
| 2023-03 | 192.168.1.148 | 50 |
| 2023-04 | 192.168.1.79 | 45 |
| 2023-05 | 192.168.1.189 | 46 |
| 2023-06 | 192.168.1.250 | 50 |
| 2023-07 | 192.168.1.146 | 48 |
| 2023-08 | 192.168.1.204 | 47 |
| 2023-09 | 192.168.1.98 | 48 |
| 2023-10 | 192.168.1.3 | 51 |
| 2023-11 | 192.168.1.109 | 47 |
| 2023-12 | 192.168.1.134 | 48 |
Si vous trouvez les bons totaux mais des IPs différentes, vérifiez la regex d’extraction de l’IP source (premier champ de la ligne).
Multiplication de matrices¶
Écrire un script map-reduce permettant de réaliser la multiplication de 2 (grandes) matrices, à partir de leurs fichiers matriceA.txt et matriceB.txt.
Pour générer des matrices de test, utiliser le script matrice.py. Celui-ci :
- crée 2 matrices avec des nombres entiers aléatoires : A de taille 20×10 et B de taille 10×15, et les stocke dans
matriceA.txtetmatriceB.txt; - affiche le résultat de leur multiplication A × B (taille attendue : 20×15).
Contrôlez que le résultat obtenu par votre algorithme est bien le même que celui affiché par matrice.py.
Cadrage map-reduce : La multiplication matricielle C = A × B se définit par C[i,j] = Σₖ A[i,k] × B[k,j]. Réfléchissez à comment encoder les indices (i, j, k) dans la clé du mapper pour que le reducer reçoive, pour chaque case (i,j) de C, tous les termes du produit à sommer.
Auto-évaluation
Cet exercice est auto-vérifiant : matrice.py affiche déjà le résultat de A × B calculé par numpy. Votre script map-reduce doit produire la même matrice 20×15.
Pour comparer rapidement, sauvegardez votre sortie au format i\tj\tvaleur (une case par ligne), puis :
import numpy as np
expected = np.loadtxt("matriceA.txt") @ np.loadtxt("matriceB.txt")
yours = np.zeros_like(expected)
with open("votre_sortie.txt") as f:
for line in f:
i, j, v = line.split()
yours[int(i), int(j)] = float(v)
print(np.allclose(yours, expected)) # doit afficher True
Piège classique : oublier de croiser correctement les éléments de A et B. Pour la case C[i,j], le mapper doit émettre, pour chaque A[i,k], une paire ((i,j), A[i,k] * B[k,j]) — il doit donc connaître l’autre matrice. Une astuce : faire passer A par le mapper et charger B en mémoire (utilisable si B est petite), ou émettre des paires intermédiaires depuis chaque matrice avec des clés bien choisies.