๐ŸŸฅ Niveau 3 : Avancรฉ

๐Ÿค– Data Engineering for ML

Bienvenue dans ce module oรน tu vas apprendre ร  construire l'infrastructure data qui alimente les systรจmes de Machine Learning. En tant que Data Engineer, tu ne crรฉes pas les modรจles, mais tu construis les pipelines, Feature Stores, et systรจmes de monitoring qui rendent le ML possible en production.


Prรฉrequis

Niveau Compรฉtence
โœ… Requis PySpark DataFrame API (M19)
โœ… Requis Delta Lake (M20)
โœ… Requis Airflow (M22, M28)
โœ… Requis Data Quality avec Great Expectations (M23)
๐Ÿ’ก Recommandรฉ Notions de base en ML (features, training, inference)

๐ŸŽฏ Objectifs du module

ร€ la fin de ce module, tu seras capable de :

  • Construire des Feature Pipelines robustes avec Spark
  • Dรฉployer et alimenter un Feature Store (Feast)
  • Crรฉer des Training Datasets sans data leakage
  • Implรฉmenter la Data Validation spรฉcifique au ML
  • Mettre en place le Data Monitoring (drift detection)
  • Comprendre l'intรฉgration avec MLflow (cรดtรฉ data)

1. Le Rรดle du Data Engineer dans le ML

1.1 ML Lifecycle vu par le Data Engineer

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                     DATA ENGINEER SCOPE IN ML                               โ”‚
โ”‚                                                                             โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚   โ”‚                    DATA ENGINEER CONSTRUIT                          โ”‚  โ”‚
โ”‚   โ”‚                                                                     โ”‚  โ”‚
โ”‚   โ”‚   Raw Data โ”€โ”€โ–ถ Data Pipelines โ”€โ”€โ–ถ Feature Pipelines โ”€โ”€โ–ถ Feature    โ”‚  โ”‚
โ”‚   โ”‚                                                          Store     โ”‚  โ”‚
โ”‚   โ”‚                                                            โ”‚        โ”‚  โ”‚
โ”‚   โ”‚   Training Data โ—€โ”€โ”€ Serving Data โ—€โ”€โ”€ Data Validation โ—€โ”€โ”€โ”€โ”€โ”˜        โ”‚  โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                                          โ”‚                                  โ”‚
โ”‚                                          โ–ผ                                  โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚   โ”‚                    DATA SCIENTIST UTILISE                           โ”‚  โ”‚
โ”‚   โ”‚                                                                     โ”‚  โ”‚
โ”‚   โ”‚   Feature Store โ”€โ”€โ–ถ Model Training โ”€โ”€โ–ถ Model Registry โ”€โ”€โ–ถ Serving  โ”‚  โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

1.2 Data Engineer vs Data Scientist vs ML Engineer

Rรดle Responsabilitรฉs
Data Engineer Pipelines de donnรฉes, Feature Store infra, Data Quality, Monitoring data
Data Scientist Feature design, Model training, Experimentation, Evaluation
ML Engineer Model deployment, Model serving, Model monitoring, MLOps

1.3 Problรจmes classiques que le DE doit rรฉsoudre

Problรจme Description Solution DE
Training-Serving Skew Features diffรฉrentes en training vs production Feature Store unique
Data Leakage Utiliser des donnรฉes du futur pour prรฉdire le passรฉ Point-in-time joins
Reproducibility Impossible de recrรฉer un training dataset Dataset versioning
Feature Inconsistency Calcul diffรฉrent selon les รฉquipes Feature pipelines centralisรฉs
Stale Features Features pas ร  jour en production Refresh pipelines, CDC

2. Feature Pipelines avec Spark

2.1 Qu'est-ce qu'une Feature ?

Une feature est une variable dรฉrivรฉe des donnรฉes brutes, utilisรฉe comme input pour un modรจle ML.

Raw Data Features dรฉrivรฉes
Transactions individuelles total_transactions_30d, avg_amount_30d
Clics sur un site pages_viewed_7d, time_on_site_avg
Historique d'achats days_since_last_purchase, favorite_category

2.2 Transformations courantes

from pyspark.sql import SparkSession
from pyspark.sql import functions as F
from pyspark.sql.window import Window

spark = SparkSession.builder \
    .appName("FeaturePipeline") \
    .master("local[*]") \
    .getOrCreate()
pythonVoir le code
# Crรฉer des donnรฉes d'exemple
from pyspark.sql import SparkSession
from pyspark.sql import functions as F
from pyspark.sql.window import Window
from pyspark.sql.types import *
from datetime import datetime, timedelta

spark = SparkSession.builder \
    .appName("FeaturePipeline") \
    .master("local[*]") \
    .config("spark.sql.adaptive.enabled", "true") \
    .getOrCreate()

spark.sparkContext.setLogLevel("WARN")

# Donnรฉes de transactions
transactions_data = [
    ("C001", "TXN001", 150.0, "Electronics", "2024-01-15"),
    ("C001", "TXN002", 25.0, "Food", "2024-01-18"),
    ("C001", "TXN003", 200.0, "Electronics", "2024-01-25"),
    ("C002", "TXN004", 75.0, "Clothing", "2024-01-10"),
    ("C002", "TXN005", 50.0, "Food", "2024-01-20"),
    ("C003", "TXN006", 500.0, "Electronics", "2024-01-05"),
    ("C003", "TXN007", 30.0, "Food", "2024-01-08"),
    ("C003", "TXN008", 120.0, "Clothing", "2024-01-22"),
    ("C001", "TXN009", 80.0, "Food", "2024-02-01"),
    ("C002", "TXN010", 300.0, "Electronics", "2024-02-05"),
]

transactions_schema = StructType([
    StructField("customer_id", StringType(), False),
    StructField("transaction_id", StringType(), False),
    StructField("amount", DoubleType(), False),
    StructField("category", StringType(), False),
    StructField("transaction_date", StringType(), False),
])

transactions_df = spark.createDataFrame(transactions_data, transactions_schema) \
    .withColumn("transaction_date", F.to_date("transaction_date"))

print("๐Ÿ“ฆ Transactions brutes :")
transactions_df.show()
pythonVoir le code
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# FEATURE 1 : Agrรฉgations simples
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

