Aller au contenu principal

Serialisation et versionnement de modeles

Theorie 45 min Module 2

Introduction

L'entrainement d'un modele peut prendre des minutes, des heures ou meme des jours. Une fois que vous avez un modele performant, vous devez le sauvegarder pour pouvoir le reutiliser plus tard — pour la prediction, le deploiement ou le partage avec les collegues. Ce processus s'appelle la serialisation.

Analogie du monde reel

La serialisation est comme mettre un plat cuisine au congelateur. Vous avez passe des heures a preparer un ragout complexe (entrainer le modele). Au lieu de recommencer a chaque fois, vous le congelez (serialisez) et le rechauffez (deserialisez) quand vous en avez besoin. Le plat conserve toutes ses saveurs sans avoir a cuisiner de nouveau.


1. Pourquoi serialiser les modeles ?

RaisonDetail
DeploiementUn modele en memoire Python ne peut pas servir une API. Il doit etre sauvegarde sur disque.
ReproductibilitePouvoir recreer exactement les memes predictions 6 mois plus tard.
CollaborationPartager un modele avec un collegue sans lui demander de le reentrainer.
VersionnementConserver plusieurs versions et pouvoir revenir en arriere.
EfficaciteEviter de reentrainer un modele couteux a chaque redemarrage du serveur.

2. Pickle — Le standard Python

pickle est le module de serialisation natif de Python. Il convertit un objet Python en une sequence d'octets et vice versa.

Sauvegarder un modele avec Pickle

import pickle
from sklearn.ensemble import RandomForestClassifier

# Train model
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)

# Serialize (save)
with open('model_rf.pkl', 'wb') as f:
pickle.dump(model, f)

print("Model saved successfully!")

Charger un modele avec Pickle

# Deserialize (load)
with open('model_rf.pkl', 'rb') as f:
loaded_model = pickle.load(f)

# Verify it works
predictions = loaded_model.predict(X_test)
print(f"Loaded model accuracy: {loaded_model.score(X_test, y_test):.4f}")

Sauvegarder un pipeline complet

Pipeline, pas seulement le modele !

En production, vous devez sauvegarder le pipeline complet (pretraitement + modele), pas seulement le modele. Sinon, vous devrez reproduire manuellement les etapes de pretraitement, ce qui est source d'erreurs.

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

pipeline = Pipeline([
('scaler', StandardScaler()),
('clf', RandomForestClassifier(n_estimators=100, random_state=42))
])
pipeline.fit(X_train, y_train)

# Save the ENTIRE pipeline
with open('pipeline_rf.pkl', 'wb') as f:
pickle.dump(pipeline, f)

# Load and predict — no manual preprocessing needed!
with open('pipeline_rf.pkl', 'rb') as f:
loaded_pipeline = pickle.load(f)

predictions = loaded_pipeline.predict(X_test) # scaler + model applied

Risques de securite de Pickle

Alerte de securite critique

Ne chargez JAMAIS un fichier pickle provenant d'une source non fiable. pickle.load() peut executer du code arbitraire. Un fichier pickle malveillant peut supprimer des fichiers, installer des logiciels malveillants ou voler des donnees.

# ⚠️ EXAMPLE OF MALICIOUS PICKLE — DO NOT ACTUALLY USE
import pickle
import os

class MaliciousModel:
def __reduce__(self):
return (os.system, ('rm -rf /',)) # Deletes everything!

# If someone gives you this pickle file and you load it...
# pickle.load(malicious_file) → YOUR SYSTEM IS COMPROMISED

Regles de securite :

  1. Ne jamais charger un pickle d'une source inconnue
  2. Valider l'integrite du fichier (checksum SHA256)
  3. Utiliser des environnements isoles (conteneurs Docker)
  4. Considerer des alternatives plus sures (ONNX, joblib avec precaution)

3. Joblib — Optimise pour les donnees scientifiques

joblib est une alternative a pickle specifiquement optimisee pour les objets contenant de grands tableaux NumPy (ce qui est le cas pour la plupart des modeles ML).

