Module 8 — Service par lots, en ligne et en flux
Le modèle du registre est prêt. Comment le met-on entre les mains des utilisateurs ? La question a trois réponses, et le mauvais choix coûte cher : un service en ligne quand un lot suffisait gaspille des ressources ; un lot quand un service en ligne est nécessaire rend l'application inutilisable.
Les trois modes, en une phrase chacun
- Par lots (
batch) : on prédit en avance sur tous les abonnés chaque nuit, on stocke les résultats dans une table, l'application les lit. Latence utile : celle d'une requête SQL, quelques millisecondes. Fraîcheur : jusqu'à 24 heures selon la fréquence des lots. - En ligne (
online) : l'application interroge une API à chaque besoin, le service calcule la prédiction à la demande. Latence utile : dizaines de millisecondes. Fraîcheur : instantanée. - En flux (
streaming) : un flux d'événements (Kafka, Pulsar) est consommé par un opérateur qui produit un flux de prédictions. Latence utile : millisecondes à quelques secondes. Fraîcheur : celle du flux entrant.
L'arbre de décision qui règle 90 % des cas
Trois questions suffisent :
- Le résultat sert-il un utilisateur qui attend devant l'écran ?
Oui → au moins
online. Non →batchtrès souvent. - La liste des entrées à noter est-elle connue à l'avance ? Oui →
batch. Non →onlineoustreaming. - Y a-t-il un flux d'événements source ? Oui, et il faut agir vite →
streaming. Sinon →online.
Le modèle de résiliation de notre fil rouge nourrit un tableau de bord
consulté chaque matin par les conseillers, sur une liste connue
d'abonnés : c'est un cas typique de batch nocturne. Un modèle de
détection de fraude à la volée sur une transaction bancaire est
online sans discussion. Un modèle qui doit réagir à chaque événement
d'un capteur industriel est streaming.
Le squelette d'un service par lots
# jobs/scorer_les_abonnes.py
import pandas as pd, mlflow.pyfunc, os
from sqlalchemy import create_engine
def main() -> None:
moteur = create_engine(os.environ["BDD_URL"])
modele = mlflow.pyfunc.load_model(os.environ["MODELE_URI"])
abonnes = pd.read_sql(REQUETE_ABONNES_ACTIFS, moteur)
variables = construire_variables(abonnes) # meme code qu'en ligne
proba = modele.predict(variables)
pd.DataFrame({
"id_abonne": abonnes["id_abonne"],
"proba_resiliation": proba,
"version_modele": os.environ["MODELE_VERSION"],
"date_score": pd.Timestamp.utcnow(),
}).to_sql("scores_resiliation", moteur, if_exists="append", index=False)
Ce script est planifié par cron, Airflow ou Argo Workflows, sur une
base horaire, quotidienne ou hebdomadaire. Il tient sur une machine
modeste ; il traite des millions d'abonnés en quelques minutes ; il
n'ouvre aucun port réseau.
Le squelette d'un service en ligne
# src/servir.py
from fastapi import FastAPI
from pydantic import BaseModel
import mlflow.pyfunc, os
class RequetePrediction(BaseModel):
id_abonne: str
variables: dict
app = FastAPI()
app.state.modele = mlflow.pyfunc.load_model(os.environ["MODELE_URI"])
@app.post("/predire")
def predire(req: RequetePrediction) -> dict:
df = construire_variables_depuis_dict(req.variables) # meme code qu'en lot
proba = app.state.modele.predict(df)[0]
return {"id_abonne": req.id_abonne, "proba": float(proba)}
Ce service est déployé derrière un équilibreur de charge, dimensionné à la charge attendue. Il exige une supervision de la latence (module 9), un mécanisme de repli en cas de panne, et une politique de gestion des versions du contrat d'API.
Le fil du service en flux
# consommateurs/scorer_flux.py
from confluent_kafka import Consumer, Producer
import mlflow.pyfunc, json, os
modele = mlflow.pyfunc.load_model(os.environ["MODELE_URI"])
consommateur = Consumer({"bootstrap.servers": "kafka:9092",
"group.id": "scoreur-churn",
"auto.offset.reset": "latest"})
producteur = Producer({"bootstrap.servers": "kafka:9092"})
consommateur.subscribe(["evenements-abonnes"])
while True:
msg = consommateur.poll(1.0)
if not msg or msg.error(): continue
event = json.loads(msg.value())
proba = float(modele.predict(construire_variables(event))[0])
producteur.produce("scores-churn", json.dumps({
"id_abonne": event["id_abonne"], "proba": proba,
}))
Le service en flux se pense en trois quantités : le débit (événements par seconde), la latence de bout en bout (arrivée à sortie) et la garantie (au moins une fois, au plus une fois, exactement une fois). Ces trois quantités sont dictées par le cas d'usage, pas par les moyens techniques.
Le piège majeur : la cohérence des variables
Un modèle a été entraîné avec une variable taux_utilisation_30j
calculée par une agrégation SQL. Le service en ligne reconstruit cette
même variable en Python à la volée. Un jour, l'auteur du SQL a exclu les
week-ends, mais pas l'auteur du Python. Le modèle en ligne reçoit alors
une variable qui n'a plus le même sens que celle sur laquelle il a été
entraîné : les prédictions dérivent silencieusement.
Le cours 33 (feature store) est la solution structurelle : les variables sont calculées une seule fois, stockées, et servies à l'entraînement comme à l'inférence par la même interface. En attendant, trois pratiques limitent le risque :
- Une seule fonction
construire_variables()partagée entre le job par lots, le service en ligne et le consommateur en flux. - Un test de non-régression qui compare, pour un échantillon donné, la variable produite en ligne à la variable produite par l'agrégation.
- La journalisation des variables en entrée du modèle (module 9), pour détecter la dérive au plus tôt.
Face à un doute, commencez par un service par lots. Il coûte 10 fois
moins cher, il n'ouvre aucun port, il est facile à surveiller et à
revalider. On ne passe à l'online que lorsqu'un besoin métier
explicite exige la fraîcheur immédiate, et au streaming que lorsqu'un
flux d'événements est déjà en place et qu'attendre la fin d'un lot est
un délai inacceptable.
En résumé
- Trois modes : lots (fraîcheur
24h, latence quelquesms), en ligne (fraîcheur immédiate, latence dizaines dems), en flux (fraîcheur du flux, latence à la seconde). - Trois questions règlent le choix : y a-t-il un utilisateur qui attend, la liste est-elle connue, existe-t-il un flux source.
- Le code de construction des variables doit être le même entre les trois modes ; sans quoi la prédiction en production ne signifie plus la même chose que la prédiction à l'entraînement.
- Face au doute, commencez par le lot ; il coûte moins cher, il s'observe mieux, il se remplace plus facilement.
Le module 9 traite l'observation du modèle en production : dérive des données, dérive du concept, alertes qui informent sans noyer.