aggregation_features = transactions_df \
    .groupBy("customer_id") \
    .agg(
        F.count("*").alias("total_transactions"),
        F.sum("amount").alias("total_spent"),
        F.avg("amount").alias("avg_transaction_amount"),
        F.min("amount").alias("min_transaction_amount"),
        F.max("amount").alias("max_transaction_amount"),
        F.stddev("amount").alias("stddev_transaction_amount"),
        F.countDistinct("category").alias("unique_categories"),
        F.max("transaction_date").alias("last_transaction_date"),
        F.min("transaction_date").alias("first_transaction_date"),
    )

print("๐Ÿ“Š Features d'agrรฉgation :")
aggregation_features.show(truncate=False)
pythonVoir le code
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# FEATURE 2 : Window Functions (features temporelles)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

# Dรฉfinir les fenรชtres
window_30d = Window.partitionBy("customer_id") \
    .orderBy(F.col("transaction_date").cast("long")) \
    .rangeBetween(-30 * 86400, 0)  # 30 jours en secondes

window_7d = Window.partitionBy("customer_id") \
    .orderBy(F.col("transaction_date").cast("long")) \
    .rangeBetween(-7 * 86400, 0)

# Features rolling
rolling_features = transactions_df \
    .withColumn("transaction_ts", F.col("transaction_date").cast("timestamp")) \
    .withColumn("amount_sum_30d", F.sum("amount").over(window_30d)) \
    .withColumn("amount_avg_30d", F.avg("amount").over(window_30d)) \
    .withColumn("txn_count_30d", F.count("*").over(window_30d)) \
    .withColumn("amount_sum_7d", F.sum("amount").over(window_7d)) \
    .withColumn("txn_count_7d", F.count("*").over(window_7d))

print("๐Ÿ“ˆ Features avec Window Functions :")
rolling_features.select(
    "customer_id", "transaction_date", "amount",
    "amount_sum_30d", "txn_count_30d", "amount_sum_7d"
).show()
pythonVoir le code
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# FEATURE 3 : Encoding catรฉgoriel
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

# One-Hot Encoding manuel (pivot)
category_pivot = transactions_df \
    .groupBy("customer_id") \
    .pivot("category") \
    .agg(F.count("*")) \
    .fillna(0)

print("๐Ÿท๏ธ One-Hot Encoding des catรฉgories :")
category_pivot.show()

# Catรฉgorie favorite
favorite_category = transactions_df \
    .groupBy("customer_id", "category") \
    .agg(F.sum("amount").alias("category_total")) \
    .withColumn(
        "rank",
        F.row_number().over(
            Window.partitionBy("customer_id").orderBy(F.desc("category_total"))
        )
    ) \
    .filter(F.col("rank") == 1) \
    .select("customer_id", F.col("category").alias("favorite_category"))

print("โญ Catรฉgorie favorite par client :")
favorite_category.show()
pythonVoir le code
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# FEATURE 4 : Features de rรฉcence (RFM)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

reference_date = "2024-02-10"

rfm_features = transactions_df \
    .groupBy("customer_id") \
    .agg(
        F.max("transaction_date").alias("last_transaction"),
        F.count("*").alias("frequency"),
        F.sum("amount").alias("monetary")
    ) \
    .withColumn(
        "recency_days",
        F.datediff(F.lit(reference_date), F.col("last_transaction"))
    ) \
    .withColumn(
        "is_active_30d",
        F.when(F.col("recency_days") <= 30, 1).otherwise(0)
    )

print("๐Ÿ“… Features RFM (Recency, Frequency, Monetary) :")
rfm_features.show()
pythonVoir le code
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# PIPELINE COMPLET : Assembler toutes les features
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def build_customer_features(transactions_df, reference_date):
    """
    Pipeline complet de feature engineering pour les clients.
    
    Args:
        transactions_df: DataFrame des transactions
        reference_date: Date de rรฉfรฉrence pour les calculs de rรฉcence
    
    Returns:
        DataFrame avec toutes les features client
    """
    
    # 1. Agrรฉgations de base
    base_features = transactions_df \
        .groupBy("customer_id") \
        .agg(
            F.count("*").alias("total_transactions"),
            F.sum("amount").alias("total_spent"),
            F.avg("amount").alias("avg_transaction_amount"),
            F.stddev("amount").alias("stddev_amount"),
            F.countDistinct("category").alias("unique_categories"),
            F.max("transaction_date").alias("last_transaction_date"),
            F.min("transaction_date").alias("first_transaction_date"),
        )
    
    # 2. Features de rรฉcence
    recency_features = base_features \
        .withColumn(
            "recency_days",
            F.datediff(F.lit(reference_date), F.col("last_transaction_date"))
        ) \
        .withColumn(
            "customer_tenure_days",
            F.datediff(F.lit(reference_date), F.col("first_transaction_date"))
        ) \
        .withColumn(
            "is_active_30d",
            F.when(F.col("recency_days") <= 30, 1).otherwise(0)
        )
    
    # 3. Catรฉgorie favorite
    favorite_cat = transactions_df \
        .groupBy("customer_id", "category") \
        .agg(F.count("*").alias("cat_count")) \
        .withColumn(
            "rank",
            F.row_number().over(
                Window.partitionBy("customer_id").orderBy(F.desc("cat_count"))
            )
        ) \
        .filter(F.col("rank") == 1) \
        .select("customer_id", F.col("category").alias("favorite_category"))
    
    # 4. One-hot des catรฉgories
    category_ohe = transactions_df \
        .groupBy("customer_id") \
        .pivot("category") \
        .agg(F.count("*")) \
        .fillna(0)
    
    # Renommer les colonnes OHE
    for col_name in category_ohe.columns:
        if col_name != "customer_id":
            category_ohe = category_ohe.withColumnRenamed(
                col_name, f"category_{col_name.lower()}_count"
            )
    
    # 5. Joindre toutes les features
    final_features = recency_features \
        .join(favorite_cat, "customer_id", "left") \
        .join(category_ohe, "customer_id", "left") \
        .withColumn("feature_timestamp", F.lit(reference_date).cast("timestamp"))
    
    return final_features

# Exรฉcuter le pipeline
customer_features = build_customer_features(transactions_df, "2024-02-10")

print("๐ŸŽฏ Features client complรจtes :")
customer_features.show(truncate=False)
customer_features.printSchema()