Avantages de Joblib par rapport a Pickle

FonctionnalitePickleJoblib
Vitesse (grands tableaux)Standard⚡ 2-10x plus rapide
Taille de fichierStandard📦 Compression integree
Grands objets NumPyPerformance moyenneOptimise
Securite⚠️ Risque⚠️ Similaire a pickle
CompatibiliteTout objet PythonTout objet Python

Utiliser Joblib

import joblib

# Save model (no compression)
joblib.dump(model, 'model_rf.joblib')

# Save with compression (smaller file, slightly slower)
joblib.dump(model, 'model_rf_compressed.joblib', compress=3)

# Load model
loaded_model = joblib.load('model_rf.joblib')

# Verify
print(f"Loaded model accuracy: {loaded_model.score(X_test, y_test):.4f}")

Comparaison des tailles de fichiers

import os
import pickle
import joblib

# Save with different methods
with open('model_pickle.pkl', 'wb') as f:
pickle.dump(model, f)

joblib.dump(model, 'model_joblib.joblib')
joblib.dump(model, 'model_joblib_c3.joblib', compress=3)
joblib.dump(model, 'model_joblib_c9.joblib', compress=9)

# Compare sizes
files = ['model_pickle.pkl', 'model_joblib.joblib',
'model_joblib_c3.joblib', 'model_joblib_c9.joblib']

for f in files:
size_kb = os.path.getsize(f) / 1024
print(f"{f:30s}{size_kb:8.1f} KB")
Quand utiliser Joblib ?

Preferez joblib pour les modeles scikit-learn en general. L'avantage est particulierement significatif pour les modeles avec de grands tableaux internes (Random Forest avec beaucoup d'arbres, grandes matrices de poids, etc.).


4. ONNX — Interoperabilite multi-plateforme

ONNX (Open Neural Network Exchange) est un format ouvert concu pour la portabilite des modeles entre les frameworks et les langages de programmation.

Convertir un modele scikit-learn en ONNX

# pip install skl2onnx onnxruntime

from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType

# Define input shape
initial_type = [('float_input', FloatTensorType([None, X_train.shape[1]]))]

# Convert model to ONNX
onnx_model = convert_sklearn(model, initial_types=initial_type)

# Save ONNX model
with open('model_rf.onnx', 'wb') as f:
f.write(onnx_model.SerializeToString())

print("ONNX model saved successfully!")

Utiliser un modele ONNX pour l'inference

import onnxruntime as ort
import numpy as np

# Load ONNX model
session = ort.InferenceSession('model_rf.onnx')

# Get input name
input_name = session.get_inputs()[0].name

# Run inference
X_test_float = X_test.astype(np.float32)
onnx_predictions = session.run(None, {input_name: X_test_float})

predicted_labels = onnx_predictions[0]
print(f"ONNX predictions (first 5): {predicted_labels[:5]}")

Avantages et limites de ONNX

AvantageDetail
PortabiliteMeme modele en Python, C++, JavaScript, mobile
PerformanceONNX Runtime est souvent plus rapide que scikit-learn natif
SecuritePas d'execution de code arbitraire (contrairement a pickle)
StandardisationFormat ouvert supporte par Microsoft, Meta, AWS
LimiteDetail
ConversionTous les modeles/operations ne sont pas supportes
ComplexiteConfiguration plus complexe que pickle/joblib
DebogagePlus difficile d'inspecter le modele
PipelineLes pipelines complexes peuvent ne pas se convertir facilement

5. Tableau comparatif — Formats de serialisation

CriterePickleJoblibONNX
Facilite d'utilisation⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Performance (grands modeles)⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Securite⭐⭐⭐⭐⭐
Portabilite⭐⭐ (Python uniquement)⭐⭐ (Python uniquement)⭐⭐⭐⭐⭐
Compression❌ Manuelle✅ IntegreeN/A
Support des pipelines✅ Complet✅ Complet⚠️ Partiel
Inference rapideStandardStandard⚡ Optimisee
EcosystemeStandard Pythonscikit-learnMulti-framework
Cas d'usagePrototypage rapideModeles sklearn en productionDeploiement multi-plateforme
Recommandation
  • Developpement / Prototypagejoblib (simple et efficace)
  • Production Pythonjoblib avec compression
  • Production multi-langageONNX
  • A eviter en productionpickle brut (preferez joblib)

