Files
ENI-ml-mlops/SYNTHESE_TP03.md
2026-07-22 11:22:50 +02:00

10 KiB

TP03 - Synthèse : exposer un modèle ML via une API REST

Fil rouge : prédiction de la consommation électrique (kWh). On sélectionne le meilleur modèle du TP02, on l'enregistre dans le MLflow Model Registry, on le promeut via un alias, puis on l'expose par une API REST FastAPI.

Ce qui a été construit

  • log_model ajouté à chaque run d'entraînement (lab/modeling/cli.py, lab/modeling_ridge/cli.py) : sauvegarde l'artefact complet du modèle sur S3 (s3://mlflow-artifacts).
  • Modèle enregistré : electricity-consumption (Registry MLflow). Versions créées avec le flag --register : v1 = full (6 features, RMSE val 6.576), v2 = mixed (4 features, RMSE 6.671).
  • Promotion : alias champion -> v1 (full), via lab/registry/cli.py promote.
  • API FastAPI (lab/serving/) : GET /health, POST /predict, POST /predict/batch, Swagger /docs. Le modèle est chargé au démarrage par models:/electricity-consumption@champion.
  • Exposition : https://api.192-168-122-143.nip.io/docs (reverse-proxy Caddy -> uvicorn :8000).

Partie 1 - Sélection et enregistrement du modèle

1.1 - Que contiennent les artefacts du modèle sauvegardé par MLflow, pourquoi sont-ils utiles ? Sauvegarder un modèle ne se limite pas à ses coefficients : MLflow enregistre tout l'environnement d'exécution. Contenu observé (s3://mlflow-artifacts/1/models/<id>/artifacts/) :

  • model.pkl : le modèle sérialisé (poids/coefficients) ;
  • MLmodel : métadonnées (flavors sklearn/pyfunc, signature = schéma entrées/sorties) ;
  • requirements.txt, conda.yaml, python_env.yaml : versions exactes des dépendances ;
  • input_example.json, serving_input_example.json : exemple d'entrée. Utiles pour recharger le modèle partout (load_model), reproduire l'environnement (mêmes versions -> mêmes prédictions), et connaître le contrat d'E/S (signature).

1.2 - Quel mécanisme vous permet de promouvoir un modèle ? L'alias du Model Registry : MlflowClient().set_registered_model_alias(name, "champion", version) fait pointer un alias mobile vers une version précise. (Les anciens stages Staging/Production sont dépréciés en MLflow 3.x au profit des alias + tags.)

1.3 - Plusieurs environnements de production (un par région), plusieurs modèles : comment les identifier ? Avec des alias et tags spécifiques : par ex. des alias production-eu, production-us, champion-north... sur un même modèle enregistré, et/ou un modèle enregistré par région, complétés par des tags (région, environnement) portés par le modèle ou la version. Le Registry gère plusieurs alias par modèle et des tags arbitraires : l'API cible alors models:/<nom>@<alias-région>.


Partie 2 - Service de prédiction

Étape 1 - Création de l'API

2.1 - Quel endpoint de vérification ? Quel code de statut attendu ? GET /health, qui renvoie 200 OK avec {"status":"ok"}.

2.2 - À quoi sert un endpoint de santé ? À vérifier que le service est vivant et prêt (liveness/readiness), sans exécuter de vraie prédiction. Il est sondé par l'orchestrateur / load-balancer / monitoring pour router le trafic, redémarrer un conteneur en échec, ou alerter.

Étape 2 - Requête de prédiction

2.3 - Quelles informations le client doit-il fournir ? Pourquoi ? L'identifiant client (client_id) et, en paramètre, la date de prédiction. Ce sont les seules informations du contexte métier que le client connaît : elles disent qui et quand. Elles servent de clé pour retrouver le reste côté serveur.

2.4 - Quelles informations le client ne peut-il pas fournir ? Comment le service les récupère-t-il ? Les features calculées (lags, moyennes glissantes) issues de l'historique de consommation : le client ne les possède/calcule pas. Le service les récupère lui-même depuis un feature store / une base, à partir du client_id (+ date). Ici, c'est simulé par un dictionnaire Python (lab/serving/features.py).

2.5 - Quelle méthode HTTP pour le endpoint de prédiction ? Pourquoi ? POST : la requête transporte un corps JSON structuré (et potentiellement volumineux en batch), et déclenche un calcul (action, non une simple lecture cacheable de ressource comme le ferait GET).

Étape 3 - Récupération des features

2.6 - Rappel des features nécessaires à la prédiction. Modèle promu = stratégie full, soit les 6 features : lag_1d, lag_7d, lag_30d, lag_365d, rolling_mean_7d, rolling_mean_30d.

2.7 - Dans un système réel, d'où proviennent ces features ? D'un feature store / pipeline de features : un job (batch ou streaming) calcule lags et moyennes glissantes depuis la série temporelle brute, les stocke dans une base (ex. Feast), et les sert à l'inférence. Elles doivent être calculées de façon identique à l'entraînement (éviter le training/serving skew).

2.8 - Pourquoi séparer récupération des features et calcul de la prédiction ? Séparation des responsabilités : la source des features peut évoluer (BDD, cache, feature store) sans toucher au modèle ; le modèle reste une fonction pure features -> prédiction, testable et réutilisable. « L'application du modèle n'est qu'une étape de la chaîne de prédiction. »

2.9 (Bonus) - Si les features ne peuvent pas être calculées/récupérées ? L'API ne doit pas planter : elle renvoie une erreur explicite. Client/features introuvables -> 404 ; payload invalide -> 422 ; feature store indisponible (panne transitoire) -> 503. Toujours un JSON d'erreur clair.

Étape 4 - Chargement du modèle depuis le Registry

2.10 - Pourquoi un alias plutôt qu'un numéro de version ? L'alias (champion) est stable et mobile : l'API charge toujours models:/electricity-consumption@champion et l'on re-pointe l'alias vers une nouvelle version sans modifier ni redéployer le code. Un numéro de version est figé : chaque changement de modèle imposerait d'éditer la config et de redéployer. L'alias découple « quel modèle est en prod » (décision côté MLflow) du code de service.

2.11 - Avantages du Registry par rapport à un simple fichier modèle ? Versioning centralisé (historique de toutes les versions), alias/tags, lignée vers le run d'entraînement (params/métriques), chargement par URI depuis n'importe où, workflow de promotion, traçabilité/audit, environnement (requirements) attaché. Un simple fichier n'offre rien de tout cela (pas d'historique, pas de métadonnées, distribution manuelle et fragile).

2.12 (Bonus) - Si les requirements du modèle sont incohérents avec l'environnement de l'API ? Risque : incompatibilité de versions (scikit-learn, numpy) -> erreur de désérialisation ou écarts numériques silencieux. Architecture : isoler le modèle dans son propre runtime construit à partir de son requirements.txt (image conteneur dédiée par modèle, ex. mlflow models build-docker / MLflow serving), l'API l'appelant via HTTP ; ou figer l'environnement de l'API depuis les requirements du modèle. On découple l'API des dépendances du modèle.

Étape 5 - Endpoint de prédiction

2.13 - Étapes lorsqu'une requête de prédiction arrive.

  1. Valider le payload (schéma Pydantic). 2. Récupérer les features du client (404 si inconnu).
  2. Assembler le vecteur de features dans l'ordre attendu par le modèle. 4. model.predict.
  3. Formater et renvoyer la réponse JSON (200).

2.14 - Quel format de réponse ? Quel statut HTTP ? JSON : {client_id, prediction_kwh, model_name, model_version}, statut 200 OK.


Partie 3 (Bonus) - Gestion des erreurs

3.1 - L'application doit-elle échouer ou intercepter cette erreur ? Intercepter. Un client_id inconnu est une erreur cliente (mauvaise entrée), pas un bug serveur : le service reste debout et renvoie une réponse d'erreur propre.

3.2 - Quel code HTTP est adapté ? 404 Not Found (la ressource/le client demandé n'existe pas). (422 si le payload lui-même est malformé.) Implémenté via HTTPException(status_code=404, ...).


Partie 4 (Bonus) - Prédictions en batch

4.1 - Un modèle doit-il forcément être exposé par API ? Dans quel cas ? Non. L'API (temps réel) convient aux prédictions à la demande, individuelles, à faible latence (appli interactive). Pour de gros volumes calculés périodiquement (ex. tous les clients chaque nuit), l'inférence batch (job planifié qui écrit les résultats en base) est plus adaptée et moins coûteuse. On expose par API quand on a besoin de prédictions fraîches, unitaires et synchrones.

4.2 - Quelles briques restent identiques entre batch et temps réel ? Le modèle (même artefact du Registry), la logique/définition des features, le préprocessing, le code de prédiction (features -> prédiction). Ce qui diffère : le déclencheur/orchestration (requête HTTP vs job planifié), les E/S (un JSON unitaire vs une table en masse) et le profil latence/débit.

4.3 - Comment récupérer les informations de prédiction ? En masse : lire les features de tous les clients pour la date depuis le feature store / une table (BDD ou parquet), appeler model.predict sur le lot entier (vectorisé), puis écrire les résultats en base/fichier. Ici, POST /predict/batch prend une liste de client_ids et renvoie la liste des prédictions (+ unknown_client_ids pour les clients ignorés).


Reproduire

Sur la VM, depuis /home/user/tp :

set -a; source .env; set +a           # MLflow + creds S3 (Garage)

# Partie 1 : enregistrer + promouvoir
python -m lab.modeling.cli full  --register     # v1 (champion)
python -m lab.modeling.cli mixed --register      # v2 (comparaison)
python -m lab.registry.cli versions              # consulter les versions/alias
python -m lab.registry.cli promote --version 1 --alias champion

# Partie 2-4 : servir l'API
./serve.sh                                        # uvicorn :8000  (Swagger /docs)
curl -s localhost:8000/health
curl -s -X POST localhost:8000/predict -H 'content-type: application/json' -d '{"client_id":"MT_124"}'

Depuis le poste (certificat ENI de confiance) : https://api.192-168-122-143.nip.io/docs.