Pourquoi vos benchmarks bio-IA paraissent robustes mais échouent au premier stress test MLOps
MLOps, Benchmark, Bioinformatics, Machine Learning, Reproductibilité, Fiabilité, GridSearchCV, Production
Problème :
BioResistanceAI implémente un benchmark (multi-modèles, grid search, cross-validation, parallélisation par antibiotique), mais plusieurs choix d’implémentation créent une dette MLOps discrète : checkpoint JSON partagé entre workers, espace de recherche très coûteux, et couplage fragile entre groupes de features et module d’explicabilité.
Approche :
Le dépôt fournit une base solide pour industrialiser un benchmark fiable : orchestration claire, configuration YAML, scoring homogène et pipeline de contributions par groupes. L’enjeu est de transformer cette base en protocole production-ready en explicitant les contrats techniques (concurrence, reproductibilité, gouvernance des features, budget compute).
1 Introduction
Le projet se situe au croisement de la bio-informatique et du machine learning appliqué : il entraîne plusieurs familles de modèles (sklearn, xgboost, lightgbm, skorch) pour prédire des résistances aux antibiotiques, avec une évaluation systématique pilotée par configuration.
Le point critique en production n’est pas seulement la performance brute. Un benchmark fiable doit aussi garantir :
- la cohérence des résultats malgré le parallélisme ;
- la maîtrise du coût quand les grilles explosent ;
- la stabilité sémantique entre préparation des features et explicabilité.
Sans ces garanties, un score élevé devient un signal faible pour décider quoi déployer.
2 Étapes techniques / Pipeline
Le coeur du pipeline est dans pipelines/model_data.py : chaque antibiotique est traité en parallèle.
# pipelines/model_data.py (simplifié)
with tqdm_joblib(tqdm(desc="Antibiotiques", total=len(abx_list))) as progress_bar:
results = joblib.Parallel(n_jobs=5)(
joblib.delayed(run_for_abx)(
abx, X, y, feature_types, model_config, scaler_str,
test_size, random_state, cv, scoring_func, n_jobs
)
for abx in abx_list
)Ce pattern est efficace pour la vitesse, mais il fait apparaître une tension classique MLOps : plusieurs workers manipulent ensuite le même checkpoint partiel.
# pipelines/model_data.py (simplifié)
partial_result_path = Path("results/partial_results.json")
if partial_result_path.exists():
with open(partial_result_path, "r") as f:
partial_results = json.load(f)
...
with open(result_path, "w") as f:
json.dump(current_results, f, indent=2)Le fichier JSON est relu puis réécrit sans verrou explicite. En environnement concurrent, cela ouvre la porte aux écrasements silencieux ou aux états intermédiaires incohérents.
L’entraînement repose sur GridSearchCV centralisé dans src/models/train_model.py :
# src/models/train_model.py (simplifié)
clf = GridSearchCV(
estimator, params,
scoring=scorer,
cv=cv,
n_jobs=n_jobs,
error_score="raise",
)
clf.fit(X_train, y_train)Avec cv: 5 (config/train_config.yaml) et des grilles riches (config/models.yaml), le coût combinatoire peut rapidement devenir dominant. Exemple : SVC combine kernel, C, gamma, class_weight; les modèles boosting ajoutent encore des dimensions d’exploration.
Enfin, l’explicabilité par permutation est structurée par groupes de features :
# src/features/feature_groups.py
def get_feature_groups(X):
return {
"gpa": [c for c in X.columns if c.startswith("gpa_")],
"snps": [c for c in X.columns if c.startswith("snp_")],
"genexp": [c for c in X.columns if c.startswith("genexp_")]
}Mais dans src/models/contributions.py, la clé attendue est genexp_ :
# src/models/contributions.py
genexp_cols = feature_types.get("genexp_", [])Le pipeline reste exécutable, mais la gouvernance des noms devient incohérente entre modules, ce qui peut fausser l’interprétation des contributions de groupe.
3 Stratégie d’adoption
Pour répliquer ce pattern dans une équipe MLOps sans hériter de ses fragilités, l’ordre le plus réaliste est :
- Fiabiliser la persistance concurrente : remplacer
partial_results.jsonpartagé par des fichiers atomiques par antibiotique ou un stockage transactionnel. - Encadrer le budget benchmark : fixer un plafond de combinaisons et un budget temps/compute par run, puis réduire la grille en itérations successives.
- Unifier la gouvernance des features : définir un contrat unique de nommage et valider ce contrat en tests d’intégration.
- Durcir la reproductibilité : propager une stratégie de seed cohérente pour tous les frameworks (sklearn, torch/skorch, GPU-enabled boosters).
- Séparer benchmark et déploiement : conserver ce pipeline comme banc d’essai, mais isoler les garde-fous de production (versionning, monitoring, rollback) dans une couche dédiée.
Frictions probables :
- historique de code orienté exploration rapide plutôt qu’industrialisation ;
- arbitrage difficile entre profondeur de recherche et coût GPU ;
- dépendances hétérogènes entre librairies qui ne partagent pas le même niveau de déterminisme.
Réduction concrète :
- tests de non-régression sur schéma de features ;
- tests de robustesse en exécution parallèle ;
- journalisation systématique des paramètres et artefacts par antibiotique et par modèle.
4 Conclusion
BioResistanceAI montre un cas très réaliste de benchmark “performant mais fragile” : l’architecture permet de comparer beaucoup de modèles vite, mais la fiabilité MLOps dépend de détails d’implémentation souvent sous-estimés.
Les gains sont réels (comparaison large, extensibilité, support multi-framework), mais les trade-offs le sont aussi : coût compute élevé, concurrence d’écriture, ambiguïtés d’explicabilité et reproductibilité incomplète.
La leçon pratique : un benchmark ne devient utile en production que lorsqu’il est traité comme un produit d’ingénierie, pas comme un simple script de scoring.
5 Lien vers le projet
La présentation complète du projet est disponible ici : BioResistanceAI.
Le code source est disponible sur GitHub : https://github.com/NCSdecoopman/BioResistanceAI.
6 Références code
pipelines\main_pipeline.py: orchestre l’exécution des étapes de préparation puis benchmark.pipelines\model_data.py: contient la logique centrale d’entraînement/évaluation, le parallélisme et la sauvegarde partielle.src\models\train_model.py: implémente la recherche d’hyperparamètres viaGridSearchCV.config\models.yaml: définit l’espace de recherche des modèles et options CPU/GPU.config\train_config.yaml: fixe les paramètres globaux d’évaluation (split, CV, métrique, parallélisme).src\features\feature_groups.py: crée les groupes de features utilisés pour l’explicabilité.src\models\contributions.py: calcule les contributions par permutation de groupes et met en évidence les dépendances de nommage.src\data\split_scale.py: gère le split train/test et la stratégie de scaling.