2.3 Point-in-Time Correctness (ร‰viter le Data Leakage)

Data Leakage = utiliser des informations du futur pour prรฉdire le passรฉ.

โŒ MAUVAIS (Data Leakage) :
   Pour prรฉdire si le client achรจte le 15 janvier,
   on utilise ses transactions du 20 janvier โ†’ TRICHE !

โœ… BON (Point-in-Time Correct) :
   Pour prรฉdire si le client achรจte le 15 janvier,
   on utilise UNIQUEMENT ses transactions AVANT le 15 janvier.
def build_features_as_of(transactions_df, as_of_date):
    """
    Construire les features en utilisant UNIQUEMENT les donnรฉes
    disponibles AVANT as_of_date.
    """
    # Filtrer les transactions AVANT la date
    filtered = transactions_df.filter(
        F.col("transaction_date") < as_of_date
    )
    
    return build_customer_features(filtered, as_of_date)

3. Infrastructure Feature Store

3.1 Pourquoi un Feature Store ?

| Problรจme sans Feature Store | Solution avec Feature Store | |-----------------------------|-----------------------------|| | Features calculรฉes diffรฉremment en training vs serving | Single source of truth | | Duplication du code de features | Rรฉutilisation | | Pas de dรฉcouverte des features existantes | Feature discovery & catalog | | Point-in-time joins complexes | Built-in time-travel | | Latence รฉlevรฉe en serving | Online store low-latency |

3.2 Architecture Feature Store

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                         FEATURE STORE ARCHITECTURE                          โ”‚
โ”‚                                                                             โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                                       โ”‚
โ”‚   โ”‚  Feature        โ”‚     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚   โ”‚  Pipelines      โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚           OFFLINE STORE                     โ”‚  โ”‚
โ”‚   โ”‚  (Spark/Airflow)โ”‚     โ”‚  (Data Warehouse / Delta Lake / Parquet)    โ”‚  โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ”‚  - Historical features                      โ”‚  โ”‚
โ”‚                           โ”‚  - Training data generation                 โ”‚  โ”‚
โ”‚                           โ”‚  - Backfill support                         โ”‚  โ”‚
โ”‚                           โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                                              โ”‚                              โ”‚
โ”‚                                    Materialization Job                      โ”‚
โ”‚                                              โ”‚                              โ”‚
โ”‚                           โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚                           โ”‚           ONLINE STORE                      โ”‚  โ”‚
โ”‚                           โ”‚  (Redis / DynamoDB / Cassandra)             โ”‚  โ”‚
โ”‚                           โ”‚  - Latest feature values only               โ”‚  โ”‚
โ”‚                           โ”‚  - Low-latency serving (<10ms)              โ”‚  โ”‚
โ”‚                           โ”‚  - Real-time inference                      โ”‚  โ”‚
โ”‚                           โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

3.3 Feast : Feature Store Open Source

Feast (Feature Store) est le Feature Store open-source le plus populaire.

Installation

pip install feast[redis]

# Crรฉer un projet Feast
feast init my_feature_store
cd my_feature_store

Structure du projet

my_feature_store/
โ”œโ”€โ”€ feature_store.yaml      # Configuration
โ”œโ”€โ”€ features.py             # Dรฉfinition des features
โ””โ”€โ”€ data/
    โ””โ”€โ”€ customer_features.parquet

Configuration feature_store.yaml

project: my_ml_project
registry: data/registry.db
provider: local

offline_store:
  type: file
  # En production : type: snowflake / bigquery / redshift

online_store:
  type: redis
  connection_string: localhost:6379
  # Alternatives : dynamodb, datastore, sqlite (local)

entity_key_serialization_version: 2

Dรฉfinition des features features.py

from datetime import timedelta
from feast import Entity, Feature, FeatureView, FileSource, ValueType
from feast.types import Float64, Int64, String

# 1. Dรฉfinir l'entitรฉ (la clรฉ)
customer = Entity(
    name="customer_id",
    value_type=ValueType.STRING,
    description="Unique customer identifier"
)

# 2. Dรฉfinir la source de donnรฉes
customer_features_source = FileSource(
    path="data/customer_features.parquet",
    timestamp_field="feature_timestamp",
)

# 3. Dรฉfinir la Feature View
customer_features_view = FeatureView(
    name="customer_features",
    entities=[customer],
    ttl=timedelta(days=1),  # Time-to-live dans l'online store
    schema=[
        Feature(name="total_transactions", dtype=Int64),
        Feature(name="total_spent", dtype=Float64),
        Feature(name="avg_transaction_amount", dtype=Float64),
        Feature(name="recency_days", dtype=Int64),
        Feature(name="is_active_30d", dtype=Int64),
        Feature(name="favorite_category", dtype=String),
    ],
    source=customer_features_source,
    online=True,  # Matรฉrialiser dans l'online store
)

Commandes Feast

# Appliquer les dรฉfinitions
feast apply

# Matรฉrialiser dans l'online store
feast materialize 2024-01-01 2024-02-10

# Matรฉrialisation incrรฉmentale
feast materialize-incremental $(date +%Y-%m-%d)

Utilisation Python

from feast import FeatureStore
from datetime import datetime
import pandas as pd

store = FeatureStore(repo_path=".")

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# OFFLINE : Rรฉcupรฉrer des features historiques (training)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

entity_df = pd.DataFrame({
    "customer_id": ["C001", "C002", "C003"],
    "event_timestamp": [datetime(2024, 2, 1)] * 3
})

training_df = store.get_historical_features(
    entity_df=entity_df,
    features=[
        "customer_features:total_transactions",
        "customer_features:total_spent",
        "customer_features:recency_days",
    ]
).to_df()

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# ONLINE : Rรฉcupรฉrer les features en temps rรฉel (serving)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

online_features = store.get_online_features(
    features=[
        "customer_features:total_spent",
        "customer_features:is_active_30d",
    ],
    entity_rows=[{"customer_id": "C001"}]
).to_dict()

print(online_features)
# {'customer_id': ['C001'], 'total_spent': [375.0], 'is_active_30d': [1]}

3.4 Pipeline d'alimentation du Feature Store

# feature_pipeline_dag.py (Airflow)

