Pourquoi vos benchmarks MLOps paraissent solides mais cassent dès qu’on change de dataset
MLOps, Benchmark, Reproductibilité, Data Engineering, Évaluation, Scikit-learn, Production
Problème :
Dans MissingDataLab, le benchmark est techniquement riche (répétitions, grilles d’hyperparamètres, parallélisation, métriques), mais certaines hypothèses restent implicites : règles métier codées dans le moteur, protocole d’évaluation non standard et espace expérimental coûteux. Résultat : des scores qui semblent stables localement, mais qui peuvent perdre en portabilité et en lisibilité opérationnelle quand on change de contexte.
Approche :
Le repo fournit un excellent cas d’école MLOps : config centralisée (config.yaml), orchestration systématique (main.py), calibration numérique contrôlée (minimize + root_scalar), puis benchmark multi-modèles (ParameterGrid, KFold, métriques de score/temps/mémoire). L’article montre comment transformer ce design en benchmark production-ready : contrat explicite, traçabilité des hypothèses, contrôle de fuite d’évaluation et optimisation coût/performance.
1 Introduction
La stack est un pipeline data Python orienté expérimentation offline : NumPy/SciPy/scikit-learn pour la simulation et l’imputation, YAML pour la configuration d’expérience, puis agrégation des métriques (score, temps, mémoire, variance).
Le problème est critique en production pour une raison simple : un benchmark n’est pas qu’un score, c’est un système d’évaluation. Si les hypothèses de génération, de validation et de mesure ne sont pas explicites, on industrialise de faux signaux : mauvais choix de modèle, coûts infra inutilement élevés, et dette méthodologique.
2 Étapes techniques / Pipeline
Le point d’entrée main.py orchestre les répétitions, proportions, mécanismes et méthodes d’imputation. La configuration combinatoire est explicite dans config/config.yaml : 7 proportions, 3 mécanismes, 100 répétitions d’amputation, 10 répétitions d’imputation.
# main.py (simplifié)
for i in range(evaluation_amputation):
for proportion in proportions:
for item in col:
for mechanism in item["mechanisms"]:
data_ampute_np = generate_ampute_np(...)
results_list = Parallel(n_jobs=-1)(
delayed(generate_impute_np)(...)
for _ in range(evaluation_imputation)
)Ce bloc montre une décision de scalabilité explicite : paralléliser agressivement les imputations (n_jobs=-1) pour absorber un espace d’expériences large, au prix d’un coût CPU/RAM potentiellement élevé. En MLOps, c’est le premier levier à gouverner, car la facture benchmark peut croître plus vite que la qualité des résultats.
Ensuite, la génération des masques repose sur sigmoid_amputation() dans src/ampute/ampute_utils.py. On y trouve la double logique centrale : calibration numérique + pattern métier.
# src/ampute/ampute_utils.py (simplifié)
optimized_coeffs = generate_coefficients(data, proportion)
if activate_pattern:
patterns = {
("Age_Group", "Smoking_Prevalence"): (-0.5, 0.07),
("Access_to_Counseling", "Smoking_Prevalence"): (0.6, 0.07)
}
# surcharge des premiers coefficients selon les noms de colonnes
optimized_coeffs[:2] = set_coefficients(...)
intercepts = fit_intercepts(data, optimized_coeffs, proportion)
prob_missing = sigmoid((data @ optimized_coeffs).reshape(-1, 1) + intercepts)Le point fort est la calibration explicite. generate_coefficients() (dans src/ampute/math_utils.py) utilise minimize(..., method="trust-constr") avec contraintes sur les coefficients, puis fit_intercepts() résout la racine pour coller à la proportion cible.
En pratique MLOps, cette étape constitue un composant “qualité de benchmark” : elle garantit que la difficulté injectée dans l’expérience est maîtrisée et comparable entre runs.
# src/ampute/math_utils.py (simplifié)
result = minimize(
objective,
initial_coeffs,
args=(X, target_proportion),
constraints=constraints,
method="trust-constr"
)
result = root_scalar(f, method="brentq", bracket=[-50, 50], xtol=1e-8)Techniquement, c’est une bonne pratique de simulation : séparer la structure des effets (coeffs) et le niveau global (intercepts).
Mais le compromis est net : la surcharge par noms de colonnes injecte un savoir métier local directement dans le moteur, ce qui réduit la portabilité du benchmark.
Enfin, le benchmark d’imputation avancée (src/impute/advance_impute.py) applique ParameterGrid + KFold, mais avec un choix d’implémentation qui doit être interprété correctement :
# src/impute/advance_impute.py (simplifié)
for train_idx, test_idx in kf.split(data_ampute):
imputer = imputers[method](column, type, **params)
data_imputed = imputer.fit_transform(data_ampute.copy())
idx_NA_test = [c for c in idx_NA if c in test_idx]
score = score_imputed(data_original, data_imputed, column, idx_NA_test, type)L’imputeur est entraîné sur toute la matrice amputée à chaque pli, puis le score est calculé sur les indices du pli test. Ce design simplifie l’implémentation et stabilise parfois les métriques, mais il ne correspond pas à une séparation stricte train/test au sens classique.
Pour une équipe MLOps, c’est un choix à expliciter dans la documentation benchmark, sinon les comparaisons inter-projets deviennent trompeuses.
3 Stratégie d’adoption
Pour reproduire ce pattern dans une équipe MLOps mature, l’ordre réaliste est le suivant :
- Contractualiser les hypothèses : extraire les patterns de colonnes et règles de simulation dans une config versionnée, plutôt qu’en dur dans le code.
- Séparer moteur et politique : conserver
generate_coefficients()+fit_intercepts()comme noyau de calibration, puis isoler les règles métier dans une couche dédiée. - Normaliser le protocole d’évaluation : documenter explicitement si le benchmark est de type fit global ou séparation stricte, pour éviter les comparaisons ambiguës.
- Maîtriser le coût calcul : réduire l’espace
ParameterGrid, fixer les seeds de bout en bout, et suivre le ratio gain-métrique / coût-infra.
Frictions probables :
- Dette existante : code historique couplé à un dataset spécifique.
- Organisation : exigences différentes entre data science, ML platform et engineering.
- Tooling : peu de garde-fous automatiques (tests de calibration, tests de protocole, budget compute).
Réduction concrète des frictions :
- ajouter des tests unitaires qui vérifient les propriétés de calibration à tolérance fixée ;
- ajouter des tests d’intégration qui échouent si des hypothèses de schéma ne sont pas satisfaites ;
- tracer systématiquement la configuration complète (proportions, mécanismes, hyperparamètres, seed) dans les artefacts ;
- définir un budget de benchmark (CPU, RAM, temps) comme critère de validation MLOps.
4 Conclusion
Le gain principal du design de MissingDataLab est sa capacité à produire des expériences comparables : calibration fine, pipeline modulaire, comparaison multi-méthodes, agrégation de métriques utile à la décision.
Les trade-offs sont toutefois structurants : plus de complexité, onboarding plus difficile, et risque d’interprétation erronée si les hypothèses implicites (schéma de données, protocole de CV, coût compute) ne sont pas rendues explicites.
En production, la bonne pratique n’est pas seulement “obtenir un meilleur score”, mais “rendre le benchmark auditable, testable, portable et économiquement soutenable”.
5 Références code
Le dépôt complet est sur GitHub : https://github.com/NCSdecoopman/MissingDataLab. Une présentation plus large du projet est disponible dans la section projets.
main.py: orchestre les boucles d’expérimentation et la parallélisation des répétitions d’imputation.config\config.yaml: définit l’espace expérimental réel (proportions, mécanismes, répétitions, méthodes).src\ampute\generate.py: route les mécanismes MCAR/MAR/MNAR et applique le masque aux données.src\ampute\ampute_utils.py: implémente la génération sigmoïde et les patterns métier basés sur les noms de colonnes.src\ampute\math_utils.py: implémente l’optimisation des coefficients et l’ajustement des intercepts à proportion cible.src\impute\advance_impute.py: réalise la recherche d’hyperparamètres, la validation croisée et le scoring des imputations avancées.src\utils\metrics_utils.py: calcule les métriques utilisées pour comparer les imputeurs (MSE/accuracy).