Fiche mémo – Jour 2
En une minute
Section intitulée « En une minute »Programmer, c’est écrire les règles soi-même. Apprendre, c’est laisser un modèle les déduire d’exemples : en
supervisé avec une étiquette (régression, classification), en non supervisé sans étiquette (clustering). Avant de
modéliser, on explore : on agrège ou on échantillonne dans Spark, puis toPandas() sur un petit résultat, puis
Matplotlib ou Seaborn. Un modèle Spark ML est un Pipeline : préparation (StringIndexer, OneHotEncoder,
VectorAssembler, StandardScaler) puis algorithme, entraîné sur le train et jugé sur le test. Un score excellent
doit éveiller le soupçon : une feature inconnue au moment de prédire (total_amount, payment_type) est une fuite
de données.
Notions clés
Section intitulée « Notions clés »| Notion | Définition en une ligne | Piège fréquent |
|---|---|---|
| Supervisé / non supervisé | Avec ou sans étiquette à prédire. | « Montant anormal » : classification si on a des exemples étiquetés, sinon détection d’anomalies. |
| Régression / classification / clustering | Prédire un nombre / une catégorie / former des groupes. | « Nombre de courses demain à 18h » est une régression, pas un clustering. |
| Cycle d’un projet ML | Question, exploration, features, entraînement, évaluation, déploiement. 80 % du temps sur les étapes 2 et 3. | Sauter l’exploration et rater le palier du forfait JFK. |
toPandas() | Rapatrie tout le DataFrame dans le Driver. | Sur tout trips, la mémoire du Driver sature. Faire groupBy ou sample avant. |
| Estimator / Transformer / Model | fit apprend et renvoie un Model ; transform applique. Un Model est un Transformer. | StringIndexer et OneHotEncoder sont des Estimators ; VectorAssembler un Transformer. |
pyspark.ml / pyspark.mllib | L’API DataFrames / l’ancienne API RDD, en maintenance depuis Spark 2.0. | Lire un vieux tutoriel mllib et croire qu’il est à jour. |
Pipeline | Enchaîne les étapes ; fit renvoie un PipelineModel. Un seul objet à sauvegarder. | Préparer les données hors du Pipeline : décalage entre entraînement et production. |
handleInvalid="keep" | Réserve un index aux catégories inconnues. | Sans lui, une catégorie absente du train fait planter transform. |
| One-hot | Un index devient un vecteur 0/1. | Laisser l’index brut : Brooklyn (2) serait « plus grand » que Manhattan (0). |
| Train / test | On apprend sur 80 %, on juge sur 20 % jamais vus. seed fixe pour comparer. | Évaluer sur le train mesure la mémoire, pas la généralisation. |
| RMSE | Erreur typique, en unité du label, qui pénalise les grosses erreurs. | La comparer seulement entre modèles évalués sur le même test. |
| R² | Part de la variance expliquée, 1 = parfait, 0 = la moyenne. | Un R² proche de 1 peut signaler une fuite. |
| Arbre de régression | Des questions en cascade, une moyenne par feuille. maxDepth règle la profondeur. | Trop profond : sur-apprentissage. |
| Fuite de données | Feature inconnue au moment de prédire. | total_amount contient déjà le tarif. L’heure d’arrivée ne permet pas de prédire la durée. |
| Random Forest / LinearSVC | Des arbres qui votent, avec probabilité / la frontière à plus grande marge, linéaire. | Oublier StandardScaler avant la SVM. LinearSVC ne donne pas de probabilité. |
| Exactitude / précision / rappel | (VP + VN) / total ; VP / (VP + FP) ; VP / (VP + FN). | L’exactitude trompe si les classes sont déséquilibrées. |
| ROC / AUC | Taux de vrais positifs selon le taux de faux positifs, pour tous les seuils ; aire sous la courbe. | AUC = 0,5 : le modèle répond au hasard. |
| Sur-apprentissage | Le modèle colle au train et généralise mal. | Remèdes : réduire maxDepth, plus de données, validation croisée. |
| K-means, k | Regroupe autour de k centres ; la silhouette juge k. | Choisir k sur la silhouette et sur le sens métier. |
StandardScaler | Met les variables à la même échelle. | Sans lui, le montant écrase l’heure. Ne pas comparer des silhouettes avec et sans scaler. |
| KNN | k plus proches voisins : supervisé, absent de MLlib. | Le confondre avec K-means. |
| GraphFrames | Sommets = zones, arêtes = courses ; degrés, PageRank, motifs. Package externe. | JAR absent : calculer les degrés avec un groupBy. |
| Persistance | model.write().overwrite().save(...), PipelineModel.load(...). Portable Python ↔ Scala ↔ Java. | Sauvegarder le modèle seul sans sa préparation. |
| SparkR | Déprécié depuis Spark 4.0 ; sparklyr pour R. | Lancer un projet R sur SparkR aujourd’hui. |
Code à connaître
Section intitulée « Code à connaître »Des extraits, pas des programmes complets : chacun suppose les imports et les variables du corrigé indiqué en
commentaire (jour2/corriges/ dans le dépôt). Imports communs :
from pyspark.sql import functions as Ffrom pyspark.ml import Pipeline, PipelineModelfrom pyspark.ml.feature import StringIndexer, OneHotEncoder, VectorAssembler, StandardScalerfrom pyspark.ml.regression import DecisionTreeRegressorfrom pyspark.ml.clustering import KMeansfrom pyspark.ml.evaluation import RegressionEvaluator, BinaryClassificationEvaluator, ClusteringEvaluatorfrom pyspark.ml.functions import vector_to_array# tp5_explorer.py – explorer : agréger dans Spark, puis toPandas() sur un petit résultattranches = (trips.withColumn("tranche", (F.floor(F.col("total_amount") / 5) * 5).cast("int")) .groupBy("tranche").count().orderBy("tranche").toPandas())ech = trips.sample(fraction=0.03, seed=1).select("trip_distance", "fare_amount").toPandas()# tp6_regression.py – préparation commune, puis un modèle en dernière étape du Pipelinepreparation = [ StringIndexer(inputCol="Borough", outputCol="borough_idx", handleInvalid="keep"), # texte → index OneHotEncoder(inputCols=["borough_idx"], outputCols=["borough_vec"]), # index → vecteur 0/1 VectorAssembler(inputCols=["trip_distance", "heure", "passenger_count", "borough_vec"], outputCol="features"), # nom attendu par défaut]train, test = df.randomSplit([0.8, 0.2], seed=42)model = Pipeline(stages=preparation + [DecisionTreeRegressor(labelCol="fare_amount", maxDepth=6)]).fit(train)pred = model.transform(test)RegressionEvaluator(labelCol="fare_amount", metricName="rmse").evaluate(pred)# tp6_regression.py – persistance : on sauvegarde tout le pipeline, préparation comprisemodel.write().overwrite().save("out/modele_montant")recharge = PipelineModel.load("out/modele_montant")# tp7_classification.py – classification : AUC, matrice de confusion, probabilité de la classe 1 (rf_pred = rf_model.transform(test))BinaryClassificationEvaluator(metricName="areaUnderROC").evaluate(rf_pred)rf_pred.groupBy("label").pivot("prediction").count().show()rf_pred.select("label", vector_to_array("probability")[1].alias("p")) # probability est un vecteur# tp8_clustering.py – K-means : normaliser, puis juger k avec la silhouetteassembleur = VectorAssembler(inputCols=["trip_distance", "total_amount", "heure"], outputCol="brut")scaler = StandardScaler(inputCol="brut", outputCol="features", withMean=True, withStd=True)model = Pipeline(stages=[assembleur, scaler, KMeans(k=k, seed=1)]).fit(df)ClusteringEvaluator(featuresCol="features", metricName="silhouette").evaluate(model.transform(df))# tp8_clustering.py (bonus) – GraphFrames : sommets avec id, arêtes avec src et dstfrom graphframes import GraphFrameg = GraphFrame(zones.withColumnRenamed("LocationID", "id"), trips.select(F.col("PULocationID").alias("src"), F.col("DOLocationID").alias("dst")))g.pageRank(resetProbability=0.15, maxIter=10).vertices.orderBy(F.desc("pagerank")).show(5)Chiffres des TP
Section intitulée « Chiffres des TP »| Valeur | Contexte |
|---|---|
| 120 000 courses | Jeu synthétique du fil rouge, 1 mois |
| 3 %, tranches de 5 $ | TP 5 : échantillon du nuage de points, histogramme des montants |
| 70 $ | Palier horizontal du nuage : le forfait JFK |
| 8h et 18h | Les deux pics de trafic de la heatmap |
| 5 lundis, mardis, mercredis | Janvier 2024 : comparer les heures entre elles, pas les jours |
80 / 20, seed=42 | Découpage train / test du TP 6 (seed=7 au TP 7) |
| RMSE 5,34 $, R² 0,914 | TP 6, régression linéaire |
| RMSE 4,66 $, R² 0,935 | TP 6, arbre maxDepth=6. Réussi si < 5 $ |
| ≈ 4,8 $ contre ≈ 4,7 $ | Arbre avec la distance seule, puis avec toutes les features |
feature 0 <= 5.96 | Premier découpage de l’arbre : la distance d’abord. 127 nœuds en profondeur 6 |
| RMSE ≈ 2,4 $ | En ajoutant RatecodeID : erreur divisée par deux, une feature métier vaut mieux que tous les réglages |
| R² = 0,976 | Avec total_amount dans les features : fuite |
| 52 % | Taux de pourboire des courses par carte : classes équilibrées |
| 17 963 | Courses de test du TP 7 |
| 5 595 / 3 789 / 2 780 / 5 799 | VP / FN / FP / VN du Random Forest, seuil 0,5 |
| 63 % / 67 % / 60 % | Exactitude / précision / rappel du Random Forest |
| AUC 0,70 / 0,70 | Random Forest / LinearSVC. Réussi si > 0,65 |
| AUC 0,77 | Avec payment_type : fuite (espèces ⇒ pas de pourboire enregistré) |
| 0,3 à 0,7 | Plage des probabilités : seuils de la ROC pris sur 50 quantiles |
| 3 plis × 4 combinaisons = 12 | Entraînements du CrossValidator en bonus |
| 99 % en train, 60 % en test | Cas d’école du sur-apprentissage |
| k = 2 à 6 | Valeurs testées en K-means au TP 8 |
| Silhouette 0,66 / 0,53 | k = 3 / k = 6 |
| 2,2 mi · 21 $ · 18 h ; 2,2 mi · 21 $ · 9 h ; 14,1 mi · 87 $ · 14 h | Les 3 familles : courtes du soir, courtes du matin, longues / aéroport |
Questions que pose un formateur
Section intitulée « Questions que pose un formateur »- Pourquoi ne pas faire
toPandas()directement surtrips? Tout remonterait dans le Driver et saturerait sa mémoire. On fait d’abordgroupByousample. - Que signifie la ligne horizontale du nuage distance / tarif ? Le forfait JFK à 70 $. Le tarif ne dépend alors plus de la distance.
- Pourquoi l’arbre bat-il la régression linéaire ? Il capte le palier du forfait JFK. Une droite ne le peut pas.
StringIndexer: Estimator ou Transformer ? Estimator : il apprend la liste des catégories. Et il fauthandleInvalid="keep"pour les catégories absentes du train.- R² = 0,976 avec
total_amount: bonne nouvelle ? Non.total_amountcontient déjà le tarif et n’est connu qu’après la course. Le R² n’atteint pas 1 parce que le pourboire, les taxes et les péages varient. - Un détecteur de fraude affiche 99 % d’exactitude en répondant toujours « pas de fraude ». Que regarder ? Les classes sont déséquilibrées et le rappel de la classe fraude vaut 0. Regarder le rappel, l’AUC et la courbe précision-rappel.
- Pourquoi sauvegarder le Random Forest plutôt que la SVM, à AUC presque égale ? Le TP 11 a besoin de la colonne
probability, queLinearSVCne fournit pas. - Sans
StandardScaler, l’heure n’a joué aucun rôle dans K-means. Pourquoi ? Le montant, en dizaines de dollars, domine la distance euclidienne. L’heure (0 à 23) pèse trop peu pour séparer les groupes. - KNN et K-means, c’est pareil ? Non. KNN est supervisé (classer un point selon ses k voisins étiquetés) et absent de MLlib ; K-means est un clustering non supervisé.
Dépôt des participants : formations2026/formation-spark-ml (branche participants)
Contenu pédagogique © Ambient IT · 140 Avenue Jean Lolive, 93500 Pantin · formation@ambient-it.net · +33 (0)1 48 10 09 46
Organisme de formation enregistré sous le numéro 11 75 54743 75. Cet enregistrement ne vaut pas agrément de l'État.