from airflow import DAG
from airflow.operators.python import PythonOperator
from airflow.providers.apache.spark.operators.spark_submit import SparkSubmitOperator
from datetime import datetime, timedelta

default_args = {
    'owner': 'data-engineering',
    'depends_on_past': True,
    'start_date': datetime(2024, 1, 1),
    'retries': 2,
    'retry_delay': timedelta(minutes=5),
}

with DAG(
    'feature_pipeline',
    default_args=default_args,
    schedule_interval='@daily',
    catchup=False,
) as dag:
    
    # 1. Calculer les features avec Spark
    compute_features = SparkSubmitOperator(
        task_id='compute_customer_features',
        application='/jobs/compute_features.py',
        application_args=['--date', '{{ ds }}'],
        conf={
            'spark.executor.memory': '4g',
            'spark.executor.cores': '2',
        },
    )
    
    # 2. Valider les features
    validate_features = PythonOperator(
        task_id='validate_features',
        python_callable=run_feature_validation,
        op_kwargs={'date': '{{ ds }}'},
    )
    
    # 3. Matรฉrialiser dans le Feature Store
    materialize = PythonOperator(
        task_id='materialize_features',
        python_callable=materialize_to_feast,
        op_kwargs={'end_date': '{{ ds }}'},
    )
    
    compute_features >> validate_features >> materialize

4. Training Data Pipelines

4.1 Gรฉnรฉrer des Datasets Reproductibles

Un bon training dataset doit รชtre :

  • Reproductible : on peut le recrรฉer exactement
  • Versionnรฉ : on sait quelle version a รฉtรฉ utilisรฉe
  • Point-in-time correct : pas de data leakage

