Du notebook à l'artefact : sérialiser et versionner un modèle

Première leçon du cours Déployer un modèle (API, MLOps de base), en accès libre : environ 15 minutes, 3 exercices et 3 questions de quiz. C'est la leçon de base du catalogue ; dans l'espace étudiant, elle est réécrite pour ton profil.

Objectifs

  • Regrouper prétraitement et modèle dans un Pipeline unique
  • Sauvegarder et recharger un modèle avec joblib en connaissant les risques
  • Documenter un artefact : version, features, métriques, empreinte et dépendances

SérialisationArtefact de modèleReproductibilité

Un modèle qui reste dans un notebook n’apporte aucune valeur. Déployer, c’est le rendre utilisable par d’autres systèmes : une application, un tableau de bord, un traitement nocturne. Ce cours suit un fil rouge : un modèle de prédiction de churn (départ client), que nous allons sauvegarder, exposer en API, conteneuriser, tester et surveiller.

Un seul objet : le Pipeline

L’erreur la plus fréquente en production est d’appliquer un prétraitement différent de celui de l’entraînement (une normalisation oubliée, des colonnes dans le mauvais ordre). On l’évite en sauvegardant le prétraitement et le modèle ensemble :

import joblib
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression

modele = make_pipeline(StandardScaler(), LogisticRegression())
modele.fit(X_train, y_train)               # X_train : DataFrame avec colonnes nommées

joblib.dump(modele, "modele.joblib")       # sérialisation
modele_charge = joblib.load("modele.joblib")
assert (modele_charge.predict(X_test) == modele.predict(X_test)).all()

Pour PyTorch, on sauvegarde le state_dict (torch.save(modele.state_dict(), "poids.pt")) avec le code de la classe du modèle. Pour servir un modèle depuis un autre langage ou accélérer l’inférence, le format ONNX (skl2onnx, torch.onnx.export) est une option portable.

Attention à la sécurité

pickle et joblib peuvent exécuter du code arbitraire au chargement. Ne chargez jamais un fichier dont vous ne maîtrisez pas l’origine (téléchargé, reçu par e-mail). Pour partager des modèles scikit-learn, la bibliothèque skops propose un format qui vérifie les types avant chargement ; pour les poids de réseaux, le format safetensors est la norme.

Les versions comptent

Un modèle sérialisé avec scikit-learn 1.9 peut refuser de se charger, ou se comporter différemment, avec une autre version. Figez donc vos dépendances :

# requirements.txt
scikit-learn==1.9.1
pandas==3.1.1
numpy==2.5.4
joblib==1.6.0

Fixez aussi les graines aléatoires (random_state) pour pouvoir ré-entraîner à l’identique.

Documenter l’artefact

Un fichier .joblib seul ne dit rien. Accompagnez-le d’une fiche (au format JSON) :

import hashlib, json, datetime, io
import sklearn
from sklearn.metrics import roc_auc_score

tampon = io.BytesIO()
joblib.dump(modele, tampon)
fiche = {
    "nom": "churn",
    "version": "1.0.0",
    "features": list(X_train.columns),          # ordre attendu en entrée
    "metriques": {"auc_test": round(roc_auc_score(y_test, modele.predict_proba(X_test)[:, 1]), 4)},
    "sha256": hashlib.sha256(tampon.getvalue()).hexdigest(),   # empreinte de l'artefact
    "sklearn": sklearn.__version__,
    "entraine_le": datetime.datetime.now(datetime.timezone.utc).isoformat(),
    "donnees": "clients_2026-09.parquet",
}
with open("modele.json", "w") as f:
    json.dump(fiche, f, indent=2)

L’empreinte SHA-256 permet de vérifier que le fichier déployé est bien celui qui a été validé. La version suit souvent le versionnement sémantique : majeure si le contrat d’entrée change, mineure pour un ré-entraînement compatible, correctif pour une correction mineure.

Des outils comme MLflow automatisent ce suivi (paramètres, métriques, artefacts, registre de modèles) ; nous les verrons en leçon 4.

À retenir

Sauvegardez un Pipeline complet, figez les versions, ne chargez que des fichiers de confiance et documentez chaque artefact avec ses features, ses métriques et son empreinte.

Exercices