6. Strategies de versionnement de modeles

Pourquoi versionner les modeles ?

Voir l'historique des versions de modeles
SituationSans versionnementAvec versionnement
Le nouveau modele est pire que l'ancien😱 Panique, impossible de revenir😌 Retour arriere en 1 minute
Audit reglementaire❌ Impossible de prouver quel modele etait actif✅ Historique complet
Test A/B❌ Un seul modele possible✅ Comparer v1 vs v2 en production
Bug en production😰 Quel modele cause le bug ?🔍 Trace de version exacte

Convention de nommage

models/
├── iris_classifier_v1.0.0_2025-01-15.joblib
├── iris_classifier_v1.1.0_2025-02-01.joblib
├── iris_classifier_v2.0.0_2025-03-10.joblib
├── metadata/
│ ├── iris_classifier_v1.0.0_metadata.json
│ ├── iris_classifier_v1.1.0_metadata.json
│ └── iris_classifier_v2.0.0_metadata.json

Sauvegarder des modeles avec des metadonnees

import json
import joblib
from datetime import datetime
from sklearn.metrics import accuracy_score, f1_score

def save_model_with_metadata(model, X_test, y_test, version, model_name):
"""Save a model alongside its metadata for tracking."""
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
filename = f"{model_name}_v{version}_{timestamp}.joblib"

# Save model
joblib.dump(model, filename)

# Generate and save metadata
y_pred = model.predict(X_test)
metadata = {
"model_name": model_name,
"version": version,
"timestamp": timestamp,
"filename": filename,
"metrics": {
"accuracy": round(accuracy_score(y_test, y_pred), 4),
"f1_score": round(f1_score(y_test, y_pred, average='weighted'), 4),
},
"hyperparameters": model.get_params() if hasattr(model, 'get_params') else {},
"training_samples": len(X_test),
"python_version": "3.10",
"sklearn_version": "1.3.0",
}

metadata_file = f"{model_name}_v{version}_metadata.json"
with open(metadata_file, 'w') as f:
json.dump(metadata, f, indent=2, default=str)

print(f"✅ Model saved: {filename}")
print(f"📋 Metadata saved: {metadata_file}")
return filename, metadata

# Usage
save_model_with_metadata(
model=best_model,
X_test=X_test, y_test=y_test,
version="1.0.0",
model_name="iris_classifier"
)

7. MLflow — Registre et suivi de modeles

MLflow est une plateforme open source pour gerer le cycle de vie du ML : suivi des experiences, versionnement des modeles et deploiement.

Suivi basique avec MLflow

# pip install mlflow
import mlflow
import mlflow.sklearn
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, f1_score

mlflow.set_experiment("iris-classification")

with mlflow.start_run(run_name="random_forest_v1"):
# Log hyperparameters
mlflow.log_param("n_estimators", 100)
mlflow.log_param("max_depth", 5)
mlflow.log_param("random_state", 42)

# Train model
model = RandomForestClassifier(n_estimators=100, max_depth=5, random_state=42)
model.fit(X_train, y_train)

# Evaluate
y_pred = model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
f1 = f1_score(y_test, y_pred, average='weighted')

# Log metrics
mlflow.log_metric("accuracy", accuracy)
mlflow.log_metric("f1_score", f1)

# Log model
mlflow.sklearn.log_model(model, "model")

print(f"Run ID: {mlflow.active_run().info.run_id}")
print(f"Accuracy: {accuracy:.4f}, F1: {f1:.4f}")

Charger un modele depuis MLflow