4.2 Point-in-Time Joins

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                       POINT-IN-TIME JOIN                                    โ”‚
โ”‚                                                                             โ”‚
โ”‚   Events (ce qu'on prรฉdit)          Features (inputs du modรจle)            โ”‚
โ”‚   โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€             โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€              โ”‚
โ”‚                                                                             โ”‚
โ”‚   customer_id | event_date          customer_id | feature_date | features  โ”‚
โ”‚   C001        | 2024-02-01          C001        | 2024-01-15   | {...}     โ”‚
โ”‚   C001        | 2024-02-15          C001        | 2024-02-01   | {...}     โ”‚
โ”‚                                     C001        | 2024-02-10   | {...}     โ”‚
โ”‚                                                                             โ”‚
โ”‚   Pour l'event du 2024-02-01 :                                             โ”‚
โ”‚   โ†’ Utiliser les features du 2024-01-15 (la plus rรฉcente AVANT l'event)    โ”‚
โ”‚                                                                             โ”‚
โ”‚   Pour l'event du 2024-02-15 :                                             โ”‚
โ”‚   โ†’ Utiliser les features du 2024-02-10 (la plus rรฉcente AVANT l'event)    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
pythonVoir le code
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# POINT-IN-TIME JOIN avec Spark
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

# Crรฉer des donnรฉes d'exemple

# Events : ce qu'on veut prรฉdire (ex: churn, achat)
events_data = [
    ("C001", "2024-02-01", 1),  # Client C001 a churnรฉ le 1er fรฉvrier
    ("C002", "2024-02-05", 0),  # Client C002 n'a pas churnรฉ
    ("C003", "2024-02-10", 1),  # Client C003 a churnรฉ le 10 fรฉvrier
]
events_df = spark.createDataFrame(
    events_data, 
    ["customer_id", "event_date", "label"]
).withColumn("event_date", F.to_date("event_date"))

# Features avec timestamps (plusieurs versions par client)
features_data = [
    ("C001", "2024-01-15", 5, 500.0),
    ("C001", "2024-01-25", 6, 550.0),
    ("C002", "2024-01-20", 3, 200.0),
    ("C002", "2024-02-01", 4, 280.0),
    ("C003", "2024-01-10", 10, 1000.0),
    ("C003", "2024-02-05", 11, 1100.0),
]
features_df = spark.createDataFrame(
    features_data,
    ["customer_id", "feature_date", "total_transactions", "total_spent"]
).withColumn("feature_date", F.to_date("feature_date"))

print("๐Ÿ“… Events (labels) :")
events_df.show()

print("๐Ÿ“Š Features (avec historique) :")
features_df.show()
pythonVoir le code
def point_in_time_join(events_df, features_df, entity_col, event_ts_col, feature_ts_col):
    """
    Join point-in-time correct : pour chaque event, rรฉcupรฉrer
    les features les plus rรฉcentes AVANT l'event.
    
    Args:
        events_df: DataFrame avec les events et leurs timestamps
        features_df: DataFrame avec les features et leurs timestamps
        entity_col: Colonne de jointure (ex: customer_id)
        event_ts_col: Colonne timestamp dans events_df
        feature_ts_col: Colonne timestamp dans features_df
    
    Returns:
        DataFrame avec events + features point-in-time correct
    """
    
    # 1. Joindre sur l'entitรฉ + feature_date < event_date
    joined = events_df.alias("e").join(
        features_df.alias("f"),
        (F.col(f"e.{entity_col}") == F.col(f"f.{entity_col}")) &
        (F.col(f"f.{feature_ts_col}") < F.col(f"e.{event_ts_col}")),
        "left"
    )
    
    # 2. Garder uniquement la feature la plus rรฉcente avant l'event
    window = Window.partitionBy(f"e.{entity_col}", f"e.{event_ts_col}") \
                   .orderBy(F.col(f"f.{feature_ts_col}").desc())
    
    result = joined \
        .withColumn("_rank", F.row_number().over(window)) \
        .filter(F.col("_rank") == 1) \
        .drop("_rank", f"f.{entity_col}")
    
    return result

# Exรฉcuter le join point-in-time
training_data = point_in_time_join(
    events_df, 
    features_df,
    entity_col="customer_id",
    event_ts_col="event_date",
    feature_ts_col="feature_date"
)

print("๐ŸŽฏ Training Dataset (Point-in-Time Correct) :")
training_data.select(
    "customer_id", "event_date", "label", 
    "feature_date", "total_transactions", "total_spent"
).show()

# Vรฉrification : feature_date est toujours < event_date โœ“

4.3 Dataset Versioning avec Delta Lake

# Sauvegarder le training dataset avec versioning

training_data.write \
    .format("delta") \
    .mode("overwrite") \
    .option("overwriteSchema", "true") \
    .save("data/training_datasets/churn_model")

# Ajouter des mรฉtadonnรฉes
from delta.tables import DeltaTable

delta_table = DeltaTable.forPath(spark, "data/training_datasets/churn_model")

# Voir l'historique des versions
delta_table.history().select(
    "version", "timestamp", "operation", "operationMetrics"
).show(truncate=False)

# Time travel : rรฉcupรฉrer une version spรฉcifique
training_v2 = spark.read \
    .format("delta") \
    .option("versionAsOf", 2) \
    .load("data/training_datasets/churn_model")

# Ou par timestamp
training_at_date = spark.read \
    .format("delta") \
    .option("timestampAsOf", "2024-01-15 10:00:00") \
    .load("data/training_datasets/churn_model")

4.4 Data Splits

def create_train_val_test_split(df, train_ratio=0.7, val_ratio=0.15, test_ratio=0.15, seed=42):
    """
    Split un dataset en train/validation/test de maniรจre reproductible.
    
    Pour les donnรฉes temporelles, prรฉfรฉrer un split par date !
    """
    assert abs(train_ratio + val_ratio + test_ratio - 1.0) < 0.001
    
    # Random split
    train_df, val_df, test_df = df.randomSplit(
        [train_ratio, val_ratio, test_ratio], 
        seed=seed
    )
    
    return train_df, val_df, test_df

def create_temporal_split(df, date_col, train_end, val_end):
    """
    Split temporel (recommandรฉ pour รฉviter le leakage) :
    - Train : donnรฉes avant train_end
    - Val : donnรฉes entre train_end et val_end  
    - Test : donnรฉes aprรจs val_end
    """
    train_df = df.filter(F.col(date_col) < train_end)
    val_df = df.filter(
        (F.col(date_col) >= train_end) & 
        (F.col(date_col) < val_end)
    )
    test_df = df.filter(F.col(date_col) >= val_end)
    
    return train_df, val_df, test_df

5. Data Validation pour ML

5.1 Pourquoi la Data Quality est Critique pour ML

"Garbage In, Garbage Out" โ€” mais en pire pour le ML !

Problรจme de data โ†’ Modรจle apprend le bruit โ†’ Prรฉdictions fausses en production
Problรจme de donnรฉes Impact sur le ML
Missing values Modรจle biaisรฉ ou crash
Outliers extrรชmes Poids aberrants
Data leakage Mรฉtriques sur-optimistes, crash en prod
Distribution drift Performance dรฉgradรฉe en prod
Class imbalance non dรฉtectรฉ Modรจle prรฉdit toujours la classe majoritaire

5.2 Great Expectations pour ML Data

import great_expectations as gx

# Crรฉer le contexte
context = gx.get_context()

# Crรฉer une expectation suite pour les features ML
suite = context.add_expectation_suite("ml_features_validation")
pythonVoir le code
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# VALIDATIONS ML avec Great Expectations (conceptuel)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

ml_feature_expectations = {
    "expectations": [
        # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        # 1. Complรฉtude : pas de valeurs manquantes sur les features critiques
        # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        {
            "expectation_type": "expect_column_values_to_not_be_null",
            "kwargs": {"column": "customer_id"}
        },
        {
            "expectation_type": "expect_column_values_to_not_be_null",
            "kwargs": {"column": "total_transactions"}
        },
        
        # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        # 2. Plage de valeurs : dรฉtecter les outliers
        # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        {
            "expectation_type": "expect_column_values_to_be_between",
            "kwargs": {
                "column": "total_spent",
                "min_value": 0,
                "max_value": 100000  # Alerter si > 100k
            }
        },
        {
            "expectation_type": "expect_column_values_to_be_between",
            "kwargs": {
                "column": "recency_days",
                "min_value": 0,
                "max_value": 365
            }
        },
        
        # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        # 3. Distribution : moyennes et รฉcarts-types attendus
        # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        {
            "expectation_type": "expect_column_mean_to_be_between",
            "kwargs": {
                "column": "avg_transaction_amount",
                "min_value": 50,
                "max_value": 500
            }
        },
        {
            "expectation_type": "expect_column_stdev_to_be_between",
            "kwargs": {
                "column": "total_spent",
                "min_value": 10,
                "max_value": 5000
            }
        },
        
        # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        # 4. Cardinalitรฉ : vรฉrifier les catรฉgories
        # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        {
            "expectation_type": "expect_column_values_to_be_in_set",
            "kwargs": {
                "column": "favorite_category",
                "value_set": ["Electronics", "Clothing", "Food", "Other", None]
            }
        },
        
        # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        # 5. Unicitรฉ : pas de doublons
        # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        {
            "expectation_type": "expect_column_values_to_be_unique",
            "kwargs": {"column": "customer_id"}
        },
        
        # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        # 6. Volume : nombre de lignes attendu
        # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
        {
            "expectation_type": "expect_table_row_count_to_be_between",
            "kwargs": {
                "min_value": 1000,
                "max_value": 1000000
            }
        },
    ]
}

print("โœ… Expectations ML dรฉfinies :")
for exp in ml_feature_expectations["expectations"]:
    print(f"  - {exp['expectation_type']} sur {exp['kwargs'].get('column', 'table')}")

5.3 Validations Spรฉcifiques ML

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# Validation du label (target)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def validate_classification_labels(df, label_col, expected_classes):
    """
    Valider les labels pour un problรจme de classification.
    """
    # 1. Pas de nulls dans le label
    null_count = df.filter(F.col(label_col).isNull()).count()
    assert null_count == 0, f"Found {null_count} null labels!"
    
    # 2. Labels dans les classes attendues
    actual_classes = set(df.select(label_col).distinct().toPandas()[label_col].tolist())
    unexpected = actual_classes - set(expected_classes)
    assert len(unexpected) == 0, f"Unexpected labels: {unexpected}"
    
    # 3. Vรฉrifier le class imbalance
    class_counts = df.groupBy(label_col).count().toPandas()
    min_count = class_counts['count'].min()
    max_count = class_counts['count'].max()
    imbalance_ratio = max_count / min_count
    
    if imbalance_ratio > 10:
        print(f"โš ๏ธ WARNING: Class imbalance ratio = {imbalance_ratio:.1f}")
        print(f"   Consider using class weights or resampling.")
    
    return True

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# Validation anti-leakage
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def validate_no_leakage(df, event_date_col, feature_date_col):
    """
    Vรฉrifier qu'aucune feature n'est du futur par rapport ร  l'event.
    """
    leakage_count = df.filter(
        F.col(feature_date_col) >= F.col(event_date_col)
    ).count()
    
    if leakage_count > 0:
        raise ValueError(f"๐Ÿšจ DATA LEAKAGE DETECTED! {leakage_count} rows have future features!")
    
    print("โœ… No data leakage detected")
    return True

