Aller au contenu

Fiche mémo – Jour 2

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.

NotionDéfinition en une lignePiè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 / clusteringPré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 MLQuestion, 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 / Modelfit apprend et renvoie un Model ; transform applique. Un Model est un Transformer.StringIndexer et OneHotEncoder sont des Estimators ; VectorAssembler un Transformer.
pyspark.ml / pyspark.mllibL’API DataFrames / l’ancienne API RDD, en maintenance depuis Spark 2.0.Lire un vieux tutoriel mllib et croire qu’il est à jour.
PipelineEnchaî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-hotUn index devient un vecteur 0/1.Laisser l’index brut : Brooklyn (2) serait « plus grand » que Manhattan (0).
Train / testOn 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.
RMSEErreur 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égressionDes questions en cascade, une moyenne par feuille. maxDepth règle la profondeur.Trop profond : sur-apprentissage.
Fuite de donnéesFeature 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 / LinearSVCDes 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 / AUCTaux 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-apprentissageLe modèle colle au train et généralise mal.Remèdes : réduire maxDepth, plus de données, validation croisée.
K-means, kRegroupe autour de k centres ; la silhouette juge k.Choisir k sur la silhouette et sur le sens métier.
StandardScalerMet les variables à la même échelle.Sans lui, le montant écrase l’heure. Ne pas comparer des silhouettes avec et sans scaler.
KNNk plus proches voisins : supervisé, absent de MLlib.Le confondre avec K-means.
GraphFramesSommets = zones, arêtes = courses ; degrés, PageRank, motifs. Package externe.JAR absent : calculer les degrés avec un groupBy.
Persistancemodel.write().overwrite().save(...), PipelineModel.load(...). Portable Python ↔ Scala ↔ Java.Sauvegarder le modèle seul sans sa préparation.
SparkRDéprécié depuis Spark 4.0 ; sparklyr pour R.Lancer un projet R sur SparkR aujourd’hui.

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 F
from pyspark.ml import Pipeline, PipelineModel
from pyspark.ml.feature import StringIndexer, OneHotEncoder, VectorAssembler, StandardScaler
from pyspark.ml.regression import DecisionTreeRegressor
from pyspark.ml.clustering import KMeans
from pyspark.ml.evaluation import RegressionEvaluator, BinaryClassificationEvaluator, ClusteringEvaluator
from pyspark.ml.functions import vector_to_array
# tp5_explorer.py – explorer : agréger dans Spark, puis toPandas() sur un petit résultat
tranches = (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 Pipeline
preparation = [
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 comprise
model.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 silhouette
assembleur = 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 dst
from graphframes import GraphFrame
g = 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)
ValeurContexte
120 000 coursesJeu 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 18hLes deux pics de trafic de la heatmap
5 lundis, mardis, mercredisJanvier 2024 : comparer les heures entre elles, pas les jours
80 / 20, seed=42Découpage train / test du TP 6 (seed=7 au TP 7)
RMSE 5,34 $, R² 0,914TP 6, régression linéaire
RMSE 4,66 $, R² 0,935TP 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.96Premier 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,976Avec total_amount dans les features : fuite
52 %Taux de pourboire des courses par carte : classes équilibrées
17 963Courses de test du TP 7
5 595 / 3 789 / 2 780 / 5 799VP / FN / FP / VN du Random Forest, seuil 0,5
63 % / 67 % / 60 %Exactitude / précision / rappel du Random Forest
AUC 0,70 / 0,70Random Forest / LinearSVC. Réussi si > 0,65
AUC 0,77Avec payment_type : fuite (espèces ⇒ pas de pourboire enregistré)
0,3 à 0,7Plage des probabilités : seuils de la ROC pris sur 50 quantiles
3 plis × 4 combinaisons = 12Entraînements du CrossValidator en bonus
99 % en train, 60 % en testCas d’école du sur-apprentissage
k = 2 à 6Valeurs testées en K-means au TP 8
Silhouette 0,66 / 0,53k = 3 / k = 6
2,2 mi · 21 $ · 18 h ; 2,2 mi · 21 $ · 9 h ; 14,1 mi · 87 $ · 14 hLes 3 familles : courtes du soir, courtes du matin, longues / aéroport
  1. Pourquoi ne pas faire toPandas() directement sur trips ? Tout remonterait dans le Driver et saturerait sa mémoire. On fait d’abord groupBy ou sample.
  2. Que signifie la ligne horizontale du nuage distance / tarif ? Le forfait JFK à 70 $. Le tarif ne dépend alors plus de la distance.
  3. Pourquoi l’arbre bat-il la régression linéaire ? Il capte le palier du forfait JFK. Une droite ne le peut pas.
  4. StringIndexer : Estimator ou Transformer ? Estimator : il apprend la liste des catégories. Et il faut handleInvalid="keep" pour les catégories absentes du train.
  5. R² = 0,976 avec total_amount : bonne nouvelle ? Non. total_amount contient 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.
  6. 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.
  7. Pourquoi sauvegarder le Random Forest plutôt que la SVM, à AUC presque égale ? Le TP 11 a besoin de la colonne probability, que LinearSVC ne fournit pas.
  8. 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.
  9. 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.