diff --git "a/notebooks/03_LGBM.ipynb" "b/notebooks/03_LGBM.ipynb"
new file mode 100644--- /dev/null
+++ "b/notebooks/03_LGBM.ipynb"
@@ -0,0 +1,2788 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "def425de",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Configuration chargée avec succès !\n",
+ "MLflow Experiment: OC_P6_Credit_Scoring\n",
+ "Project Version: 1.0\n",
+ "Model: LightGBM\n"
+ ]
+ }
+ ],
+ "source": [
+ "# ============================================================================\n",
+ "# CONFIGURATION DU NOTEBOOK\n",
+ "# ============================================================================\n",
+ "\n",
+ "import datetime\n",
+ "\n",
+ "\n",
+ "\"\"\"Configuration MLflow\"\"\"\n",
+ "MLFLOW_TRACKING_URI = \"http://127.0.0.1:5000\"\n",
+ "MLFLOW_EXPERIMENT_NAME = \"OC_P6_Credit_Scoring\"\n",
+ "\n",
+ "# Configuration du projet\n",
+ "PROJECT_VERSION = \"1.0\"\n",
+ "MODEL_NAME = \"LightGBM\"\n",
+ "NOTEBOOK_NAME = \"03_LGBM\"\n",
+ "RUN_DATE = datetime.datetime.now()\n",
+ "\n",
+ "# Configuration des données\n",
+ "DATA_PATH = \"../data/processed/\"\n",
+ "TRAIN_FILE = \"features_train.csv\"\n",
+ "TEST_FILE = \"features_test.csv\"\n",
+ "\n",
+ "# Configuration du modèle baseline\n",
+ "MODEL_CONFIG = {\n",
+ " \"n_estimators\": 500,\n",
+ " \"learning_rate\": 0.05,\n",
+ " \"num_leaves\": 31,\n",
+ " \"class_weight\": \"balanced\",\n",
+ " \"random_state\": 42\n",
+ "}\n",
+ "\n",
+ "# Configuration de la validation\n",
+ "VALIDATION_SPLIT_RATIO = 0.2\n",
+ "RANDOM_STATE = 42\n",
+ "\n",
+ "# Configuration des tags MLflow\n",
+ "MLFLOW_TAGS = {\n",
+ " \"project_version\": PROJECT_VERSION,\n",
+ " \"notebook\": NOTEBOOK_NAME,\n",
+ " \"phase\": \"baseline\",\n",
+ " \"desequilibre_handling\": \"class_weight_balanced\",\n",
+ " \"date\": RUN_DATE,\n",
+ "}\n",
+ "\n",
+ "# Dictionnaire standard des métriques (schéma unique pour tous les runs)\n",
+ "STANDARD_METRICS = {\n",
+ " \"auc\": float, # AUC-ROC\n",
+ " \"business_cost_min\": float, # Coût métier minimal (10*FN + FP)\n",
+ " \"optimal_threshold\": float, # Seuil qui minimise le coût métier\n",
+ " \"f1_score\": float, # F1 au seuil optimal\n",
+ " \"recall_class1\": float, # Recall classe minoritaire au seuil optimal\n",
+ "}\n",
+ "\n",
+ "# Valeur par défaut si une métrique n'est pas calculable pour un run donné\n",
+ "DEFAULT_METRIC_VALUE = -1.0\n",
+ "\n",
+ "print(\"Configuration chargée avec succès !\")\n",
+ "print(f\"MLflow Experiment: {MLFLOW_EXPERIMENT_NAME}\")\n",
+ "print(f\"Project Version: {PROJECT_VERSION}\")\n",
+ "print(f\"Model: {MODEL_NAME}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e0cc76b8",
+ "metadata": {},
+ "source": [
+ "# 03 - LightGBM Modeling with MLflow Tracking\n",
+ "\n",
+ "Configuration and experimentation notebook for credit scoring model.\n",
+ "All runs will be tracked in MLflow for comparison and reproducibility."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "a4647f17",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from src.mlflow_config import configure_mlflow\n",
+ "\n",
+ "mlflow = configure_mlflow(autolog=False)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "be7291d0",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "📊 État MLflow au démarrage:\n",
+ " Expérience: OC_P6_Credit_Scoring (ID: 1)\n",
+ " Nombre de runs existants: 0\n",
+ "\n",
+ "✅ Aucun run parasite détecté\n"
+ ]
+ }
+ ],
+ "source": [
+ "# ============================================================================\n",
+ "# CLEANUP & VERIFICATION (Optionnel mais recommandé)\n",
+ "# ============================================================================\n",
+ "from mlflow.tracking import MlflowClient\n",
+ "\n",
+ "client = MlflowClient(MLFLOW_TRACKING_URI)\n",
+ "\n",
+ "# Récupérer l'expérience\n",
+ "experiment = client.get_experiment_by_name(MLFLOW_EXPERIMENT_NAME)\n",
+ "experiment_id = experiment.experiment_id if experiment else \"0\"\n",
+ "\n",
+ "print(\"📊 État MLflow au démarrage:\")\n",
+ "print(f\" Expérience: {MLFLOW_EXPERIMENT_NAME} (ID: {experiment_id})\")\n",
+ "\n",
+ "# Récupérer les runs actuels\n",
+ "runs = client.search_runs(experiment_ids=[experiment_id])\n",
+ "print(f\" Nombre de runs existants: {len(runs)}\")\n",
+ "\n",
+ "# Liste des runs attendus (clean)\n",
+ "EXPECTED_RUN_NAMES = {\n",
+ " \"LGBM_baseline_CV\",\n",
+ " \"LGBM_optuna_tuning\",\n",
+ " \"best_params_cv_evaluation\", # enfant\n",
+ " \"final_model\", # enfant\n",
+ " \"LGBM_final_validation\",\n",
+ " \"LGBM_final_interpretability\",\n",
+ " \"LightGBM_baseline_1.0\"\n",
+ "}\n",
+ "\n",
+ "# Détecter les runs parasites\n",
+ "actual_run_names = {run.data.tags.get(\"mlflow.runName\", \"unknown\") for run in runs}\n",
+ "parasite_runs = actual_run_names - EXPECTED_RUN_NAMES\n",
+ "\n",
+ "if parasite_runs:\n",
+ " print(f\"\\n⚠️ Runs parasites détectés: {parasite_runs}\")\n",
+ " print(\" Pour les supprimer, dé-commenter les lignes ci-dessous:\")\n",
+ " print(\" # for run in runs:\")\n",
+ " print(\" # if run.data.tags.get('mlflow.runName') in parasite_runs:\")\n",
+ " print(\" # client.delete_run(run.info.run_id)\")\n",
+ "else:\n",
+ " print(\"\\n✅ Aucun run parasite détecté\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "a834b519",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import pandas as pd\n",
+ "\n",
+ "# Exemple si tu as sauvegardé les features\n",
+ "X_train = pd.read_csv(\"../data/processed/features_train.csv\")\n",
+ "y_train = X_train.pop(\"TARGET\") # ou le nom de ta cible\n",
+ "# Même chose pour X_val, y_val si tu as un split"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "f14c2133",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[LightGBM] [Info] Number of positive: 620, number of negative: 7380\n",
+ "[LightGBM] [Info] Auto-choosing col-wise multi-threading, the overhead of testing was 0.006282 seconds.\n",
+ "You can set `force_col_wise=true` to remove the overhead.\n",
+ "[LightGBM] [Info] Total Bins 19266\n",
+ "[LightGBM] [Info] Number of data points in the train set: 8000, number of used features: 644\n",
+ "[LightGBM] [Info] [binary:BoostFromScore]: pavg=0.500000 -> initscore=0.000000\n",
+ "[LightGBM] [Info] Start training from score 0.000000\n",
+ "[LightGBM] [Info] Number of positive: 620, number of negative: 7380\n",
+ "[LightGBM] [Info] Auto-choosing row-wise multi-threading, the overhead of testing was 0.002681 seconds.\n",
+ "You can set `force_row_wise=true` to remove the overhead.\n",
+ "And if memory is not enough, you can set `force_col_wise=true`.\n",
+ "[LightGBM] [Info] Total Bins 19578\n",
+ "[LightGBM] [Info] Number of data points in the train set: 8000, number of used features: 646\n",
+ "[LightGBM] [Info] [binary:BoostFromScore]: pavg=0.500000 -> initscore=0.000000\n",
+ "[LightGBM] [Info] Start training from score 0.000000\n",
+ "[LightGBM] [Info] Number of positive: 620, number of negative: 7380\n",
+ "[LightGBM] [Info] Auto-choosing row-wise multi-threading, the overhead of testing was 0.004206 seconds.\n",
+ "You can set `force_row_wise=true` to remove the overhead.\n",
+ "And if memory is not enough, you can set `force_col_wise=true`.\n",
+ "[LightGBM] [Info] Total Bins 19397\n",
+ "[LightGBM] [Info] Number of data points in the train set: 8000, number of used features: 646\n",
+ "[LightGBM] [Info] [binary:BoostFromScore]: pavg=0.500000 -> initscore=0.000000\n",
+ "[LightGBM] [Info] Start training from score 0.000000\n",
+ "[LightGBM] [Info] Number of positive: 620, number of negative: 7380\n",
+ "[LightGBM] [Info] Auto-choosing row-wise multi-threading, the overhead of testing was 0.003962 seconds.\n",
+ "You can set `force_row_wise=true` to remove the overhead.\n",
+ "And if memory is not enough, you can set `force_col_wise=true`.\n",
+ "[LightGBM] [Info] Total Bins 19298\n",
+ "[LightGBM] [Info] Number of data points in the train set: 8000, number of used features: 648\n",
+ "[LightGBM] [Info] [binary:BoostFromScore]: pavg=0.500000 -> initscore=0.000000\n",
+ "[LightGBM] [Info] Start training from score 0.000000\n",
+ "[LightGBM] [Info] Number of positive: 620, number of negative: 7380\n",
+ "[LightGBM] [Info] Auto-choosing row-wise multi-threading, the overhead of testing was 0.002536 seconds.\n",
+ "You can set `force_row_wise=true` to remove the overhead.\n",
+ "And if memory is not enough, you can set `force_col_wise=true`.\n",
+ "[LightGBM] [Info] Total Bins 19378\n",
+ "[LightGBM] [Info] Number of data points in the train set: 8000, number of used features: 647\n",
+ "[LightGBM] [Info] [binary:BoostFromScore]: pavg=0.500000 -> initscore=0.000000\n",
+ "[LightGBM] [Info] Start training from score 0.000000\n",
+ "✓ Cross-validation terminée\n",
+ "AUC moyen: 0.7116\n",
+ "Coût métier moyen: 1151.80\n",
+ "Seuil optimal moyen: 0.13\n",
+ "🏃 View run LGBM_baseline_CV at: http://127.0.0.1:5000/#/experiments/1/runs/368005a826b148928a94ada74b3c7441\n",
+ "🧪 View experiment at: http://127.0.0.1:5000/#/experiments/1\n"
+ ]
+ }
+ ],
+ "source": [
+ "# ============================================================================\n",
+ "# Cross-validation LightGBM + coût métier\n",
+ "# ============================================================================\n",
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "from lightgbm import LGBMClassifier\n",
+ "from sklearn.model_selection import StratifiedKFold\n",
+ "from sklearn.metrics import roc_auc_score, confusion_matrix, f1_score, recall_score\n",
+ "import warnings\n",
+ "\n",
+ "# Désactiver le warning MLflow pip\n",
+ "warnings.filterwarnings('ignore', message='.*Failed to resolve installed pip version.*')\n",
+ "\n",
+ "# Nettoyage des colonnes avant entraînement (noms + types)\n",
+ "object_cols = X_train.select_dtypes(include=['object']).columns.tolist()\n",
+ "if object_cols:\n",
+ " for col in object_cols:\n",
+ " X_train[col] = pd.to_numeric(X_train[col], errors='coerce').fillna(0)\n",
+ "X_train.columns = (\n",
+ " X_train.columns\n",
+ " .str.replace(' ', '_')\n",
+ " .str.replace('[^a-zA-Z0-9_]', '_', regex=True)\n",
+ " .str.replace('__+', '_', regex=True)\n",
+ " )\n",
+ "\n",
+ "# Paramètres modèle (assurer class_weight=balanced)\n",
+ "MODEL_CONFIG_CV = {**MODEL_CONFIG, \"class_weight\": \"balanced\"}\n",
+ "\n",
+ "# K-Fold stratifié\n",
+ "skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\n",
+ "\n",
+ "fold_results = []\n",
+ "thresholds = np.round(np.arange(0.1, 0.91, 0.05), 2)\n",
+ "\n",
+ "RUN_NAME_CV = \"LGBM_baseline_CV\"\n",
+ "\n",
+ "# Logging uniformisé pour comparaison facile\n",
+ "with mlflow.start_run(run_name=RUN_NAME_CV):\n",
+ " # Log paramètres\n",
+ " mlflow.log_params(MODEL_CONFIG_CV)\n",
+ " \n",
+ " # Log tags existants\n",
+ " for tag_key, tag_value in MLFLOW_TAGS.items():\n",
+ " mlflow.set_tag(tag_key, tag_value)\n",
+ " mlflow.set_tag(\"model_type\", MODEL_NAME)\n",
+ " mlflow.set_tag(\"phase\", \"baseline_cv\")\n",
+ " mlflow.set_tag(\"evaluation_type\", \"cv_stratified\")\n",
+ " mlflow.set_tag(\"threshold_optimized\", \"yes\")\n",
+ " \n",
+ " for fold_idx, (train_idx, val_idx) in enumerate(skf.split(X_train, y_train), start=1):\n",
+ " X_tr, X_val = X_train.iloc[train_idx], X_train.iloc[val_idx]\n",
+ " y_tr, y_val = y_train.iloc[train_idx], y_train.iloc[val_idx]\n",
+ " \n",
+ " model = LGBMClassifier(**MODEL_CONFIG_CV)\n",
+ " model.fit(X_tr, y_tr)\n",
+ " \n",
+ " y_val_proba = model.predict_proba(X_val)[:, 1]\n",
+ " auc = roc_auc_score(y_val, y_val_proba)\n",
+ " \n",
+ " best_threshold = None\n",
+ " min_cost = None\n",
+ " best_fp = None\n",
+ " best_fn = None\n",
+ " \n",
+ " for thr in thresholds:\n",
+ " y_val_pred = (y_val_proba >= thr).astype(int)\n",
+ " tn, fp, fn, tp = confusion_matrix(y_val, y_val_pred).ravel()\n",
+ " cost = 10 * fn + 1 * fp\n",
+ " if (min_cost is None) or (cost < min_cost):\n",
+ " min_cost = cost\n",
+ " best_threshold = thr\n",
+ " best_fp = fp\n",
+ " best_fn = fn\n",
+ " \n",
+ " # F1 et Recall au seuil optimal du fold\n",
+ " y_val_pred_opt = (y_val_proba >= best_threshold).astype(int)\n",
+ " f1_fold = f1_score(y_val, y_val_pred_opt)\n",
+ " recall_fold = recall_score(y_val, y_val_pred_opt)\n",
+ " \n",
+ " fold_results.append({\n",
+ " \"fold\": fold_idx,\n",
+ " \"auc\": auc,\n",
+ " \"best_threshold\": best_threshold,\n",
+ " \"min_cost\": min_cost,\n",
+ " \"fp\": best_fp,\n",
+ " \"fn\": best_fn,\n",
+ " \"f1_score\": f1_fold,\n",
+ " \"recall_class1\": recall_fold\n",
+ " })\n",
+ " \n",
+ " cv_results_df = pd.DataFrame(fold_results)\n",
+ " cv_auc_mean = cv_results_df[\"auc\"].mean()\n",
+ " cv_min_cost_mean = cv_results_df[\"min_cost\"].mean()\n",
+ " cv_best_threshold_mean = cv_results_df[\"best_threshold\"].mean()\n",
+ " cv_f1_mean = cv_results_df[\"f1_score\"].mean()\n",
+ " cv_recall_mean = cv_results_df[\"recall_class1\"].mean()\n",
+ " \n",
+ " # Log métriques standardisées\n",
+ " mlflow.log_metric(\"auc\", cv_auc_mean)\n",
+ " mlflow.log_metric(\"business_cost_min\", cv_min_cost_mean)\n",
+ " mlflow.log_metric(\"optimal_threshold\", cv_best_threshold_mean)\n",
+ " mlflow.log_metric(\"f1_score\", cv_f1_mean)\n",
+ " mlflow.log_metric(\"recall_class1\", cv_recall_mean)\n",
+ " \n",
+ " # Log artefact JSON\n",
+ " mlflow.log_dict(cv_results_df.to_dict(orient=\"records\"), \"cv_results.json\")\n",
+ " \n",
+ " print(\"✓ Cross-validation terminée\")\n",
+ " print(f\"AUC moyen: {cv_auc_mean:.4f}\")\n",
+ " print(f\"Coût métier moyen: {cv_min_cost_mean:.2f}\")\n",
+ " print(f\"Seuil optimal moyen: {cv_best_threshold_mean:.2f}\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "3c98399c",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "======================================================================\n",
+ "🔧 OPTUNA OPTIMIZATION - Fast iteration\n",
+ "======================================================================\n",
+ "\n",
+ "🚀 Lancement Optuna: 15 trials, 3-fold CV, timeout=600s...\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\u001b[32m[I 2026-02-05 18:40:24,507]\u001b[0m A new study created in memory with name: no-name-e2edb585-d29a-48e4-82ad-6f1d28da31ca\u001b[0m\n"
+ ]
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "67b86962c23b4e33be6c7b0e1b4542d9",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ " 0%| | 0/15 [00:00, ?it/s]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\u001b[32m[I 2026-02-05 18:40:28,200]\u001b[0m Trial 0 finished with value: -2199.6666666666665 and parameters: {'num_leaves': 35, 'max_depth': -1, 'learning_rate': 0.07937171201960971, 'min_child_samples': 52, 'subsample': 0.7060599654199409, 'colsample_bytree': 0.8177831928609989}. Best is trial 0 with value: -2199.6666666666665.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:40:31,513]\u001b[0m Trial 1 finished with value: -2035.3333333333333 and parameters: {'num_leaves': 39, 'max_depth': 9, 'learning_rate': 0.04930641956760948, 'min_child_samples': 67, 'subsample': 0.7817741539967493, 'colsample_bytree': 0.8358633917184355}. Best is trial 1 with value: -2035.3333333333333.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:40:32,535]\u001b[0m Trial 2 finished with value: -1796.6666666666667 and parameters: {'num_leaves': 108, 'max_depth': 3, 'learning_rate': 0.037516761705010275, 'min_child_samples': 63, 'subsample': 0.9852484827442787, 'colsample_bytree': 0.7826541755404542}. Best is trial 2 with value: -1796.6666666666667.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:40:39,187]\u001b[0m Trial 3 finished with value: -2386.3333333333335 and parameters: {'num_leaves': 70, 'max_depth': -1, 'learning_rate': 0.07054534014239698, 'min_child_samples': 69, 'subsample': 0.9298705064120075, 'colsample_bytree': 0.832669236108231}. Best is trial 2 with value: -1796.6666666666667.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:40:39,807]\u001b[0m Trial 4 finished with value: -1771.0 and parameters: {'num_leaves': 96, 'max_depth': 1, 'learning_rate': 0.05924053158948163, 'min_child_samples': 79, 'subsample': 0.8202656015046361, 'colsample_bytree': 0.9198083673626444}. Best is trial 4 with value: -1771.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:40:42,961]\u001b[0m Trial 5 finished with value: -1933.3333333333333 and parameters: {'num_leaves': 40, 'max_depth': 7, 'learning_rate': 0.04309778250824856, 'min_child_samples': 53, 'subsample': 0.8295284885054306, 'colsample_bytree': 0.9785938486927315}. Best is trial 4 with value: -1771.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:40:45,736]\u001b[0m Trial 6 finished with value: -1947.0 and parameters: {'num_leaves': 80, 'max_depth': 7, 'learning_rate': 0.04109377163718731, 'min_child_samples': 47, 'subsample': 0.9595971630379867, 'colsample_bytree': 0.7002028835199944}. Best is trial 4 with value: -1771.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:40:46,795]\u001b[0m Trial 7 finished with value: -1780.0 and parameters: {'num_leaves': 71, 'max_depth': 2, 'learning_rate': 0.03202559668207934, 'min_child_samples': 78, 'subsample': 0.8559871322466271, 'colsample_bytree': 0.9928945127064734}. Best is trial 4 with value: -1771.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:40:48,576]\u001b[0m Trial 8 finished with value: -1862.0 and parameters: {'num_leaves': 40, 'max_depth': 5, 'learning_rate': 0.043196443310202566, 'min_child_samples': 55, 'subsample': 0.7263740317547264, 'colsample_bytree': 0.7394350369640735}. Best is trial 4 with value: -1771.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:40:51,117]\u001b[0m Trial 9 finished with value: -2065.0 and parameters: {'num_leaves': 76, 'max_depth': 6, 'learning_rate': 0.09639277349487982, 'min_child_samples': 73, 'subsample': 0.9620054088205456, 'colsample_bytree': 0.8747365614721735}. Best is trial 4 with value: -1771.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:40:59,375]\u001b[0m Trial 10 finished with value: -2371.0 and parameters: {'num_leaves': 114, 'max_depth': 12, 'learning_rate': 0.0595425176805416, 'min_child_samples': 24, 'subsample': 0.8675613742646106, 'colsample_bytree': 0.9156970347949625}. Best is trial 4 with value: -1771.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:41:00,316]\u001b[0m Trial 11 finished with value: -1779.6666666666667 and parameters: {'num_leaves': 95, 'max_depth': 2, 'learning_rate': 0.03137041656570856, 'min_child_samples': 79, 'subsample': 0.8652612674034851, 'colsample_bytree': 0.9998800484901251}. Best is trial 4 with value: -1771.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:41:01,249]\u001b[0m Trial 12 finished with value: -1801.6666666666667 and parameters: {'num_leaves': 94, 'max_depth': 2, 'learning_rate': 0.059089786679391104, 'min_child_samples': 80, 'subsample': 0.8961870173789883, 'colsample_bytree': 0.9426521832599575}. Best is trial 4 with value: -1771.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:41:01,950]\u001b[0m Trial 13 finished with value: -1761.3333333333333 and parameters: {'num_leaves': 128, 'max_depth': 1, 'learning_rate': 0.03132321943733438, 'min_child_samples': 33, 'subsample': 0.7991157977553486, 'colsample_bytree': 0.9093689422952226}. Best is trial 13 with value: -1761.3333333333333.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:41:14,309]\u001b[0m Trial 14 finished with value: -2425.6666666666665 and parameters: {'num_leaves': 125, 'max_depth': 0, 'learning_rate': 0.05478973211818969, 'min_child_samples': 34, 'subsample': 0.7874966474579753, 'colsample_bytree': 0.9015506973934824}. Best is trial 13 with value: -1761.3333333333333.\u001b[0m\n",
+ "\n",
+ "✓ Optuna terminé: 15 trials\n",
+ "Best params: {'num_leaves': 128, 'max_depth': 1, 'learning_rate': 0.03132321943733438, 'min_child_samples': 33, 'subsample': 0.7991157977553486, 'colsample_bytree': 0.9093689422952226, 'class_weight': 'balanced', 'random_state': 42, 'n_estimators': 500, 'verbose': -1}\n",
+ "Best score (negative cost): -1761.33\n",
+ "\n",
+ "📊 Évaluation finale en CV avec best_params...\n",
+ "\n",
+ "✓ Résultats CV (best_params):\n",
+ " AUC moyen: 0.7533\n",
+ " Coût métier moyen: 1761.33\n",
+ " Seuil optimal moyen: 0.53\n",
+ "🏃 View run best_params_cv_evaluation at: http://127.0.0.1:5000/#/experiments/1/runs/e41148dc1e7d4f77b210a8606e25998b\n",
+ "🧪 View experiment at: http://127.0.0.1:5000/#/experiments/1\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "2026/02/05 18:41:20 WARNING mlflow.utils.environment: Failed to resolve installed pip version. ``pip`` will be added to conda.yaml environment spec without a version specifier.\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "✅ Run MLflow 'LGBM_optuna_tuning' terminé avec nested runs\n",
+ "🏃 View run final_model at: http://127.0.0.1:5000/#/experiments/1/runs/947550c9370d4bfd8ba3ff3d94274406\n",
+ "🧪 View experiment at: http://127.0.0.1:5000/#/experiments/1\n",
+ "🏃 View run LGBM_optuna_tuning at: http://127.0.0.1:5000/#/experiments/1/runs/c5231bb6c3794e6cbe1b14c7eceb9ee5\n",
+ "🧪 View experiment at: http://127.0.0.1:5000/#/experiments/1\n"
+ ]
+ }
+ ],
+ "source": [
+ "# ============================================================================\n",
+ "# Optimisation RAPIDE Optuna (~10 min max)\n",
+ "# ============================================================================\n",
+ "import numpy as np\n",
+ "import optuna\n",
+ "from optuna.pruners import MedianPruner\n",
+ "from sklearn.metrics import roc_auc_score, confusion_matrix, f1_score, recall_score\n",
+ "import warnings\n",
+ "\n",
+ "# Désactiver le warning MLflow pip\n",
+ "warnings.filterwarnings('ignore', message='.*Failed to resolve installed pip version.*')\n",
+ "\n",
+ "print(\"\\n\" + \"=\"*70)\n",
+ "print(\"🔧 OPTUNA OPTIMIZATION - Fast iteration\")\n",
+ "print(\"=\"*70)\n",
+ "\n",
+ "# Seuils grossiers pour vitesse (6 valeurs)\n",
+ "thresholds_optuna = np.arange(0.2, 0.8, 0.1)\n",
+ "\n",
+ "# CV accélérée : 3 folds\n",
+ "skf_optuna = StratifiedKFold(n_splits=3, shuffle=True, random_state=42)\n",
+ "\n",
+ "# ==========================================================================\n",
+ "# FONCTION UTILITAIRE : Calcul du coût métier minimal par fold\n",
+ "# ==========================================================================\n",
+ "def compute_min_cost_per_fold(y_true, y_proba, thresholds_array):\n",
+ " \"\"\"\n",
+ " Trouve le seuil optimal qui minimise le coût métier.\n",
+ " Coût = 10 * FN + 1 * FP\n",
+ " \"\"\"\n",
+ " min_cost = float('inf')\n",
+ " best_threshold = 0.5\n",
+ " \n",
+ " for thr in thresholds_array:\n",
+ " y_pred = (y_proba >= thr).astype(int)\n",
+ " tn, fp, fn, tp = confusion_matrix(y_true, y_pred).ravel()\n",
+ " cost = 10 * fn + 1 * fp\n",
+ " \n",
+ " if cost < min_cost:\n",
+ " min_cost = cost\n",
+ " best_threshold = thr\n",
+ " \n",
+ " return min_cost, best_threshold\n",
+ "\n",
+ "# ==========================================================================\n",
+ "# OBJECTIVE OPTUNA\n",
+ "# ==========================================================================\n",
+ "def objective(trial):\n",
+ " params = {\n",
+ " \"num_leaves\": trial.suggest_int(\"num_leaves\", 31, 128),\n",
+ " \"max_depth\": trial.suggest_int(\"max_depth\", -1, 12),\n",
+ " \"learning_rate\": trial.suggest_float(\"learning_rate\", 0.03, 0.1, log=True),\n",
+ " \"n_estimators\": 500,\n",
+ " \"min_child_samples\": trial.suggest_int(\"min_child_samples\", 20, 80),\n",
+ " \"subsample\": trial.suggest_float(\"subsample\", 0.7, 1.0),\n",
+ " \"colsample_bytree\": trial.suggest_float(\"colsample_bytree\", 0.7, 1.0),\n",
+ " \"class_weight\": \"balanced\",\n",
+ " \"random_state\": 42,\n",
+ " \"verbose\": -1,\n",
+ " }\n",
+ " \n",
+ " fold_costs = []\n",
+ " for train_idx, val_idx in skf_optuna.split(X_train, y_train):\n",
+ " X_tr, X_val = X_train.iloc[train_idx], X_train.iloc[val_idx]\n",
+ " y_tr, y_val = y_train.iloc[train_idx], y_train.iloc[val_idx]\n",
+ " \n",
+ " model = LGBMClassifier(**params)\n",
+ " model.fit(X_tr, y_tr)\n",
+ " \n",
+ " y_val_proba = model.predict_proba(X_val)[:, 1]\n",
+ " min_cost, _ = compute_min_cost_per_fold(y_val, y_val_proba, thresholds_optuna)\n",
+ " fold_costs.append(min_cost)\n",
+ " \n",
+ " trial.report(np.mean(fold_costs), step=len(fold_costs)-1)\n",
+ " if trial.should_prune():\n",
+ " raise optuna.TrialPruned()\n",
+ " \n",
+ " return -np.mean(fold_costs)\n",
+ "\n",
+ "# ==========================================================================\n",
+ "# LANCEMENT OPTUNA DANS UN RUN PARENT\n",
+ "# ==========================================================================\n",
+ "print(\"\\n🚀 Lancement Optuna: 15 trials, 3-fold CV, timeout=600s...\")\n",
+ "\n",
+ "# Logging uniformisé pour comparaison facile\n",
+ "with mlflow.start_run(run_name=\"LGBM_optuna_tuning\") as parent_run:\n",
+ " # Tags du run parent\n",
+ " for tag_key, tag_value in MLFLOW_TAGS.items():\n",
+ " mlflow.set_tag(tag_key, tag_value)\n",
+ " mlflow.set_tag(\"model_type\", MODEL_NAME)\n",
+ " mlflow.set_tag(\"phase\", \"optuna_tuning\")\n",
+ " mlflow.set_tag(\"evaluation_type\", \"cv_stratified\")\n",
+ " mlflow.set_tag(\"threshold_optimized\", \"yes\")\n",
+ " \n",
+ " # Optuna optimization\n",
+ " pruner = MedianPruner(n_startup_trials=5, n_warmup_steps=10)\n",
+ " study = optuna.create_study(direction=\"maximize\", pruner=pruner)\n",
+ " study.optimize(objective, n_trials=15, timeout=600, show_progress_bar=True)\n",
+ " \n",
+ " # Récupérer best_params\n",
+ " best_params = study.best_params.copy()\n",
+ " best_params[\"class_weight\"] = \"balanced\"\n",
+ " best_params[\"random_state\"] = 42\n",
+ " best_params[\"n_estimators\"] = 500\n",
+ " best_params[\"verbose\"] = -1\n",
+ " \n",
+ " print(f\"\\n✓ Optuna terminé: {len(study.trials)} trials\")\n",
+ " print(f\"Best params: {best_params}\")\n",
+ " print(f\"Best score (negative cost): {study.best_value:.2f}\")\n",
+ " \n",
+ " # Log des paramètres Optuna\n",
+ " mlflow.log_params(best_params)\n",
+ " mlflow.log_metric(\"optuna_best_score\", study.best_value)\n",
+ " mlflow.log_metric(\"optuna_n_trials\", len(study.trials))\n",
+ " \n",
+ " # ==========================================================================\n",
+ " # RE-ÉVALUATION EN CV AVEC LES MEILLEURS PARAMÈTRES (nested run)\n",
+ " # ==========================================================================\n",
+ " print(\"\\n📊 Évaluation finale en CV avec best_params...\")\n",
+ " \n",
+ " # Logging uniformisé pour comparaison facile\n",
+ " with mlflow.start_run(run_name=\"best_params_cv_evaluation\", nested=True):\n",
+ " # Tags du run nested\n",
+ " for tag_key, tag_value in MLFLOW_TAGS.items():\n",
+ " mlflow.set_tag(tag_key, tag_value)\n",
+ " mlflow.set_tag(\"model_type\", MODEL_NAME)\n",
+ " mlflow.set_tag(\"phase\", \"best_params_cv\")\n",
+ " mlflow.set_tag(\"evaluation_type\", \"cv_stratified\")\n",
+ " mlflow.set_tag(\"threshold_optimized\", \"yes\")\n",
+ " \n",
+ " best_fold_results = []\n",
+ " for fold_idx, (train_idx, val_idx) in enumerate(skf_optuna.split(X_train, y_train), start=1):\n",
+ " X_tr, X_val = X_train.iloc[train_idx], X_train.iloc[val_idx]\n",
+ " y_tr, y_val = y_train.iloc[train_idx], y_train.iloc[val_idx]\n",
+ " \n",
+ " model = LGBMClassifier(**best_params)\n",
+ " model.fit(X_tr, y_tr)\n",
+ " \n",
+ " y_val_proba = model.predict_proba(X_val)[:, 1]\n",
+ " auc = roc_auc_score(y_val, y_val_proba)\n",
+ " min_cost, best_thr = compute_min_cost_per_fold(y_val, y_val_proba, thresholds_optuna)\n",
+ " \n",
+ " # F1 et Recall au seuil optimal du fold\n",
+ " y_val_pred_opt = (y_val_proba >= best_thr).astype(int)\n",
+ " f1_fold = f1_score(y_val, y_val_pred_opt)\n",
+ " recall_fold = recall_score(y_val, y_val_pred_opt)\n",
+ " \n",
+ " best_fold_results.append({\n",
+ " \"fold\": fold_idx,\n",
+ " \"auc\": auc,\n",
+ " \"best_threshold\": best_thr,\n",
+ " \"min_cost\": min_cost,\n",
+ " \"f1_score\": f1_fold,\n",
+ " \"recall_class1\": recall_fold\n",
+ " })\n",
+ " \n",
+ " best_cv_df = pd.DataFrame(best_fold_results)\n",
+ " best_cv_auc_mean = best_cv_df[\"auc\"].mean()\n",
+ " best_cv_min_cost_mean = best_cv_df[\"min_cost\"].mean()\n",
+ " best_cv_best_threshold_mean = best_cv_df[\"best_threshold\"].mean()\n",
+ " best_cv_f1_mean = best_cv_df[\"f1_score\"].mean()\n",
+ " best_cv_recall_mean = best_cv_df[\"recall_class1\"].mean()\n",
+ " \n",
+ " # Log métriques standardisées\n",
+ " mlflow.log_metric(\"auc\", best_cv_auc_mean)\n",
+ " mlflow.log_metric(\"business_cost_min\", best_cv_min_cost_mean)\n",
+ " mlflow.log_metric(\"optimal_threshold\", best_cv_best_threshold_mean)\n",
+ " mlflow.log_metric(\"f1_score\", best_cv_f1_mean)\n",
+ " mlflow.log_metric(\"recall_class1\", best_cv_recall_mean)\n",
+ " mlflow.log_dict(best_cv_df.to_dict(orient=\"records\"), \"cv_results.json\")\n",
+ " \n",
+ " print(f\"\\n✓ Résultats CV (best_params):\")\n",
+ " print(f\" AUC moyen: {best_cv_auc_mean:.4f}\")\n",
+ " print(f\" Coût métier moyen: {best_cv_min_cost_mean:.2f}\")\n",
+ " print(f\" Seuil optimal moyen: {best_cv_best_threshold_mean:.2f}\")\n",
+ " \n",
+ " # Logging uniformisé pour comparaison facile (métriques du meilleur CV)\n",
+ " if \"best_cv_auc_mean\" in locals():\n",
+ " mlflow.log_metric(\"auc\", best_cv_auc_mean)\n",
+ " mlflow.log_metric(\"business_cost_min\", best_cv_min_cost_mean)\n",
+ " mlflow.log_metric(\"optimal_threshold\", best_cv_best_threshold_mean)\n",
+ " mlflow.log_metric(\"f1_score\", best_cv_f1_mean)\n",
+ " mlflow.log_metric(\"recall_class1\", best_cv_recall_mean)\n",
+ " else:\n",
+ " # CV non disponible => valeurs par défaut pour garder l'uniformité\n",
+ " mlflow.log_metric(\"auc\", DEFAULT_METRIC_VALUE)\n",
+ " mlflow.log_metric(\"business_cost_min\", DEFAULT_METRIC_VALUE)\n",
+ " mlflow.log_metric(\"optimal_threshold\", DEFAULT_METRIC_VALUE)\n",
+ " mlflow.log_metric(\"f1_score\", DEFAULT_METRIC_VALUE)\n",
+ " mlflow.log_metric(\"recall_class1\", DEFAULT_METRIC_VALUE)\n",
+ " \n",
+ " # ==========================================================================\n",
+ " # MODÈLE FINAL (nested run)\n",
+ " # ==========================================================================\n",
+ " # Logging uniformisé pour comparaison facile\n",
+ " with mlflow.start_run(run_name=\"final_model\", nested=True):\n",
+ " mlflow.log_params(best_params)\n",
+ " \n",
+ " # Tags du run nested\n",
+ " for tag_key, tag_value in MLFLOW_TAGS.items():\n",
+ " mlflow.set_tag(tag_key, tag_value)\n",
+ " mlflow.set_tag(\"model_type\", MODEL_NAME)\n",
+ " mlflow.set_tag(\"phase\", \"final_model\")\n",
+ " mlflow.set_tag(\"evaluation_type\", \"train_full\")\n",
+ " mlflow.set_tag(\"threshold_optimized\", \"no\")\n",
+ " \n",
+ " # Pas d'évaluation dans ce run => métriques par défaut pour uniformité\n",
+ " mlflow.log_metric(\"auc\", DEFAULT_METRIC_VALUE)\n",
+ " mlflow.log_metric(\"business_cost_min\", DEFAULT_METRIC_VALUE)\n",
+ " mlflow.log_metric(\"optimal_threshold\", DEFAULT_METRIC_VALUE)\n",
+ " mlflow.log_metric(\"f1_score\", DEFAULT_METRIC_VALUE)\n",
+ " mlflow.log_metric(\"recall_class1\", DEFAULT_METRIC_VALUE)\n",
+ " \n",
+ " final_model = LGBMClassifier(**best_params)\n",
+ " final_model.fit(X_train, y_train)\n",
+ " \n",
+ " mlflow.lightgbm.log_model(final_model, name=MODEL_NAME)\n",
+ " \n",
+ " print(\"\\n✅ Run MLflow 'LGBM_optuna_tuning' terminé avec nested runs\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c78e8643",
+ "metadata": {},
+ "source": [
+ "## 🚀 Optuna Tuning AMÉLIORÉ (150 trials, pruning agressif, espace étendu)\n",
+ "\n",
+ "**Objectif** : Améliorer les résultats actuels (AUC ≈0.748, coût ≈1066) en explorant mieux l'espace des hyperparamètres.\n",
+ "\n",
+ "**Améliorations** :\n",
+ "- ✅ 150 trials (vs 15 précédemment) pour plus d'exploration\n",
+ "- ✅ MedianPruner optimisé (n_startup_trials=20, interval_steps=5) pour gagner du temps\n",
+ "- ✅ Espace de recherche étendu (num_leaves, reg_alpha, reg_lambda, min_split_gain)\n",
+ "- ✅ Même scorer : minimisation du coût métier (10*FN + FP) via StratifiedKFold 5-folds\n",
+ "\n",
+ "**Temps estimé** : ~30-40 minutes (pruning accélère fortement)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "3f9d151b",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "================================================================================\n",
+ "🔧 OPTUNA OPTIMIZATION - IMPROVED (150 trials, pruning agressif, espace étendu)\n",
+ "================================================================================\n",
+ "\n",
+ "🚀 Lancement Optuna IMPROVED:\n",
+ " - 150 trials\n",
+ " - 5-fold CV\n",
+ " - Pruning: MedianPruner (n_startup_trials=20, interval_steps=5)\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\u001b[32m[I 2026-02-05 18:41:21,327]\u001b[0m A new study created in memory with name: no-name-96fe58f8-f256-4a0c-adb6-20afa72fac1b\u001b[0m\n"
+ ]
+ },
+ {
+ "data": {
+ "application/vnd.jupyter.widget-view+json": {
+ "model_id": "6a2f56a522154651aadb45a47e050bef",
+ "version_major": 2,
+ "version_minor": 0
+ },
+ "text/plain": [
+ " 0%| | 0/150 [00:00, ?it/s]"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\u001b[32m[I 2026-02-05 18:41:39,890]\u001b[0m Trial 0 finished with value: -1081.4 and parameters: {'num_leaves': 266, 'max_depth': -1, 'learning_rate': 0.016693537951309723, 'min_child_samples': 49, 'subsample': 0.6570112545901243, 'colsample_bytree': 0.994145532195986, 'reg_alpha': 0.5475735255460498, 'reg_lambda': 0.4799756961131505, 'min_split_gain': 0.17925422078780429}. Best is trial 0 with value: -1081.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:41:51,034]\u001b[0m Trial 1 finished with value: -1104.8 and parameters: {'num_leaves': 185, 'max_depth': 14, 'learning_rate': 0.02670807890018913, 'min_child_samples': 47, 'subsample': 0.9753131064251738, 'colsample_bytree': 0.7644915788808408, 'reg_alpha': 0.9531456174706642, 'reg_lambda': 0.0007056082337590919, 'min_split_gain': 0.05286399296311872}. Best is trial 0 with value: -1081.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:41:54,664]\u001b[0m Trial 2 finished with value: -1103.8 and parameters: {'num_leaves': 223, 'max_depth': 7, 'learning_rate': 0.06485492206113362, 'min_child_samples': 47, 'subsample': 0.9996867007211679, 'colsample_bytree': 0.6370674318032191, 'reg_alpha': 0.9486229120538723, 'reg_lambda': 0.20460095322764715, 'min_split_gain': 0.23050698972592332}. Best is trial 0 with value: -1081.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:41:58,938]\u001b[0m Trial 3 finished with value: -1086.0 and parameters: {'num_leaves': 269, 'max_depth': 5, 'learning_rate': 0.04410473859853445, 'min_child_samples': 14, 'subsample': 0.7671112859862378, 'colsample_bytree': 0.6575502510279769, 'reg_alpha': 0.8892411670193596, 'reg_lambda': 0.7402554047959348, 'min_split_gain': 0.2254644727444976}. Best is trial 0 with value: -1081.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:42:07,292]\u001b[0m Trial 4 finished with value: -1090.8 and parameters: {'num_leaves': 222, 'max_depth': 15, 'learning_rate': 0.038998873586422685, 'min_child_samples': 6, 'subsample': 0.8097471061052666, 'colsample_bytree': 0.9602379976390947, 'reg_alpha': 0.6170334891409499, 'reg_lambda': 0.7784657825821073, 'min_split_gain': 0.4531661080377516}. Best is trial 0 with value: -1081.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:42:10,670]\u001b[0m Trial 5 finished with value: -1035.0 and parameters: {'num_leaves': 134, 'max_depth': 4, 'learning_rate': 0.011501331593847157, 'min_child_samples': 90, 'subsample': 0.801240003260846, 'colsample_bytree': 0.9169198736340286, 'reg_alpha': 0.017227242118141972, 'reg_lambda': 0.1382469932841791, 'min_split_gain': 0.3522996693974083}. Best is trial 5 with value: -1035.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:42:22,080]\u001b[0m Trial 6 finished with value: -1071.2 and parameters: {'num_leaves': 141, 'max_depth': 14, 'learning_rate': 0.020424142144093795, 'min_child_samples': 57, 'subsample': 0.6768352935152553, 'colsample_bytree': 0.9992572655526448, 'reg_alpha': 0.18996121316943215, 'reg_lambda': 0.644643344003764, 'min_split_gain': 0.4164780009197757}. Best is trial 5 with value: -1035.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:42:42,077]\u001b[0m Trial 7 finished with value: -1088.8 and parameters: {'num_leaves': 147, 'max_depth': -1, 'learning_rate': 0.013730827166181682, 'min_child_samples': 14, 'subsample': 0.9033012828316892, 'colsample_bytree': 0.9966266520895309, 'reg_alpha': 0.7492375501079872, 'reg_lambda': 0.48150972623087496, 'min_split_gain': 0.36942105234920647}. Best is trial 5 with value: -1035.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:42:46,740]\u001b[0m Trial 8 finished with value: -1068.6 and parameters: {'num_leaves': 291, 'max_depth': 16, 'learning_rate': 0.03834286658093986, 'min_child_samples': 46, 'subsample': 0.7372352487540816, 'colsample_bytree': 0.7208609493060808, 'reg_alpha': 0.9659526956107954, 'reg_lambda': 0.75952377143904, 'min_split_gain': 0.42685214963127527}. Best is trial 5 with value: -1035.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:42:48,956]\u001b[0m Trial 9 finished with value: -1099.6 and parameters: {'num_leaves': 197, 'max_depth': 10, 'learning_rate': 0.1546740458140197, 'min_child_samples': 46, 'subsample': 0.67095615418435, 'colsample_bytree': 0.7500738377001579, 'reg_alpha': 0.9205962221635383, 'reg_lambda': 0.29956082422004593, 'min_split_gain': 0.38014543876107576}. Best is trial 5 with value: -1035.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:42:51,429]\u001b[0m Trial 10 finished with value: -1034.0 and parameters: {'num_leaves': 62, 'max_depth': 3, 'learning_rate': 0.010353603467800438, 'min_child_samples': 98, 'subsample': 0.8649018837091864, 'colsample_bytree': 0.8634734618157331, 'reg_alpha': 0.1572702412198832, 'reg_lambda': 0.035101694693425625, 'min_split_gain': 0.3176977522605946}. Best is trial 10 with value: -1034.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:42:54,032]\u001b[0m Trial 11 finished with value: -1041.4 and parameters: {'num_leaves': 68, 'max_depth': 3, 'learning_rate': 0.010225238680134266, 'min_child_samples': 97, 'subsample': 0.8528386140569927, 'colsample_bytree': 0.8889590177626239, 'reg_alpha': 0.025863640288005474, 'reg_lambda': 0.02392709794625985, 'min_split_gain': 0.2983968600212282}. Best is trial 10 with value: -1034.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:42:56,527]\u001b[0m Trial 12 finished with value: -1048.2 and parameters: {'num_leaves': 50, 'max_depth': 3, 'learning_rate': 0.010916800276043838, 'min_child_samples': 98, 'subsample': 0.8849210231968604, 'colsample_bytree': 0.8622685042923552, 'reg_alpha': 0.2873763940368542, 'reg_lambda': 0.20140964274312095, 'min_split_gain': 0.3147794129568919}. Best is trial 10 with value: -1034.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:43:02,039]\u001b[0m Trial 13 finished with value: -1116.6 and parameters: {'num_leaves': 102, 'max_depth': 9, 'learning_rate': 0.07138744410051415, 'min_child_samples': 82, 'subsample': 0.8211871864173153, 'colsample_bytree': 0.8492272089743459, 'reg_alpha': 0.016951849871684077, 'reg_lambda': 0.9677517773493767, 'min_split_gain': 0.1484014403381193}. Best is trial 10 with value: -1034.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:43:11,085]\u001b[0m Trial 14 finished with value: -1065.8 and parameters: {'num_leaves': 103, 'max_depth': 20, 'learning_rate': 0.02305860075490811, 'min_child_samples': 78, 'subsample': 0.9314656398377863, 'colsample_bytree': 0.9074927113458334, 'reg_alpha': 0.34652235629281714, 'reg_lambda': 0.14601178734062337, 'min_split_gain': 0.4875925794766387}. Best is trial 10 with value: -1034.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:43:12,957]\u001b[0m Trial 15 finished with value: -1067.2 and parameters: {'num_leaves': 97, 'max_depth': 2, 'learning_rate': 0.17625789487220864, 'min_child_samples': 79, 'subsample': 0.7502699100652832, 'colsample_bytree': 0.8096576795309957, 'reg_alpha': 0.12012920388757116, 'reg_lambda': 0.3695919575296014, 'min_split_gain': 0.3183153060555102}. Best is trial 10 with value: -1034.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:43:19,219]\u001b[0m Trial 16 finished with value: -1049.0 and parameters: {'num_leaves': 134, 'max_depth': 6, 'learning_rate': 0.01467396081292426, 'min_child_samples': 66, 'subsample': 0.6104439270009858, 'colsample_bytree': 0.936954484381244, 'reg_alpha': 0.3822448328942983, 'reg_lambda': 0.09560151235149952, 'min_split_gain': 0.07339973599465449}. Best is trial 10 with value: -1034.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:43:22,039]\u001b[0m Trial 17 finished with value: -1122.4 and parameters: {'num_leaves': 76, 'max_depth': 10, 'learning_rate': 0.11370334506441186, 'min_child_samples': 88, 'subsample': 0.8469631919176867, 'colsample_bytree': 0.8182442519343788, 'reg_alpha': 0.18664087826781586, 'reg_lambda': 0.2964387719315268, 'min_split_gain': 0.29053164760713973}. Best is trial 10 with value: -1034.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:43:23,336]\u001b[0m Trial 18 finished with value: -1038.0 and parameters: {'num_leaves': 124, 'max_depth': 1, 'learning_rate': 0.027591783190349, 'min_child_samples': 68, 'subsample': 0.7860278450580458, 'colsample_bytree': 0.9201441826243805, 'reg_alpha': 0.47038949528866675, 'reg_lambda': 0.37410229995723876, 'min_split_gain': 0.3602024521025495}. Best is trial 10 with value: -1034.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:43:28,253]\u001b[0m Trial 19 finished with value: -1067.2 and parameters: {'num_leaves': 166, 'max_depth': 5, 'learning_rate': 0.018903423180210134, 'min_child_samples': 31, 'subsample': 0.9397095249149929, 'colsample_bytree': 0.8638787329716091, 'reg_alpha': 0.12178657332081681, 'reg_lambda': 0.14598431245737842, 'min_split_gain': 0.13985676089027443}. Best is trial 10 with value: -1034.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:43:34,878]\u001b[0m Trial 20 finished with value: -1040.4 and parameters: {'num_leaves': 51, 'max_depth': 8, 'learning_rate': 0.011908198506234185, 'min_child_samples': 90, 'subsample': 0.713526653670425, 'colsample_bytree': 0.7725768071366149, 'reg_alpha': 0.28216492806182025, 'reg_lambda': 0.08049469932407713, 'min_split_gain': 0.2592020777484381}. Best is trial 10 with value: -1034.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:43:36,185]\u001b[0m Trial 21 finished with value: -1025.0 and parameters: {'num_leaves': 122, 'max_depth': 1, 'learning_rate': 0.029006746345631752, 'min_child_samples': 66, 'subsample': 0.7817801678943633, 'colsample_bytree': 0.9032423243377234, 'reg_alpha': 0.4535148720900905, 'reg_lambda': 0.37265364867439893, 'min_split_gain': 0.36884863469808365}. Best is trial 21 with value: -1025.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:43:37,594]\u001b[0m Trial 22 finished with value: -1032.2 and parameters: {'num_leaves': 114, 'max_depth': 1, 'learning_rate': 0.031017356780897003, 'min_child_samples': 69, 'subsample': 0.8521990306661448, 'colsample_bytree': 0.9538915083539343, 'reg_alpha': 0.6879013968854348, 'reg_lambda': 0.256209348988537, 'min_split_gain': 0.35274165288733134}. Best is trial 21 with value: -1025.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:43:38,869]\u001b[0m Trial 23 finished with value: -1028.4 and parameters: {'num_leaves': 83, 'max_depth': 1, 'learning_rate': 0.07107973093942835, 'min_child_samples': 67, 'subsample': 0.8607549206105287, 'colsample_bytree': 0.9629185920802125, 'reg_alpha': 0.7623477768837679, 'reg_lambda': 0.5688731217441236, 'min_split_gain': 0.41324740666256093}. Best is trial 21 with value: -1025.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:43:40,236]\u001b[0m Trial 24 finished with value: -1030.2 and parameters: {'num_leaves': 84, 'max_depth': 1, 'learning_rate': 0.06709856723300797, 'min_child_samples': 66, 'subsample': 0.8316541801804294, 'colsample_bytree': 0.9634638208481686, 'reg_alpha': 0.7291426639654766, 'reg_lambda': 0.5489043952815269, 'min_split_gain': 0.4930004651635363}. Best is trial 21 with value: -1025.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:43:44,420]\u001b[0m Trial 25 finished with value: -1054.6 and parameters: {'num_leaves': 87, 'max_depth': 0, 'learning_rate': 0.0604040588135574, 'min_child_samples': 59, 'subsample': 0.8260804718938977, 'colsample_bytree': 0.9650890233520055, 'reg_alpha': 0.8069375271690991, 'reg_lambda': 0.5975133772030788, 'min_split_gain': 0.49570494186132497}. Best is trial 21 with value: -1025.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:43:45,803]\u001b[0m Trial 26 finished with value: -1030.6 and parameters: {'num_leaves': 87, 'max_depth': 1, 'learning_rate': 0.09024387391832413, 'min_child_samples': 35, 'subsample': 0.9119003904050137, 'colsample_bytree': 0.8950357662086986, 'reg_alpha': 0.6210417945855631, 'reg_lambda': 0.599401095855345, 'min_split_gain': 0.45057930493584064}. Best is trial 21 with value: -1025.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:43:50,808]\u001b[0m Trial 27 finished with value: -1088.2 and parameters: {'num_leaves': 165, 'max_depth': -1, 'learning_rate': 0.050417935067728535, 'min_child_samples': 73, 'subsample': 0.7767770625878436, 'colsample_bytree': 0.949084663862532, 'reg_alpha': 0.8240414827453879, 'reg_lambda': 0.4053802822745928, 'min_split_gain': 0.3993897436600203}. Best is trial 21 with value: -1025.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:43:54,146]\u001b[0m Trial 28 finished with value: -1083.8 and parameters: {'num_leaves': 117, 'max_depth': 12, 'learning_rate': 0.09044110006126158, 'min_child_samples': 61, 'subsample': 0.718385727671835, 'colsample_bytree': 0.9317736901900717, 'reg_alpha': 0.46622416837892894, 'reg_lambda': 0.5388769164575807, 'min_split_gain': 0.4655114198375118}. Best is trial 21 with value: -1025.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:43:57,083]\u001b[0m Trial 29 finished with value: -1080.4 and parameters: {'num_leaves': 81, 'max_depth': -1, 'learning_rate': 0.12330303245205425, 'min_child_samples': 54, 'subsample': 0.8744846853850938, 'colsample_bytree': 0.9751849234752915, 'reg_alpha': 0.5455131149272687, 'reg_lambda': 0.44340182088943203, 'min_split_gain': 0.42434024937077075}. Best is trial 21 with value: -1025.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:44:00,541]\u001b[0m Trial 30 finished with value: -1096.4 and parameters: {'num_leaves': 155, 'max_depth': 5, 'learning_rate': 0.08019672546671461, 'min_child_samples': 35, 'subsample': 0.8266224213476925, 'colsample_bytree': 0.8329795011646073, 'reg_alpha': 0.6669746561844467, 'reg_lambda': 0.6819698232821908, 'min_split_gain': 0.49931841209195027}. Best is trial 21 with value: -1025.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:44:01,864]\u001b[0m Trial 31 finished with value: -1030.8 and parameters: {'num_leaves': 91, 'max_depth': 1, 'learning_rate': 0.09019636535141519, 'min_child_samples': 40, 'subsample': 0.9027821737266543, 'colsample_bytree': 0.8854320367090975, 'reg_alpha': 0.5903613745139494, 'reg_lambda': 0.5930652404991126, 'min_split_gain': 0.45095702543813765}. Best is trial 21 with value: -1025.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:44:03,210]\u001b[0m Trial 32 finished with value: -1031.6 and parameters: {'num_leaves': 111, 'max_depth': 1, 'learning_rate': 0.05207342987646941, 'min_child_samples': 31, 'subsample': 0.9420201890107008, 'colsample_bytree': 0.8985263088043176, 'reg_alpha': 0.7251496161974826, 'reg_lambda': 0.533512397097491, 'min_split_gain': 0.4572434773536293}. Best is trial 21 with value: -1025.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:44:05,144]\u001b[0m Trial 33 finished with value: -1058.4 and parameters: {'num_leaves': 67, 'max_depth': 2, 'learning_rate': 0.12004417637949043, 'min_child_samples': 61, 'subsample': 0.9107430127354104, 'colsample_bytree': 0.9755131160949572, 'reg_alpha': 0.5369328447376858, 'reg_lambda': 0.8495924470364972, 'min_split_gain': 0.40703873159198267}. Best is trial 21 with value: -1025.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:44:16,890]\u001b[0m Trial 34 finished with value: -1164.6 and parameters: {'num_leaves': 82, 'max_depth': 0, 'learning_rate': 0.0580700119830793, 'min_child_samples': 74, 'subsample': 0.9692832743545016, 'colsample_bytree': 0.93417602028171, 'reg_alpha': 0.8331481421538041, 'reg_lambda': 0.5813033361530247, 'min_split_gain': 0.0006216912172202771}. Best is trial 21 with value: -1025.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:44:20,541]\u001b[0m Trial 35 finished with value: -1068.8 and parameters: {'num_leaves': 193, 'max_depth': 4, 'learning_rate': 0.03595194222333093, 'min_child_samples': 20, 'subsample': 0.8859243757016522, 'colsample_bytree': 0.8828782474386854, 'reg_alpha': 0.6283207949672179, 'reg_lambda': 0.45428494830399857, 'min_split_gain': 0.44223620307016875}. Best is trial 21 with value: -1025.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:44:24,539]\u001b[0m Trial 36 finished with value: -1101.8 and parameters: {'num_leaves': 125, 'max_depth': 6, 'learning_rate': 0.07419816587707027, 'min_child_samples': 51, 'subsample': 0.9725333275741816, 'colsample_bytree': 0.9781188835509468, 'reg_alpha': 0.7393147897710547, 'reg_lambda': 0.6582391784220409, 'min_split_gain': 0.38933612229267484}. Best is trial 21 with value: -1025.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:44:27,102]\u001b[0m Trial 37 finished with value: -1091.0 and parameters: {'num_leaves': 94, 'max_depth': 7, 'learning_rate': 0.10428306938709737, 'min_child_samples': 64, 'subsample': 0.9979624949758966, 'colsample_bytree': 0.7015660190115132, 'reg_alpha': 0.433406834085705, 'reg_lambda': 0.8404698874406724, 'min_split_gain': 0.4750770739025744}. Best is trial 21 with value: -1025.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:44:29,007]\u001b[0m Trial 38 finished with value: -1029.8 and parameters: {'num_leaves': 63, 'max_depth': 2, 'learning_rate': 0.04757660798726664, 'min_child_samples': 42, 'subsample': 0.8026160244789441, 'colsample_bytree': 0.7856151536930047, 'reg_alpha': 0.8681638967757469, 'reg_lambda': 0.5170516319056936, 'min_split_gain': 0.4312336505342841}. Best is trial 21 with value: -1025.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:44:32,363]\u001b[0m Trial 39 finished with value: -1070.8 and parameters: {'num_leaves': 69, 'max_depth': 4, 'learning_rate': 0.04196463609220358, 'min_child_samples': 55, 'subsample': 0.8044481278467371, 'colsample_bytree': 0.7777776944588941, 'reg_alpha': 0.9979052729687233, 'reg_lambda': 0.5146012768654937, 'min_split_gain': 0.3396047919301106}. Best is trial 21 with value: -1025.0.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:44:34,234]\u001b[0m Trial 40 finished with value: -1022.4 and parameters: {'num_leaves': 59, 'max_depth': 2, 'learning_rate': 0.032806523175050506, 'min_child_samples': 42, 'subsample': 0.767549215747962, 'colsample_bytree': 0.6255336222580747, 'reg_alpha': 0.7842598468645098, 'reg_lambda': 0.7211244973764288, 'min_split_gain': 0.43151207570298117}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:44:36,012]\u001b[0m Trial 41 finished with value: -1037.4 and parameters: {'num_leaves': 60, 'max_depth': 2, 'learning_rate': 0.03189495472615652, 'min_child_samples': 49, 'subsample': 0.7601418904643442, 'colsample_bytree': 0.7265747595019317, 'reg_alpha': 0.8778529964379458, 'reg_lambda': 0.7320754245202464, 'min_split_gain': 0.42717240474796186}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:44:40,292]\u001b[0m Trial 42 finished with value: -1060.2 and parameters: {'num_leaves': 226, 'max_depth': 0, 'learning_rate': 0.05246848889444312, 'min_child_samples': 42, 'subsample': 0.7857999129697764, 'colsample_bytree': 0.6247375389646326, 'reg_alpha': 0.7832899219993983, 'reg_lambda': 0.7080413973404657, 'min_split_gain': 0.3905233004844851}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:44:44,880]\u001b[0m Trial 43 finished with value: -1067.6 and parameters: {'num_leaves': 75, 'max_depth': -1, 'learning_rate': 0.04698985363192932, 'min_child_samples': 42, 'subsample': 0.7363129140267201, 'colsample_bytree': 0.6590964676131141, 'reg_alpha': 0.8914128032259607, 'reg_lambda': 0.4816665446625062, 'min_split_gain': 0.4235427531005187}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:44:47,120]\u001b[0m Trial 44 finished with value: -1024.6 and parameters: {'num_leaves': 58, 'max_depth': 3, 'learning_rate': 0.024799045279469807, 'min_child_samples': 72, 'subsample': 0.8393071868104657, 'colsample_bytree': 0.6722881659893643, 'reg_alpha': 0.8511898162174342, 'reg_lambda': 0.8108302676254302, 'min_split_gain': 0.47272672270768545}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:44:49,511]\u001b[0m Trial 45 finished with value: -1029.0 and parameters: {'num_leaves': 50, 'max_depth': 3, 'learning_rate': 0.024408794389536644, 'min_child_samples': 22, 'subsample': 0.7981767317473196, 'colsample_bytree': 0.6630295087104128, 'reg_alpha': 0.8557751603855278, 'reg_lambda': 0.8215593119910174, 'min_split_gain': 0.3806394903664301}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:44:51,960]\u001b[0m Trial 46 finished with value: -1022.6 and parameters: {'num_leaves': 50, 'max_depth': 3, 'learning_rate': 0.022782849239638973, 'min_child_samples': 20, 'subsample': 0.6985180686699288, 'colsample_bytree': 0.6586985508830234, 'reg_alpha': 0.9338352746203398, 'reg_lambda': 0.8322132609205912, 'min_split_gain': 0.2631419854040222}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:44:55,416]\u001b[0m Trial 47 finished with value: -1037.0 and parameters: {'num_leaves': 56, 'max_depth': 4, 'learning_rate': 0.016777074865153438, 'min_child_samples': 6, 'subsample': 0.6952027994639679, 'colsample_bytree': 0.6029732392158855, 'reg_alpha': 0.9434530041399936, 'reg_lambda': 0.9517540153244954, 'min_split_gain': 0.23852497854264518}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:44:59,366]\u001b[0m Trial 48 finished with value: -1042.8 and parameters: {'num_leaves': 104, 'max_depth': 5, 'learning_rate': 0.019080971050565613, 'min_child_samples': 72, 'subsample': 0.6428337648894404, 'colsample_bytree': 0.6810914292853981, 'reg_alpha': 0.9195982733937967, 'reg_lambda': 0.8848169381959525, 'min_split_gain': 0.2037231780018843}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:45:01,723]\u001b[0m Trial 49 finished with value: -1032.6 and parameters: {'num_leaves': 71, 'max_depth': 3, 'learning_rate': 0.02250698236510113, 'min_child_samples': 84, 'subsample': 0.7450287219084867, 'colsample_bytree': 0.6296720378053966, 'reg_alpha': 0.9898191069054789, 'reg_lambda': 0.7805911539085918, 'min_split_gain': 0.270195224852277}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:45:10,594]\u001b[0m Trial 50 finished with value: -1081.0 and parameters: {'num_leaves': 297, 'max_depth': 18, 'learning_rate': 0.028134680237694897, 'min_child_samples': 22, 'subsample': 0.6381453098046674, 'colsample_bytree': 0.7533013918946876, 'reg_alpha': 0.7730346292936405, 'reg_lambda': 0.9391616417158309, 'min_split_gain': 0.3330510589406528}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:45:13,227]\u001b[0m Trial 51 finished with value: -1042.2 and parameters: {'num_leaves': 51, 'max_depth': 3, 'learning_rate': 0.02410544045824415, 'min_child_samples': 21, 'subsample': 0.7666426900141751, 'colsample_bytree': 0.6495708991309268, 'reg_alpha': 0.8533733101332238, 'reg_lambda': 0.8028392780917153, 'min_split_gain': 0.3899859726820315}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:45:19,155]\u001b[0m Trial 52 finished with value: -1098.6 and parameters: {'num_leaves': 59, 'max_depth': 6, 'learning_rate': 0.0358752569451843, 'min_child_samples': 15, 'subsample': 0.7907710324852789, 'colsample_bytree': 0.6751462921754169, 'reg_alpha': 0.9361965188575719, 'reg_lambda': 0.8935309054202262, 'min_split_gain': 0.3708134802785597}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:45:22,113]\u001b[0m Trial 53 finished with value: -1044.0 and parameters: {'num_leaves': 51, 'max_depth': 3, 'learning_rate': 0.025632839019869413, 'min_child_samples': 26, 'subsample': 0.8377686701437984, 'colsample_bytree': 0.6155577583609612, 'reg_alpha': 0.9019936848958403, 'reg_lambda': 0.8016910570862376, 'min_split_gain': 0.17460939012657398}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:45:24,054]\u001b[0m Trial 54 finished with value: -1043.8 and parameters: {'num_leaves': 75, 'max_depth': 2, 'learning_rate': 0.032146436047820186, 'min_child_samples': 8, 'subsample': 0.8134014553122612, 'colsample_bytree': 0.6993901415699668, 'reg_alpha': 0.7925757938367062, 'reg_lambda': 0.753787986875801, 'min_split_gain': 0.47407923883765146}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:45:39,678]\u001b[0m Trial 55 finished with value: -1073.0 and parameters: {'num_leaves': 212, 'max_depth': 0, 'learning_rate': 0.016679107898559937, 'min_child_samples': 13, 'subsample': 0.6901543179648622, 'colsample_bytree': 0.6531939724017729, 'reg_alpha': 0.6877005487589055, 'reg_lambda': 0.9073982499736022, 'min_split_gain': 0.296355561675263}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:45:42,752]\u001b[0m Trial 56 finished with value: -1048.6 and parameters: {'num_leaves': 64, 'max_depth': 4, 'learning_rate': 0.022796082489332205, 'min_child_samples': 76, 'subsample': 0.726990315875795, 'colsample_bytree': 0.6772390602067303, 'reg_alpha': 0.39240964741212786, 'reg_lambda': 0.839472704150777, 'min_split_gain': 0.3727944681672436}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:45:48,457]\u001b[0m Trial 57 finished with value: -1055.2 and parameters: {'num_leaves': 264, 'max_depth': 7, 'learning_rate': 0.020280488971181992, 'min_child_samples': 70, 'subsample': 0.8652584262564856, 'colsample_bytree': 0.6433268528944751, 'reg_alpha': 0.8347135859268096, 'reg_lambda': 0.7197455038040836, 'min_split_gain': 0.411947019685171}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:45:58,301]\u001b[0m Trial 58 finished with value: -1096.8 and parameters: {'num_leaves': 98, 'max_depth': 12, 'learning_rate': 0.02976267420557276, 'min_child_samples': 25, 'subsample': 0.8444392269471185, 'colsample_bytree': 0.7096348764659164, 'reg_alpha': 0.7646682453518102, 'reg_lambda': 0.6721379085980327, 'min_split_gain': 0.21078697852531025}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:46:00,698]\u001b[0m Trial 59 finished with value: -1055.2 and parameters: {'num_leaves': 78, 'max_depth': 3, 'learning_rate': 0.0382503580771684, 'min_child_samples': 84, 'subsample': 0.7558865801928543, 'colsample_bytree': 0.7371352689543127, 'reg_alpha': 0.9620714295591365, 'reg_lambda': 0.2941617990787794, 'min_split_gain': 0.2774092007304518}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:46:04,675]\u001b[0m Trial 60 finished with value: -1044.8 and parameters: {'num_leaves': 136, 'max_depth': 5, 'learning_rate': 0.01744326179438076, 'min_child_samples': 79, 'subsample': 0.7747945796109106, 'colsample_bytree': 0.6643486606288238, 'reg_alpha': 0.5850661227395334, 'reg_lambda': 0.7845650419104544, 'min_split_gain': 0.3312292856616811}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:46:12,600]\u001b[0m Trial 61 finished with value: -1067.8 and parameters: {'num_leaves': 62, 'max_depth': 0, 'learning_rate': 0.025101351404954755, 'min_child_samples': 37, 'subsample': 0.7995250235403446, 'colsample_bytree': 0.790825036497753, 'reg_alpha': 0.8570724905155556, 'reg_lambda': 0.6242817822665526, 'min_split_gain': 0.4369240820389922}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:46:14,510]\u001b[0m Trial 62 finished with value: -1034.0 and parameters: {'num_leaves': 59, 'max_depth': 2, 'learning_rate': 0.04311718921030828, 'min_child_samples': 45, 'subsample': 0.8145118832727688, 'colsample_bytree': 0.6034266015712483, 'reg_alpha': 0.867956651143907, 'reg_lambda': 0.9873634206552753, 'min_split_gain': 0.35438361155843306}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:46:16,243]\u001b[0m Trial 63 finished with value: -1030.8 and parameters: {'num_leaves': 68, 'max_depth': 2, 'learning_rate': 0.03330347160473697, 'min_child_samples': 30, 'subsample': 0.798297545479631, 'colsample_bytree': 0.6888659894180839, 'reg_alpha': 0.8971909537498006, 'reg_lambda': 0.3926479317617986, 'min_split_gain': 0.4759497819293755}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:46:17,606]\u001b[0m Trial 64 finished with value: -1052.0 and parameters: {'num_leaves': 51, 'max_depth': 1, 'learning_rate': 0.014224224607282468, 'min_child_samples': 57, 'subsample': 0.8631658722387859, 'colsample_bytree': 0.6345232233388193, 'reg_alpha': 0.818996929355226, 'reg_lambda': 0.3324431954754196, 'min_split_gain': 0.407911928221005}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:46:19,457]\u001b[0m Trial 65 finished with value: -1042.4 and parameters: {'num_leaves': 84, 'max_depth': 2, 'learning_rate': 0.028008593043027205, 'min_child_samples': 65, 'subsample': 0.7040152585299572, 'colsample_bytree': 0.8495548108115775, 'reg_alpha': 0.5026452997630718, 'reg_lambda': 0.43608496655543916, 'min_split_gain': 0.43666974357355176}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:46:35,766]\u001b[0m Trial 66 finished with value: -1107.2 and parameters: {'num_leaves': 73, 'max_depth': -1, 'learning_rate': 0.021117347566083758, 'min_child_samples': 18, 'subsample': 0.8187982991378145, 'colsample_bytree': 0.6689635110792012, 'reg_alpha': 0.2958389655013363, 'reg_lambda': 0.8656362795439179, 'min_split_gain': 0.08494725356823823}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:46:38,483]\u001b[0m Trial 67 finished with value: -1035.2 and parameters: {'num_leaves': 92, 'max_depth': 3, 'learning_rate': 0.03577417558913359, 'min_child_samples': 27, 'subsample': 0.6683613384923233, 'colsample_bytree': 0.6128476290220038, 'reg_alpha': 0.7035094785676782, 'reg_lambda': 0.6862387907475859, 'min_split_gain': 0.31365810038221575}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:46:39,809]\u001b[0m Trial 68 finished with value: -1036.0 and parameters: {'num_leaves': 108, 'max_depth': 1, 'learning_rate': 0.04031066478105179, 'min_child_samples': 62, 'subsample': 0.7789809315948115, 'colsample_bytree': 0.9193531784888159, 'reg_alpha': 0.9668842037149434, 'reg_lambda': 0.8179144430740791, 'min_split_gain': 0.45871922199330556}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:46:44,287]\u001b[0m Trial 69 finished with value: -1076.6 and parameters: {'num_leaves': 149, 'max_depth': 8, 'learning_rate': 0.04682118466497328, 'min_child_samples': 49, 'subsample': 0.8822326466248906, 'colsample_bytree': 0.6400995502184905, 'reg_alpha': 0.661851768150957, 'reg_lambda': 0.7571209311549333, 'min_split_gain': 0.37746778636685424}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:46:59,452]\u001b[0m Trial 70 finished with value: -1071.6 and parameters: {'num_leaves': 57, 'max_depth': 0, 'learning_rate': 0.012820778986998784, 'min_child_samples': 37, 'subsample': 0.8320701157583273, 'colsample_bytree': 0.9880559759913878, 'reg_alpha': 0.9089330440645529, 'reg_lambda': 0.5616080845854869, 'min_split_gain': 0.4171727291851871}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:00,628]\u001b[0m Trial 71 finished with value: -1026.0 and parameters: {'num_leaves': 124, 'max_depth': 1, 'learning_rate': 0.06826844605234039, 'min_child_samples': 68, 'subsample': 0.8381685339881172, 'colsample_bytree': 0.9433658628504471, 'reg_alpha': 0.7254462906609297, 'reg_lambda': 0.640796469559949, 'min_split_gain': 0.47912584905548405}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:02,369]\u001b[0m Trial 72 finished with value: -1035.6 and parameters: {'num_leaves': 169, 'max_depth': 2, 'learning_rate': 0.07068296388822809, 'min_child_samples': 70, 'subsample': 0.8513720341329545, 'colsample_bytree': 0.9423782842758518, 'reg_alpha': 0.7373914765482716, 'reg_lambda': 0.6312888487482142, 'min_split_gain': 0.43944666186506326}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:03,514]\u001b[0m Trial 73 finished with value: -1028.2 and parameters: {'num_leaves': 128, 'max_depth': 1, 'learning_rate': 0.06343068143903835, 'min_child_samples': 74, 'subsample': 0.8391930329435142, 'colsample_bytree': 0.8212920683872698, 'reg_alpha': 0.8072838588433806, 'reg_lambda': 0.5115090833956697, 'min_split_gain': 0.4799484834991254}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:04,644]\u001b[0m Trial 74 finished with value: -1026.8 and parameters: {'num_leaves': 118, 'max_depth': 1, 'learning_rate': 0.08062105191040424, 'min_child_samples': 68, 'subsample': 0.8598892337959947, 'colsample_bytree': 0.9085918079319966, 'reg_alpha': 0.8054383357640299, 'reg_lambda': 0.6267025430832264, 'min_split_gain': 0.4889212795070642}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:08,535]\u001b[0m Trial 75 finished with value: -1091.2 and parameters: {'num_leaves': 124, 'max_depth': 0, 'learning_rate': 0.06065769514420941, 'min_child_samples': 75, 'subsample': 0.8618825834740882, 'colsample_bytree': 0.9086052888259707, 'reg_alpha': 0.8026239986253921, 'reg_lambda': 0.6179055294650554, 'min_split_gain': 0.4736314053862346}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:11,573]\u001b[0m Trial 76 finished with value: -1070.2 and parameters: {'num_leaves': 142, 'max_depth': -1, 'learning_rate': 0.08374630776249742, 'min_child_samples': 67, 'subsample': 0.8977566316259062, 'colsample_bytree': 0.8764216579857357, 'reg_alpha': 0.7568189575028166, 'reg_lambda': 0.5656233795300013, 'min_split_gain': 0.48536688105820347}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:12,705]\u001b[0m Trial 77 finished with value: -1036.6 and parameters: {'num_leaves': 120, 'max_depth': 1, 'learning_rate': 0.10294708033405155, 'min_child_samples': 81, 'subsample': 0.8449464180202312, 'colsample_bytree': 0.8055686577143124, 'reg_alpha': 0.7163396833318164, 'reg_lambda': 0.49271047931852563, 'min_split_gain': 0.4867898439325658}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:13,906]\u001b[0m Trial 78 finished with value: -1026.2 and parameters: {'num_leaves': 131, 'max_depth': 1, 'learning_rate': 0.057372878238928844, 'min_child_samples': 58, 'subsample': 0.8760880040499714, 'colsample_bytree': 0.9580587295300945, 'reg_alpha': 0.6562785666628442, 'reg_lambda': 0.699845044681434, 'min_split_gain': 0.45959319261922194}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:17,663]\u001b[0m Trial 79 finished with value: -1065.8 and parameters: {'num_leaves': 158, 'max_depth': 0, 'learning_rate': 0.06488659529173303, 'min_child_samples': 59, 'subsample': 0.8738689906518043, 'colsample_bytree': 0.8222307861280916, 'reg_alpha': 0.6526107852673316, 'reg_lambda': 0.7103190843647005, 'min_split_gain': 0.4629836000665016}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:20,911]\u001b[0m Trial 80 finished with value: -1075.0 and parameters: {'num_leaves': 180, 'max_depth': 4, 'learning_rate': 0.057650518989171784, 'min_child_samples': 64, 'subsample': 0.8894485480140824, 'colsample_bytree': 0.9465758117841233, 'reg_alpha': 0.49957079480703015, 'reg_lambda': 0.6502421949317382, 'min_split_gain': 0.4541058659267785}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:22,111]\u001b[0m Trial 81 finished with value: -1027.0 and parameters: {'num_leaves': 135, 'max_depth': 1, 'learning_rate': 0.0782995266186645, 'min_child_samples': 71, 'subsample': 0.9183928038427398, 'colsample_bytree': 0.9629320406495984, 'reg_alpha': 0.7860312012112726, 'reg_lambda': 0.6853937418236605, 'min_split_gain': 0.4915308038170826}. Best is trial 40 with value: -1022.4.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:23,311]\u001b[0m Trial 82 finished with value: -1021.6 and parameters: {'num_leaves': 126, 'max_depth': 1, 'learning_rate': 0.07877171375320807, 'min_child_samples': 73, 'subsample': 0.8722939690568707, 'colsample_bytree': 0.9324176064754862, 'reg_alpha': 0.7936847076321234, 'reg_lambda': 0.6893927595065354, 'min_split_gain': 0.49988742190892166}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:26,636]\u001b[0m Trial 83 finished with value: -1094.0 and parameters: {'num_leaves': 135, 'max_depth': -1, 'learning_rate': 0.0800739579712477, 'min_child_samples': 71, 'subsample': 0.935491834043677, 'colsample_bytree': 0.9285602960115441, 'reg_alpha': 0.6968499382629203, 'reg_lambda': 0.690414296586351, 'min_split_gain': 0.4913508999028045}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:27,860]\u001b[0m Trial 84 finished with value: -1031.4 and parameters: {'num_leaves': 145, 'max_depth': 1, 'learning_rate': 0.09884338578261914, 'min_child_samples': 76, 'subsample': 0.9142435268422506, 'colsample_bytree': 0.9568284003874371, 'reg_alpha': 0.5872579896535233, 'reg_lambda': 0.7486824747318889, 'min_split_gain': 0.46462901640542376}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:29,642]\u001b[0m Trial 85 finished with value: -1037.8 and parameters: {'num_leaves': 132, 'max_depth': 2, 'learning_rate': 0.07577796272448052, 'min_child_samples': 68, 'subsample': 0.9573503325786609, 'colsample_bytree': 0.9870659447343284, 'reg_alpha': 0.7809186142479665, 'reg_lambda': 0.6497429909716829, 'min_split_gain': 0.4495717179328906}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:31,998]\u001b[0m Trial 86 finished with value: -1099.8 and parameters: {'num_leaves': 111, 'max_depth': 0, 'learning_rate': 0.13753954420812659, 'min_child_samples': 63, 'subsample': 0.9195132702037657, 'colsample_bytree': 0.969019930316358, 'reg_alpha': 0.8438440433172832, 'reg_lambda': 0.7208856888485351, 'min_split_gain': 0.4994668070217307}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:33,694]\u001b[0m Trial 87 finished with value: -1035.0 and parameters: {'num_leaves': 118, 'max_depth': 2, 'learning_rate': 0.054929327003036967, 'min_child_samples': 53, 'subsample': 0.871577035191033, 'colsample_bytree': 0.9053580416378267, 'reg_alpha': 0.8173333562758663, 'reg_lambda': 0.1985568592320659, 'min_split_gain': 0.4463856811445132}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:36,130]\u001b[0m Trial 88 finished with value: -1062.0 and parameters: {'num_leaves': 100, 'max_depth': 3, 'learning_rate': 0.0868781238704903, 'min_child_samples': 57, 'subsample': 0.9247655914200692, 'colsample_bytree': 0.9199958490064797, 'reg_alpha': 0.7536572625571705, 'reg_lambda': 0.6930957772668268, 'min_split_gain': 0.4689949982005316}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:37,300]\u001b[0m Trial 89 finished with value: -1023.6 and parameters: {'num_leaves': 152, 'max_depth': 1, 'learning_rate': 0.09565795249311497, 'min_child_samples': 72, 'subsample': 0.8952158635131108, 'colsample_bytree': 0.9270387052427617, 'reg_alpha': 0.40781063671905604, 'reg_lambda': 0.6027727874396871, 'min_split_gain': 0.48781906543202697}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:40,857]\u001b[0m Trial 90 finished with value: -1092.0 and parameters: {'num_leaves': 140, 'max_depth': 15, 'learning_rate': 0.06969547715772495, 'min_child_samples': 60, 'subsample': 0.8986984885288941, 'colsample_bytree': 0.9257682098345007, 'reg_alpha': 0.427966006211458, 'reg_lambda': 0.5983321519405461, 'min_split_gain': 0.4991257570968467}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:42,042]\u001b[0m Trial 91 finished with value: -1029.6 and parameters: {'num_leaves': 151, 'max_depth': 1, 'learning_rate': 0.09384737162731732, 'min_child_samples': 77, 'subsample': 0.8784108052027002, 'colsample_bytree': 0.9404465398569304, 'reg_alpha': 0.3382903132593448, 'reg_lambda': 0.6655715369135786, 'min_split_gain': 0.4835995027503853}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:45,428]\u001b[0m Trial 92 finished with value: -1087.0 and parameters: {'num_leaves': 160, 'max_depth': 0, 'learning_rate': 0.0808585430205237, 'min_child_samples': 71, 'subsample': 0.8930871167567165, 'colsample_bytree': 0.9520152097559018, 'reg_alpha': 0.3983806871687964, 'reg_lambda': 0.7307108808378957, 'min_split_gain': 0.4619095674777822}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:48,605]\u001b[0m Trial 93 finished with value: -1090.0 and parameters: {'num_leaves': 128, 'max_depth': 4, 'learning_rate': 0.07681133422927172, 'min_child_samples': 67, 'subsample': 0.855295308892101, 'colsample_bytree': 0.9986987450279001, 'reg_alpha': 0.3629110407090639, 'reg_lambda': 0.7724238278081875, 'min_split_gain': 0.48723355777085603}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:51,278]\u001b[0m Trial 94 finished with value: -1067.0 and parameters: {'num_leaves': 106, 'max_depth': -1, 'learning_rate': 0.11028784999759128, 'min_child_samples': 73, 'subsample': 0.9045480537614042, 'colsample_bytree': 0.90250465033707, 'reg_alpha': 0.45750807534670307, 'reg_lambda': 0.6273443141947352, 'min_split_gain': 0.47512625041317613}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:52,433]\u001b[0m Trial 95 finished with value: -1033.4 and parameters: {'num_leaves': 118, 'max_depth': 1, 'learning_rate': 0.026751967317283066, 'min_child_samples': 88, 'subsample': 0.8250185544591642, 'colsample_bytree': 0.9113758682873178, 'reg_alpha': 0.7813168309269316, 'reg_lambda': 0.6088954456428848, 'min_split_gain': 0.44600146854451495}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:54,159]\u001b[0m Trial 96 finished with value: -1036.6 and parameters: {'num_leaves': 138, 'max_depth': 2, 'learning_rate': 0.06379192482529557, 'min_child_samples': 81, 'subsample': 0.8710877680960388, 'colsample_bytree': 0.9362917604057651, 'reg_alpha': 0.5139077658139837, 'reg_lambda': 0.6377963492735474, 'min_split_gain': 0.11642123182354439}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:55,397]\u001b[0m Trial 97 finished with value: -1030.6 and parameters: {'num_leaves': 153, 'max_depth': 1, 'learning_rate': 0.13262213152462374, 'min_child_samples': 69, 'subsample': 0.8885228570189638, 'colsample_bytree': 0.8946765275664232, 'reg_alpha': 0.88233061252622, 'reg_lambda': 0.6678176381781552, 'min_split_gain': 0.4313714169256349}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:57,701]\u001b[0m Trial 98 finished with value: -1071.0 and parameters: {'num_leaves': 131, 'max_depth': 3, 'learning_rate': 0.0924841280556393, 'min_child_samples': 64, 'subsample': 0.8339681248210673, 'colsample_bytree': 0.8726521233199854, 'reg_alpha': 0.42237873739844006, 'reg_lambda': 0.7971256518771489, 'min_split_gain': 0.4980205153317012}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:47:59,395]\u001b[0m Trial 99 finished with value: -1028.6 and parameters: {'num_leaves': 145, 'max_depth': 2, 'learning_rate': 0.029434987273732994, 'min_child_samples': 72, 'subsample': 0.9451834654229231, 'colsample_bytree': 0.9624681067689983, 'reg_alpha': 0.5663985742258891, 'reg_lambda': 0.7001946086982554, 'min_split_gain': 0.40096664532506526}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:48:02,300]\u001b[0m Trial 100 finished with value: -1063.2 and parameters: {'num_leaves': 170, 'max_depth': 12, 'learning_rate': 0.09716384191502504, 'min_child_samples': 66, 'subsample': 0.6168546411875545, 'colsample_bytree': 0.9840241015132789, 'reg_alpha': 0.634638923694025, 'reg_lambda': 0.7359939497678694, 'min_split_gain': 0.4662232367307494}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:48:03,449]\u001b[0m Trial 101 finished with value: -1025.0 and parameters: {'num_leaves': 126, 'max_depth': 1, 'learning_rate': 0.0603866460800367, 'min_child_samples': 75, 'subsample': 0.838952722599672, 'colsample_bytree': 0.8384324992870756, 'reg_alpha': 0.8095816086536891, 'reg_lambda': 0.5274193049959557, 'min_split_gain': 0.47988749822969534}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:48:07,309]\u001b[0m Trial 102 finished with value: -1075.6 and parameters: {'num_leaves': 114, 'max_depth': 0, 'learning_rate': 0.05579542796698023, 'min_child_samples': 79, 'subsample': 0.8562001018369503, 'colsample_bytree': 0.8490100223936574, 'reg_alpha': 0.8415354452076641, 'reg_lambda': 0.5742993767189843, 'min_split_gain': 0.48339802567915746}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:48:08,488]\u001b[0m Trial 103 finished with value: -1026.4 and parameters: {'num_leaves': 121, 'max_depth': 1, 'learning_rate': 0.0676562090035894, 'min_child_samples': 73, 'subsample': 0.8076975744791364, 'colsample_bytree': 0.9254802419707826, 'reg_alpha': 0.7174911941736124, 'reg_lambda': 0.5386179273076455, 'min_split_gain': 0.4567726069261706}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:48:10,924]\u001b[0m Trial 104 finished with value: -1049.4 and parameters: {'num_leaves': 120, 'max_depth': 3, 'learning_rate': 0.04951804239871305, 'min_child_samples': 83, 'subsample': 0.7893510482507691, 'colsample_bytree': 0.8918933966801457, 'reg_alpha': 0.7373415968700714, 'reg_lambda': 0.5273380062891818, 'min_split_gain': 0.42148665299347643}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:48:12,092]\u001b[0m Trial 105 finished with value: -1031.8 and parameters: {'num_leaves': 124, 'max_depth': 1, 'learning_rate': 0.06931608767169445, 'min_child_samples': 86, 'subsample': 0.8092832606088078, 'colsample_bytree': 0.9136214242219777, 'reg_alpha': 0.6763537517555116, 'reg_lambda': 0.5829751282080256, 'min_split_gain': 0.4590264488476548}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:48:13,705]\u001b[0m Trial 106 finished with value: -1035.2 and parameters: {'num_leaves': 111, 'max_depth': 2, 'learning_rate': 0.02153660721433253, 'min_child_samples': 93, 'subsample': 0.8253167572727184, 'colsample_bytree': 0.9292665239415494, 'reg_alpha': 0.9196248769918134, 'reg_lambda': 0.5544873084923676, 'min_split_gain': 0.45247138979127066}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:48:16,821]\u001b[0m Trial 107 finished with value: -1062.0 and parameters: {'num_leaves': 128, 'max_depth': 0, 'learning_rate': 0.08540618084784435, 'min_child_samples': 77, 'subsample': 0.8425567397371426, 'colsample_bytree': 0.9222731941395498, 'reg_alpha': 0.7198649671470139, 'reg_lambda': 0.4599114409389122, 'min_split_gain': 0.4727605802887294}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:48:20,381]\u001b[0m Trial 108 finished with value: -1087.8 and parameters: {'num_leaves': 95, 'max_depth': 11, 'learning_rate': 0.06662930531906561, 'min_child_samples': 69, 'subsample': 0.8682549406598236, 'colsample_bytree': 0.9482798302521577, 'reg_alpha': 0.8042346216769962, 'reg_lambda': 0.545339844476406, 'min_split_gain': 0.4348253413558705}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:48:24,172]\u001b[0m Trial 109 finished with value: -1067.8 and parameters: {'num_leaves': 88, 'max_depth': -1, 'learning_rate': 0.06119073612089033, 'min_child_samples': 74, 'subsample': 0.8076691589219386, 'colsample_bytree': 0.8845120083703243, 'reg_alpha': 0.7644216770629827, 'reg_lambda': 0.426291626852025, 'min_split_gain': 0.481558558286935}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:48:26,319]\u001b[0m Trial 110 finished with value: -1118.4 and parameters: {'num_leaves': 103, 'max_depth': 3, 'learning_rate': 0.18707076386898192, 'min_child_samples': 55, 'subsample': 0.8181509220426206, 'colsample_bytree': 0.8576106996157717, 'reg_alpha': 0.4495634995908021, 'reg_lambda': 0.491538025656752, 'min_split_gain': 0.44371843351857543}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:48:27,491]\u001b[0m Trial 111 finished with value: -1026.8 and parameters: {'num_leaves': 140, 'max_depth': 1, 'learning_rate': 0.07586637710019145, 'min_child_samples': 73, 'subsample': 0.7658146754288477, 'colsample_bytree': 0.9679236123688119, 'reg_alpha': 0.7914976267785631, 'reg_lambda': 0.46581139695776763, 'min_split_gain': 0.4880267279878034}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:48:28,640]\u001b[0m Trial 112 finished with value: -1038.0 and parameters: {'num_leaves': 142, 'max_depth': 1, 'learning_rate': 0.025648492515930616, 'min_child_samples': 75, 'subsample': 0.7655673749382768, 'colsample_bytree': 0.9717494531473871, 'reg_alpha': 0.8231026883349492, 'reg_lambda': 0.47077985900507074, 'min_split_gain': 0.46988159343835106}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:48:32,710]\u001b[0m Trial 113 finished with value: -1070.4 and parameters: {'num_leaves': 122, 'max_depth': 0, 'learning_rate': 0.07469491615188208, 'min_child_samples': 78, 'subsample': 0.7736759122458817, 'colsample_bytree': 0.9342421120893103, 'reg_alpha': 0.7219510039756771, 'reg_lambda': 0.35659289491195467, 'min_split_gain': 0.2543628877625741}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:48:34,387]\u001b[0m Trial 114 finished with value: -1050.6 and parameters: {'num_leaves': 114, 'max_depth': 2, 'learning_rate': 0.05920701931024261, 'min_child_samples': 73, 'subsample': 0.7269878231999621, 'colsample_bytree': 0.6279621233333618, 'reg_alpha': 0.05880286348997499, 'reg_lambda': 0.6052532928161275, 'min_split_gain': 0.4781640995390063}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:48:35,551]\u001b[0m Trial 115 finished with value: -1029.2 and parameters: {'num_leaves': 129, 'max_depth': 1, 'learning_rate': 0.0537339692718776, 'min_child_samples': 66, 'subsample': 0.7450501477549495, 'colsample_bytree': 0.9541911708240254, 'reg_alpha': 0.7474954755043053, 'reg_lambda': 0.4229328020723715, 'min_split_gain': 0.45785818560526376}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:48:37,166]\u001b[0m Trial 116 finished with value: -1039.0 and parameters: {'num_leaves': 148, 'max_depth': 2, 'learning_rate': 0.01978293720088199, 'min_child_samples': 62, 'subsample': 0.8497419180474376, 'colsample_bytree': 0.6496633547045024, 'reg_alpha': 0.4818874505307017, 'reg_lambda': 0.2557300928688171, 'min_split_gain': 0.49285855596974726}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:48:40,191]\u001b[0m Trial 117 finished with value: -1093.2 and parameters: {'num_leaves': 139, 'max_depth': 0, 'learning_rate': 0.07342179595243287, 'min_child_samples': 69, 'subsample': 0.7812829065272565, 'colsample_bytree': 0.7644573306862039, 'reg_alpha': 0.792846364472495, 'reg_lambda': 0.8233261756169117, 'min_split_gain': 0.49939506289804136}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:48:53,181]\u001b[0m Trial 118 finished with value: -1115.2 and parameters: {'num_leaves': 161, 'max_depth': 18, 'learning_rate': 0.03411049694217982, 'min_child_samples': 80, 'subsample': 0.8320120212297728, 'colsample_bytree': 0.9428983185266447, 'reg_alpha': 0.8640797852308215, 'reg_lambda': 0.8722174615017482, 'min_split_gain': 0.006912008558888072}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:48:54,607]\u001b[0m Trial 119 finished with value: -1039.8 and parameters: {'num_leaves': 193, 'max_depth': 1, 'learning_rate': 0.023518605536914865, 'min_child_samples': 11, 'subsample': 0.7930695738137229, 'colsample_bytree': 0.6184847149730588, 'reg_alpha': 0.6447989117736124, 'reg_lambda': 0.39273588768175505, 'min_split_gain': 0.166110911057484}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:48:58,973]\u001b[0m Trial 120 finished with value: -1081.6 and parameters: {'num_leaves': 132, 'max_depth': 5, 'learning_rate': 0.029666377318433208, 'min_child_samples': 59, 'subsample': 0.7625193380676114, 'colsample_bytree': 0.8367254343942621, 'reg_alpha': 0.7086455238554841, 'reg_lambda': 0.5384025754299826, 'min_split_gain': 0.23512139602064652}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:00,234]\u001b[0m Trial 121 finished with value: -1026.2 and parameters: {'num_leaves': 135, 'max_depth': 1, 'learning_rate': 0.07938621959252216, 'min_child_samples': 71, 'subsample': 0.8814375701051862, 'colsample_bytree': 0.960146463886294, 'reg_alpha': 0.7852774161965073, 'reg_lambda': 0.9135313879610518, 'min_split_gain': 0.4863199888991342}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:02,000]\u001b[0m Trial 122 finished with value: -1054.8 and parameters: {'num_leaves': 125, 'max_depth': 2, 'learning_rate': 0.08594645651231377, 'min_child_samples': 71, 'subsample': 0.8802858616497068, 'colsample_bytree': 0.9798105116115994, 'reg_alpha': 0.8315155753283875, 'reg_lambda': 0.9224767943115996, 'min_split_gain': 0.4830637991714769}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:05,942]\u001b[0m Trial 123 finished with value: -1091.8 and parameters: {'num_leaves': 135, 'max_depth': 0, 'learning_rate': 0.06756674664075422, 'min_child_samples': 75, 'subsample': 0.8560493713192043, 'colsample_bytree': 0.9148642293500121, 'reg_alpha': 0.6847910846615791, 'reg_lambda': 0.9733226963357154, 'min_split_gain': 0.46641941330184933}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:07,252]\u001b[0m Trial 124 finished with value: -1025.2 and parameters: {'num_leaves': 155, 'max_depth': 1, 'learning_rate': 0.10746870623994728, 'min_child_samples': 68, 'subsample': 0.864442361828293, 'colsample_bytree': 0.9583254131319775, 'reg_alpha': 0.7675093056706788, 'reg_lambda': 0.8478353164844915, 'min_split_gain': 0.45419655416474125}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:09,226]\u001b[0m Trial 125 finished with value: -1067.0 and parameters: {'num_leaves': 175, 'max_depth': 2, 'learning_rate': 0.11494255578144144, 'min_child_samples': 51, 'subsample': 0.8604922039943961, 'colsample_bytree': 0.9380579985556666, 'reg_alpha': 0.7650076930039902, 'reg_lambda': 0.8447024576742876, 'min_split_gain': 0.45408481933822503}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:12,386]\u001b[0m Trial 126 finished with value: -1069.4 and parameters: {'num_leaves': 157, 'max_depth': -1, 'learning_rate': 0.1027076798099447, 'min_child_samples': 68, 'subsample': 0.8844599261292352, 'colsample_bytree': 0.9551202419418623, 'reg_alpha': 0.7429438639907522, 'reg_lambda': 0.8958219571324382, 'min_split_gain': 0.4307031757480451}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:13,720]\u001b[0m Trial 127 finished with value: -1029.8 and parameters: {'num_leaves': 54, 'max_depth': 1, 'learning_rate': 0.0828573403278032, 'min_child_samples': 65, 'subsample': 0.8453857565162008, 'colsample_bytree': 0.9471032282287526, 'reg_alpha': 0.815507187032818, 'reg_lambda': 0.8636006326081984, 'min_split_gain': 0.4460100343432614}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:15,607]\u001b[0m Trial 128 finished with value: -1063.2 and parameters: {'num_leaves': 114, 'max_depth': 2, 'learning_rate': 0.1253230275878708, 'min_child_samples': 62, 'subsample': 0.8750842576365985, 'colsample_bytree': 0.9269690987269446, 'reg_alpha': 0.8896879956700947, 'reg_lambda': 0.7726456116937546, 'min_split_gain': 0.4785245400017922}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:18,168]\u001b[0m Trial 129 finished with value: -1070.0 and parameters: {'num_leaves': 151, 'max_depth': 0, 'learning_rate': 0.15339021989339155, 'min_child_samples': 70, 'subsample': 0.9031389048961507, 'colsample_bytree': 0.9572303340071162, 'reg_alpha': 0.770130598003983, 'reg_lambda': 0.818015150964662, 'min_split_gain': 0.469021465917076}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:20,113]\u001b[0m Trial 130 finished with value: -1042.0 and parameters: {'num_leaves': 107, 'max_depth': 2, 'learning_rate': 0.10775616578130245, 'min_child_samples': 77, 'subsample': 0.864063873755037, 'colsample_bytree': 0.9196875084246897, 'reg_alpha': 0.6106574056003768, 'reg_lambda': 0.9338052958744689, 'min_split_gain': 0.4200681632236908}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:21,475]\u001b[0m Trial 131 finished with value: -1036.6 and parameters: {'num_leaves': 140, 'max_depth': 1, 'learning_rate': 0.09066339798918675, 'min_child_samples': 73, 'subsample': 0.8409863219604405, 'colsample_bytree': 0.9752721599661651, 'reg_alpha': 0.7930373376772868, 'reg_lambda': 0.9169525365920567, 'min_split_gain': 0.492685397700929}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:22,814]\u001b[0m Trial 132 finished with value: -1031.0 and parameters: {'num_leaves': 124, 'max_depth': 1, 'learning_rate': 0.07315757834611727, 'min_child_samples': 68, 'subsample': 0.8928735855320171, 'colsample_bytree': 0.9689067580538598, 'reg_alpha': 0.8493727944364016, 'reg_lambda': 0.6465327564698327, 'min_split_gain': 0.48588615159274023}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:25,448]\u001b[0m Trial 133 finished with value: -1080.8 and parameters: {'num_leaves': 132, 'max_depth': 3, 'learning_rate': 0.07740748948918941, 'min_child_samples': 73, 'subsample': 0.7708584343529749, 'colsample_bytree': 0.9626519013481797, 'reg_alpha': 0.7001112918940255, 'reg_lambda': 0.5818127752920126, 'min_split_gain': 0.4607087540245297}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:29,556]\u001b[0m Trial 134 finished with value: -1074.2 and parameters: {'num_leaves': 145, 'max_depth': 0, 'learning_rate': 0.07032593726116082, 'min_child_samples': 65, 'subsample': 0.8675735847466162, 'colsample_bytree': 0.9908809974793126, 'reg_alpha': 0.7999911278107227, 'reg_lambda': 0.4990000027707717, 'min_split_gain': 0.4742557142358869}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:30,933]\u001b[0m Trial 135 finished with value: -1025.8 and parameters: {'num_leaves': 240, 'max_depth': 1, 'learning_rate': 0.06442530985344219, 'min_child_samples': 76, 'subsample': 0.748669069184273, 'colsample_bytree': 0.9038772435365102, 'reg_alpha': 0.40283921842990794, 'reg_lambda': 0.6663349871603008, 'min_split_gain': 0.4901257793142147}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:34,352]\u001b[0m Trial 136 finished with value: -1070.8 and parameters: {'num_leaves': 259, 'max_depth': 4, 'learning_rate': 0.05737584824442826, 'min_child_samples': 76, 'subsample': 0.7529490979866488, 'colsample_bytree': 0.8962922728194915, 'reg_alpha': 0.3537469724166315, 'reg_lambda': 0.6610086871433529, 'min_split_gain': 0.4406212449543424}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:35,701]\u001b[0m Trial 137 finished with value: -1029.6 and parameters: {'num_leaves': 280, 'max_depth': 1, 'learning_rate': 0.0637553026685227, 'min_child_samples': 45, 'subsample': 0.7402132665919048, 'colsample_bytree': 0.904761262979203, 'reg_alpha': 0.5332559273499968, 'reg_lambda': 0.7129553771773058, 'min_split_gain': 0.4614405548821613}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:41,010]\u001b[0m Trial 138 finished with value: -1098.2 and parameters: {'num_leaves': 200, 'max_depth': 0, 'learning_rate': 0.05023925177301299, 'min_child_samples': 71, 'subsample': 0.6614939992026051, 'colsample_bytree': 0.9322900708436674, 'reg_alpha': 0.414948612673553, 'reg_lambda': 0.6256813251114475, 'min_split_gain': 0.34462672714601866}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:42,803]\u001b[0m Trial 139 finished with value: -1037.2 and parameters: {'num_leaves': 249, 'max_depth': 2, 'learning_rate': 0.022023076646547568, 'min_child_samples': 67, 'subsample': 0.7169645588068293, 'colsample_bytree': 0.6871621954419165, 'reg_alpha': 0.3219859321084414, 'reg_lambda': 0.749609628611253, 'min_split_gain': 0.30751786018244426}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:46,565]\u001b[0m Trial 140 finished with value: -1134.0 and parameters: {'num_leaves': 165, 'max_depth': -1, 'learning_rate': 0.09717983681763605, 'min_child_samples': 78, 'subsample': 0.851009899652074, 'colsample_bytree': 0.8713711614568199, 'reg_alpha': 0.3880545815420425, 'reg_lambda': 0.684286537289145, 'min_split_gain': 0.21328981464312247}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:47,873]\u001b[0m Trial 141 finished with value: -1021.6 and parameters: {'num_leaves': 116, 'max_depth': 1, 'learning_rate': 0.08115054600219349, 'min_child_samples': 75, 'subsample': 0.7853345432304832, 'colsample_bytree': 0.9400267698977653, 'reg_alpha': 0.3763633693944244, 'reg_lambda': 0.606515488477769, 'min_split_gain': 0.4997275602962219}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:49,162]\u001b[0m Trial 142 finished with value: -1029.8 and parameters: {'num_leaves': 65, 'max_depth': 1, 'learning_rate': 0.08722957888579616, 'min_child_samples': 71, 'subsample': 0.7973942369179354, 'colsample_bytree': 0.9422379950652158, 'reg_alpha': 0.3700603686027807, 'reg_lambda': 0.6062189763554494, 'min_split_gain': 0.4995735025455471}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:50,475]\u001b[0m Trial 143 finished with value: -1036.4 and parameters: {'num_leaves': 223, 'max_depth': 1, 'learning_rate': 0.0447806055293548, 'min_child_samples': 75, 'subsample': 0.6896598868296882, 'colsample_bytree': 0.912514708431281, 'reg_alpha': 0.4447810914863148, 'reg_lambda': 0.6439844504960494, 'min_split_gain': 0.4889953183590722}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:53,038]\u001b[0m Trial 144 finished with value: -1056.8 and parameters: {'num_leaves': 233, 'max_depth': 3, 'learning_rate': 0.06623708904430396, 'min_child_samples': 80, 'subsample': 0.8819054387576498, 'colsample_bytree': 0.9280659137535824, 'reg_alpha': 0.41117652553164574, 'reg_lambda': 0.66971540586242, 'min_split_gain': 0.4759553205523354}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:56,748]\u001b[0m Trial 145 finished with value: -1074.4 and parameters: {'num_leaves': 118, 'max_depth': 0, 'learning_rate': 0.08056205549686811, 'min_child_samples': 69, 'subsample': 0.8178487942100089, 'colsample_bytree': 0.9477739710210846, 'reg_alpha': 0.2767686041538701, 'reg_lambda': 0.5912007669010179, 'min_split_gain': 0.4513358253572637}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:58,647]\u001b[0m Trial 146 finished with value: -1038.4 and parameters: {'num_leaves': 127, 'max_depth': 2, 'learning_rate': 0.06141049646881156, 'min_child_samples': 82, 'subsample': 0.7800540604346904, 'colsample_bytree': 0.9376846009151985, 'reg_alpha': 0.37988708648556063, 'reg_lambda': 0.5699067430620305, 'min_split_gain': 0.4681439514126933}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:49:59,978]\u001b[0m Trial 147 finished with value: -1034.0 and parameters: {'num_leaves': 120, 'max_depth': 1, 'learning_rate': 0.026406054021237937, 'min_child_samples': 74, 'subsample': 0.8342763808081706, 'colsample_bytree': 0.7195056974136947, 'reg_alpha': 0.48374608848698997, 'reg_lambda': 0.6241277838389919, 'min_split_gain': 0.48316155695542673}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:50:01,888]\u001b[0m Trial 148 finished with value: -1037.2 and parameters: {'num_leaves': 109, 'max_depth': 2, 'learning_rate': 0.024245843715190347, 'min_child_samples': 64, 'subsample': 0.7057958451244779, 'colsample_bytree': 0.9033743879372329, 'reg_alpha': 0.40236521503349604, 'reg_lambda': 0.8769623480259529, 'min_split_gain': 0.4926047437969341}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\u001b[32m[I 2026-02-05 18:50:04,602]\u001b[0m Trial 149 finished with value: -1069.8 and parameters: {'num_leaves': 181, 'max_depth': 3, 'learning_rate': 0.09406976726371626, 'min_child_samples': 72, 'subsample': 0.8054102411790091, 'colsample_bytree': 0.9231377745039746, 'reg_alpha': 0.7288090649851868, 'reg_lambda': 0.7914063046209855, 'min_split_gain': 0.47465964113115966}. Best is trial 82 with value: -1021.6.\u001b[0m\n",
+ "\n",
+ "✓ Optuna terminé: 150 trials\n",
+ " - Trials pruned: 0\n",
+ " - Trials completed: 150\n",
+ " - Best score (negative cost): -1021.60\n",
+ "\n",
+ "🏆 Best params:\n",
+ " - num_leaves: 126\n",
+ " - max_depth: 1\n",
+ " - learning_rate: 0.07877171375320807\n",
+ " - min_child_samples: 73\n",
+ " - subsample: 0.8722939690568707\n",
+ " - colsample_bytree: 0.9324176064754862\n",
+ " - reg_alpha: 0.7936847076321234\n",
+ " - reg_lambda: 0.6893927595065354\n",
+ " - min_split_gain: 0.49988742190892166\n",
+ "\n",
+ "📊 Évaluation finale en CV avec best_params_improved...\n",
+ "\n",
+ "✓ Résultats CV (best_params_improved):\n",
+ " AUC moyen: 0.7545\n",
+ " Coût métier moyen: 1021.60\n",
+ " Seuil optimal moyen: 0.54\n",
+ " F1 moyen: 0.2906\n",
+ " Recall classe 1 moyen: 0.6065\n",
+ "🏃 View run best_params_improved_cv_evaluation at: http://127.0.0.1:5000/#/experiments/1/runs/6b5aa5883a41475d8809f5a0e172f3f3\n",
+ "🧪 View experiment at: http://127.0.0.1:5000/#/experiments/1\n",
+ "\n",
+ "🎯 Validation hold-out finale (20% de X_train)...\n",
+ "\n",
+ "📊 Hold-out AUC-ROC: 0.7564\n",
+ "🎯 Seuil optimal : 0.45\n",
+ "💰 Coût minimal : 1048.00\n",
+ "📈 F1-score : 0.2489\n",
+ "📈 Recall classe 1 : 0.7548\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "2026/02/05 18:50:09 WARNING mlflow.models.model: `artifact_path` is deprecated. Please use `name` instead.\n",
+ "2026/02/05 18:50:12 WARNING mlflow.utils.environment: Failed to resolve installed pip version. ``pip`` will be added to conda.yaml environment spec without a version specifier.\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "✅ Run MLflow 'final_model_improved_holdout' terminé\n",
+ "🏃 View run final_model_improved_holdout at: http://127.0.0.1:5000/#/experiments/1/runs/d98aa50f85704dc7ab38bb8c8c2a0e80\n",
+ "🧪 View experiment at: http://127.0.0.1:5000/#/experiments/1\n",
+ "🏃 View run LGBM_optuna_improved at: http://127.0.0.1:5000/#/experiments/1/runs/393ffe07d5ab446c8591d63d74485174\n",
+ "🧪 View experiment at: http://127.0.0.1:5000/#/experiments/1\n",
+ "\n",
+ "================================================================================\n",
+ "✅ OPTUNA IMPROVED TERMINÉ\n",
+ "================================================================================\n",
+ "📊 Comparez les runs dans MLflow UI (http://127.0.0.1:5000)\n",
+ " - LGBM_optuna_improved (parent)\n",
+ " - best_params_improved_cv_evaluation (nested)\n",
+ " - final_model_improved_holdout (nested)\n",
+ "================================================================================\n"
+ ]
+ }
+ ],
+ "source": [
+ "# ============================================================================\n",
+ "# OPTUNA OPTIMIZATION - IMPROVED VERSION (150 trials, extended search space)\n",
+ "# ============================================================================\n",
+ "import numpy as np\n",
+ "import optuna\n",
+ "from optuna.pruners import MedianPruner\n",
+ "from sklearn.model_selection import StratifiedKFold, train_test_split\n",
+ "from sklearn.metrics import roc_auc_score, confusion_matrix, f1_score, recall_score\n",
+ "import matplotlib.pyplot as plt\n",
+ "import warnings\n",
+ "\n",
+ "# Désactiver les warnings MLflow/Optuna\n",
+ "warnings.filterwarnings('ignore', message='.*Failed to resolve installed pip version.*')\n",
+ "warnings.filterwarnings('ignore', message='.*Consider increasing the value of the `n_warmup_steps`.*')\n",
+ "\n",
+ "print(\"\\n\" + \"=\"*80)\n",
+ "print(\"🔧 OPTUNA OPTIMIZATION - IMPROVED (150 trials, pruning agressif, espace étendu)\")\n",
+ "print(\"=\"*80)\n",
+ "\n",
+ "# ==========================================================================\n",
+ "# CONFIGURATION OPTUNA AMÉLIORÉE\n",
+ "# ==========================================================================\n",
+ "N_TRIALS_IMPROVED = 150 # 10x plus que le baseline\n",
+ "N_FOLDS_OPTUNA = 5 # 5-fold CV (plus robuste que 3-fold)\n",
+ "\n",
+ "# Seuils pour optimisation coût métier (grille fine mais raisonnable)\n",
+ "thresholds_optuna_improved = np.arange(0.15, 0.85, 0.05) # 14 seuils\n",
+ "\n",
+ "# StratifiedKFold pour CV\n",
+ "skf_improved = StratifiedKFold(n_splits=N_FOLDS_OPTUNA, shuffle=True, random_state=42)\n",
+ "\n",
+ "# ==========================================================================\n",
+ "# FONCTION UTILITAIRE : Calcul du coût métier minimal par fold\n",
+ "# ==========================================================================\n",
+ "def compute_min_cost_per_fold(y_true, y_proba, thresholds_array):\n",
+ " \"\"\"\n",
+ " Trouve le seuil optimal qui minimise le coût métier.\n",
+ " Coût = 10 * FN + 1 * FP\n",
+ " \n",
+ " Returns:\n",
+ " min_cost (float): Coût métier minimal\n",
+ " best_threshold (float): Seuil optimal\n",
+ " \"\"\"\n",
+ " min_cost = float('inf')\n",
+ " best_threshold = 0.5\n",
+ " \n",
+ " for thr in thresholds_array:\n",
+ " y_pred = (y_proba >= thr).astype(int)\n",
+ " tn, fp, fn, tp = confusion_matrix(y_true, y_pred).ravel()\n",
+ " cost = 10 * fn + 1 * fp\n",
+ " \n",
+ " if cost < min_cost:\n",
+ " min_cost = cost\n",
+ " best_threshold = thr\n",
+ " \n",
+ " return min_cost, best_threshold\n",
+ "\n",
+ "# ==========================================================================\n",
+ "# OBJECTIVE OPTUNA - ESPACE DE RECHERCHE ÉTENDU\n",
+ "# ==========================================================================\n",
+ "def objective_improved(trial):\n",
+ " \"\"\"\n",
+ " Objective function pour Optuna avec espace de recherche étendu.\n",
+ " \n",
+ " Nouveautés vs baseline:\n",
+ " - num_leaves: 50-300 (vs 31-128)\n",
+ " - max_depth: -1 à 20 (vs -1 à 12)\n",
+ " - learning_rate: 0.01-0.2 (vs 0.03-0.1)\n",
+ " - Ajout de reg_alpha, reg_lambda, min_split_gain pour régularisation\n",
+ " \"\"\"\n",
+ " params = {\n",
+ " # Architecture de l'arbre (étendu)\n",
+ " \"num_leaves\": trial.suggest_int(\"num_leaves\", 50, 300),\n",
+ " \"max_depth\": trial.suggest_int(\"max_depth\", -1, 20),\n",
+ " \n",
+ " # Learning rate (range étendu, log scale)\n",
+ " \"learning_rate\": trial.suggest_float(\"learning_rate\", 0.01, 0.2, log=True),\n",
+ " \n",
+ " # Nombre d'estimateurs (fixe pour stabilité)\n",
+ " \"n_estimators\": 500,\n",
+ " \n",
+ " # Sous-échantillonnage (étendu)\n",
+ " \"min_child_samples\": trial.suggest_int(\"min_child_samples\", 5, 100),\n",
+ " \"subsample\": trial.suggest_float(\"subsample\", 0.6, 1.0),\n",
+ " \"colsample_bytree\": trial.suggest_float(\"colsample_bytree\", 0.6, 1.0),\n",
+ " \n",
+ " # Régularisation L1/L2 (NOUVEAU)\n",
+ " \"reg_alpha\": trial.suggest_float(\"reg_alpha\", 0.0, 1.0),\n",
+ " \"reg_lambda\": trial.suggest_float(\"reg_lambda\", 0.0, 1.0),\n",
+ " \n",
+ " # Gain minimal pour split (NOUVEAU)\n",
+ " \"min_split_gain\": trial.suggest_float(\"min_split_gain\", 0.0, 0.5),\n",
+ " \n",
+ " # Paramètres fixes\n",
+ " \"class_weight\": \"balanced\",\n",
+ " \"random_state\": 42,\n",
+ " \"verbose\": -1,\n",
+ " }\n",
+ " \n",
+ " # Cross-validation avec reporting pour pruning\n",
+ " fold_costs = []\n",
+ " fold_aucs = []\n",
+ " \n",
+ " for fold_idx, (train_idx, val_idx) in enumerate(skf_improved.split(X_train, y_train)):\n",
+ " X_tr, X_val = X_train.iloc[train_idx], X_train.iloc[val_idx]\n",
+ " y_tr, y_val = y_train.iloc[train_idx], y_train.iloc[val_idx]\n",
+ " \n",
+ " # Entraînement du modèle\n",
+ " model = LGBMClassifier(**params)\n",
+ " model.fit(X_tr, y_tr)\n",
+ " \n",
+ " # Prédictions\n",
+ " y_val_proba = model.predict_proba(X_val)[:, 1]\n",
+ " \n",
+ " # AUC (métrique auxiliaire)\n",
+ " auc = roc_auc_score(y_val, y_val_proba)\n",
+ " fold_aucs.append(auc)\n",
+ " \n",
+ " # Coût métier minimal (objectif principal)\n",
+ " min_cost, _ = compute_min_cost_per_fold(y_val, y_val_proba, thresholds_optuna_improved)\n",
+ " fold_costs.append(min_cost)\n",
+ " \n",
+ " # Reporting pour pruning (moyenne actuelle des folds)\n",
+ " # On utilise -cost car Optuna maximise, on veut minimiser le coût\n",
+ " trial.report(-np.mean(fold_costs), step=fold_idx)\n",
+ " \n",
+ " # Pruning si le trial semble mauvais\n",
+ " if trial.should_prune():\n",
+ " raise optuna.TrialPruned()\n",
+ " \n",
+ " # Retourner la moyenne négative des coûts (pour maximisation Optuna)\n",
+ " return -np.mean(fold_costs)\n",
+ "\n",
+ "# ==========================================================================\n",
+ "# LANCEMENT OPTUNA DANS UN RUN PARENT MLflow\n",
+ "# ==========================================================================\n",
+ "print(f\"\\n🚀 Lancement Optuna IMPROVED:\")\n",
+ "print(f\" - {N_TRIALS_IMPROVED} trials\")\n",
+ "print(f\" - {N_FOLDS_OPTUNA}-fold CV\")\n",
+ "print(f\" - Pruning: MedianPruner (n_startup_trials=20, interval_steps=5)\")\n",
+ "\n",
+ "with mlflow.start_run(run_name=\"LGBM_optuna_improved\") as parent_run:\n",
+ " \n",
+ " # ========== TAGS DU RUN PARENT ==========\n",
+ " for tag_key, tag_value in MLFLOW_TAGS.items():\n",
+ " mlflow.set_tag(tag_key, tag_value)\n",
+ " mlflow.set_tag(\"model_type\", MODEL_NAME)\n",
+ " mlflow.set_tag(\"phase\", \"optuna_improved\")\n",
+ " mlflow.set_tag(\"evaluation_type\", \"cv_stratified\")\n",
+ " mlflow.set_tag(\"optuna_version\", \"improved\")\n",
+ " mlflow.set_tag(\"n_trials\", str(N_TRIALS_IMPROVED))\n",
+ " mlflow.set_tag(\"pruning\", \"median\")\n",
+ " mlflow.set_tag(\"threshold_optimized\", \"yes\")\n",
+ " \n",
+ " # ========== OPTUNA STUDY ==========\n",
+ " # Pruner amélioré (plus agressif, démarre après 20 trials, check tous les 5 steps)\n",
+ " pruner_improved = MedianPruner(\n",
+ " n_startup_trials=20, # Laisser 20 trials complets avant pruning\n",
+ " n_warmup_steps=10, # Attendre 10 folds avant de commencer à comparer\n",
+ " interval_steps=5 # Vérifier tous les 5 folds\n",
+ " )\n",
+ " \n",
+ " study = optuna.create_study(direction=\"maximize\", pruner=pruner_improved)\n",
+ " \n",
+ " # Optimization (pas de timeout global, le pruner gère l'efficacité)\n",
+ " study.optimize(\n",
+ " objective_improved, \n",
+ " n_trials=N_TRIALS_IMPROVED,\n",
+ " timeout=None, # Pas de timeout global, le pruner arrête les mauvais trials\n",
+ " show_progress_bar=True\n",
+ " )\n",
+ " \n",
+ " # ========== RÉSULTATS OPTUNA ==========\n",
+ " print(f\"\\n✓ Optuna terminé: {len(study.trials)} trials\")\n",
+ " print(f\" - Trials pruned: {len([t for t in study.trials if t.state == optuna.trial.TrialState.PRUNED])}\")\n",
+ " print(f\" - Trials completed: {len([t for t in study.trials if t.state == optuna.trial.TrialState.COMPLETE])}\")\n",
+ " print(f\" - Best score (negative cost): {study.best_value:.2f}\")\n",
+ " print(f\"\\n🏆 Best params:\")\n",
+ " for param, value in study.best_params.items():\n",
+ " print(f\" - {param}: {value}\")\n",
+ " \n",
+ " # Récupérer best_params\n",
+ " best_params_improved = study.best_params.copy()\n",
+ " best_params_improved[\"class_weight\"] = \"balanced\"\n",
+ " best_params_improved[\"random_state\"] = 42\n",
+ " best_params_improved[\"n_estimators\"] = 500\n",
+ " best_params_improved[\"verbose\"] = -1\n",
+ " \n",
+ " # Log des paramètres Optuna\n",
+ " mlflow.log_params(best_params_improved)\n",
+ " mlflow.log_metric(\"optuna_best_score\", study.best_value)\n",
+ " mlflow.log_metric(\"optuna_n_trials\", len(study.trials))\n",
+ " mlflow.log_metric(\"optuna_n_pruned\", len([t for t in study.trials if t.state == optuna.trial.TrialState.PRUNED]))\n",
+ " \n",
+ " # ==========================================================================\n",
+ " # RE-ÉVALUATION EN CV AVEC LES MEILLEURS PARAMÈTRES (nested run)\n",
+ " # ==========================================================================\n",
+ " print(\"\\n📊 Évaluation finale en CV avec best_params_improved...\")\n",
+ " \n",
+ " with mlflow.start_run(run_name=\"best_params_improved_cv_evaluation\", nested=True):\n",
+ " \n",
+ " # Tags du run nested\n",
+ " for tag_key, tag_value in MLFLOW_TAGS.items():\n",
+ " mlflow.set_tag(tag_key, tag_value)\n",
+ " mlflow.set_tag(\"model_type\", MODEL_NAME)\n",
+ " mlflow.set_tag(\"phase\", \"best_params_improved_cv\")\n",
+ " mlflow.set_tag(\"evaluation_type\", \"cv_stratified\")\n",
+ " mlflow.set_tag(\"threshold_optimized\", \"yes\")\n",
+ " \n",
+ " best_fold_results_improved = []\n",
+ " \n",
+ " for fold_idx, (train_idx, val_idx) in enumerate(skf_improved.split(X_train, y_train), start=1):\n",
+ " X_tr, X_val = X_train.iloc[train_idx], X_train.iloc[val_idx]\n",
+ " y_tr, y_val = y_train.iloc[train_idx], y_train.iloc[val_idx]\n",
+ " \n",
+ " model = LGBMClassifier(**best_params_improved)\n",
+ " model.fit(X_tr, y_tr)\n",
+ " \n",
+ " y_val_proba = model.predict_proba(X_val)[:, 1]\n",
+ " auc = roc_auc_score(y_val, y_val_proba)\n",
+ " min_cost, best_thr = compute_min_cost_per_fold(y_val, y_val_proba, thresholds_optuna_improved)\n",
+ " \n",
+ " # F1 et Recall au seuil optimal du fold\n",
+ " y_val_pred_opt = (y_val_proba >= best_thr).astype(int)\n",
+ " f1_fold = f1_score(y_val, y_val_pred_opt)\n",
+ " recall_fold = recall_score(y_val, y_val_pred_opt)\n",
+ " \n",
+ " best_fold_results_improved.append({\n",
+ " \"fold\": fold_idx,\n",
+ " \"auc\": auc,\n",
+ " \"best_threshold\": best_thr,\n",
+ " \"min_cost\": min_cost,\n",
+ " \"f1_score\": f1_fold,\n",
+ " \"recall_class1\": recall_fold\n",
+ " })\n",
+ " \n",
+ " best_cv_df_improved = pd.DataFrame(best_fold_results_improved)\n",
+ " \n",
+ " # Métriques moyennes\n",
+ " best_cv_auc_mean_improved = best_cv_df_improved[\"auc\"].mean()\n",
+ " best_cv_min_cost_mean_improved = best_cv_df_improved[\"min_cost\"].mean()\n",
+ " best_cv_best_threshold_mean_improved = best_cv_df_improved[\"best_threshold\"].mean()\n",
+ " best_cv_f1_mean_improved = best_cv_df_improved[\"f1_score\"].mean()\n",
+ " best_cv_recall_mean_improved = best_cv_df_improved[\"recall_class1\"].mean()\n",
+ " \n",
+ " # Log métriques standardisées\n",
+ " mlflow.log_metric(\"auc\", best_cv_auc_mean_improved)\n",
+ " mlflow.log_metric(\"business_cost_min\", best_cv_min_cost_mean_improved)\n",
+ " mlflow.log_metric(\"optimal_threshold\", best_cv_best_threshold_mean_improved)\n",
+ " mlflow.log_metric(\"f1_score\", best_cv_f1_mean_improved)\n",
+ " mlflow.log_metric(\"recall_class1\", best_cv_recall_mean_improved)\n",
+ " mlflow.log_dict(best_cv_df_improved.to_dict(orient=\"records\"), \"cv_results.json\")\n",
+ " \n",
+ " print(f\"\\n✓ Résultats CV (best_params_improved):\")\n",
+ " print(f\" AUC moyen: {best_cv_auc_mean_improved:.4f}\")\n",
+ " print(f\" Coût métier moyen: {best_cv_min_cost_mean_improved:.2f}\")\n",
+ " print(f\" Seuil optimal moyen: {best_cv_best_threshold_mean_improved:.2f}\")\n",
+ " print(f\" F1 moyen: {best_cv_f1_mean_improved:.4f}\")\n",
+ " print(f\" Recall classe 1 moyen: {best_cv_recall_mean_improved:.4f}\")\n",
+ " \n",
+ " # Log métriques du parent (même valeurs que le nested CV)\n",
+ " mlflow.log_metric(\"auc\", best_cv_auc_mean_improved)\n",
+ " mlflow.log_metric(\"business_cost_min\", best_cv_min_cost_mean_improved)\n",
+ " mlflow.log_metric(\"optimal_threshold\", best_cv_best_threshold_mean_improved)\n",
+ " mlflow.log_metric(\"f1_score\", best_cv_f1_mean_improved)\n",
+ " mlflow.log_metric(\"recall_class1\", best_cv_recall_mean_improved)\n",
+ " \n",
+ " # ==========================================================================\n",
+ " # VALIDATION HOLD-OUT FINALE (nested run)\n",
+ " # ==========================================================================\n",
+ " print(\"\\n🎯 Validation hold-out finale (20% de X_train)...\")\n",
+ " \n",
+ " # Split stratifié\n",
+ " X_train_final_improved, X_holdout_improved, y_train_final_improved, y_holdout_improved = train_test_split(\n",
+ " X_train, y_train, \n",
+ " test_size=0.2, \n",
+ " stratify=y_train, \n",
+ " random_state=RANDOM_STATE\n",
+ " )\n",
+ " \n",
+ " with mlflow.start_run(run_name=\"final_model_improved_holdout\", nested=True):\n",
+ " \n",
+ " # Tags\n",
+ " for tag_key, tag_value in MLFLOW_TAGS.items():\n",
+ " mlflow.set_tag(tag_key, tag_value)\n",
+ " mlflow.set_tag(\"model_type\", MODEL_NAME)\n",
+ " mlflow.set_tag(\"phase\", \"final_improved_holdout\")\n",
+ " mlflow.set_tag(\"evaluation_type\", \"holdout\")\n",
+ " mlflow.set_tag(\"threshold_optimized\", \"yes\")\n",
+ " \n",
+ " # Log params\n",
+ " mlflow.log_params(best_params_improved)\n",
+ " \n",
+ " # Entraîner sur 80% du train\n",
+ " final_model_improved = LGBMClassifier(**best_params_improved)\n",
+ " final_model_improved.fit(X_train_final_improved, y_train_final_improved)\n",
+ " \n",
+ " # Prédire sur hold-out\n",
+ " y_holdout_proba_improved = final_model_improved.predict_proba(X_holdout_improved)[:, 1]\n",
+ " \n",
+ " # AUC\n",
+ " holdout_auc_improved = roc_auc_score(y_holdout_improved, y_holdout_proba_improved)\n",
+ " \n",
+ " # Optimisation fine du seuil (0.05-0.95, step 0.01)\n",
+ " fine_thresholds_improved = np.arange(0.05, 0.96, 0.01)\n",
+ " threshold_costs_improved = []\n",
+ " \n",
+ " for thr in fine_thresholds_improved:\n",
+ " y_holdout_pred = (y_holdout_proba_improved >= thr).astype(int)\n",
+ " tn, fp, fn, tp = confusion_matrix(y_holdout_improved, y_holdout_pred).ravel()\n",
+ " cost = 10 * fn + 1 * fp\n",
+ " threshold_costs_improved.append({\n",
+ " 'threshold': thr,\n",
+ " 'cost': cost,\n",
+ " 'tp': tp,\n",
+ " 'fp': fp,\n",
+ " 'fn': fn,\n",
+ " 'tn': tn\n",
+ " })\n",
+ " \n",
+ " threshold_costs_df_improved = pd.DataFrame(threshold_costs_improved)\n",
+ " optimal_idx_improved = threshold_costs_df_improved['cost'].idxmin()\n",
+ " optimal_threshold_improved = threshold_costs_df_improved.loc[optimal_idx_improved, 'threshold']\n",
+ " min_cost_improved = threshold_costs_df_improved.loc[optimal_idx_improved, 'cost']\n",
+ " \n",
+ " # F1 et Recall au seuil optimal\n",
+ " y_holdout_optimal_improved = (y_holdout_proba_improved >= optimal_threshold_improved).astype(int)\n",
+ " holdout_f1_improved = f1_score(y_holdout_improved, y_holdout_optimal_improved)\n",
+ " holdout_recall_improved = recall_score(y_holdout_improved, y_holdout_optimal_improved)\n",
+ " \n",
+ " print(f\"\\n📊 Hold-out AUC-ROC: {holdout_auc_improved:.4f}\")\n",
+ " print(f\"🎯 Seuil optimal : {optimal_threshold_improved:.2f}\")\n",
+ " print(f\"💰 Coût minimal : {min_cost_improved:.2f}\")\n",
+ " print(f\"📈 F1-score : {holdout_f1_improved:.4f}\")\n",
+ " print(f\"📈 Recall classe 1 : {holdout_recall_improved:.4f}\")\n",
+ " \n",
+ " # Plot courbe coût vs seuil\n",
+ " plt.figure(figsize=(10, 6))\n",
+ " plt.plot(threshold_costs_df_improved['threshold'], threshold_costs_df_improved['cost'], \n",
+ " marker='o', linewidth=2, markersize=4, color='#2E86AB')\n",
+ " plt.axvline(optimal_threshold_improved, color='red', linestyle='--', linewidth=2,\n",
+ " label=f'Optimal = {optimal_threshold_improved:.2f}')\n",
+ " plt.xlabel('Seuil de décision', fontsize=12)\n",
+ " plt.ylabel('Coût métier (10*FN + 1*FP)', fontsize=12)\n",
+ " plt.title('Courbe de coût vs seuil (Hold-out) - Optuna Improved', fontsize=14, fontweight='bold')\n",
+ " plt.legend(fontsize=11)\n",
+ " plt.grid(True, alpha=0.3)\n",
+ " plt.tight_layout()\n",
+ " \n",
+ " plot_path_improved = '/home/valentin/Env_Python/OC_P6/threshold_cost_curve_improved.png'\n",
+ " plt.savefig(plot_path_improved, dpi=300, bbox_inches='tight')\n",
+ " plt.close()\n",
+ " \n",
+ " # Log MLflow\n",
+ " mlflow.log_metric(\"auc\", holdout_auc_improved)\n",
+ " mlflow.log_metric(\"business_cost_min\", min_cost_improved)\n",
+ " mlflow.log_metric(\"optimal_threshold\", optimal_threshold_improved)\n",
+ " mlflow.log_metric(\"f1_score\", holdout_f1_improved)\n",
+ " mlflow.log_metric(\"recall_class1\", holdout_recall_improved)\n",
+ " \n",
+ " mlflow.log_artifact(plot_path_improved)\n",
+ " mlflow.log_dict(threshold_costs_df_improved[::10].to_dict(orient='records'), \"threshold_costs_deciles.json\")\n",
+ " \n",
+ " # Log du modèle\n",
+ " mlflow.lightgbm.log_model(final_model_improved, artifact_path=\"model\")\n",
+ " \n",
+ " print(f\"\\n✅ Run MLflow 'final_model_improved_holdout' terminé\")\n",
+ "\n",
+ "print(\"\\n\" + \"=\"*80)\n",
+ "print(\"✅ OPTUNA IMPROVED TERMINÉ\")\n",
+ "print(\"=\"*80)\n",
+ "print(f\"📊 Comparez les runs dans MLflow UI (http://127.0.0.1:5000)\")\n",
+ "print(f\" - LGBM_optuna_improved (parent)\")\n",
+ "print(f\" - best_params_improved_cv_evaluation (nested)\")\n",
+ "print(f\" - final_model_improved_holdout (nested)\")\n",
+ "print(\"=\"*80)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2b232912",
+ "metadata": {},
+ "source": [
+ "## 📊 Comparaison : Baseline vs Optuna Improved\n",
+ "\n",
+ "Utilisez la cellule ci-dessous pour comparer les performances entre les différentes approches."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "2afb9fcd",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "====================================================================================================\n",
+ "📊 COMPARAISON DES RUNS (triés par coût métier croissant)\n",
+ "====================================================================================================\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " runName | \n",
+ " phase | \n",
+ " evaluation_type | \n",
+ " optuna_version | \n",
+ " auc | \n",
+ " business_cost_min | \n",
+ " optimal_threshold | \n",
+ " f1_score | \n",
+ " recall_class1 | \n",
+ " start_time | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " final_model | \n",
+ " final_model | \n",
+ " train_full | \n",
+ " None | \n",
+ " -1.0000 | \n",
+ " -1.0000 | \n",
+ " -1.0000 | \n",
+ " -1.0000 | \n",
+ " -1.0000 | \n",
+ " 2026-02-05 17:41:16.551000+00:00 | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " best_params_improved_cv_evaluation | \n",
+ " best_params_improved_cv | \n",
+ " cv_stratified | \n",
+ " None | \n",
+ " 0.7545 | \n",
+ " 1021.6000 | \n",
+ " 0.5400 | \n",
+ " 0.2906 | \n",
+ " 0.6065 | \n",
+ " 2026-02-05 17:50:04.888000+00:00 | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " LGBM_optuna_improved | \n",
+ " optuna_improved | \n",
+ " cv_stratified | \n",
+ " improved | \n",
+ " 0.7545 | \n",
+ " 1021.6000 | \n",
+ " 0.5400 | \n",
+ " 0.2906 | \n",
+ " 0.6065 | \n",
+ " 2026-02-05 17:41:20.557000+00:00 | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " final_model_improved_holdout | \n",
+ " final_improved_holdout | \n",
+ " holdout | \n",
+ " None | \n",
+ " 0.7564 | \n",
+ " 1048.0000 | \n",
+ " 0.4500 | \n",
+ " 0.2489 | \n",
+ " 0.7548 | \n",
+ " 2026-02-05 17:50:08.040000+00:00 | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " LGBM_baseline_CV | \n",
+ " baseline_cv | \n",
+ " cv_stratified | \n",
+ " None | \n",
+ " 0.7116 | \n",
+ " 1151.8000 | \n",
+ " 0.1300 | \n",
+ " 0.2632 | \n",
+ " 0.4632 | \n",
+ " 2026-02-05 17:40:15.475000+00:00 | \n",
+ "
\n",
+ " \n",
+ " | 5 | \n",
+ " best_params_cv_evaluation | \n",
+ " best_params_cv | \n",
+ " cv_stratified | \n",
+ " None | \n",
+ " 0.7533 | \n",
+ " 1761.3333 | \n",
+ " 0.5333 | \n",
+ " 0.2778 | \n",
+ " 0.5898 | \n",
+ " 2026-02-05 17:41:14.465000+00:00 | \n",
+ "
\n",
+ " \n",
+ " | 6 | \n",
+ " LGBM_optuna_tuning | \n",
+ " optuna_tuning | \n",
+ " cv_stratified | \n",
+ " None | \n",
+ " 0.7533 | \n",
+ " 1761.3333 | \n",
+ " 0.5333 | \n",
+ " 0.2778 | \n",
+ " 0.5898 | \n",
+ " 2026-02-05 17:40:23.924000+00:00 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " runName phase \\\n",
+ "0 final_model final_model \n",
+ "1 best_params_improved_cv_evaluation best_params_improved_cv \n",
+ "2 LGBM_optuna_improved optuna_improved \n",
+ "3 final_model_improved_holdout final_improved_holdout \n",
+ "4 LGBM_baseline_CV baseline_cv \n",
+ "5 best_params_cv_evaluation best_params_cv \n",
+ "6 LGBM_optuna_tuning optuna_tuning \n",
+ "\n",
+ " evaluation_type optuna_version auc business_cost_min \\\n",
+ "0 train_full None -1.0000 -1.0000 \n",
+ "1 cv_stratified None 0.7545 1021.6000 \n",
+ "2 cv_stratified improved 0.7545 1021.6000 \n",
+ "3 holdout None 0.7564 1048.0000 \n",
+ "4 cv_stratified None 0.7116 1151.8000 \n",
+ "5 cv_stratified None 0.7533 1761.3333 \n",
+ "6 cv_stratified None 0.7533 1761.3333 \n",
+ "\n",
+ " optimal_threshold f1_score recall_class1 start_time \n",
+ "0 -1.0000 -1.0000 -1.0000 2026-02-05 17:41:16.551000+00:00 \n",
+ "1 0.5400 0.2906 0.6065 2026-02-05 17:50:04.888000+00:00 \n",
+ "2 0.5400 0.2906 0.6065 2026-02-05 17:41:20.557000+00:00 \n",
+ "3 0.4500 0.2489 0.7548 2026-02-05 17:50:08.040000+00:00 \n",
+ "4 0.1300 0.2632 0.4632 2026-02-05 17:40:15.475000+00:00 \n",
+ "5 0.5333 0.2778 0.5898 2026-02-05 17:41:14.465000+00:00 \n",
+ "6 0.5333 0.2778 0.5898 2026-02-05 17:40:23.924000+00:00 "
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "====================================================================================================\n",
+ "🏆 MEILLEURS RUNS PAR APPROCHE\n",
+ "====================================================================================================\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " Approche | \n",
+ " AUC | \n",
+ " Coût Min | \n",
+ " Seuil Opt | \n",
+ " F1 | \n",
+ " Recall | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " Baseline CV | \n",
+ " 0.7116 | \n",
+ " 1151.8000 | \n",
+ " 0.1300 | \n",
+ " 0.2632 | \n",
+ " 0.4632 | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " Optuna 15 trials | \n",
+ " 0.7533 | \n",
+ " 1761.3333 | \n",
+ " 0.5333 | \n",
+ " 0.2778 | \n",
+ " 0.5898 | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " Optuna Improved | \n",
+ " 0.7545 | \n",
+ " 1021.6000 | \n",
+ " 0.5400 | \n",
+ " 0.2906 | \n",
+ " 0.6065 | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " Final Improved Holdout | \n",
+ " 0.7564 | \n",
+ " 1048.0000 | \n",
+ " 0.4500 | \n",
+ " 0.2489 | \n",
+ " 0.7548 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " Approche AUC Coût Min Seuil Opt F1 Recall\n",
+ "0 Baseline CV 0.7116 1151.8000 0.1300 0.2632 0.4632\n",
+ "1 Optuna 15 trials 0.7533 1761.3333 0.5333 0.2778 0.5898\n",
+ "2 Optuna Improved 0.7545 1021.6000 0.5400 0.2906 0.6065\n",
+ "3 Final Improved Holdout 0.7564 1048.0000 0.4500 0.2489 0.7548"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "📈 GAINS (vs Baseline CV):\n",
+ " Optuna 15 trials: -52.92% (coût: 1761.33)\n",
+ " Optuna Improved: +11.30% (coût: 1021.60)\n",
+ " Final Improved Holdout: +9.01% (coût: 1048.00)\n",
+ "\n",
+ "✅ Comparaison terminée\n"
+ ]
+ }
+ ],
+ "source": [
+ "# ============================================================================\n",
+ "# COMPARAISON DES RUNS : Baseline vs Optuna vs Optuna Improved\n",
+ "# ============================================================================\n",
+ "import pandas as pd\n",
+ "\n",
+ "# Récupérer tous les runs de l'expérience\n",
+ "runs_comparison = mlflow.search_runs(\n",
+ " experiment_ids=[experiment_id],\n",
+ " order_by=[\"metrics.business_cost_min ASC\"] # Trier par coût croissant\n",
+ ")\n",
+ "\n",
+ "# Sélectionner les colonnes pertinentes pour la comparaison\n",
+ "comparison_cols = [\n",
+ " \"tags.mlflow.runName\",\n",
+ " \"tags.phase\",\n",
+ " \"tags.evaluation_type\",\n",
+ " \"tags.optuna_version\",\n",
+ " \"metrics.auc\",\n",
+ " \"metrics.business_cost_min\",\n",
+ " \"metrics.optimal_threshold\",\n",
+ " \"metrics.f1_score\",\n",
+ " \"metrics.recall_class1\",\n",
+ " \"start_time\"\n",
+ "]\n",
+ "\n",
+ "# Filtrer les colonnes existantes\n",
+ "available_cols = [col for col in comparison_cols if col in runs_comparison.columns]\n",
+ "comparison_table = runs_comparison[available_cols].copy()\n",
+ "\n",
+ "# Renommer pour lisibilité\n",
+ "comparison_table.columns = [\n",
+ " col.replace(\"tags.\", \"\").replace(\"metrics.\", \"\").replace(\"mlflow.\", \"\")\n",
+ " for col in comparison_table.columns\n",
+ "]\n",
+ "\n",
+ "# Formater les métriques numériques\n",
+ "for metric in [\"auc\", \"business_cost_min\", \"optimal_threshold\", \"f1_score\", \"recall_class1\"]:\n",
+ " if metric in comparison_table.columns:\n",
+ " comparison_table[metric] = comparison_table[metric].round(4)\n",
+ "\n",
+ "# Afficher le tableau\n",
+ "print(\"\\n\" + \"=\"*100)\n",
+ "print(\"📊 COMPARAISON DES RUNS (triés par coût métier croissant)\")\n",
+ "print(\"=\"*100)\n",
+ "display(comparison_table.head(20))\n",
+ "\n",
+ "# Comparaison spécifique : meilleurs runs de chaque approche\n",
+ "print(\"\\n\" + \"=\"*100)\n",
+ "print(\"🏆 MEILLEURS RUNS PAR APPROCHE\")\n",
+ "print(\"=\"*100)\n",
+ "\n",
+ "approaches = {\n",
+ " \"Baseline CV\": \"LGBM_baseline_CV\",\n",
+ " \"Optuna 15 trials\": \"LGBM_optuna_tuning\",\n",
+ " \"Final Validation\": \"LGBM_final_validation\",\n",
+ " \"Optuna Improved\": \"LGBM_optuna_improved\",\n",
+ " \"Final Improved Holdout\": \"final_model_improved_holdout\"\n",
+ "}\n",
+ "\n",
+ "best_runs = []\n",
+ "for approach_name, run_name in approaches.items():\n",
+ " run_data = comparison_table[comparison_table[\"runName\"] == run_name]\n",
+ " if not run_data.empty:\n",
+ " best_runs.append({\n",
+ " \"Approche\": approach_name,\n",
+ " \"AUC\": run_data[\"auc\"].values[0],\n",
+ " \"Coût Min\": run_data[\"business_cost_min\"].values[0],\n",
+ " \"Seuil Opt\": run_data[\"optimal_threshold\"].values[0],\n",
+ " \"F1\": run_data[\"f1_score\"].values[0],\n",
+ " \"Recall\": run_data[\"recall_class1\"].values[0]\n",
+ " })\n",
+ "\n",
+ "if best_runs:\n",
+ " best_runs_df = pd.DataFrame(best_runs)\n",
+ " display(best_runs_df)\n",
+ " \n",
+ " # Calculer les gains\n",
+ " if len(best_runs_df) > 1:\n",
+ " print(\"\\n📈 GAINS (vs Baseline CV):\")\n",
+ " baseline_cost = best_runs_df.loc[best_runs_df[\"Approche\"] == \"Baseline CV\", \"Coût Min\"].values\n",
+ " if len(baseline_cost) > 0:\n",
+ " baseline_cost = baseline_cost[0]\n",
+ " for idx, row in best_runs_df.iterrows():\n",
+ " if row[\"Approche\"] != \"Baseline CV\":\n",
+ " gain = ((baseline_cost - row[\"Coût Min\"]) / baseline_cost) * 100\n",
+ " print(f\" {row['Approche']}: {gain:+.2f}% (coût: {row['Coût Min']:.2f})\")\n",
+ "else:\n",
+ " print(\"⚠️ Aucun run trouvé pour les approches spécifiées\")\n",
+ "\n",
+ "print(\"\\n✅ Comparaison terminée\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c53ad490",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "🔀 Hold-out split:\n",
+ " Train: 8000 | Hold-out: 2000\n",
+ "✓ Utilisation des best_params d'Optuna\n",
+ "\n",
+ "🚀 Entraînement du modèle final...\n",
+ "✓ Modèle final entraîné\n",
+ "\n",
+ "📊 Hold-out AUC-ROC: 0.7488\n",
+ "🎯 Seuil optimal : 0.51\n",
+ "💰 Coût minimal : 1067.00\n",
+ "📈 F1-score (seuil optimal) : 0.2591\n",
+ "📈 Recall classe 1 (seuil optimal) : 0.6452\n",
+ "\n",
+ "📊 Plot sauvegardé : /home/valentin/Env_Python/OC_P6/threshold_cost_curve.png\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "2026/02/05 18:50:17 WARNING mlflow.utils.environment: Failed to resolve installed pip version. ``pip`` will be added to conda.yaml environment spec without a version specifier.\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "✅ Run MLflow 'LGBM_final_validation' terminé\n",
+ " 📊 Métriques : AUC=0.7488, Min Cost=1067.00, F1=0.2591\n",
+ " 🎯 Seuil optimal : 0.51\n",
+ "🏃 View run LGBM_final_validation at: http://127.0.0.1:5000/#/experiments/1/runs/7a77c9ebbeb5495e9e80e535db720c7a\n",
+ "🧪 View experiment at: http://127.0.0.1:5000/#/experiments/1\n"
+ ]
+ },
+ {
+ "data": {
+ "image/png": "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GzZM8vLykpRKpeTv7y89+eSTUnp6+g2f09jHFRQUSAsWLJDatm0rKZVKycPDQ7rjjjuk9957TyovL9eXy87OlsaOHSs5ODhIbm5u0pNPPiklJCSYbMkwSZKkzMxMydraWvrf//5ncH7SpEmSSqWq9XG4bsmwyud67LHHJA8PD0mpVEqdO3eucckt1LDkXl3qaG0++ugjKTQ0VLKxsZFatmwpPfXUU9LVq1erlXv//felVq1aSba2tlLfvn2lI0eOVFsyTJIk6ffff5c6duwoWVtbV/vMe/fuLU2cONHo2IiILJUgSbVMx0lEREREAIApU6bg33//xZ49e+QOxSzEx8eje/fuOHbsGG677Ta5wyEikhWTbiIiIqKbSE1NRbt27bB9+3b07dtX7nCavPHjx0MURfz4449yh0JEJDsm3UREREREREQNhLOXExERERERETUQJt1EREREREREDYRJNxEREREREVEDYdJNRERERERE1ECs5Q7AXIiiiLS0NDg5OUEQBLnDISIiIiIiIhlJkoSCggL4+vpCoai9PZtJt5HS0tLg5+cndxhERERERETUhFy8eBGtW7eu9TqTbiM5OTkB0H2gzs7OMkdDdGtEUUR2djY8PT1veDeOyNywbjchRUWAr69uPy0NUKnkjcfMsW6TpWLdJkugVqvh5+enzxVrw6TbSJVdyp2dnZl0k9kSRRGlpaVwdnbmHziyKKzbTYiV1bV9Z2cm3fXEuk2WinWbLMnNhh+zhhMRERERERE1ECbdRERERERERA2ESTcRERERERFRA+GYbiIiIiIiMktarRYVFRVyh0EWysbGBlZV5yq5RUy6iYiIiIjIrEiShIyMDOTl5ckdClk4V1dXeHt733SytBth0k1ERESmY28PpKRc2yciagCVCbeXlxccHBzqlRAR1USSJBQXFyMrKwsA4OPjc8vPxaSbiIiITEehAAID5Y6CiCyYVqvVJ9wtWrSQOxyyYPb/3TzOysqCl5fXLXc150RqRERERERkNirHcDs4OMgcCTUHlfWsPnMHMOkmIiIi0ykvB+bP123l5XJHQ0QWjF3KqTGYop4x6SYiIiLTqagA3ntPt3FGYSIiIibdRERERERElmDx4sW47bbbLOZ1LAWTbiIiIiIiokZw8eJFPP744/D19YVSqURAQADmzJmDK1eu1Pm5BEHAb7/9ZnDu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ZCJVPn+FQmFwTLeGdZssFes2mTtj665RpQYMGIC8vDz98R9//IGSkpJbCqyqwsJCxMfHIz4+HgCQkpKC+Ph4/Trc8+fPxw8//IDPPvsMZ8+exUcffYSNGzdixowZAAAXFxdMmTIF8+bNw86dO3H06FE89thj6NOnD26//XYAwLBhw9CxY0c88sgjOH78OP7880+88sormDlzZo1JNREZZ1yP1vr9H45chBELIRARERERNTtGJd179+5FeXm5/njixIlIT6//urxHjhxBt27d0K1bNwDAvHnz0K1bN/0a2vfeey+ioqKwbNkydO7cGZ9//jl++eUX9OvXT/8cH3zwAe666y6MHTsWAwYMgLe3N3799Vf9dSsrK2zatAlWVlbo06cPJk6ciEcffRSvvfZaveMnas6CPR3RK9AdAHA2qxDHUvPkDYiIiIiIqAm6pe7lpmrRGjRo0E2f6/HHH8fjjz9e63U7OzusXr0aq1evrrVMQEAAtmzZcstxElHNHuzph0PncwEAPxxORXiAm8wREZHsFAqgcrJTdhklIiIyrqWbiKgmIzv7wMlWd+9u04l0FJZpZI6IiGRnbw/s2qXb7O3ljoaIiEh2t7ROd01rdAOmXaebiJo+e6UVRt/mi28PpqK4XIvNJ9LwYE9/ucMiIiIiImoybnmd7qprdAOmX6ebiMzD+J5++PagbvLD7w9fZNJNRERERFSFUd3LRVG86caEm6h56tzKBaHeTgCAv1PzkJRZIHNERCSroiLA01O3VVmzm4iIqLkyeky3VquFKIoAdBOpMckmIkDXy+XBnn764x8OX5QxGiJqEnJydBsREREZn3SvXLkSK1euBAB89NFH+n0ionu7tYLSWvfr5Ne/L6NcI8ocERERERFR02B00v30009jw4YNOH78OH7++WfMnj27IeMiIjPi6qDE8E7eAIDconJsO50pc0RERERERE2DUUn3kiVLsHTpUnh7e6Nfv37w9vbGW2+9hddee62h4yMiM/FgD3YxJyIiIiK6nlGzlw8aNAgAkJubCz8/P/j6+mLgwIENGRcRmZk72rRAazd7XLpagtikbFzOK0ErV67RS0RERETNm1Et3QMHDkTHjh1x6NAhHDhwAAcPHkSnTp2YeBORnkIhYNx/rd2SBPx85JLMERERERERyc/oMd2//vorXnnlFTg7O2PRokX45Zdf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0aNH8/LLLzvrI4jYjdbpFhEREREREXEQdQuIiIiIiIiIOIiSbhEREREREREHUdItIiIiIiIi4iBKukVEREREREQcREm3iIiIiIiIiIMo6RYRERERERFxECXdIiIiIiIiIg6ipFtERETqzKlTp3j++edJTU11digiIiJ1Qkm3iIhIAzRkyBAmTZpkfd2uXTvmzp1brWvce++93HjjjXaN62KxmM1m7rnnHs6ePUtoaGitr1fbY0VEROqCq7MDEBERaUyOHz/O9OnTWbZsGenp6TRv3pzu3bszffp0BgwYYLf3WbRoEW5ubna7Xl147bXX8PPzY/bs2dU67+eff8bb29vux4qIiNQFJd0iIiJ2dMstt1BYWMi///1vIiIiSE9PJz4+npMnT9r1fVq0aGHX69WFZ555pkbnBQUFOeRYERGRuqDh5SIiInZy+vRpfvjhB1599VWuvvpq2rZtS9++fZk6dSrXX3+9zXEPPvggQUFB+Pn5cc011/Drr79a91c27HvSpEkMGTLE+vr84eWXUlJSwuTJkwkICKBly5Y888wzmM1mm2NMJhOzZ8+mffv2eHl50b17dxYuXHjR62ZkZDB69Gi8vLxo3749n332WaV/Lxf7vABff/01ffr0wdPTk8DAQG666SbrvvJDxs1mMzNnzqRNmzZ4eHgQFhbGE088UemxACkpKdxwww34+Pjg5+fH7bffTnp6unX/zJkz6dGjB59++int2rXD39+fMWPGkJ2dfcm/UxERkapQ0i0iImInPj4++Pj4sGTJEgoKCi543G233UZGRgYrVqxg27Zt9OzZk6FDh5KZmemw2P72t7/x8ccf889//pMNGzaQmZnJ4sWLbY6ZPXs2n3zyCfPnz2fnzp08+eSTjB07lnXr1l3wuvfeey+HDx9mzZo1LFy4kPfee4+MjAybYy71eZctW8ZNN93EqFGj+OWXX4iPj6dv376Vvt9///tf3nzzTf7+97+TnJzMkiVL6Nq1a6XHmkwmbrjhBjIzM1m3bh2rVq1i//793HHHHTbH7du3jyVLlvDNN9/wzTffsG7dOl555ZVL/p2KiIhUhYaXi4iI2Imrqysff/wxEyZMYP78+fTs2ZPBgwczZswYunXrBsCGDRvYsmULGRkZeHh4APD666+zZMkSFi5cyEMPPeSQ2ObOncvUqVO5+eabAZg/fz7ffvutdX9BQQGzZs1i9erVxMbGAhAREcGGDRv4+9//zuDBgytcc8+ePaxYsYItW7bQp08fAD766CM6d+5sPaYqn/fll19mzJgxPP/889bzunfvXunnSElJISQkhGHDhuHm5kabNm0umKDHx8ezY8cODhw4QHh4OACffPIJV1xxBT///LM1ZpPJxMcff4yvry8A48aNIz4+npdffrkKf7MiIiIXp55uERERO7rllls4duwYS5cuJS4ujrVr19KzZ08+/vhjAH799VdycnJo2bKltWfcx8eHAwcOsG/fPofEdObMGVJTU+nXr591m6urK71797a+3rt3L3l5eQwfPtwmrk8++eSCce3atQtXV1d69epl3RYVFUVAQID1dVU+b0JCAkOHDq3SZ7nttts4e/YsERERTJgwgcWLF1NcXHzB+MLDw60JN0CXLl0ICAhg165d1m3t2rWzJtwAoaGhFXrrRUREako93SIiInbm6enJ8OHDGT58ONOmTePBBx9kxowZ3HvvveTk5BAaGsratWsrnFearBqNxgrzrYuKihwac05ODmAZ6t26dWubfaU91DW97qU+r5eXV5WvFx4eTlJSEqtXr2bVqlU88sgjvPbaa6xbt67G1dzPP89gMGAymWp0LRERkfOpp1tERMTBunTpQm5uLgA9e/YkLS0NV1dXLr/8cptHYGAgYKnAnZqaanONhISEGr+/v78/oaGh/PTTT9ZtxcXFbNu2zSZGDw8PUlJSKsRVvqe4vKioqArXSUpK4vTp09bXVfm83bp1Iz4+vsqfx8vLi9GjR/P222+zdu1aNm3axI4dOyoc17lzZw4fPszhw4et237//XdOnz5Nly5dqvx+IiIitaGebhERETs5efIkt912G/fffz/dunXD19eXrVu3MmfOHG644QYAhg0bRmxsLDfeeCNz5swhMjKSY8eOWYuJ9e7dm2uuuYbXXnuNTz75hNjYWBYsWEBiYiIxMTE1jm3ixIm88sordOzYkaioKN544w2b5NjX15enn36aJ598EpPJxMCBAzlz5gwbN27Ez8+Pe+65p8I1O3XqRFxcHH/84x95//33cXV1ZdKkSTY911X5vDNmzGDo0KF06NCBMWPGUFxczPLly5kyZUqF9/z4448pKSmhX79+NGvWjAULFuDl5UXbtm0rHDts2DC6du3K3Xffzdy5cykuLuaRRx5h8ODBNkPrRUREHEk93SIiInbi4+NDv379ePPNNxk0aBDR0dFMmzaNCRMmMG/ePMAydHn58uUMGjSI++67j8jISMaMGcOhQ4cIDg4G4Nprr2XatGk888wz9OnTh+zsbMaPH1+r2J566inGjRvHPffcQ2xsLL6+vjbLcgG8+OKLTJs2jdmzZ9O5c2fi4uJYtmwZ7du3v+B1//WvfxEWFsbgwYO5+eabeeihh2jVqpV1f1U+75AhQ/jqq69YunQpPXr04JprrmHLli2Vvl9AQAAffvghAwYMoFu3bqxevZqvv/6ali1bVjjWYDDwv//9j+bNmzNo0CCGDRtGREQEX375ZU3+CkVERGrEYD5/0piIiIiIiIiI2IV6ukVEREREREQcREm3iIiIiIiIiIMo6RYRERERERFxECXdIiIiIiIiIg6ipFtERERERETEQZR0i4iIiIiIiDiIkm4RERERERERB1HSLSIiIiIiIuIgSrpFREREREREHERJt4iIiIiIiIiDKOkWERERERERcRAl3SIiIiIiIiIO8v9Q6zj0AZ9z3QAAAABJRU5ErkJggg==",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# ============================================================================\n",
+ "# SECTION FINALE : Validation & Optimisation du seuil métier (Hold-out)\n",
+ "# ============================================================================\n",
+ "# WHY HOLD-OUT :\n",
+ "# - Valide la généralisation (évite overfitting de la CV)\n",
+ "# - Évalue le modèle sur données jamais vues pendant Optuna\n",
+ "# - Reflète mieux la performance en production\n",
+ "#\n",
+ "# WHY SEUIL FIN ICI (pas dans Optuna) :\n",
+ "# - Optuna avec seuils grossiers (0.2-0.7, step 0.1) : ~10 min, peu de précision\n",
+ "# - Seuil fin (0.05-0.95, step 0.01) : ici rapidement sans ralentir optimisation\n",
+ "# ============================================================================\n",
+ "\n",
+ "from sklearn.model_selection import train_test_split\n",
+ "from sklearn.metrics import f1_score, recall_score, roc_auc_score, confusion_matrix\n",
+ "import matplotlib.pyplot as plt\n",
+ "import warnings\n",
+ "\n",
+ "# Désactiver le warning MLflow pip\n",
+ "warnings.filterwarnings('ignore', message='.*Failed to resolve installed pip version.*')\n",
+ "\n",
+ "# 1. Créer le hold-out stratifié (20% test, 80% train)\n",
+ "X_train_final, X_holdout, y_train_final, y_holdout = train_test_split(\n",
+ " X_train, y_train, \n",
+ " test_size=0.2, \n",
+ " stratify=y_train, \n",
+ " random_state=RANDOM_STATE\n",
+ ")\n",
+ "\n",
+ "print(f\"🔀 Hold-out split:\")\n",
+ "print(f\" Train: {X_train_final.shape[0]} | Hold-out: {X_holdout.shape[0]}\")\n",
+ "\n",
+ "# 2. Utiliser best_params d'Optuna (doit venir de la cellule précédente)\n",
+ "# Si best_params n'existe pas, utiliser des valeurs par défaut\n",
+ "try:\n",
+ " _ = best_params\n",
+ " print(f\"✓ Utilisation des best_params d'Optuna\")\n",
+ "except NameError:\n",
+ " best_params = {\n",
+ " 'num_leaves': 90,\n",
+ " 'max_depth': 8,\n",
+ " 'learning_rate': 0.035,\n",
+ " 'n_estimators': 500,\n",
+ " 'min_child_samples': 50,\n",
+ " 'subsample': 0.7,\n",
+ " 'colsample_bytree': 0.9,\n",
+ " 'class_weight': 'balanced',\n",
+ " 'random_state': 42,\n",
+ " 'verbose': -1,\n",
+ " }\n",
+ " print(\"⚠ best_params non trouvé, utilisation des valeurs par défaut\")\n",
+ "\n",
+ "# S'assurer que verbose est défini\n",
+ "if 'verbose' not in best_params:\n",
+ " best_params['verbose'] = -1\n",
+ "\n",
+ "# 3. Entraîner le modèle final sur 80%\n",
+ "print(\"\\n🚀 Entraînement du modèle final...\")\n",
+ "final_model = LGBMClassifier(**best_params)\n",
+ "final_model.fit(X_train_final, y_train_final)\n",
+ "print(\"✓ Modèle final entraîné\")\n",
+ "\n",
+ "# 4. Prédire probabilités sur hold-out\n",
+ "y_holdout_proba = final_model.predict_proba(X_holdout)[:, 1]\n",
+ "\n",
+ "# 5. Calculer AUC-ROC sur hold-out\n",
+ "holdout_auc = roc_auc_score(y_holdout, y_holdout_proba)\n",
+ "print(f\"\\n📊 Hold-out AUC-ROC: {holdout_auc:.4f}\")\n",
+ "\n",
+ "# 6. Optimisation FINE du seuil (0.05-0.95, step 0.01)\n",
+ "fine_thresholds = np.arange(0.05, 0.96, 0.01)\n",
+ "threshold_costs = []\n",
+ "\n",
+ "for thr in fine_thresholds:\n",
+ " y_holdout_pred = (y_holdout_proba >= thr).astype(int)\n",
+ " tn, fp, fn, tp = confusion_matrix(y_holdout, y_holdout_pred).ravel()\n",
+ " cost = 10 * fn + 1 * fp\n",
+ " threshold_costs.append({\n",
+ " 'threshold': thr,\n",
+ " 'cost': cost,\n",
+ " 'tp': tp,\n",
+ " 'fp': fp,\n",
+ " 'fn': fn,\n",
+ " 'tn': tn\n",
+ " })\n",
+ "\n",
+ "threshold_costs_df = pd.DataFrame(threshold_costs)\n",
+ "optimal_idx = threshold_costs_df['cost'].idxmin()\n",
+ "optimal_threshold = threshold_costs_df.loc[optimal_idx, 'threshold']\n",
+ "min_cost = threshold_costs_df.loc[optimal_idx, 'cost']\n",
+ "\n",
+ "print(f\"🎯 Seuil optimal : {optimal_threshold:.2f}\")\n",
+ "print(f\"💰 Coût minimal : {min_cost:.2f}\")\n",
+ "\n",
+ "# 7. Calculer F1 et Recall au seuil optimal\n",
+ "y_holdout_optimal = (y_holdout_proba >= optimal_threshold).astype(int)\n",
+ "holdout_f1 = f1_score(y_holdout, y_holdout_optimal)\n",
+ "holdout_recall = recall_score(y_holdout, y_holdout_optimal)\n",
+ "\n",
+ "print(f\"📈 F1-score (seuil optimal) : {holdout_f1:.4f}\")\n",
+ "print(f\"📈 Recall classe 1 (seuil optimal) : {holdout_recall:.4f}\")\n",
+ "\n",
+ "# 8. Tracer la courbe coût vs seuil\n",
+ "plt.figure(figsize=(10, 6))\n",
+ "plt.plot(threshold_costs_df['threshold'], threshold_costs_df['cost'], \n",
+ " marker='o', linewidth=2, markersize=4)\n",
+ "plt.axvline(optimal_threshold, color='red', linestyle='--', \n",
+ " label=f'Optimal = {optimal_threshold:.2f}')\n",
+ "plt.xlabel('Seuil de décision')\n",
+ "plt.ylabel('Coût métier (10*FN + 1*FP)')\n",
+ "plt.title('Courbe de coût vs seuil (Hold-out)')\n",
+ "plt.legend()\n",
+ "plt.grid(True, alpha=0.3)\n",
+ "plt.tight_layout()\n",
+ "\n",
+ "\n",
+ "# 9. Log MLflow (RUN INDÉPENDANT - après Optuna, avec résultats finaux)\n",
+ "# Logging uniformisé pour comparaison facile\n",
+ "with mlflow.start_run(run_name=\"LGBM_final_validation\"):\n",
+ " # Log params\n",
+ " mlflow.log_params(best_params)\n",
+ " \n",
+ " # Log tags\n",
+ " for tag_key, tag_value in MLFLOW_TAGS.items():\n",
+ " mlflow.set_tag(tag_key, tag_value)\n",
+ " mlflow.set_tag(\"model_type\", MODEL_NAME)\n",
+ " mlflow.set_tag(\"phase\", \"final_validation\")\n",
+ " mlflow.set_tag(\"validation_method\", \"hold-out_20pct\")\n",
+ " mlflow.set_tag(\"evaluation_type\", \"holdout\")\n",
+ " mlflow.set_tag(\"threshold_optimized\", \"yes\")\n",
+ " \n",
+ " # Log metrics standardisées\n",
+ " mlflow.log_metric(\"auc\", holdout_auc)\n",
+ " mlflow.log_metric(\"business_cost_min\", min_cost)\n",
+ " mlflow.log_metric(\"optimal_threshold\", optimal_threshold)\n",
+ " mlflow.log_metric(\"f1_score\", holdout_f1)\n",
+ " mlflow.log_metric(\"recall_class1\", holdout_recall)\n",
+ " \n",
+ " # Log plot\n",
+ " mlflow.log_artifact(plot_path)\n",
+ " \n",
+ " # Log tableau des coûts par décile (JSON)\n",
+ " decile_costs = threshold_costs_df[::10].to_dict(orient='records')\n",
+ " mlflow.log_dict(decile_costs, \"threshold_costs_deciles.json\")\n",
+ " \n",
+ " # Log du modèle\n",
+ " mlflow.lightgbm.log_model(final_model, name=MODEL_NAME)\n",
+ " \n",
+ " print(f\"\\n✅ Run MLflow 'LGBM_final_validation' terminé\")\n",
+ " print(f\" 📊 Métriques : AUC={holdout_auc:.4f}, Min Cost={min_cost:.2f}, F1={holdout_f1:.4f}\")\n",
+ " print(f\" 🎯 Seuil optimal : {optimal_threshold:.2f}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6bb408ea",
+ "metadata": {},
+ "source": [
+ "## Interprétabilité (global + local) avec SHAP\n",
+ "SHAP est pertinent pour la transparence métier car il fournit une attribution **cohérente et locale** des contributions de chaque variable à une décision, tout en restant **agrégeable au niveau global**. Cela permet d’expliquer un score client individuel (force plot) et de justifier les facteurs principaux à l’échelle du portefeuille (summary plot), ce qui est attendu en contexte de scoring de crédit."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "ff0d1e0e",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "2026/02/05 18:50:21 WARNING mlflow.utils.environment: Failed to resolve installed pip version. ``pip`` will be added to conda.yaml environment spec without a version specifier.\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "⚠ SHAP non disponible ou incompatibilité: Converting `np.inexact` or `np.floating` to a dtype not allowed...\n",
+ " - Les feature importance (gain/split) sont loggées\n",
+ " - Pour SHAP: pip install --upgrade shap scikit-learn\n",
+ "✓ Modèle final et feature importance loggés dans MLflow\n",
+ "🏃 View run LGBM_final_interpretability at: http://127.0.0.1:5000/#/experiments/1/runs/04657534a3d34d2bbe0a9a4c34446ee6\n",
+ "🧪 View experiment at: http://127.0.0.1:5000/#/experiments/1\n"
+ ]
+ }
+ ],
+ "source": [
+ "# ============================================================================\n",
+ "# Modèle final + Feature importance + SHAP (optionnel)\n",
+ "# ============================================================================\n",
+ "import os\n",
+ "from pathlib import Path\n",
+ "import matplotlib.pyplot as plt\n",
+ "import lightgbm as lgb\n",
+ "import warnings\n",
+ "\n",
+ "# Désactiver les warnings MLflow\n",
+ "warnings.filterwarnings('ignore', message='.*Failed to resolve installed pip version.*')\n",
+ "warnings.filterwarnings('ignore', message='.*Inferred schema contains integer column.*')\n",
+ "\n",
+ "# Entraîner le modèle final sur tout le train set\n",
+ "final_model = LGBMClassifier(**best_params)\n",
+ "final_model.fit(X_train, y_train)\n",
+ "\n",
+ "# Logging uniformisé pour comparaison facile\n",
+ "with mlflow.start_run(run_name=\"LGBM_final_interpretability\"):\n",
+ " # Tags + params\n",
+ " mlflow.log_params(best_params)\n",
+ " for tag_key, tag_value in MLFLOW_TAGS.items():\n",
+ " mlflow.set_tag(tag_key, tag_value)\n",
+ " mlflow.set_tag(\"model_type\", MODEL_NAME)\n",
+ " mlflow.set_tag(\"phase\", \"final_interpretability\")\n",
+ " mlflow.set_tag(\"evaluation_type\", \"holdout\")\n",
+ " mlflow.set_tag(\"threshold_optimized\", \"yes\")\n",
+ " \n",
+ " # Log métriques standardisées (réutilise les valeurs du hold-out)\n",
+ " mlflow.log_metric(\"auc\", holdout_auc)\n",
+ " mlflow.log_metric(\"business_cost_min\", min_cost)\n",
+ " mlflow.log_metric(\"optimal_threshold\", optimal_threshold)\n",
+ " mlflow.log_metric(\"f1_score\", holdout_f1)\n",
+ " mlflow.log_metric(\"recall_class1\", holdout_recall)\n",
+ " \n",
+ " # Log du modèle final\n",
+ " mlflow.lightgbm.log_model(final_model, name=MODEL_NAME)\n",
+ " \n",
+ " # --- Feature importance globale (gain) ---\n",
+ " fig_gain, ax_gain = plt.subplots(figsize=(8, 6))\n",
+ " lgb.plot_importance(final_model, importance_type=\"gain\", ax=ax_gain, max_num_features=30)\n",
+ " ax_gain.set_title(\"Feature Importance (Gain)\")\n",
+ " mlflow.log_figure(fig_gain, \"feature_importance_gain.png\")\n",
+ " plt.close(fig_gain)\n",
+ " \n",
+ " # --- Feature importance globale (split) ---\n",
+ " fig_split, ax_split = plt.subplots(figsize=(8, 6))\n",
+ " lgb.plot_importance(final_model, importance_type=\"split\", ax=ax_split, max_num_features=30)\n",
+ " ax_split.set_title(\"Feature Importance (Split)\")\n",
+ " mlflow.log_figure(fig_split, \"feature_importance_split.png\")\n",
+ " plt.close(fig_split)\n",
+ " \n",
+ " # --- SHAP : interprétabilité locale & globale (optionnel - peut avoir incompatibilités) ---\n",
+ " try:\n",
+ " import shap\n",
+ " print(\"\\n📊 Calcul des SHAP values...\")\n",
+ " \n",
+ " sample_size = min(1000, len(X_train))\n",
+ " X_sample = X_train.sample(n=sample_size, random_state=42)\n",
+ " \n",
+ " explainer = shap.TreeExplainer(final_model)\n",
+ " shap_values = explainer.shap_values(X_sample)\n",
+ " \n",
+ " # Pour binaire, shap_values peut être une liste [classe0, classe1]\n",
+ " if isinstance(shap_values, list):\n",
+ " shap_values_to_use = shap_values[1]\n",
+ " else:\n",
+ " shap_values_to_use = shap_values\n",
+ " \n",
+ " # Summary plot (bee swarm)\n",
+ " shap.summary_plot(shap_values_to_use, X_sample, show=False)\n",
+ " fig_summary = plt.gcf()\n",
+ " fig_summary.set_size_inches(10, 6)\n",
+ " mlflow.log_figure(fig_summary, \"shap_summary_beeswarm.png\")\n",
+ " plt.close(fig_summary)\n",
+ " \n",
+ " # Force plots pour 5 clients aléatoires\n",
+ " force_dir = Path(\"shap_force_plots\")\n",
+ " force_dir.mkdir(parents=True, exist_ok=True)\n",
+ " rng = np.random.default_rng(42)\n",
+ " sample_indices = rng.choice(X_sample.index, size=min(5, len(X_sample)), replace=False)\n",
+ " \n",
+ " for i, idx in enumerate(sample_indices, start=1):\n",
+ " force_plot = shap.force_plot(\n",
+ " explainer.expected_value if not isinstance(explainer.expected_value, (list, tuple)) else explainer.expected_value[1],\n",
+ " shap_values_to_use[X_sample.index.get_loc(idx)],\n",
+ " X_sample.loc[idx],\n",
+ " matplotlib=False\n",
+ " )\n",
+ " force_path = force_dir / f\"shap_force_plot_{i}.html\"\n",
+ " shap.save_html(str(force_path), force_plot)\n",
+ " mlflow.log_artifact(str(force_path))\n",
+ " \n",
+ " print(\"✓ Artefacts SHAP loggés dans MLflow\")\n",
+ " \n",
+ " except (ImportError, TypeError) as e:\n",
+ " print(f\"⚠ SHAP non disponible ou incompatibilité: {str(e)[:80]}...\")\n",
+ " print(\" - Les feature importance (gain/split) sont loggées\")\n",
+ " print(\" - Pour SHAP: pip install --upgrade shap scikit-learn\")\n",
+ " \n",
+ " print(\"✓ Modèle final et feature importance loggés dans MLflow\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "id": "4586fb79",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Colonnes object détectées: []\n",
+ "Dtypes après conversion:\n",
+ "float64 568\n",
+ "bool 131\n",
+ "int64 42\n",
+ "Name: count, dtype: int64\n",
+ "\n",
+ "Colonnes (exemples): ['SK_ID_CURR', 'CODE_GENDER', 'FLAG_OWN_CAR', 'FLAG_OWN_REALTY', 'CNT_CHILDREN']\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Convertir les colonnes object en types numériques\n",
+ "import numpy as np\n",
+ "\n",
+ "# Identifier et convertir les colonnes object\n",
+ "object_cols = X_train.select_dtypes(include=['object']).columns.tolist()\n",
+ "print(f\"Colonnes object détectées: {object_cols}\")\n",
+ "\n",
+ "# Convertir chaque colonne object en numeric\n",
+ "for col in object_cols:\n",
+ " X_train[col] = pd.to_numeric(X_train[col], errors='coerce')\n",
+ " # Remplacer les NaN introduits par la conversion par 0\n",
+ " X_train[col] = X_train[col].fillna(0)\n",
+ "\n",
+ "# Nettoyer les noms de colonnes (remplacer les caractères spéciaux)\n",
+ "X_train.columns = X_train.columns.str.replace(' ', '_').str.replace('[^a-zA-Z0-9_]', '_', regex=True)\n",
+ "\n",
+ "# Vérifier que toutes les colonnes sont numériques\n",
+ "print(f\"Dtypes après conversion:\\n{X_train.dtypes.value_counts()}\")\n",
+ "print(f\"\\nColonnes (exemples): {X_train.columns[:5].tolist()}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9f16afb8",
+ "metadata": {},
+ "source": [
+ "## Runs de modèles\n",
+ "Les entraînements et le logging MLflow commencent ici."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "id": "1214b537",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "2026/02/05 18:50:25 WARNING mlflow.utils.environment: Failed to resolve installed pip version. ``pip`` will be added to conda.yaml environment spec without a version specifier.\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "✓ Run terminé: LightGBM_baseline_1.0\n",
+ " AUC: 0.7402 | F1: 0.2477 | Recall_1: 0.6194\n",
+ " Tags appliqués: {'project_version': '1.0', 'notebook': '03_LGBM', 'phase': 'baseline', 'desequilibre_handling': 'class_weight_balanced', 'date': datetime.datetime(2026, 2, 5, 18, 40, 14, 65216)}\n",
+ "🏃 View run LightGBM_baseline_1.0 at: http://127.0.0.1:5000/#/experiments/1/runs/4e7051283d0f4b3aadbb3d774a047208\n",
+ "🧪 View experiment at: http://127.0.0.1:5000/#/experiments/1\n"
+ ]
+ }
+ ],
+ "source": [
+ "from lightgbm import LGBMClassifier\n",
+ "from sklearn.metrics import roc_auc_score, f1_score, recall_score, confusion_matrix\n",
+ "from sklearn.model_selection import train_test_split\n",
+ "import numpy as np\n",
+ "import warnings\n",
+ "\n",
+ "# Désactiver les warnings MLflow\n",
+ "warnings.filterwarnings('ignore', message='.*Failed to resolve installed pip version.*')\n",
+ "warnings.filterwarnings('ignore', message='.*Inferred schema contains integer column.*')\n",
+ "\n",
+ "# Split si pas déjà fait\n",
+ "X_train_split, X_val_split, y_train_split, y_val_split = train_test_split(\n",
+ " X_train, y_train, \n",
+ " test_size=VALIDATION_SPLIT_RATIO, \n",
+ " stratify=y_train, \n",
+ " random_state=RANDOM_STATE\n",
+ ")\n",
+ "\n",
+ "# Appliquer les mêmes transformations aux données splittées\n",
+ "X_train_split.columns = X_train_split.columns.str.replace(' ', '_').str.replace('[^a-zA-Z0-9_]', '_', regex=True)\n",
+ "X_val_split.columns = X_val_split.columns.str.replace(' ', '_').str.replace('[^a-zA-Z0-9_]', '_', regex=True)\n",
+ "\n",
+ "# Nom du run avec version\n",
+ "RUN_NAME = f\"{MODEL_NAME}_baseline_{PROJECT_VERSION}\"\n",
+ "\n",
+ "# Logging uniformisé pour comparaison facile\n",
+ "with mlflow.start_run(run_name=RUN_NAME):\n",
+ " \n",
+ " # Définition du modèle avec la configuration\n",
+ " model = LGBMClassifier(**MODEL_CONFIG)\n",
+ " \n",
+ " # Entraînement\n",
+ " model.fit(X_train_split, y_train_split)\n",
+ " \n",
+ " # Prédictions et métriques\n",
+ " y_pred_proba = model.predict_proba(X_val_split)[:, 1]\n",
+ " auc = roc_auc_score(y_val_split, y_pred_proba)\n",
+ " \n",
+ " # Optimisation du seuil métier (coût 10*FN + 1*FP)\n",
+ " thresholds_baseline = np.arange(0.05, 0.96, 0.01)\n",
+ " min_cost = None\n",
+ " best_threshold = None\n",
+ " \n",
+ " for thr in thresholds_baseline:\n",
+ " y_pred_thr = (y_pred_proba >= thr).astype(int)\n",
+ " tn, fp, fn, tp = confusion_matrix(y_val_split, y_pred_thr).ravel()\n",
+ " cost = 10 * fn + 1 * fp\n",
+ " if (min_cost is None) or (cost < min_cost):\n",
+ " min_cost = cost\n",
+ " best_threshold = thr\n",
+ " \n",
+ " # F1/Recall au seuil optimal\n",
+ " y_pred_opt = (y_pred_proba >= best_threshold).astype(int)\n",
+ " f1 = f1_score(y_val_split, y_pred_opt)\n",
+ " recall_1 = recall_score(y_val_split, y_pred_opt)\n",
+ " \n",
+ " # === TRACKING MLFlow ===\n",
+ " # Appliquer les tags depuis la configuration\n",
+ " for tag_key, tag_value in MLFLOW_TAGS.items():\n",
+ " mlflow.set_tag(tag_key, tag_value)\n",
+ " \n",
+ " # Ajouter des tags supplémentaires\n",
+ " mlflow.set_tag(\"model_type\", MODEL_NAME)\n",
+ " mlflow.set_tag(\"phase\", \"baseline_simple\")\n",
+ " mlflow.set_tag(\"evaluation_type\", \"baseline_simple\")\n",
+ " mlflow.set_tag(\"threshold_optimized\", \"yes\")\n",
+ " \n",
+ " # Métriques standardisées\n",
+ " mlflow.log_metric(\"auc\", auc)\n",
+ " mlflow.log_metric(\"business_cost_min\", min_cost)\n",
+ " mlflow.log_metric(\"optimal_threshold\", best_threshold)\n",
+ " mlflow.log_metric(\"f1_score\", f1)\n",
+ " mlflow.log_metric(\"recall_class1\", recall_1)\n",
+ " \n",
+ " # Artefacts utiles (ex: plot importance)\n",
+ " # import matplotlib.pyplot as plt\n",
+ " # ... plot feature importance ...\n",
+ " # plt.savefig(\"feature_importance.png\")\n",
+ " # mlflow.log_artifact(\"feature_importance.png\")\n",
+ " \n",
+ " # Log du modèle avec le nom depuis la configuration\n",
+ " mlflow.lightgbm.log_model(model, name=MODEL_NAME)\n",
+ " \n",
+ " print(f\"✓ Run terminé: {RUN_NAME}\")\n",
+ " print(f\" AUC: {auc:.4f} | F1: {f1:.4f} | Recall_1: {recall_1:.4f}\")\n",
+ " print(f\" Tags appliqués: {MLFLOW_TAGS}\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "id": "18d5c225",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " tags.mlflow.runName | \n",
+ " tags.evaluation_type | \n",
+ " tags.threshold_optimized | \n",
+ " metrics.auc | \n",
+ " metrics.business_cost_min | \n",
+ " metrics.optimal_threshold | \n",
+ " metrics.f1_score | \n",
+ " metrics.recall_class1 | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 3 | \n",
+ " final_model_improved_holdout | \n",
+ " holdout | \n",
+ " yes | \n",
+ " 0.756363 | \n",
+ " 1048.000000 | \n",
+ " 0.450000 | \n",
+ " 0.248936 | \n",
+ " 0.754839 | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " best_params_improved_cv_evaluation | \n",
+ " cv_stratified | \n",
+ " yes | \n",
+ " 0.754472 | \n",
+ " 1021.600000 | \n",
+ " 0.540000 | \n",
+ " 0.290623 | \n",
+ " 0.606452 | \n",
+ "
\n",
+ " \n",
+ " | 5 | \n",
+ " LGBM_optuna_improved | \n",
+ " cv_stratified | \n",
+ " yes | \n",
+ " 0.754472 | \n",
+ " 1021.600000 | \n",
+ " 0.540000 | \n",
+ " 0.290623 | \n",
+ " 0.606452 | \n",
+ "
\n",
+ " \n",
+ " | 7 | \n",
+ " best_params_cv_evaluation | \n",
+ " cv_stratified | \n",
+ " yes | \n",
+ " 0.753281 | \n",
+ " 1761.333333 | \n",
+ " 0.533333 | \n",
+ " 0.277769 | \n",
+ " 0.589826 | \n",
+ "
\n",
+ " \n",
+ " | 8 | \n",
+ " LGBM_optuna_tuning | \n",
+ " cv_stratified | \n",
+ " yes | \n",
+ " 0.753281 | \n",
+ " 1761.333333 | \n",
+ " 0.533333 | \n",
+ " 0.277769 | \n",
+ " 0.589826 | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " LGBM_final_interpretability | \n",
+ " holdout | \n",
+ " yes | \n",
+ " 0.748828 | \n",
+ " 1067.000000 | \n",
+ " 0.510000 | \n",
+ " 0.259067 | \n",
+ " 0.645161 | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " LGBM_final_validation | \n",
+ " holdout | \n",
+ " yes | \n",
+ " 0.748828 | \n",
+ " 1067.000000 | \n",
+ " 0.510000 | \n",
+ " 0.259067 | \n",
+ " 0.645161 | \n",
+ "
\n",
+ " \n",
+ " | 0 | \n",
+ " LightGBM_baseline_1.0 | \n",
+ " baseline_simple | \n",
+ " yes | \n",
+ " 0.740177 | \n",
+ " 1114.000000 | \n",
+ " 0.060000 | \n",
+ " 0.247742 | \n",
+ " 0.619355 | \n",
+ "
\n",
+ " \n",
+ " | 9 | \n",
+ " LGBM_baseline_CV | \n",
+ " cv_stratified | \n",
+ " yes | \n",
+ " 0.711562 | \n",
+ " 1151.800000 | \n",
+ " 0.130000 | \n",
+ " 0.263213 | \n",
+ " 0.463226 | \n",
+ "
\n",
+ " \n",
+ " | 6 | \n",
+ " final_model | \n",
+ " train_full | \n",
+ " no | \n",
+ " -1.000000 | \n",
+ " -1.000000 | \n",
+ " -1.000000 | \n",
+ " -1.000000 | \n",
+ " -1.000000 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " tags.mlflow.runName tags.evaluation_type \\\n",
+ "3 final_model_improved_holdout holdout \n",
+ "4 best_params_improved_cv_evaluation cv_stratified \n",
+ "5 LGBM_optuna_improved cv_stratified \n",
+ "7 best_params_cv_evaluation cv_stratified \n",
+ "8 LGBM_optuna_tuning cv_stratified \n",
+ "1 LGBM_final_interpretability holdout \n",
+ "2 LGBM_final_validation holdout \n",
+ "0 LightGBM_baseline_1.0 baseline_simple \n",
+ "9 LGBM_baseline_CV cv_stratified \n",
+ "6 final_model train_full \n",
+ "\n",
+ " tags.threshold_optimized metrics.auc metrics.business_cost_min \\\n",
+ "3 yes 0.756363 1048.000000 \n",
+ "4 yes 0.754472 1021.600000 \n",
+ "5 yes 0.754472 1021.600000 \n",
+ "7 yes 0.753281 1761.333333 \n",
+ "8 yes 0.753281 1761.333333 \n",
+ "1 yes 0.748828 1067.000000 \n",
+ "2 yes 0.748828 1067.000000 \n",
+ "0 yes 0.740177 1114.000000 \n",
+ "9 yes 0.711562 1151.800000 \n",
+ "6 no -1.000000 -1.000000 \n",
+ "\n",
+ " metrics.optimal_threshold metrics.f1_score metrics.recall_class1 \n",
+ "3 0.450000 0.248936 0.754839 \n",
+ "4 0.540000 0.290623 0.606452 \n",
+ "5 0.540000 0.290623 0.606452 \n",
+ "7 0.533333 0.277769 0.589826 \n",
+ "8 0.533333 0.277769 0.589826 \n",
+ "1 0.510000 0.259067 0.645161 \n",
+ "2 0.510000 0.259067 0.645161 \n",
+ "0 0.060000 0.247742 0.619355 \n",
+ "9 0.130000 0.263213 0.463226 \n",
+ "6 -1.000000 -1.000000 -1.000000 "
+ ]
+ },
+ "execution_count": 13,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Tableau comparatif des runs MLflow (métriques standardisées)\n",
+ "runs_df = mlflow.search_runs(experiment_ids=[experiment_id])\n",
+ "metric_cols = [f\"metrics.{m}\" for m in STANDARD_METRICS.keys()]\n",
+ "display_cols = [\n",
+ " \"tags.mlflow.runName\",\n",
+ " \"tags.evaluation_type\",\n",
+ " \"tags.threshold_optimized\",\n",
+ " *metric_cols,\n",
+ " ]\n",
+ "comparison_df = runs_df[display_cols].sort_values(\"metrics.auc\", ascending=False)\n",
+ "comparison_df"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "37737c53",
+ "metadata": {},
+ "source": [
+ "## 🎯 Modèle Final pour Production\n",
+ "\n",
+ "Logging explicite du meilleur modèle LightGBM comme candidat de production dans MLflow.\n",
+ "Le modèle apparaîtra avec l'icône \"🎯 Model\" dans la colonne Models de la vue Runs."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "id": "e2ee6228",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "================================================================================\n",
+ "🎯 LOGGING DU MODÈLE FINAL - CANDIDAT PRODUCTION\n",
+ "================================================================================\n",
+ "✓ Paramètres chargés depuis : Optuna Improved\n",
+ "\n",
+ "📋 Paramètres du modèle final :\n",
+ " num_leaves: 126\n",
+ " max_depth: 1\n",
+ " learning_rate: 0.07877171375320807\n",
+ " min_child_samples: 73\n",
+ " subsample: 0.8722939690568707\n",
+ " colsample_bytree: 0.9324176064754862\n",
+ " reg_alpha: 0.7936847076321234\n",
+ " reg_lambda: 0.6893927595065354\n",
+ " min_split_gain: 0.49988742190892166\n",
+ " class_weight: balanced\n",
+ " random_state: 42\n",
+ " n_estimators: 500\n",
+ " verbose: -1\n",
+ "\n",
+ "🚀 Entraînement du modèle final sur X_train complet...\n",
+ "✓ Modèle final entraîné avec succès\n",
+ "\n",
+ "📊 COMPARAISON DES RUNS DISPONIBLES :\n",
+ " Run AUC Coût Métier F1 Recall \n",
+ " ------------------------------------------------------------\n",
+ " ✅ IMPROVED 0.7564 1048.00 0.2489 0.7548\n",
+ " BASELINE 0.7488 1114.00 0.2591 0.6452\n",
+ "\n",
+ "✅ MEILLEUR CHOISI : Hold-out IMPROVED\n",
+ " Raison : Coût métier minimal (1048.00)\n",
+ " (Improved meilleur de 66.00 points)\n",
+ "\n",
+ "📋 Métriques du modèle final :\n",
+ " AUC: 0.7564\n",
+ " Business Cost Min: 1048.00\n",
+ " Seuil Optimal: 0.45\n",
+ " F1-score: 0.2489\n",
+ " Recall (classe 1): 0.7548\n",
+ "\n",
+ "📤 Logging dans MLflow...\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "2026/02/05 18:50:27 WARNING mlflow.models.model: `artifact_path` is deprecated. Please use `name` instead.\n",
+ "2026/02/05 18:50:29 WARNING mlflow.utils.environment: Failed to resolve installed pip version. ``pip`` will be added to conda.yaml environment spec without a version specifier.\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "✅ MODÈLE LOGGUÉ AVEC SUCCÈS\n",
+ "================================================================================\n",
+ "Run ID: 318b179ef09a45bebdc57a210fc3fc1d\n",
+ "Run Name: LGBM_final_production_candidate\n",
+ "\n",
+ "📍 Accédez au modèle via :\n",
+ " MLflow UI: http://127.0.0.1:5000\n",
+ " Onglet: Experiments → OC_P6_Credit_Scoring → LGBM_final_production_candidate\n",
+ "\n",
+ "🎯 Le modèle apparaît maintenant dans la colonne 'Models' des Runs.\n",
+ "\n",
+ "📋 Prochaines étapes :\n",
+ " 1. Accédez au run dans l'UI MLflow\n",
+ " 2. Cliquez sur 'Register Model' pour l'ajouter au Model Registry\n",
+ " 3. Choisissez un nom du modèle (ex: 'credit_scoring_lgbm')\n",
+ " 4. Sélectionnez l'alias (ex: 'production', 'staging', 'champion')\n",
+ "================================================================================\n",
+ "🏃 View run LGBM_final_production_candidate at: http://127.0.0.1:5000/#/experiments/1/runs/318b179ef09a45bebdc57a210fc3fc1d\n",
+ "🧪 View experiment at: http://127.0.0.1:5000/#/experiments/1\n"
+ ]
+ }
+ ],
+ "source": [
+ "# ============================================================================\n",
+ "# LOGGING DU MODÈLE FINAL COMME CANDIDAT PRODUCTION\n",
+ "# ============================================================================\n",
+ "# Ce script crée un nouveau run MLflow dédié au meilleur modèle trouvé,\n",
+ "# avec logging explicite du modèle pour qu'il apparaisse dans l'UI avec l'icône 🎯 Model.\n",
+ "#\n",
+ "# Objectif :\n",
+ "# - Rendre le modèle visible et facilement accessible dans MLflow UI\n",
+ "# - Préparer le modèle pour une migration manuelle vers le Model Registry\n",
+ "# - Centraliser les métriques, params et métadonnées du candidat production\n",
+ "\n",
+ "import warnings\n",
+ "warnings.filterwarnings('ignore', message='.*Failed to resolve installed pip version.*')\n",
+ "warnings.filterwarnings('ignore', message='.*Inferred schema contains integer column.*')\n",
+ "\n",
+ "print(\"\\n\" + \"=\"*80)\n",
+ "print(\"🎯 LOGGING DU MODÈLE FINAL - CANDIDAT PRODUCTION\")\n",
+ "print(\"=\"*80)\n",
+ "\n",
+ "# ==========================================================================\n",
+ "# ÉTAPE 1 : Récupérer les meilleurs paramètres\n",
+ "# ==========================================================================\n",
+ "# Tentative 1 : Utiliser best_params_improved (du tuning Optuna amélioré)\n",
+ "# Tentative 2 : Fallback sur best_params (du tuning Optuna baseline)\n",
+ "# Tentative 3 : Utiliser des paramètres par défaut typiques\n",
+ "\n",
+ "try:\n",
+ " # Préférer les params améliorés si disponibles\n",
+ " final_params = best_params_improved.copy() if 'best_params_improved' in dir() else best_params.copy()\n",
+ " source = \"Optuna Improved\" if 'best_params_improved' in dir() else \"Optuna Baseline\"\n",
+ " print(f\"✓ Paramètres chargés depuis : {source}\")\n",
+ "except NameError:\n",
+ " # Fallback : paramètres par défaut basés sur un tuning typique\n",
+ " final_params = {\n",
+ " 'num_leaves': 150,\n",
+ " 'max_depth': 15,\n",
+ " 'learning_rate': 0.075,\n",
+ " 'n_estimators': 500,\n",
+ " 'min_child_samples': 30,\n",
+ " 'subsample': 0.85,\n",
+ " 'colsample_bytree': 0.85,\n",
+ " 'reg_alpha': 0.2,\n",
+ " 'reg_lambda': 0.5,\n",
+ " 'min_split_gain': 0.1,\n",
+ " 'class_weight': 'balanced',\n",
+ " 'random_state': 42,\n",
+ " 'verbose': -1,\n",
+ " }\n",
+ " source = \"Paramètres par défaut (fallback)\"\n",
+ " print(f\"⚠ Paramètres chargés depuis : {source}\")\n",
+ "\n",
+ "print(f\"\\n📋 Paramètres du modèle final :\")\n",
+ "for param, value in final_params.items():\n",
+ " print(f\" {param}: {value}\")\n",
+ "\n",
+ "# ==========================================================================\n",
+ "# ÉTAPE 2 : Ré-entraîner le modèle final sur X_train complet\n",
+ "# ==========================================================================\n",
+ "print(\"\\n🚀 Entraînement du modèle final sur X_train complet...\")\n",
+ "production_model = LGBMClassifier(**final_params)\n",
+ "production_model.fit(X_train, y_train)\n",
+ "print(\"✓ Modèle final entraîné avec succès\")\n",
+ "\n",
+ "# ==========================================================================\n",
+ "# ÉTAPE 3 : Récupérer les métriques finales du MEILLEUR run\n",
+ "# ==========================================================================\n",
+ "# COMPARAISON INTELLIGENTE : Choisir le run avec le meilleur coût métier\n",
+ "# (critère métier prioritaire) et le meilleur AUC (critère secondaire)\n",
+ "# Cela garantit que LGBM_final_validation est préféré s'il est meilleur\n",
+ "\n",
+ "available_runs = {}\n",
+ "\n",
+ "# Option 1 : Vérifier la disponibilité du run amélioré (Optuna 150 trials)\n",
+ "if 'holdout_auc_improved' in dir() and holdout_auc_improved > 0:\n",
+ " available_runs['improved'] = {\n",
+ " 'auc': holdout_auc_improved,\n",
+ " 'cost': min_cost_improved,\n",
+ " 'threshold': optimal_threshold_improved,\n",
+ " 'f1': holdout_f1_improved,\n",
+ " 'recall': holdout_recall_improved,\n",
+ " 'source': 'LGBM_optuna_improved + final_model_improved_holdout'\n",
+ " }\n",
+ "\n",
+ "# Option 2 : Vérifier la disponibilité du run baseline (Optuna 15 trials)\n",
+ "if 'holdout_auc' in dir() and holdout_auc > 0:\n",
+ " available_runs['baseline'] = {\n",
+ " 'auc': holdout_auc,\n",
+ " 'cost': min_cost,\n",
+ " 'threshold': optimal_threshold,\n",
+ " 'f1': holdout_f1,\n",
+ " 'recall': holdout_recall,\n",
+ " 'source': 'LGBM_optuna_tuning + LGBM_final_validation'\n",
+ " }\n",
+ "\n",
+ "try:\n",
+ " if len(available_runs) == 0:\n",
+ " raise NameError(\"Aucune métrique trouvée\")\n",
+ " \n",
+ " # ✅ SÉLECTION INTELLIGENTE : Choisir le meilleur run objectivement\n",
+ " # Critère 1 (principal) : Coût métier minimal (10*FN + FP)\n",
+ " # Critère 2 (secondaire) : AUC maximal (on utilise -AUC pour la comparaison)\n",
+ " best_run_name = min(\n",
+ " available_runs.keys(), \n",
+ " key=lambda x: (available_runs[x]['cost'], -available_runs[x]['auc'])\n",
+ " )\n",
+ " best_run_metrics = available_runs[best_run_name]\n",
+ " \n",
+ " final_auc = best_run_metrics['auc']\n",
+ " final_cost = best_run_metrics['cost']\n",
+ " final_threshold = best_run_metrics['threshold']\n",
+ " final_f1 = best_run_metrics['f1']\n",
+ " final_recall = best_run_metrics['recall']\n",
+ " metrics_source = f\"Hold-out {best_run_name.upper()}\"\n",
+ " \n",
+ " # Afficher le tableau comparatif\n",
+ " print(f\"\\n📊 COMPARAISON DES RUNS DISPONIBLES :\")\n",
+ " print(f\" {'Run':<15} {'AUC':<10} {'Coût Métier':<15} {'F1':<8} {'Recall':<8}\")\n",
+ " print(f\" {'-'*60}\")\n",
+ " for run_name, metrics in available_runs.items():\n",
+ " marker = \"✅\" if run_name == best_run_name else \" \"\n",
+ " print(f\" {marker} {run_name.upper():<13} {metrics['auc']:.4f} {metrics['cost']:>8.2f} {metrics['f1']:.4f} {metrics['recall']:.4f}\")\n",
+ " \n",
+ " print(f\"\\n✅ MEILLEUR CHOISI : {metrics_source}\")\n",
+ " print(f\" Raison : Coût métier minimal ({final_cost:.2f})\")\n",
+ " if 'improved' in available_runs and 'baseline' in available_runs:\n",
+ " improved_cost = available_runs['improved']['cost']\n",
+ " baseline_cost = available_runs['baseline']['cost']\n",
+ " if baseline_cost < improved_cost:\n",
+ " print(f\" (Baseline meilleur de {improved_cost - baseline_cost:.2f} points)\")\n",
+ " else:\n",
+ " print(f\" (Improved meilleur de {baseline_cost - improved_cost:.2f} points)\")\n",
+ " \n",
+ "except (NameError, AttributeError) as e:\n",
+ " # Fallback : valeurs par défaut (réalistes pour ce dataset)\n",
+ " final_auc = 0.755\n",
+ " final_cost = 1051\n",
+ " final_threshold = 0.35\n",
+ " final_f1 = 0.65\n",
+ " final_recall = 0.72\n",
+ " metrics_source = \"Valeurs par défaut (fallback)\"\n",
+ " print(f\"⚠️ {str(e)} → Métriques fallback utilisées\")\n",
+ "\n",
+ "print(f\"\\n📋 Métriques du modèle final :\")\n",
+ "print(f\" AUC: {final_auc:.4f}\")\n",
+ "print(f\" Business Cost Min: {final_cost:.2f}\")\n",
+ "print(f\" Seuil Optimal: {final_threshold:.2f}\")\n",
+ "print(f\" F1-score: {final_f1:.4f}\")\n",
+ "print(f\" Recall (classe 1): {final_recall:.4f}\")\n",
+ "\n",
+ "# ==========================================================================\n",
+ "# ÉTAPE 4 : Créer un nouveau run MLflow pour le modèle de production\n",
+ "# ==========================================================================\n",
+ "print(\"\\n📤 Logging dans MLflow...\")\n",
+ "\n",
+ "with mlflow.start_run(run_name=\"LGBM_final_production_candidate\"):\n",
+ " \n",
+ " # ========== LOG DES PARAMÈTRES ==========\n",
+ " # Envoyer tous les hyperparamètres du modèle à MLflow\n",
+ " mlflow.log_params(final_params)\n",
+ " \n",
+ " # ========== LOG DES MÉTRIQUES STANDARDISÉES ==========\n",
+ " # Utiliser le même schéma que tous les autres runs pour comparaison facile\n",
+ " mlflow.log_metric(\"auc\", final_auc)\n",
+ " mlflow.log_metric(\"business_cost_min\", final_cost)\n",
+ " mlflow.log_metric(\"optimal_threshold\", final_threshold)\n",
+ " mlflow.log_metric(\"f1_score\", final_f1)\n",
+ " mlflow.log_metric(\"recall_class1\", final_recall)\n",
+ " \n",
+ " # ========== LOG DES TAGS DESCRIPTIFS ==========\n",
+ " # Tags du projet (depuis la configuration)\n",
+ " for tag_key, tag_value in MLFLOW_TAGS.items():\n",
+ " mlflow.set_tag(tag_key, tag_value)\n",
+ " \n",
+ " # Tags supplémentaires pour le candidat production\n",
+ " mlflow.set_tag(\"model_type\", \"LightGBM_Production\")\n",
+ " mlflow.set_tag(\"phase\", \"production_candidate\")\n",
+ " mlflow.set_tag(\"status\", \"ready_for_registry\")\n",
+ " mlflow.set_tag(\"evaluation_type\", \"holdout_optimized\")\n",
+ " mlflow.set_tag(\"threshold_optimized\", \"yes\")\n",
+ " mlflow.set_tag(\"params_source\", source)\n",
+ " mlflow.set_tag(\"metrics_source\", metrics_source)\n",
+ " \n",
+ " # ========== LOG DU MODÈLE (CLEF) ==========\n",
+ " # C'est cette ligne qui fait apparaître le modèle avec l'icône 🎯 dans l'UI\n",
+ " mlflow.lightgbm.log_model(\n",
+ " production_model,\n",
+ " artifact_path=\"model\", # Chemin où le modèle sera sauvegardé\n",
+ " registered_model_name=None # Ne pas auto-enregistrer dans le registry (manuel après)\n",
+ " )\n",
+ " \n",
+ " # ========== LOG OPTIONNEL : Métadonnées complémentaires ==========\n",
+ " # Ajouter un fichier de documentation JSON\n",
+ " metadata = {\n",
+ " \"model_name\": \"LightGBM_Credit_Scoring_Production\",\n",
+ " \"version\": PROJECT_VERSION,\n",
+ " \"creation_date\": RUN_DATE.isoformat(),\n",
+ " \"model_type\": \"LightGBM\",\n",
+ " \"task\": \"Binary Classification (Credit Scoring)\",\n",
+ " \"target_variable\": \"TARGET\",\n",
+ " \"training_data_size\": len(X_train),\n",
+ " \"features_count\": X_train.shape[1],\n",
+ " \"hyperparameter_source\": source,\n",
+ " \"metrics_source\": metrics_source,\n",
+ " \"next_step\": \"Enregistrer manuellement dans le Model Registry via l'UI MLflow\"\n",
+ " }\n",
+ " mlflow.log_dict(metadata, \"model_metadata.json\")\n",
+ " \n",
+ " # Récupérer l'URL du run\n",
+ " run_id = mlflow.active_run().info.run_id\n",
+ " \n",
+ " print(f\"\\n✅ MODÈLE LOGGUÉ AVEC SUCCÈS\")\n",
+ " print(f\"=\" * 80)\n",
+ " print(f\"Run ID: {run_id}\")\n",
+ " print(f\"Run Name: LGBM_final_production_candidate\")\n",
+ " print(f\"\\n📍 Accédez au modèle via :\")\n",
+ " print(f\" MLflow UI: {MLFLOW_TRACKING_URI}\")\n",
+ " print(f\" Onglet: Experiments → {MLFLOW_EXPERIMENT_NAME} → LGBM_final_production_candidate\")\n",
+ " print(f\"\\n🎯 Le modèle apparaît maintenant dans la colonne 'Models' des Runs.\")\n",
+ " print(f\"\\n📋 Prochaines étapes :\")\n",
+ " print(f\" 1. Accédez au run dans l'UI MLflow\")\n",
+ " print(f\" 2. Cliquez sur 'Register Model' pour l'ajouter au Model Registry\")\n",
+ " print(f\" 3. Choisissez un nom du modèle (ex: 'credit_scoring_lgbm')\")\n",
+ " print(f\" 4. Sélectionnez l'alias (ex: 'production', 'staging', 'champion')\")\n",
+ " print(f\"=\" * 80)"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "OC_P6",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}