6. Serving Data Infrastructure

6.1 Patterns de Serving

Pattern Latence Use Case
Batch precompute Minutes-Hours Scoring quotidien, recommendations
Online store lookup <10ms Personnalisation temps rรฉel
On-demand compute 100ms+ Features complexes ร  la demande
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                     SERVING DATA PATTERNS                                   โ”‚
โ”‚                                                                             โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚   โ”‚  BATCH PRECOMPUTE                                                   โ”‚  โ”‚
โ”‚   โ”‚                                                                     โ”‚  โ”‚
โ”‚   โ”‚  Features โ”€โ”€โ–ถ Batch Scoring โ”€โ”€โ–ถ Predictions Table โ”€โ”€โ–ถ Application  โ”‚  โ”‚
โ”‚   โ”‚  (Spark)       (Spark MLlib)     (Delta/Postgres)      (lookup)    โ”‚  โ”‚
โ”‚   โ”‚                                                                     โ”‚  โ”‚
โ”‚   โ”‚  โฑ๏ธ Latency: Minutes/Hours    ๐Ÿ‘ Simple, scalable                   โ”‚  โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                                                                             โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚   โ”‚  ONLINE STORE LOOKUP                                                โ”‚  โ”‚
โ”‚   โ”‚                                                                     โ”‚  โ”‚
โ”‚   โ”‚  Request โ”€โ”€โ–ถ Feature Store โ”€โ”€โ–ถ ML Model โ”€โ”€โ–ถ Response               โ”‚  โ”‚
โ”‚   โ”‚              (Redis <10ms)     (API)        (real-time)            โ”‚  โ”‚
โ”‚   โ”‚                                                                     โ”‚  โ”‚
โ”‚   โ”‚  โฑ๏ธ Latency: <50ms total      ๐Ÿ‘ Real-time personalization         โ”‚  โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚                                                                             โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚   โ”‚  ON-DEMAND COMPUTE                                                  โ”‚  โ”‚
โ”‚   โ”‚                                                                     โ”‚  โ”‚
โ”‚   โ”‚  Request โ”€โ”€โ–ถ Compute Features โ”€โ”€โ–ถ ML Model โ”€โ”€โ–ถ Response            โ”‚  โ”‚
โ”‚   โ”‚              (on-the-fly)         (API)        (computed)          โ”‚  โ”‚
โ”‚   โ”‚                                                                     โ”‚  โ”‚
โ”‚   โ”‚  โฑ๏ธ Latency: 100ms+           ๐Ÿ‘ Always fresh, complex features    โ”‚  โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

6.2 Batch Scoring Pipeline

# batch_scoring_dag.py (Airflow)

from airflow import DAG
from airflow.providers.apache.spark.operators.spark_submit import SparkSubmitOperator

with DAG('batch_scoring', schedule_interval='@daily') as dag:
    
    # 1. Rรฉcupรฉrer les features du jour
    prepare_features = SparkSubmitOperator(
        task_id='prepare_scoring_features',
        application='/jobs/prepare_features.py',
    )
    
    # 2. Scorer avec le modรจle
    score = SparkSubmitOperator(
        task_id='batch_score',
        application='/jobs/batch_score.py',
        application_args=['--model-uri', 'models:/churn_model/Production'],
    )
    
    # 3. ร‰crire les prรฉdictions
    write_predictions = SparkSubmitOperator(
        task_id='write_predictions',
        application='/jobs/write_predictions.py',
    )
    
    prepare_features >> score >> write_predictions

6.3 Online Store avec Redis

import redis
import json

# Connexion Redis
r = redis.Redis(host='localhost', port=6379, db=0)

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# ร‰criture : Matรฉrialisation des features
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def materialize_to_redis(features_df):
    """ร‰crire les features dans Redis pour serving temps rรฉel."""
    
    for row in features_df.collect():
        customer_id = row['customer_id']
        features = {
            'total_transactions': row['total_transactions'],
            'total_spent': row['total_spent'],
            'recency_days': row['recency_days'],
            'is_active_30d': row['is_active_30d'],
        }
        
        # Clรฉ : customer_features:{customer_id}
        r.hset(f"customer_features:{customer_id}", mapping=features)
        
        # TTL : 24 heures
        r.expire(f"customer_features:{customer_id}", 86400)

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# Lecture : Serving temps rรฉel
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def get_features_for_scoring(customer_id):
    """Rรฉcupรฉrer les features pour le scoring temps rรฉel."""
    
    features = r.hgetall(f"customer_features:{customer_id}")
    
    if not features:
        return None
    
    # Convertir bytes โ†’ types Python
    return {
        k.decode(): float(v.decode())
        for k, v in features.items()
    }

# Usage
# features = get_features_for_scoring("C001")
# prediction = model.predict([features])

7. Data Monitoring pour ML

7.1 Types de Drift