# Load by run ID
run_id = "abc123def456"
model_uri = f"runs:/{run_id}/model"
loaded_model = mlflow.sklearn.load_model(model_uri)

# Load from model registry (production stage)
model_uri = "models:/iris-classifier/Production"
production_model = mlflow.sklearn.load_model(model_uri)

predictions = production_model.predict(X_new)

Cycle de vie des modeles MLflow

EtapeDescriptionQui y accede
NoneModele enregistre mais pas encore evalueDeveloppeur
StagingEn cours de validation, tests d'integrationEquipe QA
ProductionSert des predictions en temps reelAPI / Utilisateurs
ArchiveRetire, conserve pour audit et historiqueArchive

8. Sauvegarder des pipelines complets

Point critique

La plus grande source de bugs en production ML provient de l'incoherence entre le pretraitement d'entrainement et le pretraitement de production. Sauvegarder le pipeline complet elimine ce risque.

from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.ensemble import RandomForestClassifier
import joblib

# Define preprocessing for different column types
numeric_features = ['age', 'income', 'credit_score']
categorical_features = ['gender', 'education', 'employment']

preprocessor = ColumnTransformer(
transformers=[
('num', StandardScaler(), numeric_features),
('cat', OneHotEncoder(handle_unknown='ignore'), categorical_features)
]
)

# Build complete pipeline
full_pipeline = Pipeline([
('preprocessor', preprocessor),
('classifier', RandomForestClassifier(n_estimators=100, random_state=42))
])

# Train on raw data
full_pipeline.fit(X_train_raw, y_train)

# Save EVERYTHING in one file
joblib.dump(full_pipeline, 'full_pipeline_v1.0.0.joblib')

# In production: load and predict on raw input
pipeline = joblib.load('full_pipeline_v1.0.0.joblib')
predictions = pipeline.predict(new_raw_data) # preprocessing handled automatically

9. Considerations sur la taille des fichiers

ModeleTaille typiqueFormat recommande
Regression Logistique1-10 Kopickle / joblib
SVM (petit jeu de donnees)10-100 Kojoblib
Random Forest (100 arbres)1-50 Mojoblib compress=3
Random Forest (1000 arbres)50-500 Mojoblib compress=9
Deep Learning (ResNet50)100-300 MoONNX
LLM (type GPT)1-100 GoFormats specialises
import os
import joblib

# Check file size before deployment
model_file = 'full_pipeline_v1.0.0.joblib'
size_mb = os.path.getsize(model_file) / (1024 * 1024)

print(f"Model file size: {size_mb:.1f} MB")

if size_mb > 100:
print("⚠️ Model is large. Consider:")
print(" - Reducing n_estimators")
print(" - Using compression: joblib.dump(model, file, compress=9)")
print(" - Converting to ONNX for optimized runtime")
elif size_mb > 500:
print("🚨 Model too large for most API deployments")
print(" - Consider model distillation or pruning")

Resume

🔑 Points cles a retenir
  1. Toujours serialiser le pipeline complet (pretraitement + modele), pas seulement le modele.
  2. Pickle : Simple mais dangereux. Ne jamais charger d'une source non fiable.
  3. Joblib : Prefere pour scikit-learn. Compression integree, optimise pour les grands tableaux.
  4. ONNX : Pour le deploiement multi-plateforme et l'inference rapide. Plus securise.
  5. Versionnez vos modeles avec des conventions de nommage et des metadonnees (metriques, hyperparametres, date).
  6. MLflow : L'outil de reference pour le suivi d'experiences et le registre de modeles.
  7. Taille de fichier : Verifiez avant le deploiement. Compressez si necessaire.
  8. Metadonnees : Sauvegardez toujours les versions des dependances (Python, sklearn) avec le modele.

Lectures complementaires

RessourceLien
Documentation Python pickledocs.python.org/3/library/pickle
Documentation Joblibjoblib.readthedocs.io
ONNX Runtimeonnxruntime.ai
Documentation skl2onnxonnx.ai/sklearn-onnx
Documentation MLflowmlflow.org/docs/latest