"""Module de gestion des expérimentations avec MLflow. Ce module contient des fonctions pour configurer MLflow, lancer des expérimentations complètes avec logging des paramètres, métriques et artefacts, et pour logguer le modèle final dans le registry. Il gère les spécificités des modèles (notamment CatBoost) et organise les artefacts de manière structurée pour faciliter l'analyse et la comparaison des modèles. Les fonctions sont conçues pour être flexibles et réutilisables dans différents contextes d'expérimentation. """ import mlflow import mlflow.sklearn from pathlib import Path from catboost import CatBoostRegressor import time from src.features.preprocessing import get_categorical_features from src.modeling.cv import cross_validate_catboost,cross_validate_model def log_model_to_mlflow(model, model_name, model_artifact_name="model"): """ Log le modèle dans MLflow selon sa librairie. - XGBoost : mlflow.xgboost - LightGBM : mlflow.lightgbm - CatBoost : mlflow.catboost - Autres : mlflow.sklearn Args: - model : modèle entraîné - model_name : str, nom du modèle (pour les logs) - model_artifact_name : str, nom de l'artifact dans MLflow (par défaut "model") """ model_class = model.__class__.__name__ try: if "XGB" in model_class: import mlflow.xgboost mlflow.xgboost.log_model(model, name=model_artifact_name) elif "LGBM" in model_class: import mlflow.lightgbm mlflow.lightgbm.log_model(model, name=model_artifact_name) elif "CatBoost" in model_class: import mlflow.catboost mlflow.catboost.log_model(model, name=model_artifact_name) else: import mlflow.sklearn mlflow.sklearn.log_model(model, name=model_artifact_name) except Exception as e: print(f"Erreur lors du log du modèle {model_name} : {e}") def run_experiment( model_name, model, X_train, X_test, y_train, y_test, build_model_pipeline, cross_validate_model, train_model, evaluate_regression_model, cv=5 ): """ Lance une expérimentation complète avec MLflow : - pipeline - cross-validation - entraînement - évaluation test - logging MLflow Le logging MLflow inclut : - paramètres de l'expérience (model_name, cv, train_size, test_size, training_time) - métriques de la CV (MAE, RMSE, R²) - métriques du test (MAE, RMSE, R² en log et en réel) - modèle entraîné dans les artifacts Le pipeline est construit avec build_model_pipeline, qui intègre le préprocessing adapté au type de modèle. La validation croisée est réalisée avec cross_validate_model, qui gère les spécificités des modèles (notamment CatBoost). L'entraînement final est réalisé avec train_model, qui prend en compte les particularités de chaque modèle. L'évaluation est réalisée avec evaluate_regression_model, qui calcule les métriques à la fois en log et en réel. Args: - model_name : str, nom du modèle pour les logs - model : instance du modèle à entraîner - X_train, X_test, y_train, y_test : données d'entraînement et de test - build_model_pipeline : fonction pour construire le pipeline avec préprocessing - cross_validate_model : fonction pour réaliser la validation croisée - train_model : fonction pour entraîner le modèle final - evaluate_regression_model : fonction pour évaluer les performances du modèle - cv : int, nombre de folds pour la validation croisée (par défaut 5) Returns: - dict avec les résultats de l'expérimentation (métriques, modèle entraîné, temps d'entraînement) """ try: with mlflow.start_run(run_name=model_name): start_time = time.time() if isinstance(model, CatBoostRegressor): # 1. colonnes catégorielles categorical_features = get_categorical_features(X_train) # 2. CV spécifique cv_results = cross_validate_catboost( model=model, X_train=X_train, y_train=y_train, cv=cv ) # 3. train final trained_pipeline = model.fit( X_train, y_train, cat_features=categorical_features, verbose=0 ) # 4. prédiction y_pred = trained_pipeline.predict(X_test) # 5. log spécifique mlflow.log_param("native_catboost", True) mlflow.log_param("cat_features", categorical_features) else: pipeline = build_model_pipeline(model, X_train) cv_results = cross_validate_model( pipeline, X_train, y_train, cv=cv ) trained_pipeline = train_model( pipeline, X_train, y_train ) y_pred = trained_pipeline.predict(X_test) mlflow.log_param("native_catboost", False) # Metrics test test_metrics = evaluate_regression_model( y_test, y_pred ) #Params mlflow.log_param("model_name", model_name) mlflow.log_param("cv", cv) mlflow.log_param("train_size", X_train.shape[0]) mlflow.log_param("test_size", X_test.shape[0]) training_time = time.time() - start_time mlflow.log_param("training_time", training_time) # Metrics CV for key, value in cv_results.items(): mlflow.log_metric(key, value) # Metrics test for key, value in test_metrics.items(): mlflow.log_metric(key, value) # Model log_model_to_mlflow(trained_pipeline, model_name, model_artifact_name="model") return { "model": model_name, "trained_pipeline": trained_pipeline, "training_time": training_time, **cv_results, **test_metrics } except Exception as e: print(f"Erreur MLflow run ({model_name}) : {e}") return None def setup_mlflow( tracking_uri, experiment_name, artifact_root ): """ Configure MLflow : - base sqlite - dossier artifacts - création de l'expérience si besoin Args: tracking_uri (str): ex "sqlite:///mlflow.db" experiment_name (str): nom de l'expérience artifact_root (Path): dossier artifacts """ try: # Création du dossier artifacts artifact_root.mkdir(exist_ok=True, parents=True) # Connexion MLflow mlflow.set_tracking_uri(tracking_uri) # Vérifie si l'expérience existe experiment = mlflow.get_experiment_by_name(experiment_name) if experiment is None: mlflow.create_experiment( name=experiment_name, artifact_location=artifact_root.as_uri() ) # Active l'expérience mlflow.set_experiment(experiment_name) print(f"MLflow configuré sur : {experiment_name}") except Exception as e: print(f"Erreur configuration MLflow : {e}") import mlflow import mlflow.sklearn from pathlib import Path def log_final_model_to_mlflow( model, model_name, registered_model_name, metrics, params=None, cv_metrics=None, artifacts_dir=None, artifact_path="model" ): """ Log le modèle final dans MLflow : - paramètres - métriques test - métriques CV - artefacts CSV / PNG - modèle enregistré dans le registry Args: - model : modèle entraîné - model_name : str, nom du modèle pour les logs - registered_model_name : str, nom pour l'enregistrement dans le registry - metrics : dict, métriques de test à logguer - params : dict, paramètres à logguer (optionnel) - cv_metrics : dict, métriques de validation croisée à logguer (optionnel) - artifacts_dir : str ou Path, dossier contenant les artefacts à logguer (optionnel) - artifact_path : str, chemin dans MLflow pour stocker le modèle (par défaut "model") Le logging inclut : - paramètres de l'expérience (model_name, cv, train_size, test_size, training_time) - métriques de la CV (MAE, RMSE, R²) - métriques du test (MAE, RMSE, R² en log et en réel) - modèle entraîné dans les artifacts Le modèle est enregistré dans MLflow Registry avec le nom spécifié. """ with mlflow.start_run(run_name=model_name): mlflow.log_param("model_name", model_name) if params is not None: mlflow.log_params(params) # Metrics test for key, value in metrics.items(): mlflow.log_metric(key, float(value)) # Metrics CV if cv_metrics is not None: for key, value in cv_metrics.items(): mlflow.log_metric(f"cv_{key}", float(value)) # Artifacts if artifacts_dir is not None: artifacts_dir = Path(artifacts_dir) if artifacts_dir.exists(): mlflow.log_artifacts( local_dir=str(artifacts_dir), artifact_path="artifacts" ) # Model + registry mlflow.sklearn.log_model( sk_model=model, artifact_path=artifact_path, registered_model_name=registered_model_name ) print(f"Modèle enregistré dans MLflow Registry : {registered_model_name}")