Type Description Ce que le DE monitore
Data Drift Distribution des inputs change โœ… Features distributions
Concept Drift Relation inputโ†’output change โš ๏ธ Alerter le DS
Prediction Drift Distribution des outputs change โš ๏ธ Alerter le DS
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                         DATA DRIFT DETECTION                                โ”‚
โ”‚                                                                             โ”‚
โ”‚   Training Data Distribution          Production Data Distribution          โ”‚
โ”‚   โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€           โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€            โ”‚
โ”‚                                                                             โ”‚
โ”‚   amount:                             amount:                               โ”‚
โ”‚   mean = 150                          mean = 280  โ† DRIFT!                 โ”‚
โ”‚   std = 50                            std = 120   โ† DRIFT!                 โ”‚
โ”‚                                                                             โ”‚
โ”‚        โ–ฒ                                    โ–ฒ                               โ”‚
โ”‚       โ•ฑโ•ฒ                                  โ•ฑ    โ•ฒ                            โ”‚
โ”‚      โ•ฑ  โ•ฒ                               โ•ฑ      โ•ฒ                            โ”‚
โ”‚     โ•ฑ    โ•ฒ                            โ•ฑ         โ•ฒ                           โ”‚
โ”‚   โ”€โ•ฑโ”€โ”€โ”€โ”€โ”€โ”€โ•ฒโ”€โ”€โ”€โ–ถ                    โ”€โ”€โ•ฑโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฒโ”€โ”€โ–ถ                       โ”‚
โ”‚                                                                             โ”‚
โ”‚   Si drift dรฉtectรฉ โ†’ Alerter โ†’ Potentiellement retrainer le modรจle         โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

7.2 Evidently AI pour Data Monitoring

from evidently import ColumnMapping
from evidently.report import Report
from evidently.metrics import (
    DataDriftTable,
    DatasetDriftMetric,
    ColumnDriftMetric,
)
from evidently.test_suite import TestSuite
from evidently.tests import (
    TestColumnDrift,
    TestShareOfDriftedColumns,
)

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# RAPPORT DE DRIFT
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

# reference_data = donnรฉes de training
# current_data = donnรฉes de production rรฉcentes

column_mapping = ColumnMapping(
    numerical_features=['total_transactions', 'total_spent', 'avg_transaction_amount'],
    categorical_features=['favorite_category'],
)

# Crรฉer le rapport de drift
drift_report = Report(metrics=[
    DatasetDriftMetric(),
    DataDriftTable(),
])

drift_report.run(
    reference_data=reference_df,
    current_data=current_df,
    column_mapping=column_mapping
)

# Sauvegarder en HTML
drift_report.save_html("reports/drift_report.html")

# Ou obtenir les rรฉsultats en dict
results = drift_report.as_dict()
dataset_drift = results['metrics'][0]['result']['dataset_drift']
print(f"Dataset drift detected: {dataset_drift}")

# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# TESTS AUTOMATISร‰S (pour CI/CD)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

drift_tests = TestSuite(tests=[
    TestShareOfDriftedColumns(lt=0.3),  # Moins de 30% de colonnes en drift
    TestColumnDrift(column_name='total_spent'),
    TestColumnDrift(column_name='recency_days'),
])

drift_tests.run(
    reference_data=reference_df,
    current_data=current_df,
    column_mapping=column_mapping
)

# Vรฉrifier si les tests passent
test_results = drift_tests.as_dict()
all_passed = all(t['status'] == 'SUCCESS' for t in test_results['tests'])

if not all_passed:
    print("๐Ÿšจ DRIFT ALERT: Some tests failed!")
    # Envoyer une alerte (Slack, PagerDuty, etc.)
pythonVoir le code
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
# MONITORING PIPELINE SIMPLIFIร‰ (sans Evidently)
# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

def compute_feature_stats(df, feature_cols):
    """
    Calculer les statistiques de base pour le monitoring.
    """
    stats = {}
    
    for col in feature_cols:
        col_stats = df.select(
            F.mean(col).alias("mean"),
            F.stddev(col).alias("std"),
            F.min(col).alias("min"),
            F.max(col).alias("max"),
            F.expr(f"percentile_approx({col}, 0.5)").alias("median"),
            (F.count(F.when(F.col(col).isNull(), 1)) / F.count("*")).alias("null_rate"),
        ).collect()[0]
        
        stats[col] = {
            "mean": col_stats["mean"],
            "std": col_stats["std"],
            "min": col_stats["min"],
            "max": col_stats["max"],
            "median": col_stats["median"],
            "null_rate": col_stats["null_rate"],
        }
    
    return stats

def detect_drift(reference_stats, current_stats, threshold=0.2):
    """
    Dรฉtecter le drift en comparant les statistiques.
    
    Mรฉthode simple : alerte si la moyenne change de plus de threshold%.
    """
    alerts = []
    
    for col in reference_stats:
        ref_mean = reference_stats[col]["mean"]
        cur_mean = current_stats[col]["mean"]
        
        if ref_mean != 0:
            pct_change = abs(cur_mean - ref_mean) / abs(ref_mean)
            
            if pct_change > threshold:
                alerts.append({
                    "column": col,
                    "reference_mean": ref_mean,
                    "current_mean": cur_mean,
                    "pct_change": pct_change * 100,
                })
    
    return alerts

# Exemple d'utilisation
feature_cols = ["total_transactions", "total_spent", "avg_transaction_amount"]

# Simuler des donnรฉes de rรฉfรฉrence et actuelles
reference_stats = compute_feature_stats(customer_features, feature_cols)

# Simuler un drift (en production, ce serait les nouvelles donnรฉes)
drifted_data = customer_features.withColumn(
    "total_spent", F.col("total_spent") * 1.5  # +50% drift
)
current_stats = compute_feature_stats(drifted_data, feature_cols)

# Dรฉtecter le drift
alerts = detect_drift(reference_stats, current_stats, threshold=0.2)

print("๐Ÿ“Š Monitoring Results:")
if alerts:
    print("๐Ÿšจ DRIFT DETECTED:")
    for alert in alerts:
        print(f"   - {alert['column']}: {alert['pct_change']:.1f}% change")
        print(f"     (reference: {alert['reference_mean']:.2f} โ†’ current: {alert['current_mean']:.2f})")
else:
    print("โœ… No significant drift detected")

8. MLflow pour Data Engineers

En tant que Data Engineer, tu n'as pas besoin de tout connaรฎtre de MLflow. Voici ce qui te concerne.

8.1 Ce qu'un DE doit savoir

Composant MLflow Responsabilitรฉ DE Responsabilitรฉ DS/MLE
Tracking Logger les datasets utilisรฉs Logger les mรฉtriques, paramรจtres
Projects โ€” Packager le code
Models โ€” Sauvegarder les modรจles
Registry Savoir quel modรจle est en Production Promouvoir les modรจles

8.2 Logger les Datasets avec MLflow

import mlflow

