{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "b6d1e3ec",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Classification hybride d'ensembles améliorée par la technologie quantique (flux de travail pour la stabilité du réseau)\"\n",
        "description: \"Construisez et analysez un ensemble hybride quantique-classique pour la classification de la stabilité du réseau sur les QPU d' IBM à l'aide de la fonction Singularity Qiskit de Multiverse Computing.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore QEEC interpretability hyperparameters overfitting sublinear preds prec */}\n",
        "\n",
        "<span id=\"hybrid-quantum-enhanced-ensemble-classification-grid-stability-workflow\" />\n",
        "\n",
        "# Classification hybride d'ensembles améliorée par la technologie quantique (flux de travail pour la stabilité du réseau)\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "a6f69b77",
      "metadata": {},
      "source": [
        "*Estimation de l'utilisation : 20 minutes de temps QPU pour chaque tâche sur un processeur Eagle r3. (NOTE : Il s'agit uniquement d'une estimation. Votre durée d'exécution peut varier.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8bf80006",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Arrière-plan\n",
        "\n",
        "Ce tutoriel présente un processus hybride quantique-classique qui enrichit un ensemble classique d'une étape d'optimisation quantique. À l'aide de la fonction « Singularity Machine Learning –Classification » de Multiverse Computing (une fonction Qiskit), nous entraînons un ensemble d'algorithmes d'apprentissage classiques (par exemple, les arbres de décision, le k-NN, la régression logistique), puis nous affinons cet ensemble à l'aide d'une couche quantique afin d'améliorer la diversité et la généralisation. L'objectif est concret : dans le cadre d'une véritable tâche de prédiction de la stabilité du réseau, nous comparons une référence classique performante à une alternative optimisée par la mécanique quantique, en utilisant les mêmes découpages de données, afin que vous puissiez constater où l'approche quantique apporte un plus et quel en est le coût.\n",
        "\n",
        "Pourquoi cela est important : la sélection d'un bon sous-ensemble parmi de nombreux apprenants faibles est un problème combinatoire qui croît rapidement avec la taille de l'ensemble. Les heuristiques classiques telles que le boosting, le bagging et le stacking donnent de bons résultats à des échelles modérées, mais peinent à explorer efficacement des bibliothèques de modèles redondantes et de grande taille. La fonction intègre des algorithmes quantiques - en particulier QAOA (et éventuellement VQE dans d'autres configurations) - pour rechercher cet espace plus efficacement après que les apprenants classiques ont été formés, ce qui augmente les chances de trouver un sous-ensemble compact et diversifié qui se généralise mieux.\n",
        "\n",
        "Il est essentiel que l'échelle des données ne soit pas limitée par les qubits. Le gros du travail sur les données - prétraitement, formation du groupe d'apprenants et évaluation - reste classique et peut traiter des millions d'exemples. Les Qubits déterminent uniquement la taille de l'ensemble utilisé dans l'étape de sélection quantique. Ce découplage est ce qui rend l'approche viable sur le matériel d'aujourd'hui : vous conservez les flux de travail scikit-learn familiers pour les données et l'apprentissage du modèle tout en appelant l'étape quantique par le biais d'une interface d'action propre dans Qiskit Functions.\n",
        "\n",
        "Dans la pratique, bien que différents types d'apprenants puissent être fournis à l'ensemble (par exemple, arbres de décision, régression logistique ou k-NN), les arbres de décision tendent à donner les meilleurs résultats. L'optimiseur favorise systématiquement les membres les plus forts de l'ensemble : lorsque des apprenants hétérogènes sont fournis, les modèles les plus faibles, tels que les régresseurs linéaires, sont généralement élagués au profit de modèles plus expressifs, tels que les arbres de décision.\n",
        "\n",
        "Ce que vous ferez ici : préparer et équilibrer l'ensemble de données sur la stabilité de la grille; établir une base de référence classique AdaBoost; exécuter plusieurs configurations quantiques qui varient la largeur de l'ensemble et la régularisation; exécuter sur les simulateurs IBM® ou les QPU via Qiskit Serverless; et comparer l'exactitude, la précision, le rappel et F1 sur l'ensemble des exécutions. En cours de route, vous utiliserez le modèle d'action de la fonction (`create`, `fit`, `predict`, `fit_predict`, `create_fit_predict`) et les contrôles des touches :\n",
        "\n",
        "* Types de régularisation : `onsite` (λ) pour l'éparpillement direct et `alpha` pour un compromis basé sur le ratio entre les termes d'interaction et les termes sur place\n",
        "* Régularisation automatique : définir `regularization=\"auto\"` avec un ratio de sélection cible pour adapter automatiquement la densité\n",
        "* Options de l'optimiseur : simulateur ou QPU, nombre de répétitions, optimiseur classique et ses options, profondeur de transpilation, et paramètres de l'échantillonneur ou de l'estimateur « IBM Quantum »\n",
        "\n",
        "Les repères figurant dans la documentation montrent que la précision s'améliore à mesure que le nombre d'apprenants (qubits) augmente sur des problèmes difficiles, le classificateur quantique égalant ou dépassant un ensemble classique comparable. Dans ce tutoriel, vous reproduirez le flux de travail de bout en bout et examinerez si l'augmentation de la largeur de l'ensemble ou le passage à la régularisation adaptative permet d'obtenir de meilleurs résultats sur le site F1 avec une utilisation raisonnable des ressources. Il en résulte une vision fondée de la manière dont une étape d'optimisation quantique peut compléter, plutôt que remplacer, l'apprentissage d'ensemble classique dans des applications réelles.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "55b94021",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Exigences\n",
        "\n",
        "Avant de commencer ce tutoriel, assurez-vous que les paquets suivants sont installés dans votre environnement Python :\n",
        "\n",
        "* `qiskit[visualization]~=2.1.0`\n",
        "* `qiskit-serverless~=0.24.0`\n",
        "* `qiskit-ibm-runtime v0.40.1`\n",
        "* `qiskit-ibm-catalog~=0.8.0`\n",
        "* `scikit-learn==1.5.2`\n",
        "* `pandas>=2.0.0,<3.0.0`\n",
        "* `imbalanced-learn~=0.12.3`\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "7db2e559",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configuration\n",
        "\n",
        "Dans cette section, nous initialisons le client Qiskit Serverless et chargeons la fonction Singularity Machine Learning - Classification fournie par Multiverse Computing.\n",
        "Avec Qiskit Serverless, vous pouvez exécuter des flux de travail hybrides quantique-classique sur l'infrastructure cloud gérée IBM sans vous soucier de la gestion des ressources.\n",
        "Vous aurez besoin d'une clé API IBM Quantum Platform et de votre nom de ressource cloud (CRN) pour vous authentifier et accéder à Qiskit Functions.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d4fe7ee1-21ce-445c-b151-598cd4cf9227",
      "metadata": {},
      "source": [
        "<span id=\"download-the-dataset\" />\n",
        "\n",
        "### Télécharger l'ensemble de données\n",
        "\n",
        "Pour exécuter ce tutoriel, nous utilisons un **ensemble de données prétraitées de classification de la stabilité du réseau** contenant des relevés de capteurs de système électrique étiquetés.\n",
        "La cellule suivante crée automatiquement la structure de dossier requise et télécharge les fichiers de formation et de test directement dans votre environnement à l'aide de `wget`.\n",
        "Si vous avez déjà ces fichiers localement, cette étape les écrasera en toute sécurité pour assurer la cohérence des versions.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "7a32efb3-a425-4c02-804b-65029ecffb6f",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "data_tutorial/grid_ 100%[===================>] 612.94K  --.-KB/s    in 0.01s   \n",
            "data_tutorial/grid_ 100%[===================>] 108.19K  --.-KB/s    in 0.006s  \n",
            "Dataset files downloaded:\n",
            "-rw-r--r-- 1 coder coder 109K Nov  8 18:50 data_tutorial/grid_stability/test.csv\n",
            "-rw-r--r-- 1 coder coder 613K Nov  8 18:50 data_tutorial/grid_stability/train.csv\n"
          ]
        }
      ],
      "source": [
        "## Download dataset for Grid Stability Classification\n",
        "\n",
        "# Create data directory if it doesn't exist\n",
        "!mkdir -p data_tutorial/grid_stability\n",
        "\n",
        "# Download the training and test sets from the official Qiskit documentation repo\n",
        "base_url = (\n",
        "    \"https://raw.githubusercontent.com/Qiskit/documentation/\"\n",
        "    \"main/datasets/tutorials/grid_stability\"\n",
        ")\n",
        "!wget -q --show-progress -O data_tutorial/grid_stability/train.csv {base_url}/train.csv\n",
        "\n",
        "!wget -q --show-progress -O data_tutorial/grid_stability/test.csv {base_url}/test.csv\n",
        "\n",
        "# Check the files have been downloaded\n",
        "!echo \"Dataset files downloaded:\"\n",
        "!ls -lh data_tutorial/grid_stability/*.csv"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bb9aa84f-ab37-412c-b056-7043b73380fa",
      "metadata": {},
      "source": [
        "<span id=\"import-required-packages\" />\n",
        "\n",
        "### Importer les paquets requis\n",
        "\n",
        "Dans cette section, nous importons tous les paquets Python et les modules Qiskit utilisés tout au long du tutoriel.\n",
        "Il s'agit notamment de bibliothèques scientifiques de base pour le traitement des données et l'évaluation des modèles - telles que `NumPy`, `pandas`, et `scikit-learn` - ainsi que d'outils de visualisation et de composants Qiskit pour l'exécution du modèle quantique.\n",
        "Nous importons également `QiskitRuntimeService` et `QiskitFunctionsCatalog` pour nous connecter aux services IBM Quantum® et accéder à la fonction Singularity Machine Learning.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "f8c654f5-8355-4f67-b79d-c2b1c29ccc62",
      "metadata": {},
      "outputs": [],
      "source": [
        "from pprint import pprint\n",
        "from typing import Tuple\n",
        "import warnings\n",
        "\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import pandas as pd\n",
        "from imblearn.over_sampling import RandomOverSampler\n",
        "from qiskit_ibm_catalog import QiskitFunctionsCatalog\n",
        "from sklearn.ensemble import AdaBoostClassifier\n",
        "from sklearn.metrics import (\n",
        "    accuracy_score,\n",
        "    f1_score,\n",
        "    precision_score,\n",
        "    recall_score,\n",
        ")\n",
        "from sklearn.model_selection import train_test_split\n",
        "\n",
        "warnings.filterwarnings(\"ignore\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3b389b34-02e8-4a63-ae24-0bf348647b48",
      "metadata": {},
      "source": [
        "<span id=\"set-constant-variables\" />\n",
        "\n",
        "### Définir des variables constantes\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "a68bf7df-914c-4b2a-827f-657578503750",
      "metadata": {},
      "outputs": [],
      "source": [
        "IBM_TOKEN = \"\"\n",
        "IBM_INSTANCE_TEST = \"\"\n",
        "IBM_INSTANCE_QUANTUM = \"\"\n",
        "FUNCTION_NAME = \"multiverse/singularity\"\n",
        "RANDOM_STATE: int = 123\n",
        "TRAIN_PATH = \"data_tutorial/grid_stability/train.csv\"\n",
        "TEST_PATH = \"data_tutorial/grid_stability/test.csv\""
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4709dc1c-b380-49f1-95c7-89197aa5e147",
      "metadata": {},
      "source": [
        "<span id=\"connect-to-ibm-quantum-and-load-the-singularity-function\" />\n",
        "\n",
        "### Connectez-vous à IBM Quantum et chargez la fonction Singularity\n",
        "\n",
        "Ensuite, nous nous authentifions auprès des services IBM Quantum et nous chargeons la fonction Singularity Machine Learning - Classification à partir du site Qiskit Functions Catalog.\n",
        "Le site `QiskitRuntimeService` établit une connexion sécurisée avec IBM Quantum Platform à l'aide de votre jeton API et de votre CRN d'instance, ce qui permet d'accéder aux backends quantiques.\n",
        "Le site `QiskitFunctionsCatalog` est ensuite utilisé pour récupérer la fonction de singularité par son nom (`\"multiverse/singularity\"`), ce qui nous permet de l'appeler ultérieurement pour un calcul hybride quantique-classique.\n",
        "Si la configuration est réussie, vous verrez un message de confirmation indiquant que la fonction a été chargée correctement.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "bc380c46",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Successfully connected to IBM Qiskit Serverless and loaded the Singularity function.\n",
            "Catalog: <QiskitFunctionsCatalog>\n",
            "Singularity function: QiskitFunction(multiverse/singularity)\n"
          ]
        }
      ],
      "source": [
        "catalog = QiskitFunctionsCatalog(\n",
        "    token=IBM_TOKEN,\n",
        "    instance=IBM_INSTANCE_TEST,\n",
        "    channel=\"ibm_quantum_platform\",\n",
        ")\n",
        "backend = catalog.least_busy()\n",
        "\n",
        "singularity = catalog.load(FUNCTION_NAME)\n",
        "print(\n",
        "    \"Successfully connected to IBM Qiskit Serverless and \"\n",
        "    \"loaded the Singularity function.\"\n",
        ")\n",
        "print(\"Catalog:\", catalog)\n",
        "print(\"Singularity function:\", singularity)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "47d6a559-118a-4aa9-874d-9c009b5da60c",
      "metadata": {},
      "source": [
        "<span id=\"define-helper-functions\" />\n",
        "\n",
        "### Définir les fonctions d'aide\n",
        "\n",
        "Avant de réaliser les principales expériences, nous définissons quelques petites fonctions utilitaires qui simplifient le chargement des données et l'évaluation des modèles.\n",
        "\n",
        "* `load_data()` lit les fichiers CSV d'entrée dans les tableaux NumPy, en divisant les caractéristiques et les étiquettes pour assurer la compatibilité avec `scikit-learn` et les flux de travail quantiques.\n",
        "* `evaluate_predictions()` calcule les principales mesures de performance - exactitude, précision, rappel et F1-score - et signale éventuellement la durée d'exécution si des informations temporelles sont fournies.\n",
        "\n",
        "Ces fonctions d'aide simplifient les opérations répétées plus loin dans le carnet et garantissent un rapport métrique cohérent entre les classificateurs classiques et quantiques.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "46bc841e-7365-4508-b6bf-ae57db6050e1",
      "metadata": {},
      "outputs": [],
      "source": [
        "def load_data(data_path: str) -> Tuple[np.ndarray, np.ndarray]:\n",
        "    \"\"\"Load data from the given path to X and y arrays.\"\"\"\n",
        "    df: pd.DataFrame = pd.read_csv(data_path)\n",
        "    return df.iloc[:, :-1].values, df.iloc[:, -1].values\n",
        "\n",
        "\n",
        "def evaluate_predictions(predictions, y_true):\n",
        "    \"\"\"Compute and print accuracy, precision, recall, and F1 score.\"\"\"\n",
        "    accuracy = accuracy_score(y_true, predictions)\n",
        "    precision = precision_score(y_true, predictions)\n",
        "    recall = recall_score(y_true, predictions)\n",
        "    f1 = f1_score(y_true, predictions)\n",
        "    print(\"Accuracy:\", accuracy)\n",
        "    print(\"Precision:\", precision)\n",
        "    print(\"Recall:\", recall)\n",
        "    print(\"F1:\", f1)\n",
        "    return accuracy, precision, recall, f1"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "988ee237",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "## Étape 1 : Mettre en correspondance les entrées classiques avec un problème quantique\n",
        "\n",
        "Nous commençons par préparer l'ensemble de données pour l'expérimentation hybride quantique-classique. L'objectif de cette étape est de convertir les données brutes relatives à la stabilité de la grille en des séquences équilibrées d'entraînement, de validation et de test qui peuvent être utilisées de manière cohérente par les flux de travail classiques et quantiques. Le maintien de divisions identiques garantit que les comparaisons ultérieures des performances sont équitables et reproductibles.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1c084cde-a5cf-4661-a00c-aa243c8b0e44",
      "metadata": {},
      "source": [
        "<span id=\"data-loading-and-preprocessing\" />\n",
        "\n",
        "### Chargement et prétraitement des données\n",
        "\n",
        "Nous commençons par charger les fichiers CSV de formation et de test, nous créons une division de validation et nous équilibrons l'ensemble de données à l'aide d'un suréchantillonnage aléatoire. L'équilibrage permet d'éviter les biais en faveur de la classe majoritaire et fournit un signal d'apprentissage plus stable pour les modèles d'ensemble classiques et quantiques.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "0db0e914-a8f2-4a04-bec7-c15bac8104b4",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Shapes:\n",
            "  X_train_bal: (5104, 12)\n",
            "  y_train_bal: (5104,)\n",
            "  X_val: (850, 12)\n",
            "  y_val: (850,)\n",
            "  X_test: (750, 12)\n",
            "  y_test: (750,)\n"
          ]
        }
      ],
      "source": [
        "# Load and upload the data\n",
        "X_train, y_train = load_data(TRAIN_PATH)\n",
        "X_test, y_test = load_data(TEST_PATH)\n",
        "X_train, X_val, y_train, y_val = train_test_split(\n",
        "    X_train, y_train, test_size=0.2, random_state=RANDOM_STATE\n",
        ")\n",
        "\n",
        "# Balance the dataset through over-sampling of the positive class\n",
        "ros = RandomOverSampler(random_state=RANDOM_STATE)\n",
        "X_train_bal, y_train_bal = ros.fit_resample(X_train, y_train)\n",
        "\n",
        "print(\"Shapes:\")\n",
        "print(\"  X_train_bal:\", X_train_bal.shape)\n",
        "print(\"  y_train_bal:\", y_train_bal.shape)\n",
        "print(\"  X_val:\", X_val.shape)\n",
        "print(\"  y_val:\", y_val.shape)\n",
        "print(\"  X_test:\", X_test.shape)\n",
        "print(\"  y_test:\", y_test.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b8c214fd-09a8-4fa5-ab83-b0c71ad615d4",
      "metadata": {},
      "source": [
        "<span id=\"classical-baseline-adaboost-reference\" />\n",
        "\n",
        "### Référence classique : AdaBoost\n",
        "\n",
        "Avant de procéder à l'optimisation quantique, nous formons une base classique solide - un classificateur standard AdaBoost - sur les mêmes données équilibrées. Cela fournit un point de référence reproductible pour une comparaison ultérieure, permettant de quantifier si l'optimisation quantique améliore la généralisation ou l'efficacité au-delà d'un ensemble classique bien réglé.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "38ecd608-35b4-4e58-8a16-85ef98bf024a",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Classical AdaBoost baseline:\n",
            "Accuracy: 0.7893333333333333\n",
            "Precision: 1.0\n",
            "Recall: 0.7893333333333333\n",
            "F1: 0.8822652757078987\n"
          ]
        }
      ],
      "source": [
        "# ----- Classical baseline: AdaBoost -----\n",
        "baseline = AdaBoostClassifier(n_estimators=60, random_state=RANDOM_STATE)\n",
        "baseline.fit(X_train_bal, y_train_bal)\n",
        "baseline_pred = baseline.predict(X_test)\n",
        "print(\"Classical AdaBoost baseline:\")\n",
        "_ = evaluate_predictions(baseline_pred, y_test)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ac6f36e3",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "## Étape 2 : Optimiser le problème pour l'exécution sur du matériel quantique\n",
        "\n",
        "La tâche de sélection d'ensembles est formulée comme un problème d'optimisation combinatoire dans lequel chaque apprenant faible correspond à une variable de décision binaire, et où l'objectif consiste à trouver un équilibre entre précision et parcimonie grâce à un terme de régularisation. Le `QuantumEnhancedEnsembleClassifier` résout ce problème grâce à la méthode QAOA sur du matériel de type « IBM », tout en permettant toujours l'exploration par simulation. Le contrôle `optimizer_options` de la boucle hybride : `simulator=False` achemine les circuits vers le QPU sélectionné, `num_solutions` augmente l'étendue de la recherche et ( `classical_optimizer_options` pour l'optimiseur classique interne) régule la convergence; des valeurs avoisinant les 60 itérations constituent un bon compromis entre qualité et temps d'exécution. Les options de calcul quantique — telles qu'une profondeur de circuit modérée (`reps`) et un effort de transpilation standard — contribuent à garantir des performances stables sur l'ensemble des appareils. La configuration ci-dessous correspond au profil « best-results » que nous utiliserons pour les exécutions sur matériel; vous pouvez également créer une variante purement simulée en activant l'option `simulator=True` permettant d'exécuter le workflow en mode simulation sans consommer de temps de calcul sur le QPU.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "18c0d3f9-691b-449d-83ca-e6bfce2d6b99",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Configured hardware optimization profile:\n",
            "  simulator: False\n",
            "  num_solutions: 100000\n",
            "  reps: 3\n",
            "  optimization_level: 3\n",
            "  num_transpiler_runs: 30\n",
            "  classical_optimizer: COBYLA\n",
            "  classical_optimizer_options: {'maxiter': 60}\n",
            "  estimator_options: None\n",
            "  sampler_options: None\n"
          ]
        }
      ],
      "source": [
        "# QAOA / runtime configuration for best results on hardware\n",
        "optimizer_options = {\n",
        "    \"simulator\": False,  # set True to test locally without QPU\n",
        "    \"num_solutions\": 100_000,  # broaden search over candidate ensembles\n",
        "    \"reps\": 3,  # QAOA depth (circuit layers)\n",
        "    \"optimization_level\": 3,  # transpilation effort\n",
        "    \"num_transpiler_runs\": 30,  # explore multiple layouts\n",
        "    \"classical_optimizer\": \"COBYLA\",  # robust default for this landscape\n",
        "    \"classical_optimizer_options\": {\n",
        "        \"maxiter\": 60  # practical convergence budget\n",
        "    },\n",
        "    # You can pass backend-specific options;\n",
        "    # leaving None uses least-busy routing\n",
        "    \"estimator_options\": None,\n",
        "    \"sampler_options\": None,\n",
        "}\n",
        "\n",
        "print(\"Configured hardware optimization profile:\")\n",
        "for key, value in optimizer_options.items():\n",
        "    print(f\"  {key}: {value}\")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "b4d480b3",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "## Étape 3 : Exécutez à l'aide d' Qiskit primitives\n",
        "\n",
        "Nous exécutons maintenant le flux de travail complet en utilisant l'action `create_fit_predict` de la fonction Singularité pour former, optimiser et évaluer le `QuantumEnhancedEnsembleClassifier` de bout en bout sur l'infrastructure IBM. La fonction construit l'ensemble, applique l'optimisation quantique via Qiskit primitives, et renvoie à la fois les prédictions et les métadonnées du travail (y compris la durée d'exécution et l'utilisation des ressources). La répartition des données classiques de l'étape 1 est réutilisée à des fins de reproductibilité, les données de validation étant transmises à l'adresse `fit_params` afin que l'optimisation puisse ajuster les hyperparamètres en interne tout en conservant l'ensemble de test conservé.\n",
        "\n",
        "Dans cette étape, nous explorons plusieurs configurations de l'ensemble quantique pour comprendre comment les paramètres clés - en particulier `num_learners` et `regularization` - affectent à la fois la qualité des résultats et l'utilisation de la QPU.\n",
        "\n",
        "* `num_learners` détermine la largeur de l'ensemble (et implicitement le nombre de qubits), ce qui influe sur la capacité et le coût de calcul du modèle.\n",
        "* `regularization` contrôle l'éparpillement et le surajustement, en déterminant le nombre d'apprenants qui restent actifs après l'optimisation.\n",
        "\n",
        "En faisant varier ces paramètres, nous pouvons voir comment la largeur de l'ensemble et la régularisation interagissent : l'augmentation de la largeur améliore généralement F1 mais coûte plus de temps au QPU, tandis qu'une régularisation plus forte ou adaptative peut améliorer la généralisation à peu près au même niveau d'encombrement matériel. Les sous-sections suivantes présentent trois configurations représentatives pour illustrer ces effets.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8da41d4d-f06d-4b67-a8ee-b07fc0289558",
      "metadata": {},
      "source": [
        "<span id=\"baseline\" />\n",
        "\n",
        "### Ligne de base\n",
        "\n",
        "Cette configuration utilise `num_learners = 10` et `regularization = 7`.\n",
        "\n",
        "* `num_learners` contrôle la largeur de l'ensemble, c'est-à-dire le nombre d'apprenants faibles combinés et, sur le matériel quantique, le **nombre de qubits requis.** Une valeur plus élevée élargit l'espace de recherche combinatoire et peut améliorer la précision et le rappel, mais augmente également la largeur du circuit, le temps de compilation et l'utilisation globale du QPU.\n",
        "* `regularization` définit la force de la pénalité pour l'inclusion d'apprenants supplémentaires. Avec la régularisation par défaut \"onsite\", les valeurs les plus élevées renforcent l'éparpillement (moins d'apprenants conservés), tandis que les valeurs les plus faibles permettent des ensembles plus complexes.\n",
        "\n",
        "Cette configuration constitue une base de référence peu coûteuse, montrant le comportement d'un petit ensemble avant d'en augmenter la largeur ou d'en régler l'éparpillement.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "e702c986-fd7f-4ea1-a93c-8cd5ef0c4a0b",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Problem scale and regularization\n",
        "NUM_LEARNERS = 10\n",
        "REGULARIZATION = 7"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "06c261c1-a3c1-42c9-a522-63f3fc01a970",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "\n",
            "-- Submitting quantum-enhanced ensemble job --\n",
            "Action status: ok\n",
            "Action message: Classifier created, fitted, and predicted.\n",
            "Metadata:\n",
            "{'resource_usage': {'RUNNING: EXECUTING_QPU': {'QPU_TIME': 1550.1889700889587},\n",
            "                    'RUNNING: MAPPING': {'CPU_TIME': 267.05158376693726},\n",
            "                    'RUNNING: POST_PROCESSING': {'CPU_TIME': 152.4274561405182},\n",
            "                    'RUNNING: WAITING_QPU': {'CPU_TIME': 3336.8785166740417}}}\n",
            "Accuracy: 0.868\n",
            "Precision: 1.0\n",
            "Recall: 0.868\n",
            "F1: 0.9293361884368309\n"
          ]
        }
      ],
      "source": [
        "# ----- Quantum-enhanced ensemble on IBM hardware -----\n",
        "print(\"\\n-- Submitting quantum-enhanced ensemble job --\")\n",
        "job_1 = singularity.run(\n",
        "    action=\"create_fit_predict\",\n",
        "    name=\"grid_stability_qeec\",\n",
        "    quantum_classifier=\"QuantumEnhancedEnsembleClassifier\",\n",
        "    num_learners=NUM_LEARNERS,\n",
        "    regularization=REGULARIZATION,\n",
        "    optimizer_options=optimizer_options,  # from Step 2\n",
        "    backend_name=backend,  # least-busy compatible backend\n",
        "    instance=IBM_INSTANCE_QUANTUM,\n",
        "    random_state=RANDOM_STATE,\n",
        "    X_train=X_train_bal,\n",
        "    y_train=y_train_bal,\n",
        "    X_test=X_test,\n",
        "    fit_params={\"validation_data\": (X_val, y_val)},\n",
        "    options={\"save\": False},\n",
        ")\n",
        "result_1 = job_1.result()\n",
        "print(\"Action status:\", result_1.get(\"status\"))\n",
        "print(\"Action message:\", result_1.get(\"message\"))\n",
        "print(\"Metadata:\")\n",
        "pprint(result_1.get(\"metadata\"))\n",
        "qeec_pred_job_1 = np.array(result_1[\"data\"][\"predictions\"])\n",
        "_ = evaluate_predictions(qeec_pred_job_1, y_test)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "1bf0a3ae-b5b8-43d7-b72d-5a9cf6c61060",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "\n",
            "Quantum job status: DONE\n"
          ]
        }
      ],
      "source": [
        "status_1 = job_1.status()\n",
        "print(\"\\nQuantum job status:\", status_1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ae62b414-db77-45aa-bf2c-a8537fd7eba0",
      "metadata": {},
      "source": [
        "<span id=\"increase-the-number-of-learners\" />\n",
        "\n",
        "### Augmenter le nombre d'apprenants\n",
        "\n",
        "Ici, nous augmentons `num_learners` de 10 → 30 tout en conservant `regularization = 7`.\n",
        "\n",
        "* Un plus grand nombre d'apprenants élargit l'espace d'hypothèses, ce qui permet au modèle de saisir des modèles plus subtils, ce qui peut améliorer légèrement F1.\n",
        "* Dans la plupart des cas, la différence de durée d'exécution entre 10 et 30 apprenants n'est pas substantielle, ce qui indique que la largeur de circuit supplémentaire n'augmente pas de manière significative le coût d'exécution.\n",
        "* L'amélioration de la qualité suit toujours une *courbe à rendement décroissant* : les premiers gains apparaissent au fur et à mesure que l'ensemble grandit, mais ils plafonnent à mesure que les apprenants supplémentaires apportent moins de nouvelles informations.\n",
        "\n",
        "Cette expérience met en évidence le compromis qualité-efficacité - l'augmentation de la largeur de l'ensemble peut offrir de faibles gains de précision sans pénalité majeure en termes de temps d'exécution, en fonction des conditions du backend et de la transpilation.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "e5fe64b0-2713-4b65-b768-10fa5d8dbf79",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Problem scale and regularization\n",
        "NUM_LEARNERS = 30\n",
        "REGULARIZATION = 7"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "18a22fed-41c2-4407-88d4-02fbd32f3320",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "\n",
            "-- Submitting quantum-enhanced ensemble job --\n",
            "Action status: ok\n",
            "Action message: Classifier created, fitted, and predicted.\n",
            "QPU Time:\n",
            "{'resource_usage': {'RUNNING: EXECUTING_QPU': {'QPU_TIME': 1095.822762966156},\n",
            "                    'RUNNING: MAPPING': {'CPU_TIME': 680.2116754055023},\n",
            "                    'RUNNING: POST_PROCESSING': {'CPU_TIME': 154.4466371536255},\n",
            "                    'RUNNING: WAITING_QPU': {'CPU_TIME': 80.80395102500916}}}\n",
            "Accuracy: 0.8946666666666667\n",
            "Precision: 1.0\n",
            "Recall: 0.8946666666666667\n",
            "F1: 0.944405348346235\n"
          ]
        }
      ],
      "source": [
        "# ----- Quantum-enhanced ensemble on IBM hardware -----\n",
        "print(\"\\n-- Submitting quantum-enhanced ensemble job --\")\n",
        "job_2 = singularity.run(\n",
        "    action=\"create_fit_predict\",\n",
        "    name=\"grid_stability_qeec\",\n",
        "    quantum_classifier=\"QuantumEnhancedEnsembleClassifier\",\n",
        "    num_learners=NUM_LEARNERS,\n",
        "    regularization=REGULARIZATION,\n",
        "    optimizer_options=optimizer_options,  # from Step 2\n",
        "    backend_name=backend,  # least-busy compatible backend\n",
        "    instance=IBM_INSTANCE_QUANTUM,\n",
        "    random_state=RANDOM_STATE,\n",
        "    X_train=X_train_bal,\n",
        "    y_train=y_train_bal,\n",
        "    X_test=X_test,\n",
        "    fit_params={\"validation_data\": (X_val, y_val)},\n",
        "    options={\"save\": False},\n",
        ")\n",
        "result_2 = job_2.result()\n",
        "print(\"Action status:\", result_2.get(\"status\"))\n",
        "print(\"Action message:\", result_2.get(\"message\"))\n",
        "print(\"QPU Time:\")\n",
        "pprint(result_2.get(\"metadata\"))\n",
        "qeec_pred_job_2 = np.array(result_2[\"data\"][\"predictions\"])\n",
        "_ = evaluate_predictions(qeec_pred_job_2, y_test)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "edd22b98-7ae6-4444-8fe5-7279e9233c57",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "\n",
            "Quantum job status: DONE\n"
          ]
        }
      ],
      "source": [
        "status_2 = job_2.status()\n",
        "print(\"\\nQuantum job status:\", status_2)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5e94784a-3599-4911-ade9-4792b50c31bb",
      "metadata": {},
      "source": [
        "<span id=\"regularization\" />\n",
        "\n",
        "### Régularisation\n",
        "\n",
        "Dans cette configuration, nous passons à `num_learners = 60` et introduisons une régularisation adaptative afin de gérer la rareté de manière plus intuitive.\n",
        "\n",
        "* Avec `regularization = \"auto\"`, l'optimiseur trouve automatiquement une force de régularisation appropriée qui sélectionne environ `regularization_ratio * num_learners` apprenants faibles pour l'ensemble final, plutôt que de fixer la pénalité manuellement. Il s'agit d'une interface plus pratique pour gérer l'équilibre entre l'éparpillement et la taille de l'ensemble.\n",
        "* `regularization_type = \"alpha\"` définit la manière dont la pénalité est appliquée. Contrairement à `onsite`, qui est non borné `[0, ∞]`, `alpha` est borné entre `[0, 1]`, ce qui le rend plus facile à régler et à interpréter. Ce paramètre contrôle le compromis entre les pénalités individuelles et les pénalités par paire, ce qui permet d'obtenir une gamme de configurations plus homogène.\n",
        "* `regularization_desired_ratio ≈ 0.82` spécifie la proportion cible d'apprenants à garder actifs après la régularisation - ici, environ 82% des apprenants sont retenus, en éliminant automatiquement les 18% les plus faibles.\n",
        "\n",
        "Si la régularisation adaptative simplifie la configuration et aide à maintenir un ensemble équilibré, elle ne garantit pas nécessairement des performances meilleures ou plus stables. La qualité réelle dépend de la sélection d'un paramètre de régularisation approprié, et son réglage fin par validation croisée peut être coûteux sur le plan informatique. Le principal avantage réside dans l'amélioration de la facilité d'utilisation et d'interprétation plutôt que dans les gains de précision directs.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "209eb51a-e44b-4f94-8975-269ec7e3d1ca",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Problem scale and regularization\n",
        "NUM_LEARNERS = 60\n",
        "REGULARIZATION = \"auto\"\n",
        "REGULARIZATION_TYPE = \"alpha\"\n",
        "REGULARIZATION_RATIO = 0.82"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "eb2aa9db-4427-48de-aae0-c5c1da1cb998",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "\n",
            "-- Submitting quantum-enhanced ensemble job --\n",
            "Action status: ok\n",
            "Action message: Classifier created, fitted, and predicted.\n",
            "Metadata:\n",
            "{'resource_usage': {'RUNNING: EXECUTING_QPU': {'QPU_TIME': 1146.5584812164307},\n",
            "                    'RUNNING: MAPPING': {'CPU_TIME': 1387.7451872825623},\n",
            "                    'RUNNING: POST_PROCESSING': {'CPU_TIME': 171.78878355026245},\n",
            "                    'RUNNING: WAITING_QPU': {'CPU_TIME': 95.41597843170166}}}\n",
            "Accuracy: 0.908\n",
            "Precision: 1.0\n",
            "Recall: 0.908\n",
            "F1: 0.9517819706498952\n"
          ]
        }
      ],
      "source": [
        "# ----- Quantum-enhanced ensemble on IBM hardware -----\n",
        "print(\"\\n-- Submitting quantum-enhanced ensemble job --\")\n",
        "job_3 = singularity.run(\n",
        "    action=\"create_fit_predict\",\n",
        "    name=\"grid_stability_qeec\",\n",
        "    quantum_classifier=\"QuantumEnhancedEnsembleClassifier\",\n",
        "    num_learners=NUM_LEARNERS,\n",
        "    regularization=REGULARIZATION,\n",
        "    regularization_type=REGULARIZATION_TYPE,\n",
        "    regularization_desired_ratio=REGULARIZATION_RATIO,\n",
        "    optimizer_options=optimizer_options,  # from Step 2\n",
        "    backend_name=backend,  # least-busy compatible backend\n",
        "    instance=IBM_INSTANCE_QUANTUM,\n",
        "    random_state=RANDOM_STATE,\n",
        "    X_train=X_train_bal,\n",
        "    y_train=y_train_bal,\n",
        "    X_test=X_test,\n",
        "    fit_params={\"validation_data\": (X_val, y_val)},\n",
        "    options={\"save\": False},\n",
        ")\n",
        "result_3 = job_3.result()\n",
        "print(\"Action status:\", result_3.get(\"status\"))\n",
        "print(\"Action message:\", result_3.get(\"message\"))\n",
        "print(\"Metadata:\")\n",
        "pprint(result_3.get(\"metadata\"))\n",
        "qeec_pred_job_3 = np.array(result_3[\"data\"][\"predictions\"])\n",
        "_ = evaluate_predictions(qeec_pred_job_3, y_test)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "299b36cb-0ed8-4af3-b431-a87daec04c93",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "\n",
            "Quantum job status: DONE\n"
          ]
        }
      ],
      "source": [
        "status_3 = job_3.status()\n",
        "print(\"\\nQuantum job status:\", status_3)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "50b94af2",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "## Étape 4 : Post-traitement et restitution du résultat dans le format classique souhaité\n",
        "\n",
        "Nous traitons maintenant les résultats des essais classiques et quantiques, en les convertissant dans un format cohérent pour l'évaluation en aval. Cette étape compare la qualité prédictive à l'aide de mesures standard - exactitude, précision, rappel et F1 - et analyse la manière dont la largeur de l'ensemble (`num_learners`) et le contrôle de l'éparpillement (`regularization`) influencent à la fois les performances et le comportement informatique.\n",
        "\n",
        "La ligne de base classique AdaBoost constitue une référence compacte et stable pour l'apprentissage à petite échelle. Il donne de bons résultats avec des ensembles limités et un surcoût de calcul négligeable, ce qui reflète la force du boosting traditionnel lorsque l'espace d'hypothèses est encore traçable. Les configurations quantiques (`qeec_pred_job_1`, `qeec_pred_job_2`, et `qeec_pred_job_3`) étendent cette ligne de base en intégrant le processus de sélection d'ensemble dans une boucle d'optimisation quantique variationnelle. Cela permet au système d'explorer simultanément des sous-ensembles d'apprenants de taille exponentielle en superposition, en tenant compte de la nature combinatoire de la sélection d'ensemble de manière plus efficace à mesure que l'échelle augmente.\n",
        "\n",
        "Les résultats montrent que l'augmentation de `num_learners` de 10 à 30 améliore le rappel et F1, confirmant qu'un ensemble plus large capture des interactions plus riches entre les apprenants faibles. Le gain est sous-linéaire sur le matériel actuel - chaque apprenant supplémentaire produit des incréments de précision plus petits - mais le comportement d'échelle sous-jacent reste favorable parce que l'optimiseur quantique peut rechercher des espaces de configuration plus larges sans l'explosion exponentielle typique de la sélection classique de sous-ensembles. La régularisation introduit une nuance supplémentaire : un λ=7 fixe renforce la densité cohérente et stabilise la convergence, tandis que la régularisation α adaptative ajuste automatiquement la densité en fonction des corrélations entre les apprenants. Cet élagage dynamique permet souvent d'obtenir une adresse F1 légèrement plus élevée pour la même largeur de qubit, ce qui permet d'équilibrer la complexité du modèle et la généralisation.\n",
        "\n",
        "Lorsqu'elle est comparée directement à la ligne de base AdaBoost, la plus petite configuration quantique ( L=10 ) reproduit une précision similaire, ce qui valide l'exactitude du pipeline hybride. À des largeurs plus importantes, les variantes quantiques - en particulier avec l'auto-régularisation - commencent à surpasser modestement la ligne de base classique, montrant une amélioration du rappel et de F1 sans croissance linéaire du coût de calcul. Ces améliorations n'indiquent pas un \"avantage quantique\" immédiat, mais plutôt une **efficacité de mise à l'échelle** : l'optimiseur quantique conserve des performances acceptables à mesure que l'ensemble s'étend, alors qu'une approche classique serait confrontée à une croissance exponentielle de la complexité de la sélection des sous-ensembles.\n",
        "\n",
        "En pratique :\n",
        "\n",
        "* Utilisez la **ligne de base classique** pour une validation rapide et une évaluation comparative sur de petits ensembles de données.\n",
        "* Appliquer les **ensembles quantiques** lorsque la largeur du modèle ou la complexité des caractéristiques augmente - la recherche basée sur la méthode QAOA s'adapte mieux à ces régimes.\n",
        "* Employer une **α-régularisation adaptative** pour maintenir la rareté et la généralisation sans augmenter la largeur du circuit.\n",
        "* Contrôler le temps et la profondeur de la QPU pour équilibrer les gains de qualité avec les contraintes matérielles à court terme.\n",
        "\n",
        "L'ensemble de ces expériences montre que les ensembles optimisés au niveau quantique complètent les méthodes classiques : ils reproduisent la précision de base à petite échelle tout en offrant une voie vers une mise à l'échelle efficace pour les problèmes d'apprentissage combinatoires plus importants. Au fur et à mesure que le matériel s'améliore, ces avantages de mise à l'échelle devraient s'accentuer, étendant la taille et la profondeur des modèles basés sur l'ensemble au-delà de ce qui est classiquement pratique.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1cf1d610-f2e2-482c-af05-a882bb380528",
      "metadata": {},
      "source": [
        "<span id=\"evaluate-metrics-for-each-configuration\" />\n",
        "\n",
        "### Évaluer les indicateurs pour chaque configuration\n",
        "\n",
        "Nous évaluons maintenant toutes les configurations - la ligne de base classique AdaBoost et les trois ensembles quantiques - en utilisant l'aide `evaluate_predictions` pour calculer l'exactitude, la précision, le rappel et F1 sur le même ensemble de test. Cette comparaison clarifie la manière dont l'optimisation quantique évolue par rapport à l'approche classique : à petite échelle, les deux méthodes fonctionnent de manière similaire; à mesure que les ensembles se développent, la méthode quantique peut explorer plus efficacement des espaces d'hypothèses plus vastes. Le tableau qui en résulte présente ces tendances sous une forme quantitative cohérente.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "ee8cc821-8311-4c84-8f94-ffb2ec3facc3",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Accuracy: 0.7893333333333333\n",
            "Precision: 1.0\n",
            "Recall: 0.7893333333333333\n",
            "F1: 0.8822652757078987\n",
            "Accuracy: 0.868\n",
            "Precision: 1.0\n",
            "Recall: 0.868\n",
            "F1: 0.9293361884368309\n",
            "Accuracy: 0.8946666666666667\n",
            "Precision: 1.0\n",
            "Recall: 0.8946666666666667\n",
            "F1: 0.944405348346235\n",
            "Accuracy: 0.908\n",
            "Precision: 1.0\n",
            "Recall: 0.908\n",
            "F1: 0.9517819706498952\n"
          ]
        },
        {
          "data": {
            "text/html": [
              "<div>\n",
              "<style scoped>\n",
              "    .dataframe tbody tr th:only-of-type {\n",
              "        vertical-align: middle;\n",
              "    }\n",
              "\n",
              "    .dataframe tbody tr th {\n",
              "        vertical-align: top;\n",
              "    }\n",
              "\n",
              "    .dataframe thead th {\n",
              "        text-align: right;\n",
              "    }\n",
              "</style>\n",
              "<table border=\"1\" class=\"dataframe\">\n",
              "  <thead>\n",
              "    <tr style=\"text-align: right;\">\n",
              "      <th></th>\n",
              "      <th>Config</th>\n",
              "      <th>Accuracy</th>\n",
              "      <th>Precision</th>\n",
              "      <th>Recall</th>\n",
              "      <th>F1</th>\n",
              "    </tr>\n",
              "  </thead>\n",
              "  <tbody>\n",
              "    <tr>\n",
              "      <th>0</th>\n",
              "      <td>AdaBoost (Classical)</td>\n",
              "      <td>0.789333</td>\n",
              "      <td>1.0</td>\n",
              "      <td>0.789333</td>\n",
              "      <td>0.882265</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>1</th>\n",
              "      <td>QEEC L=10, reg=7</td>\n",
              "      <td>0.868000</td>\n",
              "      <td>1.0</td>\n",
              "      <td>0.868000</td>\n",
              "      <td>0.929336</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>2</th>\n",
              "      <td>QEEC L=30, reg=7</td>\n",
              "      <td>0.894667</td>\n",
              "      <td>1.0</td>\n",
              "      <td>0.894667</td>\n",
              "      <td>0.944405</td>\n",
              "    </tr>\n",
              "    <tr>\n",
              "      <th>3</th>\n",
              "      <td>QEEC L=60, reg=auto (α=0.82)</td>\n",
              "      <td>0.908000</td>\n",
              "      <td>1.0</td>\n",
              "      <td>0.908000</td>\n",
              "      <td>0.951782</td>\n",
              "    </tr>\n",
              "  </tbody>\n",
              "</table>\n",
              "</div>"
            ],
            "text/plain": [
              "                         Config  Accuracy  Precision    Recall        F1\n",
              "0          AdaBoost (Classical)  0.789333        1.0  0.789333  0.882265\n",
              "1              QEEC L=10, reg=7  0.868000        1.0  0.868000  0.929336\n",
              "2              QEEC L=30, reg=7  0.894667        1.0  0.894667  0.944405\n",
              "3  QEEC L=60, reg=auto (α=0.82)  0.908000        1.0  0.908000  0.951782"
            ]
          },
          "execution_count": 18,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "results = []\n",
        "\n",
        "# Classical baseline\n",
        "acc_b, prec_b, rec_b, f1_b = evaluate_predictions(baseline_pred, y_test)\n",
        "results.append(\n",
        "    {\n",
        "        \"Config\": \"AdaBoost (Classical)\",\n",
        "        \"Accuracy\": acc_b,\n",
        "        \"Precision\": prec_b,\n",
        "        \"Recall\": rec_b,\n",
        "        \"F1\": f1_b,\n",
        "    }\n",
        ")\n",
        "\n",
        "# Quantum runs\n",
        "for label, preds in [\n",
        "    (\"QEEC L=10, reg=7\", qeec_pred_job_1),\n",
        "    (\"QEEC L=30, reg=7\", qeec_pred_job_2),\n",
        "    (f\"QEEC L=60, reg=auto (α={REGULARIZATION_RATIO})\", qeec_pred_job_3),\n",
        "]:\n",
        "    acc, prec, rec, f1 = evaluate_predictions(preds, y_test)\n",
        "    results.append(\n",
        "        {\n",
        "            \"Config\": label,\n",
        "            \"Accuracy\": acc,\n",
        "            \"Precision\": prec,\n",
        "            \"Recall\": rec,\n",
        "            \"F1\": f1,\n",
        "        }\n",
        "    )\n",
        "\n",
        "\n",
        "df_results = pd.DataFrame(results)\n",
        "df_results"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "704cecf6-2c87-4356-b9be-26f4346c194f",
      "metadata": {},
      "source": [
        "<span id=\"visualize-quality-trends-across-configurations\" />\n",
        "\n",
        "### Visualisez les tendances en matière de qualité pour toutes les configurations\n",
        "\n",
        "Le graphique à barres groupées ci-dessous compare **la précision** et **F1** entre la ligne de base classique et les ensembles quantiques (`L=10`, `L=30`, et `L=60 auto-α` ). Il montre comment la précision se stabilise tandis que F1 s'améliore progressivement à mesure que la largeur de l'ensemble quantique augmente, démontrant que la méthode hybride permet de maintenir l'échelle des performances sans la croissance exponentielle des coûts typique de la sélection classique de sous-ensembles.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "0f15c5fb-2450-4671-9bc2-471043414df2",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/sml-classification/extracted-outputs/0f15c5fb-2450-4671-9bc2-471043414df2-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "x = np.arange(len(df_results))\n",
        "width = 0.35\n",
        "plt.figure(figsize=(7.6, 4.6))\n",
        "plt.bar(x - width / 2, df_results[\"Accuracy\"], width=width, label=\"Accuracy\")\n",
        "plt.bar(x + width / 2, df_results[\"F1\"], width=width, label=\"F1\")\n",
        "plt.xticks(x, df_results[\"Config\"], rotation=10)\n",
        "plt.ylabel(\"Score\")\n",
        "plt.title(\"Classical vs Quantum ensemble performance\")\n",
        "plt.legend()\n",
        "plt.ylim(0, 1.0)\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "745cd406-5166-4eb9-8506-0eedd71e9b79",
      "metadata": {},
      "source": [
        "<span id=\"interpretation\" />\n",
        "\n",
        "### Interprétation\n",
        "\n",
        "Le graphique confirme le modèle d'échelle attendu. La méthode classique AdaBoost donne de bons résultats pour les petits ensembles, mais devient de plus en plus coûteuse à mettre à l'échelle lorsque le nombre d'apprenants faibles augmente, car son problème de sélection de sous-ensembles prend de l'ampleur sur le plan combinatoire. Les modèles améliorés par les quanta reproduisent la précision classique à des largeurs faibles et commencent à la dépasser lorsque la taille de l'ensemble augmente, en particulier dans le cadre d'une α-régularisation adaptative. Cela reflète la capacité de l'optimiseur quantique à échantillonner et à évaluer de nombreux sous-ensembles candidats en parallèle grâce à la superposition, ce qui permet de maintenir une recherche traçable même à des largeurs plus importantes. Bien que la surcharge matérielle actuelle compense une partie des gains théoriques, la tendance illustre l'avantage de l'efficacité de la mise à l'échelle de la formulation quantique. En termes pratiques, la méthode classique reste préférable pour les repères légers, tandis que les ensembles quantiques deviennent avantageux lorsque la dimensionnalité du modèle et la taille de l'ensemble augmentent, offrant de meilleurs compromis entre la précision, la généralisation et la croissance des calculs.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a27afb68-f959-4f4a-910c-0aadafb7e18e",
      "metadata": {},
      "source": [
        "<span id=\"appendix-scaling-benefits-and-enhancements\" />\n",
        "\n",
        "## Annexe : Avantages et améliorations liés à la mise à l'échelle\n",
        "\n",
        "L'avantage de l'extensibilité du site `QuantumEnhancedEnsembleClassifier` provient de la façon dont le processus de sélection d'ensemble s'adapte à l'optimisation quantique.\n",
        "Les méthodes classiques d'apprentissage d'ensemble, telles que AdaBoost ou les forêts aléatoires, deviennent coûteuses en termes de calcul lorsque le nombre d'apprenants faibles augmente, car la sélection du sous-ensemble optimal est un problème combinatoire qui s'étend de manière exponentielle.\n",
        "\n",
        "En revanche, la formulation quantique - mise en œuvre ici via l'algorithme d'optimisation approximative quantique (QAOA) - peut explorer plus efficacement ces espaces de recherche de taille exponentielle en évaluant plusieurs configurations en superposition.\n",
        "Par conséquent, le temps de formation n'augmente pas de manière significative avec le nombre d'apprenants, ce qui permet au modèle de rester efficace même lorsque la taille de l'ensemble augmente.\n",
        "\n",
        "Bien que le matériel actuel présente certaines limitations en termes de bruit et de profondeur, ce flux de travail démontre une approche hybride à court terme dans laquelle les composants classiques et quantiques coopèrent : l'optimiseur quantique fournit un meilleur paysage d'initialisation pour la boucle classique, améliorant ainsi la convergence et la qualité du modèle final.\n",
        "Au fur et à mesure de l'évolution des processeurs quantiques, ces avantages en termes d'évolutivité devraient s'étendre à des ensembles de données plus importants, à des ensembles plus vastes et à des circuits plus profonds.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ee41a301",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## Références\n",
        "\n",
        "1. [Introduction à la Qiskit Functions](/docs/guides/functions)\n",
        "2. [Multivers Informatique Singularité Machine Learning](/docs/guides/multiverse-computing-singularity)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2cb5785c",
      "metadata": {},
      "source": [
        "<span id=\"tutorial-survey\" />\n",
        "\n",
        "## Enquête tutorielle\n",
        "\n",
        "Veuillez prendre une minute pour nous faire part de vos commentaires sur ce tutoriel. Vos commentaires nous aideront à améliorer nos offres de contenu et l'expérience des utilisateurs.\n",
        "\n",
        "[Lien vers l'enquête](https://your.feedback.ibm.com/jfe/form/SV_3BLFkNVEuh0QBWm)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "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"
    },
    "hours": 2,
    "qpuSeconds": 1200
  },
  "nbformat": 4,
  "nbformat_minor": 5
}