{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "b6d1e3ec",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Hybride quantenunterstützte Ensemble-Klassifizierung (Netzstabilitäts-Workflow)\"\n",
        "description: \"Erstellen und analysieren Sie ein hybrides quanten-klassisches Ensemble für die Klassifizierung der Netzstabilität auf QPUs von IBM unter Verwendung der Singularity Qiskit-Funktion von 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",
        "# Hybride quantenunterstützte Ensemble-Klassifizierung (Netzstabilitäts-Workflow)\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "a6f69b77",
      "metadata": {},
      "source": [
        "*Geschätzter Verbrauch: 20 Minuten QPU-Zeit für jeden Auftrag auf einem Eagle r3 Prozessor. (HINWEIS: Dies ist nur eine Schätzung. Ihre Laufzeit kann variieren.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8bf80006",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Hintergrund\n",
        "\n",
        "Dieses Tutorial veranschaulicht einen hybriden quanten-klassischen Arbeitsablauf, bei dem ein klassisches Ensemble durch einen Quantenoptimierungsschritt ergänzt wird. Mithilfe des „Singularity- Machine Learning s – Klassifizierung“ von Multiverse Computing (einer Qiskit-Funktion) trainieren wir eine Gruppe herkömmlicher Lernmodelle (z. B. Entscheidungsbäume, k-NN, logistische Regression) und verfeinern diese Gruppe anschließend mit einer Quantenschicht, um die Diversität und die Generalisierungsfähigkeit zu verbessern. Das Ziel ist praktischer Natur: Bei einer realen Aufgabe zur Vorhersage der Netzstabilität vergleichen wir eine leistungsstarke klassische Basislinie mit einer quantenoptimierten Alternative unter Verwendung derselben Datenaufteilung, damit Sie erkennen können, wo der Quantenansatz Vorteile bringt und welche Nachteile damit verbunden sind.\n",
        "\n",
        "Die Aufrechterhaltung der Netzstabilität stellt eine große Herausforderung dar, da in die Stromnetze variable erneuerbare Energiequellen und ein sich wandelnder Bedarf integriert werden. Die Vorhersage, ob die Betriebsbedingungen stabil sind, kann die Entwicklung von Werkzeugen zur Steuerung dieser immer komplexer werdenden Systeme unterstützen. In diesem Tutorial wird diese Vorhersageaufgabe anhand eines hybriden quanten-klassischen Klassifizierungs-Workflows untersucht.\n",
        "\n",
        "Warum das wichtig ist: Die Auswahl einer guten Teilmenge aus vielen schwachen Lernern ist ein kombinatorisches Problem, das mit der Größe des Ensembles schnell wächst. Klassische Heuristiken wie Boosting, Bagging und Stacking sind bei moderaten Skalen gut, haben aber Schwierigkeiten, große, redundante Modellbibliotheken effizient zu untersuchen. Die Funktion integriert Quantenalgorithmen - insbesondere QAOA (und optional VQE in anderen Konfigurationen) -, um diesen Raum effektiver zu durchsuchen, nachdem die klassischen Lerner trainiert wurden, was die Chance erhöht, eine kompakte, vielfältige Teilmenge zu finden, die besser verallgemeinert.\n",
        "\n",
        "Entscheidend ist, dass der Datenumfang nicht durch Qubits begrenzt ist. Die Schwerstarbeit an den Daten - Vorverarbeitung, Training des Lernerpools und Auswertung - bleibt klassisch und kann Millionen von Beispielen verarbeiten. Die Qubits bestimmen nur die Größe des Ensembles, das für die Quantenauswahl verwendet wird. Diese Entkopplung macht den Ansatz auf heutiger Hardware realisierbar: Sie behalten die vertrauten Scikit-Learn-Workflows für Daten- und Modelltraining bei, während Sie den Quantenschritt über eine saubere Aktionsschnittstelle in Qiskit Functions aufrufen.\n",
        "\n",
        "In der Praxis können dem Ensemble verschiedene Lerntypen zur Verfügung gestellt werden (z. B. Entscheidungsbäume, logistische Regression oder k-NN), wobei Entscheidungsbäume in der Regel am besten abschneiden. Der Optimierer bevorzugt durchweg stärkere Ensemble-Mitglieder - wenn heterogene Lerner bereitgestellt werden, werden schwächere Modelle wie lineare Regressoren in der Regel zugunsten von ausdrucksstärkeren Modellen wie Entscheidungsbäumen beschnitten.\n",
        "\n",
        "Was Sie hier tun werden: Bereiten Sie den Gitterstabilitätsdatensatz vor und gleichen Sie ihn ab; erstellen Sie eine klassische AdaBoost Basislinie; führen Sie mehrere Quantenkonfigurationen aus, die die Ensemblebreite und die Regularisierung variieren; führen Sie sie auf IBM® Simulatoren oder QPUs über Qiskit Serverless aus; und vergleichen Sie Genauigkeit, Präzision, Wiedererkennung und F1 über alle Läufe hinweg. Dabei werden Sie das Aktionsmuster der Funktion (`create`, `fit`, `predict`, `fit_predict`, `create_fit_predict`) und die Tastensteuerung verwenden:\n",
        "\n",
        "* Regularisierungsarten: `onsite` (λ) für direkte Sparsamkeit und `alpha` für einen verhältnisbasierten Ausgleich zwischen Interaktions- und Vor-Ort-Terms\n",
        "* Auto-Regularisierung: Setzen Sie `regularization=\"auto\"` mit einem Ziel-Auswahlverhältnis, um die Sparsamkeit automatisch anzupassen\n",
        "* Optimierungsoptionen: Simulator versus QPU, Wiederholungen, klassischer Optimierer und dessen Optionen, Transpilierungstiefe sowie Einstellungen für den „ IBM Quantum “-Sampler oder -Estimator\n",
        "\n",
        "Die Benchmarks in der Dokumentation zeigen, dass sich die Genauigkeit bei anspruchsvollen Problemen mit zunehmender Anzahl der Lernenden (Qubits) verbessert, wobei der Quantenklassifikator mit einem vergleichbaren klassischen Ensemble gleichzieht oder es sogar übertrifft. In diesem Tutorial werden Sie den Arbeitsablauf Ende-zu-Ende reproduzieren und untersuchen, wann eine Erhöhung der Ensemble-Breite oder ein Wechsel zur adaptiven Regularisierung eine bessere F1 bei angemessenem Ressourcenverbrauch ergibt. Das Ergebnis ist ein fundierter Blick darauf, wie ein Quantenoptimierungsschritt das klassische Ensemble-Lernen in realen Anwendungen ergänzen und nicht ersetzen kann.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "55b94021",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Anforderungen\n",
        "\n",
        "Bevor Sie mit diesem Tutorial beginnen, stellen Sie sicher, dass Sie die folgenden Pakete in Ihrer Python Umgebung installiert haben:\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",
        "## Einrichtung\n",
        "\n",
        "In diesem Abschnitt initialisieren wir den Qiskit Serverless -Client und laden die von Multiverse Computing bereitgestellte Funktion Singularity Machine Learning - Classification.\n",
        "Mit Qiskit Serverless können Sie hybride quantenklassische Workflows auf der von IBM verwalteten Cloud-Infrastruktur ausführen, ohne sich um das Ressourcenmanagement kümmern zu müssen.\n",
        "Sie benötigen einen IBM Quantum Platform API-Schlüssel und Ihren Cloud-Ressourcennamen (CRN), um sich zu authentifizieren und auf Qiskit Functions zuzugreifen.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d4fe7ee1-21ce-445c-b151-598cd4cf9227",
      "metadata": {},
      "source": [
        "<span id=\"download-the-dataset\" />\n",
        "\n",
        "### Laden Sie den Datensatz herunter\n",
        "\n",
        "Zur Durchführung dieses Tutorials verwenden wir einen vorverarbeiteten **Datensatz zur Netzstabilitätsklassifizierung**, der markierte Messwerte von Stromnetzsensoren enthält.\n",
        "Die folgende Zelle erstellt automatisch die erforderliche Ordnerstruktur und lädt sowohl die Trainings- als auch die Testdateien mit `wget` direkt in Ihre Umgebung herunter.\n",
        "Wenn Sie diese Dateien bereits lokal haben, werden sie mit diesem Schritt sicher überschrieben, um die Versionskonsistenz zu gewährleisten.\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",
        "### Erforderliche Pakete importieren\n",
        "\n",
        "In diesem Abschnitt importieren wir alle Python Pakete und Qiskit-Module, die im gesamten Lehrgang verwendet werden.\n",
        "Dazu gehören wissenschaftliche Kernbibliotheken für die Datenverarbeitung und Modellauswertung - wie `NumPy`, `pandas` und `scikit-learn` - sowie Visualisierungswerkzeuge und Qiskit-Komponenten für die Ausführung des quantengestützten Modells.\n",
        "Wir importieren auch `QiskitRuntimeService` und `QiskitFunctionsCatalog` , um eine Verbindung mit den Diensten IBM Quantum® herzustellen und auf die Funktion Singularity Machine Learning zuzugreifen.\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",
        "### Konstante Variablen festlegen\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",
        "### Verbinden Sie sich mit IBM Quantum und laden Sie die Singularity-Funktion\n",
        "\n",
        "Als Nächstes authentifizieren wir uns bei den Diensten von IBM Quantum und laden die Funktion Singularity Machine Learning - Classification von Qiskit Functions Catalog.\n",
        "Die `QiskitRuntimeService` stellt eine sichere Verbindung zu IBM Quantum Platform her, wobei Ihr API-Token und Ihre Instanz-CRN verwendet werden, um den Zugriff auf Quantum-Backends zu ermöglichen.\n",
        "Die `QiskitFunctionsCatalog` wird dann verwendet, um die Singularitätsfunktion unter ihrem Namen abzurufen (`\"multiverse/singularity\"`), so dass wir sie später für hybride quantenklassische Berechnungen aufrufen können.\n",
        "Wenn die Einrichtung erfolgreich war, erhalten Sie eine Bestätigungsmeldung, die anzeigt, dass die Funktion korrekt geladen wurde.\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",
        "### Hilfsfunktionen definieren\n",
        "\n",
        "Bevor wir die Hauptexperimente durchführen, definieren wir einige kleine Hilfsfunktionen, die das Laden von Daten und die Modellbewertung vereinfachen.\n",
        "\n",
        "* `load_data()` liest die CSV-Eingabedateien in NumPy Arrays ein, wobei Merkmale und Bezeichnungen für die Kompatibilität mit `scikit-learn` und Quantum Workflows aufgeteilt werden.\n",
        "* `evaluate_predictions()` berechnet die wichtigsten Leistungskennzahlen - Genauigkeit, Präzision, Rückruf und F1-score - und berichtet optional über die Laufzeit, wenn Zeitangaben gemacht werden.\n",
        "\n",
        "Diese Hilfsfunktionen vereinfachen wiederholte Operationen später im Notizbuch und gewährleisten eine konsistente Berichterstattung über Metriken sowohl für klassische als auch für Quantenklassifikatoren.\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",
        "## Schritt 1: Klassische Eingaben einem Quantenproblem zuordnen\n",
        "\n",
        "Wir beginnen mit der Vorbereitung des Datensatzes für hybride quantenklassische Experimente. Ziel dieses Schrittes ist es, die Rohdaten zur Gitterstabilität in ausgewogene Trainings-, Validierungs- und Test-Splits umzuwandeln, die sowohl von klassischen als auch von Quanten-Workflows konsistent verwendet werden können. Die Beibehaltung identischer Splits gewährleistet, dass spätere Leistungsvergleiche fair und reproduzierbar sind.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1c084cde-a5cf-4661-a00c-aa243c8b0e44",
      "metadata": {},
      "source": [
        "<span id=\"data-loading-and-preprocessing\" />\n",
        "\n",
        "### Datenladen und -vorverarbeitung\n",
        "\n",
        "Zunächst laden wir die Trainings- und Test-CSV-Dateien, erstellen einen Validierungssplit und gleichen den Datensatz mit einer zufälligen Überabtastung aus. Die Ausgewogenheit verhindert eine Voreingenommenheit gegenüber der Mehrheitsklasse und liefert ein stabileres Lernsignal sowohl für klassische als auch für Quanten-Ensemblemodelle.\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",
        "### Klassische Basislinie: AdaBoost reference\n",
        "\n",
        "Bevor wir eine Quantenoptimierung durchführen, trainieren wir eine starke klassische Basislinie - einen Standardklassifikator AdaBoost - auf denselben ausgewogenen Daten. Dies bietet einen reproduzierbaren Bezugspunkt für spätere Vergleiche und hilft dabei, zu quantifizieren, ob die Quantenoptimierung die Verallgemeinerung oder Effizienz über ein gut abgestimmtes klassisches Ensemble hinaus verbessert.\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",
        "## Schritt 2: Optimierung des Problems für die Ausführung auf Quantenhardware\n",
        "\n",
        "Die Auswahl des Ensembles wird als kombinatorisches Optimierungsproblem formuliert, bei dem jeder schwache Lerner eine binäre Entscheidungsvariable darstellt und das Ziel durch einen Regularisierungsterm ein Gleichgewicht zwischen Genauigkeit und Sparsität herstellt. Das löst `QuantumEnhancedEnsembleClassifier` dieses Problem mithilfe von QAOA auf „ IBM “-Hardware, ermöglicht aber weiterhin die simulatorbasierte Untersuchung. Die Steuerung `optimizer_options` des Hybrid-Schleifenzyklus: `simulator=False` leitet Schaltkreise an die ausgewählte QPU weiter, `num_solutions` erhöht die Suchbreite und ( `classical_optimizer_options` für den inneren klassischen Optimierer) steuert die Konvergenz; Werte um 60 Iterationen bieten ein gutes Gleichgewicht zwischen Qualität und Laufzeit. Optionen für das Quantencomputing – wie beispielsweise eine moderate Schaltungstiefe (`reps`) und ein Standardaufwand für die Transpilierung – tragen dazu bei, eine robuste Leistung auf allen Geräten zu gewährleisten. Die folgende Konfiguration ist das „Best-Results“-Profil, das wir für Hardware-Läufe verwenden werden; Sie können auch eine rein simulierte Variante erstellen, indem Sie die Option aktivieren, `simulator=True` um den Workflow im Trockenlauf auszuführen, ohne QPU-Zeit zu verbrauchen.\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",
        "## Schritt 3: Mit „ Qiskit primitives “ ausführen\n",
        "\n",
        "Wir führen nun den gesamten Arbeitsablauf unter Verwendung der `create_fit_predict` Aktion der Singularity-Funktion aus, um die `QuantumEnhancedEnsembleClassifier` End-to-End-Infrastruktur auf IBM zu trainieren, zu optimieren und zu bewerten. Die Funktion erstellt das Ensemble, wendet die Quantenoptimierung über Qiskit primitives an und gibt sowohl Vorhersagen als auch Auftragsmetadaten (einschließlich Laufzeit und Ressourcennutzung) zurück. Die klassische Datenaufteilung aus Schritt 1 wird aus Gründen der Reproduzierbarkeit wiederverwendet, wobei die Validierungsdaten durch `fit_params` geleitet werden, damit die Optimierung die Hyperparameter intern abstimmen kann, während die ausgeklammerte Testmenge unangetastet bleibt.\n",
        "\n",
        "In diesem Schritt untersuchen wir verschiedene Konfigurationen des Quantenensembles, um zu verstehen, wie Schlüsselparameter - insbesondere `num_learners` und `regularization` - sowohl die Ergebnisqualität als auch die QPU-Nutzung beeinflussen.\n",
        "\n",
        "* `num_learners` bestimmt die Breite des Ensembles (und implizit die Anzahl der Qubits), was sich auf die Kapazität des Modells und die Rechenkosten auswirkt.\n",
        "* `regularization` kontrolliert die Sparsamkeit und die Überanpassung und bestimmt, wie viele Lernende nach der Optimierung aktiv bleiben.\n",
        "\n",
        "Wenn wir diese Parameter variieren, können wir sehen, wie Ensemblebreite und Regularisierung interagieren: Eine größere Breite verbessert typischerweise F1, kostet aber mehr QPU-Zeit, während eine stärkere oder adaptive Regularisierung die Generalisierung bei ungefähr gleichem Hardware-Footprint verbessern kann. In den nächsten Unterabschnitten werden drei repräsentative Konfigurationen vorgestellt, um diese Auswirkungen zu veranschaulichen.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8da41d4d-f06d-4b67-a8ee-b07fc0289558",
      "metadata": {},
      "source": [
        "<span id=\"baseline\" />\n",
        "\n",
        "### Grundlegende Verpflichtungen\n",
        "\n",
        "Diese Konfiguration verwendet `num_learners = 10` und `regularization = 7`.\n",
        "\n",
        "* `num_learners` steuert die Ensemble-Breite, d. h. die Anzahl der kombinierten schwachen Lerner und - bei Quanten-Hardware - die **Anzahl der erforderlichen Qubits**. Ein größerer Wert erweitert den kombinatorischen Suchraum und kann die Genauigkeit und den Wiedererkennungswert verbessern, erhöht aber auch die Breite der Schaltkreise, die Kompilierungszeit und den Gesamtverbrauch der QPU.\n",
        "* `regularization` legt die Höhe der Strafe für die Einbeziehung zusätzlicher Lernenden fest. Bei der standardmäßigen Regularisierung \"vor Ort\" erzwingen höhere Werte eine stärkere Sparsamkeit (weniger Lerner werden beibehalten), während niedrigere Werte komplexere Ensembles ermöglichen.\n",
        "\n",
        "Dieser Aufbau bietet eine kostengünstige Grundlage, die zeigt, wie sich ein kleines Ensemble verhält, bevor die Breite skaliert oder die Sparsamkeit eingestellt wird.\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",
        "### Erhöhen Sie die Anzahl der Lernenden\n",
        "\n",
        "Hier erhöhen wir `num_learners` von 10 → 30 und behalten `regularization = 7` bei.\n",
        "\n",
        "* Mehr Lernende erweitern den Hypothesenraum und erlauben dem Modell, subtilere Muster zu erfassen, was F1 leicht verbessern kann.\n",
        "* In den meisten Fällen ist der Laufzeitunterschied zwischen 10 und 30 Lernern nicht wesentlich, was darauf hindeutet, dass die zusätzliche Schaltkreisbreite die Ausführungskosten nicht wesentlich erhöht.\n",
        "* Die Qualitätsverbesserung folgt nach wie vor einer *Kurve mit abnehmenden Erträgen* : Die anfänglichen Gewinne treten auf, wenn das Ensemble wächst, aber sie stagnieren, wenn zusätzliche Lernende weniger neue Informationen beitragen.\n",
        "\n",
        "Dieses Experiment verdeutlicht den Kompromiss zwischen Qualität und Effizienz - eine Erhöhung der Ensemble-Breite kann je nach Backend und Transpilationsbedingungen kleine Genauigkeitsgewinne ohne größere Laufzeiteinbußen bringen.\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",
        "### Regularisierung\n",
        "\n",
        "In dieser Konfiguration erhöhen wir auf `num_learners = 60` und führen eine adaptive Regularisierung ein, um die Sparsamkeit intuitiver zu verwalten.\n",
        "\n",
        "* Mit `regularization = \"auto\"` findet der Optimierer automatisch eine geeignete Regularisierungsstärke, die ungefähr `regularization_ratio * num_learners` schwache Lerner für das endgültige Ensemble auswählt, anstatt die Strafe manuell festzulegen. Dies bietet eine bequemere Schnittstelle für die Verwaltung des Gleichgewichts zwischen Sparsamkeit und Ensemblegröße.\n",
        "* `regularization_type = \"alpha\"` legt fest, wie die Strafe angewendet wird. Im Gegensatz zu `onsite`, das unbegrenzt ist `[0, ∞]`, ist `alpha` zwischen `[0, 1]` begrenzt, wodurch es leichter abzustimmen und zu interpretieren ist. Der Parameter steuert den Kompromiss zwischen individuellen und paarweisen Strafen und bietet einen glatteren Konfigurationsbereich.\n",
        "* `regularization_desired_ratio ≈ 0.82` gibt den Zielanteil der Lernenden an, die nach der Regularisierung aktiv bleiben sollen - hier werden etwa 82 % der Lernenden beibehalten, wobei die schwächsten 18 % automatisch abgeschnitten werden.\n",
        "\n",
        "Die adaptive Regularisierung vereinfacht zwar die Konfiguration und trägt zur Erhaltung eines ausgewogenen Ensembles bei, garantiert aber nicht unbedingt eine bessere oder stabilere Leistung. Die tatsächliche Qualität hängt von der Auswahl eines geeigneten Regularisierungsparameters ab, dessen Feinabstimmung durch Kreuzvalidierung sehr rechenintensiv sein kann. Der Hauptvorteil liegt eher in der verbesserten Benutzerfreundlichkeit und Interpretierbarkeit als im direkten Genauigkeitsgewinn.\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",
        "## Schritt 4: Nachbearbeitung und Rückgabe des Ergebnisses im gewünschten klassischen Format\n",
        "\n",
        "Wir verarbeiten nun die Ergebnisse der klassischen und der Quantenläufe nach und konvertieren sie in ein einheitliches Format für die nachfolgende Auswertung. In diesem Schritt wird die Vorhersagequalität anhand von Standardmetriken - Genauigkeit, Präzision, Wiedererkennungswert und F1 - verglichen und analysiert, wie Ensemblebreite (`num_learners`) und Sparsamkeitskontrolle (`regularization`) sowohl die Leistung als auch das Rechenverhalten beeinflussen.\n",
        "\n",
        "Die klassische Basislinie AdaBoost bietet eine kompakte und stabile Referenz für das Lernen in kleinem Maßstab. Es zeigt gute Leistungen bei begrenzten Ensembles und vernachlässigbarem Rechenaufwand, was die Stärke des traditionellen Boosting widerspiegelt, wenn der Hypothesenraum noch überschaubar ist. Die Quantenkonfigurationen (`qeec_pred_job_1`, `qeec_pred_job_2` und `qeec_pred_job_3`) erweitern diese Basislinie, indem sie den Ensemble-Auswahlprozess in eine Quantenoptimierungsschleife einbetten. Auf diese Weise kann das System exponentiell große Teilmengen von Lernern gleichzeitig in Überlagerung untersuchen und die kombinatorische Natur der Ensemble-Auswahl mit zunehmender Größe effizienter angehen.\n",
        "\n",
        "Die Ergebnisse zeigen, dass eine Erhöhung von `num_learners` von 10 auf 30 die Erinnerung und F1 verbessert, was bestätigt, dass ein breiteres Ensemble reichhaltigere Interaktionen zwischen schwachen Lernern erfasst. Der Gewinn ist auf aktueller Hardware sublinear - jeder zusätzliche Lernende führt zu kleineren Genauigkeitsschritten -, aber das zugrunde liegende Skalierungsverhalten bleibt günstig, weil der Quantenoptimierer breitere Konfigurationsräume durchsuchen kann, ohne den exponentiellen Blow-up, der für die klassische Teilmengenauswahl typisch ist. Die Regularisierung führt zusätzliche Nuancen ein: Ein festes λ=7 erzwingt eine konsistente Sparsamkeit und stabilisiert die Konvergenz, während die adaptive α-Regularisierung die Sparsamkeit automatisch auf der Grundlage der Korrelationen zwischen den Lernenden abstimmt. Durch dieses dynamische Pruning wird oft eine etwas höhere F1 für die gleiche Qubit-Breite erreicht, wodurch ein Gleichgewicht zwischen Modellkomplexität und Verallgemeinerung erreicht wird.\n",
        "\n",
        "Im direkten Vergleich mit der Basislinie AdaBoost erreicht die kleinste Quantenkonfiguration ( L=10 ) eine ähnliche Genauigkeit, was die Korrektheit der hybriden Pipeline bestätigt. Bei größeren Breiten beginnen die Quantenvarianten - insbesondere mit Auto-Regularisierung - die klassische Basislinie leicht zu übertreffen und zeigen eine verbesserte Wiedererkennung und F1 ohne linearen Anstieg der Rechenkosten. Diese Verbesserungen deuten nicht auf einen unmittelbaren \"Quantenvorteil\" hin, sondern eher auf eine **Skalierungseffizienz** : Der Quantenoptimierer behält eine vertretbare Leistung bei, wenn sich das Ensemble erweitert, während bei einem klassischen Ansatz die Komplexität der Teilmengenauswahl exponentiell ansteigen würde.\n",
        "\n",
        "In der Praxis:\n",
        "\n",
        "* Verwenden Sie die **klassische Baseline** für eine schnelle Validierung und ein Benchmarking auf kleinen Datensätzen.\n",
        "* Wenden Sie **Quanten-Ensembles** an, wenn die Modellbreite oder die Komplexität der Merkmale zunimmt - die QAOA-basierte Suche skaliert in diesen Bereichen besser.\n",
        "* **Adaptive α-Regularisierung** zur Beibehaltung von Sparsamkeit und Generalisierung ohne Erhöhung der Schaltkreisbreite.\n",
        "* Überwachen Sie QPU-Zeit und -Tiefe, um Qualitätsgewinne gegen kurzfristige Hardwarebeschränkungen abzuwägen.\n",
        "\n",
        "Zusammengenommen zeigen diese Experimente, dass quantenoptimierte Ensembles klassische Methoden ergänzen: Sie reproduzieren die Grundgenauigkeit auf kleinen Skalen und bieten gleichzeitig einen Weg zur effizienten Skalierung bei größeren, kombinatorischen Lernproblemen. Es ist zu erwarten, dass sich diese Skalierungsvorteile mit der Verbesserung der Hardware noch verstärken und die mögliche Größe und Tiefe von ensemble-basierten Modellen über das hinausgehen, was klassischerweise praktikabel ist.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1cf1d610-f2e2-482c-af05-a882bb380528",
      "metadata": {},
      "source": [
        "<span id=\"evaluate-metrics-for-each-configuration\" />\n",
        "\n",
        "### Metriken für jede Konfiguration bewerten\n",
        "\n",
        "Wir evaluieren nun alle Konfigurationen - die klassische AdaBoost Baseline und die drei Quanten-Ensembles - unter Verwendung der `evaluate_predictions` -Hilfe, um die Genauigkeit, Präzision, den Wiedererkennungswert und F1 auf demselben Testsatz zu berechnen. Dieser Vergleich verdeutlicht, wie die Quantenoptimierung im Vergleich zum klassischen Ansatz skaliert: Bei kleinen Breiten schneiden beide ähnlich ab; bei wachsenden Ensembles kann die Quantenmethode größere Hypothesenräume effizienter untersuchen. Die sich daraus ergebende Tabelle fasst diese Trends in einer konsistenten, quantitativen Form zusammen.\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",
        "### Qualitätstrends über Konfigurationen hinweg visualisieren\n",
        "\n",
        "Das nachstehende gruppierte Balkendiagramm vergleicht die **Genauigkeit** und **F1** zwischen der klassischen Basislinie und den Quanten-Ensembles (`L=10`, `L=30` und `L=60 auto-α`). Es zeigt, wie sich die Genauigkeit stabilisiert, während sich F1 allmählich verbessert, wenn die Quanten-Ensemble-Breite zunimmt, und demonstriert, dass die hybride Methode die Leistungsskalierung ohne das für die klassische Teilmengenauswahl typische exponentielle Kostenwachstum aufrechterhält.\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",
        "### Auslegung\n",
        "\n",
        "Die Darstellung bestätigt das erwartete Skalierungsmuster. Das klassische AdaBoost ist für kleinere Ensembles sehr leistungsfähig, wird aber mit zunehmender Anzahl schwacher Lerner immer kostspieliger, da sich das Problem der Teilmengenauswahl kombinatorisch ausweitet. Die quantenverstärkten Modelle replizieren die klassische Genauigkeit bei geringen Breiten und beginnen, sie mit zunehmender Ensemblegröße zu übertreffen, insbesondere bei adaptiver α-Regularisierung. Dies spiegelt die Fähigkeit des Quantenoptimierers wider, durch Überlagerung viele Teilmengen von Kandidaten parallel zu erfassen und zu bewerten, wodurch die Suche auch bei größeren Weiten überschaubar bleibt. Der derzeitige Hardware-Overhead gleicht zwar einen Teil der theoretischen Gewinne wieder aus, aber der Trend zeigt den Vorteil der Skalierungseffizienz der Quantenformulierung. In der Praxis ist die klassische Methode für leichte Benchmarks nach wie vor vorzuziehen, während quantenverstärkte Ensembles mit zunehmender Modelldimensionalität und Ensemblegröße vorteilhaft werden, da sie einen besseren Kompromiss zwischen Genauigkeit, Verallgemeinerung und Rechenleistung bieten.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a27afb68-f959-4f4a-910c-0aadafb7e18e",
      "metadata": {},
      "source": [
        "<span id=\"appendix-scaling-benefits-and-enhancements\" />\n",
        "\n",
        "## Anhang: Skalierungsvorteile und Verbesserungen\n",
        "\n",
        "Der Vorteil der Skalierbarkeit von `QuantumEnhancedEnsembleClassifier` ergibt sich aus der Art und Weise, wie der Ensemble-Auswahlprozess auf die Quantenoptimierung abgebildet wird.\n",
        "Klassische Ensemble-Lernmethoden wie AdaBoost oder Random Forests werden mit zunehmender Anzahl schwacher Lerner rechenintensiv, da die Auswahl der optimalen Teilmenge ein kombinatorisches Problem darstellt, das exponentiell skaliert.\n",
        "\n",
        "Im Gegensatz dazu kann die Quantenformulierung - hier implementiert durch den Quantum Approximate Optimization Algorithm (QAOA) - diese exponentiell großen Suchräume effizienter erkunden, indem sie mehrere Konfigurationen in Überlagerung auswertet.\n",
        "Infolgedessen steigt die Trainingszeit nicht signifikant mit der Anzahl der Lernenden, so dass das Modell auch bei zunehmender Ensemblebreite effizient bleibt.\n",
        "\n",
        "Während die derzeitige Hardware einige Rausch- und Tiefenbeschränkungen mit sich bringt, demonstriert dieser Arbeitsablauf einen kurzfristigen hybriden Ansatz, bei dem klassische und Quantenkomponenten zusammenarbeiten: Der Quantenoptimierer bietet eine bessere Initialisierungslandschaft für die klassische Schleife und verbessert so die Konvergenz und die endgültige Modellqualität.\n",
        "Mit der Weiterentwicklung von Quantenprozessoren dürften sich diese Skalierbarkeitsvorteile auf größere Datensätze, breitere Ensembles und tiefere Schaltkreise ausweiten.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ee41a301",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## Referenzen\n",
        "\n",
        "1. [Einführung in Qiskit Functions](/docs/guides/functions)\n",
        "2. [Multiversum Computing Singularität Machine Learning](/docs/guides/multiverse-computing-singularity)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2cb5785c",
      "metadata": {},
      "source": [
        "<span id=\"tutorial-survey\" />\n",
        "\n",
        "## Tutorial-Umfrage\n",
        "\n",
        "Bitte nehmen Sie sich eine Minute Zeit, um uns Feedback zu diesem Tutorial zu geben. Ihre Erkenntnisse helfen uns, unser Inhaltsangebot und unsere Nutzererfahrung zu verbessern.\n",
        "\n",
        "[Link zur Umfrage](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
}