# Dans ton pipeline de feature engineering
def log_dataset_to_mlflow(df, dataset_name, run_id=None):
    """
    Logger les mรฉtadonnรฉes du dataset pour traรงabilitรฉ.
    """
    with mlflow.start_run(run_id=run_id):
        # Logger les infos du dataset
        mlflow.log_param(f"{dataset_name}_rows", df.count())
        mlflow.log_param(f"{dataset_name}_cols", len(df.columns))
        mlflow.log_param(f"{dataset_name}_columns", ",".join(df.columns))
        
        # Logger les statistiques
        stats = df.describe().toPandas()
        stats.to_csv(f"/tmp/{dataset_name}_stats.csv")
        mlflow.log_artifact(f"/tmp/{dataset_name}_stats.csv")
        
        # Logger le chemin du dataset
        mlflow.log_param(f"{dataset_name}_path", f"s3://data/features/{dataset_name}")

8.3 Rรฉcupรฉrer le modรจle en Production

import mlflow

# Pour le batch scoring, rรฉcupรฉrer le modรจle en Production
model_name = "churn_model"

# Mรฉthode 1 : par stage
model = mlflow.pyfunc.load_model(f"models:/{model_name}/Production")

# Mรฉthode 2 : par version
model = mlflow.pyfunc.load_model(f"models:/{model_name}/3")

# Scorer
predictions = model.predict(features_df.toPandas())

8.4 MLflow avec Spark

import mlflow.spark

# Logger un modรจle Spark MLlib
mlflow.spark.log_model(spark_model, "model")

# Charger pour batch scoring
loaded_model = mlflow.spark.load_model("models:/my_spark_model/Production")

# Scorer directement sur un DataFrame Spark
predictions_df = loaded_model.transform(features_df)

9. Exercices Pratiques

Exercice 1 : Feature Pipeline avec Window Functions

Crรฉer un pipeline de features pour un modรจle de dรฉtection de fraude avec :

  • Montant moyen des 5 derniรจres transactions
  • Nombre de transactions dans l'heure prรฉcรฉdente
  • ร‰cart par rapport au montant moyen habituel

Exercice 2 : Setup Feast Local

  1. Installer Feast
  2. Crรฉer un projet avec les features customer
  3. Matรฉrialiser les features
  4. Rรฉcupรฉrer des features historiques

Exercice 3 : Training Dataset Point-in-Time

Crรฉer un training dataset pour prรฉdire le churn avec :

  • Events : clients qui ont churnรฉ (label=1) ou non (label=0)
  • Features : rรฉcupรฉrรฉes 7 jours AVANT l'event
  • Validation : vรฉrifier qu'il n'y a pas de leakage

Exercice 4 : Data Validation Pipeline

Implรฉmenter un pipeline de validation avec :

  • Vรฉrification des nulls
  • Vรฉrification des plages de valeurs
  • Dรฉtection d'outliers
  • Intรฉgration Airflow

Exercice 5 : Data Drift Detection

  1. Crรฉer un dataset de rรฉfรฉrence
  2. Simuler un drift sur certaines features
  3. Implรฉmenter la dรฉtection automatique
  4. Gรฉnรฉrer une alerte si drift > 20%

10. Mini-Projet : ML Data Platform

Objectif

Construire une plateforme data complรจte pour alimenter un modรจle de prรฉdiction de churn.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                      MINI-PROJET : ML DATA PLATFORM                         โ”‚
โ”‚                                                                             โ”‚
โ”‚   Raw Data (CSV)                                                            โ”‚
โ”‚        โ”‚                                                                    โ”‚
โ”‚        โ–ผ                                                                    โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                                       โ”‚
โ”‚   โ”‚ Data Ingestion  โ”‚  Bronze Layer (Delta)                                 โ”‚
โ”‚   โ”‚   (Spark)       โ”‚                                                       โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                                       โ”‚
โ”‚            โ–ผ                                                                โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                                       โ”‚
โ”‚   โ”‚ Data Validation โ”‚  Great Expectations                                   โ”‚
โ”‚   โ”‚                 โ”‚                                                       โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                                       โ”‚
โ”‚            โ–ผ                                                                โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                                       โ”‚
โ”‚   โ”‚ Feature Pipelineโ”‚  Silver Layer (Features)                              โ”‚
โ”‚   โ”‚   (Spark)       โ”‚                                                       โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                                       โ”‚
โ”‚            โ–ผ                                                                โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                               โ”‚
โ”‚   โ”‚  Feature Store  โ”‚โ”€โ”€โ”€โ”€โ–ถโ”‚ Training Datasetโ”‚  Point-in-time correct       โ”‚
โ”‚   โ”‚    (Feast)      โ”‚     โ”‚   Generator     โ”‚                               โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                               โ”‚
โ”‚            โ”‚                                                                โ”‚
โ”‚            โ–ผ                                                                โ”‚
โ”‚   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                                       โ”‚
โ”‚   โ”‚ Data Monitoring โ”‚  Drift detection                                      โ”‚
โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                                       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Livrables

  1. Data Ingestion : Script Spark pour ingรฉrer les CSV โ†’ Delta
  2. Validation : Suite Great Expectations pour les donnรฉes brutes
  3. Feature Pipeline : Job Spark complet avec toutes les features
  4. Feature Store : Configuration Feast + matรฉrialisation
  5. Training Dataset : Gรฉnรฉrateur avec point-in-time join
  6. Monitoring : Script de dรฉtection de drift
  7. Orchestration : DAG Airflow qui orchestre le tout

Donnรฉes

Utiliser les transactions crรฉรฉes dans ce notebook + gรฉnรฉrer des events de churn.

Critรจres de succรจs

  • [ ] Pipeline end-to-end fonctionnel
  • [ ] Pas de data leakage dans le training dataset
  • [ ] Validation automatique des donnรฉes
  • [ ] Drift detection opรฉrationnel
  • [ ] Documentation claire

๐Ÿ“š Ressources

Documentation

Articles

Outils

  • Feast โ€” Feature Store open-source
  • Tecton โ€” Feature Store managed
  • DVC โ€” Data versioning

โžก๏ธ Prochaine รฉtape

๐Ÿ‘‰ Module suivant : 32_data_mesh_contracts โ€” Data Mesh & Data Contracts


๐ŸŽ‰ Fรฉlicitations ! Tu maรฎtrises maintenant l'infrastructure data pour le ML.