TP3 : synthese (reponses aux questions) + README

This commit is contained in:
Johan LEROY
2026-07-22 11:22:50 +02:00
parent b0bd6cdb07
commit e063effc5d
2 changed files with 227 additions and 0 deletions

View File

@@ -56,3 +56,49 @@ Resultats et comparaisons : https://mlflow.192-168-122-143.nip.io (experience `t
## Livrable
Synthese des resultats et reponses aux questions : `SYNTHESE.md`.
---
# TP03 - Exposer un modele via une API REST (Model Registry + FastAPI)
Prolonge le TP02 : on enregistre le meilleur modele dans le **MLflow Model Registry**, on le
**promeut via un alias**, puis on l'expose par une **API REST FastAPI**.
## Prerequis (en plus du TP02)
`.env` complete avec les creds S3 Garage (voir `.env.example`) : `AWS_ACCESS_KEY_ID`,
`AWS_SECRET_ACCESS_KEY`, `MLFLOW_S3_ENDPOINT_URL`. Necessaires pour `log_model` (upload de
l'artefact) et pour le chargement du modele par l'API (download depuis `s3://mlflow-artifacts`).
## Enregistrer et promouvoir (Partie 1)
```bash
set -a; source .env; set +a
py -m lab.modeling.cli full --register # v1 -> Registry (champion vise)
py -m lab.modeling.cli mixed --register # v2 -> Registry (comparaison)
py -m lab.registry.cli versions # lister versions + alias
py -m lab.registry.cli promote --version 1 --alias champion
```
`log_model` est appele a **chaque** run (artefact sauvegarde) ; `--register` empile en plus une
version dans le Registry sous le nom `electricity-consumption`.
## Servir l'API (Parties 2 a 4)
```bash
./serve.sh # uvicorn 0.0.0.0:8000, charge models:/...@champion
curl -s localhost:8000/health # {"status":"ok"}
curl -s -X POST localhost:8000/predict \
-H 'content-type: application/json' -d '{"client_id":"MT_124"}'
curl -s -X POST localhost:8000/predict/batch \
-H 'content-type: application/json' -d '{"client_ids":["MT_124","MT_158"]}'
```
- Endpoints : `GET /health`, `POST /predict`, `POST /predict/batch`, Swagger `GET /docs`.
- Feature store **simule** par un dictionnaire (`lab/serving/features.py`) ; client inconnu -> **404**.
- Depuis le poste (cert ENI de confiance) : **https://api.192-168-122-143.nip.io/docs**
(reverse-proxy Caddy vers uvicorn). Unite systemd transitoire : `sudo systemctl status tp03-api`.
## Livrable TP03
Reponses aux questions et recap : `SYNTHESE_TP03.md`.

181
SYNTHESE_TP03.md Normal file
View File

@@ -0,0 +1,181 @@
# 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).
3. Assembler le vecteur de features **dans l'ordre attendu** par le modèle. 4. `model.predict`.
5. 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` :
```bash
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**.