Les solutions et les tests restent sur le serveur. Dans l'espace étudiant, ton code est exécuté et corrigé, et le tuteur donne des indices avant la solution.

  1. CodeDifficulté 1Python dans le navigateur2 indices

    Sauvegarder et recharger en mémoire

    Avec le modèle fourni, écrivez serialiser(modele) qui renvoie les octets du modèle sérialisé avec joblib.dump dans un io.BytesIO, et deserialiser(octets) qui reconstruit le modèle avec joblib.load.

    import numpy as np
    import pandas as pd
    from sklearn.pipeline import make_pipeline
    from sklearn.preprocessing import StandardScaler
    from sklearn.linear_model import LogisticRegression
    
    rng = np.random.default_rng(0)
    n = 500
    X = pd.DataFrame({
        "anciennete_mois": rng.integers(1, 120, n),
        "montant_mensuel": rng.uniform(10, 120, n).round(2),
        "nb_reclamations": rng.poisson(1.0, n),
    })
    logit = -1.0 - 0.03 * X["anciennete_mois"] + 0.02 * X["montant_mensuel"] + 0.8 * X["nb_reclamations"]
    y = (rng.random(n) < 1 / (1 + np.exp(-logit))).astype(int)
    modele = make_pipeline(StandardScaler(), LogisticRegression()).fit(X, y)
    FEATURES = list(X.columns)
    import io
    import joblib
    
    def serialiser(modele):
        # TODO
        pass
    
    def deserialiser(octets):
        # TODO
        pass
  2. CodeDifficulté 2Python dans le navigateur3 indices

    Fiche modèle

    Écrivez fiche_modele(modele, X_test, y_test, version) qui renvoie un dictionnaire sérialisable en JSON contenant : version, features (liste des colonnes de X_test, dans l’ordre), accuracy (float arrondi à 4 décimales), sha256 (empreinte hexadécimale des octets joblib du modèle), sklearn (version de scikit-learn) et cree_le (date ISO 8601 en UTC).

    import numpy as np
    import pandas as pd
    from sklearn.pipeline import make_pipeline
    from sklearn.preprocessing import StandardScaler
    from sklearn.linear_model import LogisticRegression
    
    rng = np.random.default_rng(0)
    n = 500
    X = pd.DataFrame({
        "anciennete_mois": rng.integers(1, 120, n),
        "montant_mensuel": rng.uniform(10, 120, n).round(2),
        "nb_reclamations": rng.poisson(1.0, n),
    })
    logit = -1.0 - 0.03 * X["anciennete_mois"] + 0.02 * X["montant_mensuel"] + 0.8 * X["nb_reclamations"]
    y = (rng.random(n) < 1 / (1 + np.exp(-logit))).astype(int)
    modele = make_pipeline(StandardScaler(), LogisticRegression()).fit(X, y)
    FEATURES = list(X.columns)
    import io, json, hashlib, datetime
    import joblib
    import sklearn
    from sklearn.metrics import accuracy_score
    
    def fiche_modele(modele, X_test, y_test, version):
        # TODO
        pass
  3. QCMDifficulté 11 indice

    Charger un modèle reçu

    Un partenaire vous envoie modele.pkl par e-mail. Que faites-vous ?

    • Je le charge avec pickle.load pour tester
    • Je ne le charge pas tel quel : le chargement d'un pickle peut exécuter du code ; je demande un format sûr ou le code d'entraînement
    • Je le renomme en .joblib
    • Je l'ouvre dans un éditeur de texte puis je le charge

Quiz de fin de leçon

  1. Pourquoi sauvegarder un Pipeline plutôt que le seul classifieur ?

    • Le fichier est plus petit
    • Pour que le prétraitement appliqué en production soit exactement celui de l'entraînement
    • Parce que joblib ne sait pas sauvegarder un classifieur seul
    • Pour accélérer l'entraînement
  2. À quoi sert l’empreinte SHA-256 d’un artefact ?

    • À chiffrer le modèle
    • À vérifier que le fichier déployé est identique à celui qui a été validé
    • À compresser le fichier
    • À mesurer la performance
  3. Le modèle a été entraîné avec scikit-learn 1.9.1. En production, quelle version installer ?

    • La dernière disponible
    • La même version exacte, figée dans requirements.txt
    • N'importe quelle version 1.x
    • Aucune, joblib suffit

La suite du cours

  1. Servir un modèle avec une API FastAPI20 min
  2. Conteneuriser le service avec Docker18 min
  3. Tests, intégration continue et registre de modèles20 min
  4. Surveiller un modèle en production : logs, dérive et ré-entraînement20 min

Déployer un modèle (API, MLOps de base) · Avancé · 4 h 03