À la fin de ce laboratoire, vous saurez : 1. Identifier les blocs de code dans un pipeline ML 2. Réaliser des études d’ablation pour prioriser les améliorations 3. Raffiner de manière ciblée les composants critiques 4. Optimiser l’effort d’amélioration d’un modèle
Prérequis
Lab 13 (Web Search SOTA) complété
Connaissance des pipelines ML
Configuration multi-provider active
Durée estimée : 40-50 minutes
1. Configuration
Pourquoi ce lab s’appuie sur un provider LLM abstrait (LiteLLM/OpenRouter) plutôt qu’un API figée : le pipeline d’ablation interroge un LLM à chaque étape (identification des blocs, estimation d’importance, raffinement). Coder un appel openai.ChatCompletion en dur verrouillerait le notebook sur un seul fournisseur — ici l’abstraction provider: openrouter permet de changer de modèle (GPT, Claude, Qwen) en éditant un paramètre. C’est la robustesse attendue d’un lab reproduit sur des environnements hétérogènes : la logique d’ablation est découplée du moteur LLM sous-jacent.
Pourquoi décomposer un pipeline ML en blocs logiques avant d’ablater : l’ablation consiste à mesurer l’impact de chaque composant en le supprimant. Mais un script ML n’est pas une liste de fonctions isolées — c’est un flux continu (imports → chargement → feature engineering → entraînement → évaluation). Le CodeBlockAnalyzer découpe ce flux en blocs sémantiques (Data Loading, Feature Engineering, Training…) pour que l’ablation opère à la bonne granularité : ni trop fine (ablater une ligne) ni trop grossière (ablater la moitié du pipeline). Sans cette décomposition, mesurer « l’importance d’un bloc » n’aurait pas d’unité de décision.
Analyseur de blocs de code pour identifier les composants a tester.
class CodeBlockAnalyzer:"""Identifie les blocs de code dans un pipeline ML."""def__init__(self, llm: LLMClient):self.llm = llmdef identify_blocks(self, code: str) -> List[CodeBlock]:"""Identifie les blocs logiques dans le code.""" prompt =f"""Analyse ce code ML et identifie les blocs logiques.CODE:```python{code[:2000]}```Identifie 3-5 blocs (feature engineering, model, training, etc.).Format JSON:[{{"id": "block1", "name": "...", "description": "...", "code": "..."}}]]JSON:""" response =self.llm.generate(prompt, temperature=0.2)try: match = re.search(r'\[.*\]', response, re.DOTALL)if match: data = json.loads(match.group(0))return [CodeBlock(**b) for b in data]except:passreturn []print("Classe CodeBlockAnalyzer definie : identification de blocs logiques dans un pipeline ML via LLM")
Classe CodeBlockAnalyzer definie : identification de blocs logiques dans un pipeline ML via LLM
3. Ablation Study Simulator
Pourquoi estimer l’importance sur une échelle 0.0–1.0 plutôt qu’exécuter réellement l’ablation : une ablation ML rigoureuse ré-entraînerait le modèle sans chaque bloc et comparerait les métriques — coûteux (N ré-entraînements) et irréalisable dans un lab pédagogique. Le simulateur demande au LLM d’estimer l’importance relative de chaque bloc à partir de son rôle sémantique : « Data Loading & Feature Engineering » est vraisemblablement plus critique que « Imports ». C’est une approximation experte qui produit un classement actionnable (quel bloc raffiner en priorité) sans le coût du benchmark complet. La rigueur scientifique exigerait de valider ensuite ce classement par une vraie ablation — le lab enseigne la méthode de priorisation, pas le verdict définitif.
class AblationStudy:"""Simule des etudes d'ablation pour prioriser les ameliorations."""def__init__(self, llm: LLMClient):self.llm = llmdef estimate_importance(self, blocks: List[CodeBlock], task: str) -> List[CodeBlock]:"""Estime l'importance relative de chaque bloc.""" blocks_desc ="\n".join([f"- {b.id}: {b.name} - {b.description[:50]}"for b in blocks ]) prompt =f"""Estime l'importance de chaque bloc pour la tache: {task}BLOCS:{blocks_desc}Donne un score d'importance de 0.0 a 1.0 pour chaque bloc.Format JSON:[{{"id": "block1", "importance": 0.9}},{{"id": "block2", "importance": 0.6}}]JSON:""" response =self.llm.generate(prompt, temperature=0.2)try: match = re.search(r'\[.*\]', response, re.DOTALL)if match: data = json.loads(match.group(0)) importance_map = {d['id']: d['importance'] for d in data}for block in blocks: block.importance = importance_map.get(block.id, 0.5)except:# Default importancefor i, block inenumerate(blocks): block.importance =0.8- i *0.1returnsorted(blocks, key=lambda b: b.importance, reverse=True)print("Classe AblationStudy definie : estimation de l'importance relative des blocs (0.0-1.0) via LLM")
Classe AblationStudy definie : estimation de l'importance relative des blocs (0.0-1.0) via LLM
4. Targeted Refinement
Pourquoi raffiner uniquement le bloc prioritaire plutôt que tout le pipeline : l’ablation a identifié qu’un seul bloc concentre l’essentiel de la valeur (le bloc d’importance maximale). Raffiner ce bloc précis — et lui seul — maximise le retour sur effort : le TargetedRefiner demande au LLM de réécrire ce bloc avec des améliorations ciblées (robustesse, lisibilité, performance). C’est l’écart entre une réécriture aveugle du pipeline entier (coût LLM × N blocs, risque de régression) et une intervention chirurgicale sur le point de levier identifié par l’ablation. C’est le principe d’optimisation qui sous-tend le MLE-STAR : diagnostiquer avant d’agir.
class TargetedRefiner:"""Raffine de maniere ciblee les composants critiques."""def__init__(self, llm: LLMClient):self.llm = llmdef refine_block(self, block: CodeBlock, task: str, current_score: float) ->str:"""Genere une version amelioree d'un bloc.""" prompt =f"""Ameliore ce bloc de code ML pour la tache: {task}BLOC: {block.name}DESCRIPTION: {block.description}CODE ACTUEL:```python{block.code[:1000]}```SCORE ACTUEL: {current_score:.3f}Genere une version amelioree qui pourrait augmenter le score.Focus sur les optimisations simples et efficaces.```python# Code ameliore```""" response =self.llm.generate(prompt, temperature=0.3) match = re.search(r'```python\s*(.*?)\s*```', response, re.DOTALL)return match.group(1).strip() if match else block.codeprint("Classe TargetedRefiner definie : raffinement cible de blocs critiques via LLM")
Classe TargetedRefiner definie : raffinement cible de blocs critiques via LLM
5. MLE-STAR Ablation Pipeline
class MLEStarAblation:"""Pipeline d'ablation complet de MLE-STAR."""def__init__(self):self.llm = LLMClient()self.block_analyzer = CodeBlockAnalyzer(self.llm)self.ablation_study = AblationStudy(self.llm)self.refiner = TargetedRefiner(self.llm)def analyze_and_refine(self, code: str, task: str, current_score: float=0.5) -> Dict:"""Analyse le code, identifie les blocs, et suggere des raffinements."""print("[ANALYZER] Identification des blocs...") blocks =self.block_analyzer.identify_blocks(code)print(f" - {len(blocks)} blocs identifies")ifnot blocks:return {'success': False, 'error': 'Impossible d identifier les blocs'}print("\n[ABLATION] Estimation de l'importance...") blocks =self.ablation_study.estimate_importance(blocks, task)print(" Resultats:")for b in blocks[:3]:print(f" - {b.name}: {b.importance:.2f}")# Raffiner le bloc le plus important top_block = blocks[0]print(f"\n[REFINER] Raffinement de '{top_block.name}'...") refined_code =self.refiner.refine_block(top_block, task, current_score)return {'success': True,'blocks': [(b.name, b.importance) for b in blocks],'top_block': top_block.name,'refined_code': refined_code }print("Classe MLEStarAblation definie : pipeline complet Analyze -> Ablation -> Refine")
Classe MLEStarAblation definie : pipeline complet Analyze -> Ablation -> Refine
6. Test avec un Exemple de Code ML
Pourquoi tester le pipeline sur un exemple réaliste (RandomForest + feature engineering) : un exemple jouet (quelques lignes sans logique réelle) ne permettrait pas au CodeBlockAnalyzer d’identifier des blocs significatifs — l’ablation n’aurait rien à classer. L’exemple choisi (chargement CSV, feature engineering ratio/log, cross_val_score, RandomForest) est représentatif d’un pipeline ML standard avec ses quatre composants naturels (imports, data loading, training, évaluation). C’est ce réalisme qui donne à l’ablation une validité de face : les importances estimées seront cohérentes parce que l’exercice porte sur du code que l’on trouverait en production.
# Exemple de code ML a analysersample_code ='''import pandas as pdfrom sklearn.ensemble import RandomForestClassifierfrom sklearn.model_selection import cross_val_score# Feature Engineeringdf = pd.read_csv('data.csv')df['feature_ratio'] = df['feature_a'] / (df['feature_b'] + 1e-6)df['feature_log'] = np.log1p(df['feature_c'])X = df.drop('target', axis=1)y = df['target']# Modelmodel = RandomForestClassifier(n_estimators=100, random_state=42)# Training & Evaluationscores = cross_val_score(model, X, y, cv=5)print(f"Accuracy: {scores.mean():.4f}")'''print("Code a analyser:")print(sample_code[:300] +"...")
Code a analyser:
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
# Feature Engineering
df = pd.read_csv('data.csv')
df['feature_ratio'] = df['feature_a'] / (df['feature_b'] + 1e-6)
df['feature_log'] = np.log1p(df['feature_c'])
X = df.drop...
Test du pipeline d’ablation sur un exemple de code ML.
# Test du pipeline d'ablationpipeline = MLEStarAblation()result = pipeline.analyze_and_refine( code=sample_code, task="binary classification tabular", current_score=0.75)print("\n"+"="*50)print("RESULTAT DE L'ABLATION:")print("="*50)print(f"\nBlocs identifies:")for name, imp in result['blocks']:print(f" - {name}: importance {imp:.2f}")print(f"\nBloc prioritaire: {result['top_block']}")
[ANALYZER] Identification des blocs...
- 4 blocs identifies
[ABLATION] Estimation de l'importance...
Resultats:
- Training & Evaluation: 0.90
- Feature Engineering: 0.80
- Model Definition: 0.70
[REFINER] Raffinement de 'Training & Evaluation'...
==================================================
RESULTAT DE L'ABLATION:
==================================================
Blocs identifies:
- Training & Evaluation: importance 0.90
- Feature Engineering: importance 0.80
- Model Definition: importance 0.70
- Import Libraries: importance 0.30
Bloc prioritaire: Training & Evaluation
Lecture des résultats d’ablation — le simulateur a identifié 4 blocs et estimé leur importance : « Training & Evaluation » = 0.90, « Feature Engineering » = 0.80, « Model Definition » = 0.70, « Import Libraries » = 0.30. Le bloc prioritaire retenu pour le raffinement est donc « Training & Evaluation ».
Ce que ce classement révèle sur la méthodologie : l’importance reflète le rôle sémantique de chaque bloc — l’entraînement et l’évaluation portent la performance mesurable du pipeline, tandis que les imports sont un prérequis mécanique sans valeur prédictive propre (0.30, dernier du classement). Ce verdict est une estimation experte du LLM, pas une ablation exécutée : il oriente l’effort de raffinement vers le bloc à plus fort levier sans payer le coût de N ré-entraînements. La rigueur consisterait à valider ce classement par une vraie ablation (ré-entraîner sans le bloc et mesurer la chute de performance) — mais pour la priorisation, l’estimation suffit à décider où agir.
Pourquoi ces libellés et ces scores ne sont pas des constantes. L’estimation est un appel LLM à température réduite (voir AblationStudy.estimate_importance) : relancer ce notebook change malgré tout les noms de blocs et leur ordre. Le même extrait de code a placé « Data Loading & Feature Engineering » en tête lors d’une exécution antérieure de ce lab, et « Training & Evaluation » dans l’exécution committée ici — les scores cités ci-dessus valent donc pour cette exécution, ils ne sont pas un résultat reproductible. C’est la raison de fond pour laquelle ce lab parle d’estimation et non d’ablation : un classement qui varie d’une exécution à l’autre ne se cite pas comme un fait, il se relit dans l’output. Les questions de réflexion en fin de lab (« Comment pourriez-vous valider l’importance expérimentalement ? ») trouvent ici leur réponse concrète — par la mesure, précisément parce que l’estimation est instable.
7. Affichage du Code Raffine
if result['success'] and result.get('refined_code'):print("\n"+"="*50)print("CODE RAFFINE:")print("="*50)print(result['refined_code'][:600] +"..."iflen(result['refined_code']) >600else result['refined_code'])
==================================================
CODE RAFFINE:
==================================================
from sklearn.model_selection import cross_val_score, StratifiedKFold, GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.ensemble import RandomForestClassifier
# Standardisation des données et utilisation d'un modèle de forêt aléatoire
pipeline = make_pipeline(StandardScaler(), RandomForestClassifier(random_state=42))
# Utilisation de StratifiedKFold pour une meilleure répartition des classes
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
# Hyperparameter tuning avec GridSearchCV
param_grid = {
'randomfo...
Lecture du code raffiné — le TargetedRefiner a réécrit le bloc prioritaire (« Training & Evaluation », importance 0.90). Le bloc d’origine (validation croisée d’un RandomForestClassifier) revient sous la forme make_pipeline(StandardScaler(), RandomForestClassifier(...)) avec StratifiedKFold(shuffle=True) et un GridSearchCV : la standardisation et la stratification s’ajoutent à l’entraînement.
Ce que cet output démontre sur le raffinement ciblé : la sortie ne contient que le bloc prioritaire — refine_block est appelé sur un seul bloc, pas sur le pipeline entier. C’est la valeur ajoutée de la méthode MLE-STAR : au lieu d’une réécriture globale coûteuse et risquée (régressions possibles sur les blocs secondaires), l’intervention est chirurgicale, et l’effort est alloué là où l’ablation a montré qu’il aurait le plus d’impact. La contrepartie est visible dans le même output : le code rendu ne se suffit pas à lui-même — il ne porte plus le chargement des données ni le feature engineering de l’extrait d’origine, et le recoller au pipeline complet reste à la charge du praticien.
8. Resume du Lab
Ce que nous avons implemente
CodeBlockAnalyzer: Identifie les blocs logiques
AblationStudy: Estime l’importance relative
TargetedRefiner: Ameliore les composants critiques ### Principe MLE-STAR L’ablation permet de:
Ne pas gaspiller d’efforts sur des composants peu impactants
Focus sur les zones a haut potentiel d’amelioration
Iterer rapidement sur les changements importants ### Limitations
L’importance est estimee par le LLM (pas testee reellement)
Une vraie ablation necessite d’executer le code
Le raffinement est suggestif, pas garanti ### Prochaine étape
Lab 15: Kaggle Challenge avec MLE-STAR complet
Exercice : Ablation sur votre propre pipeline
Appliquez l’étude d’ablation a un pipeline ML de votre choix pour identifier les composants a optimiser.
Objectifs
Ecrire un pipeline ML simple (3-5 blocs)
Analyser l’importance de chaque bloc
Raffiner le bloc le plus critique
Comparer les résultats avant/après
Instructions
# TODO: Definissez votre propre pipeline MLmon_pipeline ='''# Exemple: classification, regression, ou clusteringimport pandas as pdfrom sklearn.model_selection import train_test_splitfrom sklearn.preprocessing import StandardScalerfrom sklearn.ensemble import GradientBoostingClassifier# 1. Chargement des donnees# 2. Preprocessing# 3. Feature engineering# 4. Entrainement modele# 5. Evaluation'''# TODO: Analysez les blocspipeline = MLEStarAblation()result = pipeline.analyze_and_refine( code=mon_pipeline, task="votre tache ici", # Ex: "multiclass classification" current_score=0.70# Score actuel hypothetique)# TODO: Affichez les blocs identifiesfor name, importance in result['blocks']:print(f"{name}: {importance:.2f}")# TODO: Testez manuellement le code raffine# Comparez avec une execution reelle si possible
[ANALYZER] Identification des blocs...
- 5 blocs identifies
[ABLATION] Estimation de l'importance...
Resultats:
- Model Training: 0.90
- Data Loading: 0.80
- Model Evaluation: 0.80
[REFINER] Raffinement de 'Model Training'...
Model Training: 0.90
Data Loading: 0.80
Model Evaluation: 0.80
Data Preprocessing: 0.70
Feature Engineering: 0.60
Exercice : Impact du Feature Engineering sur l’Importance
Créez un pipeline ML avec des blocs de feature engineering spécifiques et analysez comment l’ablation de chaque bloc de features affecte l’importance estimee par le LLM.
Objectifs
Définir 3 blocs de features (original, ratio, polynomial)
Analyser l’importance estimee de chaque bloc
Rédiger un rapport comparatif des résultats d’ablation
Indice : - Bloc 1: features originales (age, tenure, monthly_charges) - Bloc 2: features ratio (charges_per_month = total_charges / tenure) - Bloc 3: features polynomiales (age_squared, tenure_log) - Observez quel bloc le LLM estime le plus important et pourquoi
# Exercice : Ablation guidee sur le feature engineering# Objectif : Comparer l'impact de differents blocs de features# TODO: Definissez 3 pipelines avec des blocs de features differentspipeline_original ="""import pandas as pdfrom sklearn.ensemble import RandomForestClassifierfrom sklearn.model_selection import train_test_split# Features originales uniquementdf = pd.read_csv('churn.csv')X = df[['age', 'tenure', 'monthly_charges', 'total_charges']]y = df['churn']X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)model = RandomForestClassifier(n_estimators=100)model.fit(X_train, y_train)score = model.score(X_test, y_test)print(f"Accuracy: {score:.4f}")"""pipeline_avec_ratios ="""# TODO etudiant : ajoutez des features ratio# Exemple: df['charges_per_month'] = df['total_charges'] / (df['tenure'] + 1)# Exemple: df['charges_age_ratio'] = df['monthly_charges'] / df['age']"""pipeline_avec_polynomial ="""# TODO etudiant : ajoutez des features polynomiales# Exemple: df['age_squared'] = df['age'] ** 2# Exemple: df['tenure_log'] = np.log1p(df['tenure'])"""# TODO: Analysez chaque pipeline avec le pipeline d'ablation# ablation = MLEStarAblation()# for name, code in [("original", pipeline_original),# ("ratios", pipeline_avec_ratios),# ("polynomial", pipeline_avec_polynomial)]:# result = ablation.analyze_and_refine(code, "customer churn prediction", current_score=0.75)# print(f"\n--- {name} ---")# for b_name, importance in result['blocks']:# print(f" {b_name}: {importance:.2f}")print("Exercice a completer : ablation guidee sur le feature engineering")
Exercice a completer : ablation guidee sur le feature engineering
Exercice : Stratégie d’Ensemble par Ablation
Utilisez l’ablation pour determiner la meilleure stratégie d’ensemble (voting, stacking, blending) pour un pipeline ML. L’objectif est de comparer les approches d’ensemble et de choisir la plus adaptee.
Objectifs
Définir 3 stratégies d’ensemble dans un pipeline ML
Appliquer l’ablation pour comparer leur importance relative
Recommander la meilleure stratégie avec justification
Indice : - Voting : VotingClassifier avec plusieurs modèles - Stacking : StackingClassifier avec meta-learner - Blending : predictions sur un validation set - L’ablation permet de determiner si l’ensemble apporte reellement un gain vs. un seul modèle
# Exercice : Comparaison de strategies d'ensemble par ablation# Objectif : Determiner la meilleure strategie d'ensemble# TODO: Definissez un pipeline avec strategie d'ensembleensemble_pipeline ="""import pandas as pdimport numpy as npfrom sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier, VotingClassifierfrom sklearn.linear_model import LogisticRegressionfrom sklearn.model_selection import cross_val_scorefrom sklearn.preprocessing import StandardScaler# Chargement et preparationdf = pd.read_csv('data.csv')X = df.drop('target', axis=1)y = df['target']# Modeles individuelsrf = RandomForestClassifier(n_estimators=100, random_state=42)gb = GradientBoostingClassifier(n_estimators=100, random_state=42)lr = LogisticRegression(max_iter=1000)# Strategie d'ensemble: Votingensemble = VotingClassifier( estimators=[('rf', rf), ('gb', gb), ('lr', lr)], voting='soft')# Evaluationscores = cross_val_score(ensemble, X, y, cv=5, scoring='roc_auc')print(f"Ensemble AUC: {scores.mean():.4f} (+/- {scores.std():.4f})")"""# TODO: Analysez les blocs avec le pipeline d'ablation# pipeline_ablation = MLEStarAblation()# result = pipeline_ablation.analyze_and_refine(# code=ensemble_pipeline,# task="binary classification with ensemble",# current_score=0.80# )# # print("Blocs identifies:")# for name, importance in result['blocks']:# print(f" {name}: {importance:.2f}")# print(f"\nBloc prioritaire: {result['top_block']}")# TODO: Comparez avec un pipeline sans ensemble (modele unique)# Quel est le gain estime de l'ensemble par rapport au modele seul ?gain_estime =None# TODO etudiant : estimez le gain (ex: +0.02 AUC)# TODO: Recommandez la meilleure strategie# Justification : Voting est plus simple, Stacking plus performant mais plus couteuxrecommandation =None# TODO etudiant : "voting" / "stacking" / "single_model"print("Exercice a completer : comparaison de strategies d'ensemble par ablation")
Exercice a completer : comparaison de strategies d'ensemble par ablation
Questions de reflexion
L’estimation d’importance par le LLM correspond-elle a votre intuition ?
Quelles seraient les limites d’une ablation automatisee ?
References
Meyes, R., Lu, M., Waubert de Puiseau, C., & Meisen, T. (2019). Ablation Studies in Artificial Neural Networks. arXiv:1901.08644. https://arxiv.org/abs/1901.08644
Nam, J., et al. (2025). MLE-STAR: Machine Learning Engineering Agent via Search and Targeted Refinement. Google Research. arXiv:2506.15692. https://arxiv.org/abs/2506.15692
Xi, Z., et al. (2023). The Rise and Potential of Large Language Model Based Agents: A Survey. arXiv:2309.07864.