From 9aff3632fb404985a731c2c925dc4e85fcb3be05 Mon Sep 17 00:00:00 2001 From: philippeschoeb Date: Thu, 9 Apr 2026 12:39:23 -0400 Subject: [PATCH 1/2] CACIB notebook v1 --- ...oject_Bankruptcy_Prediction_tutorial.ipynb | 2268 +++++++++++++++++ 1 file changed, 2268 insertions(+) create mode 100644 QML_with_MerLin/CACIB_project_Bankruptcy_Prediction_tutorial.ipynb diff --git a/QML_with_MerLin/CACIB_project_Bankruptcy_Prediction_tutorial.ipynb b/QML_with_MerLin/CACIB_project_Bankruptcy_Prediction_tutorial.ipynb new file mode 100644 index 0000000..5ea5bf9 --- /dev/null +++ b/QML_with_MerLin/CACIB_project_Bankruptcy_Prediction_tutorial.ipynb @@ -0,0 +1,2268 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d1701273", + "metadata": {}, + "source": [ + "## 0. Overview\n", + "\n", + "As part of a collaboration, teams from Crédit Agricole CIB and Quandela have developed a hybrid classical-quantum model to classify [fallen angels](https://www.investopedia.com/terms/f/fallenangel.asp) from a credit scoring dataset. In simple terms, a fallen angel is a bond that used to be safe, but that is now risky (its quality decreased). However, the dataset used in the original project is private, so we will instead work, in this notebook, with an open-source dataset on company bankruptcy prediction. This task is similar to fallen angel classification in many ways:\n", + "- Modelization of a regime change (from healty to unhealthy)\n", + "- Time dependent\n", + "- Goal of detecting early warning signs\n", + "- Imbalanced dataset (bankruptcy and fallen angels are rarer than their counterpart)\n", + "- Similar feature space that relies on economic metrics\n", + "\n", + "The [specific dataset](https://www.kaggle.com/datasets/fedesoriano/company-bankruptcy-prediction) we use is a bankruptcy dataset from the Taiwan Economic Journal (1999-2009). A [notebook on prediction for this dataset](https://www.kaggle.com/code/ahmedtronic/company-bankruptcy-prediction) is also presented, which helped with the construction of this current notebook.\n", + "\n", + "# Bankruptcy Prediction Tutorial\n", + "(CA CIB and Quandela teams solution)\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "fb05fe05", + "metadata": {}, + "source": [ + "## 1. Imports\n", + "\n", + "All imports and handdling of random seeds." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "70094232", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 240, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "import merlin as ML\n", + "import numpy as np\n", + "import os\n", + "import pandas as pd\n", + "import random\n", + "import torch\n", + "from imblearn.over_sampling import RandomOverSampler\n", + "from scipy.optimize import minimize\n", + "from scipy.special import expit\n", + "from sklearn.base import BaseEstimator, ClassifierMixin\n", + "from sklearn.decomposition import PCA\n", + "from sklearn.ensemble import AdaBoostClassifier\n", + "from sklearn.metrics import auc, precision_recall_curve, roc_curve\n", + "from sklearn.model_selection import train_test_split\n", + "from sklearn.neighbors import KNeighborsClassifier\n", + "from sklearn.preprocessing import StandardScaler\n", + "from sklearn.tree import DecisionTreeClassifier\n", + "from tqdm.auto import tqdm\n", + "\n", + "# Deterministic setup\n", + "SEED = 42\n", + "os.environ[\"PYTHONHASHSEED\"] = str(SEED)\n", + "random.seed(SEED)\n", + "np.random.seed(SEED)\n", + "torch.manual_seed(SEED)" + ] + }, + { + "cell_type": "markdown", + "id": "2933467f", + "metadata": {}, + "source": [ + "## 2. Dataset\n", + "\n", + "We will fetch the bankruptcy prediction dataset from [Kaggle](https://www.kaggle.com), an online platform to share datasets, build and run machine learning models, and compete in data science challenges. To access the data, you need to have (or will need to create) an access token on Kaggle. Here are the simple steps to create your access token:\n", + "1. Go to [kaggle.com](kaggle.com)\n", + "2. Register or sign in if you already have an account\n", + "3. Once that is done, click on your profile image in the top right corner\n", + "4. Select account\n", + "5. Scroll to the API section and click on \"Create New Token\"\n", + "\n", + "This should trigger a download of a file called `kaggle.json` containing your username and API key.\n", + "\n", + "In this file, copy your API key and set it as a variable in the next cell\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "32f5ac94", + "metadata": {}, + "outputs": [], + "source": [ + "KAGGLE_API_TOKEN=\"KGAT_4accc1301911b126b14cbdc4ab59fb28\"" + ] + }, + { + "cell_type": "markdown", + "id": "6ad3fc8a", + "metadata": {}, + "source": [ + "Next we install kaggle." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "53dc9f27", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: kaggle in ./.venv/lib/python3.13/site-packages (2.0.0)\n", + "Requirement already satisfied: bleach in ./.venv/lib/python3.13/site-packages (from kaggle) (6.3.0)\n", + "Requirement already satisfied: kagglesdk<1.0,>=0.1.15 in ./.venv/lib/python3.13/site-packages (from kaggle) (0.1.16)\n", + "Requirement already satisfied: packaging in ./.venv/lib/python3.13/site-packages (from kaggle) (26.0)\n", + "Requirement already satisfied: protobuf in ./.venv/lib/python3.13/site-packages (from kaggle) (7.34.1)\n", + "Requirement already satisfied: python-dateutil in ./.venv/lib/python3.13/site-packages (from kaggle) (2.9.0.post0)\n", + "Requirement already satisfied: python-slugify in ./.venv/lib/python3.13/site-packages (from kaggle) (8.0.4)\n", + "Requirement already satisfied: requests in ./.venv/lib/python3.13/site-packages (from kaggle) (2.33.1)\n", + "Requirement already satisfied: tqdm in ./.venv/lib/python3.13/site-packages (from kaggle) (4.67.3)\n", + "Requirement already satisfied: urllib3>=1.15.1 in ./.venv/lib/python3.13/site-packages (from kaggle) (2.6.3)\n", + "Requirement already satisfied: webencodings in ./.venv/lib/python3.13/site-packages (from bleach->kaggle) (0.5.1)\n", + "Requirement already satisfied: six>=1.5 in ./.venv/lib/python3.13/site-packages (from python-dateutil->kaggle) (1.17.0)\n", + "Requirement already satisfied: text-unidecode>=1.3 in ./.venv/lib/python3.13/site-packages (from python-slugify->kaggle) (1.3)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in ./.venv/lib/python3.13/site-packages (from requests->kaggle) (3.4.6)\n", + "Requirement already satisfied: idna<4,>=2.5 in ./.venv/lib/python3.13/site-packages (from requests->kaggle) (3.11)\n", + "Requirement already satisfied: certifi>=2023.5.7 in ./.venv/lib/python3.13/site-packages (from requests->kaggle) (2026.2.25)\n", + "\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m25.1.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m26.0.1\u001b[0m\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n" + ] + } + ], + "source": [ + "!pip install kaggle" + ] + }, + { + "cell_type": "markdown", + "id": "6d6db79d", + "metadata": {}, + "source": [ + "We can now access the dataset and download it locally. This cell should download a zip file called `american-companies-bankruptcy-prediction-dataset.zip`." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "0934c170", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Dataset URL: https://www.kaggle.com/datasets/fedesoriano/company-bankruptcy-prediction\n", + "License(s): copyright-authors\n", + "Downloading company-bankruptcy-prediction.zip to /Users/philippeschoeb/Documents/fallen_angels_project/ensemble-quantum-classifier\n", + "100%|██████████████████████████████████████| 4.63M/4.63M [00:01<00:00, 4.73MB/s]\n", + "\n" + ] + } + ], + "source": [ + "!kaggle datasets download -d fedesoriano/company-bankruptcy-prediction" + ] + }, + { + "cell_type": "markdown", + "id": "37780378", + "metadata": {}, + "source": [ + "We unzip this file to obtain the dataset." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "47d367e9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Archive: company-bankruptcy-prediction.zip\n", + " inflating: data.csv \n" + ] + } + ], + "source": [ + "!unzip company-bankruptcy-prediction.zip" + ] + }, + { + "cell_type": "markdown", + "id": "25bb4b78", + "metadata": {}, + "source": [ + "We finally have our dataset locally. We now need to preprocess it before moving on to the model implementation." + ] + }, + { + "cell_type": "code", + "execution_count": 241, + "id": "75da5cdb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Bankrupt?ROA(C) before interest and depreciation before interestROA(A) before interest and % after taxROA(B) before interest and depreciation after taxOperating Gross MarginRealized Sales Gross MarginOperating Profit RatePre-tax net Interest RateAfter-tax net Interest RateNon-industry income and expenditure/revenue...Net Income to Total AssetsTotal assets to GNP priceNo-credit IntervalGross Profit to SalesNet Income to Stockholder's EquityLiability to EquityDegree of Financial Leverage (DFL)Interest Coverage Ratio (Interest expense to EBIT)Net Income FlagEquity to Liability
010.3705940.4243890.4057500.6014570.6014570.9989690.7968870.8088090.302646...0.7168450.0092190.6228790.6014530.8278900.2902020.0266010.56405010.016469
110.4642910.5382140.5167300.6102350.6102350.9989460.7973800.8093010.303556...0.7952970.0083230.6236520.6102370.8399690.2838460.2645770.57017510.020794
210.4260710.4990190.4722950.6014500.6013640.9988570.7964030.8083880.302035...0.7746700.0400030.6238410.6014490.8367740.2901890.0265550.56370610.016474
310.3998440.4512650.4577330.5835410.5835410.9987000.7969670.8089660.303350...0.7395550.0032520.6229290.5835380.8346970.2817210.0266970.56466310.023982
410.4650220.5384320.5222980.5987830.5987830.9989730.7973660.8093040.303475...0.7950160.0038780.6235210.5987820.8399730.2785140.0247520.57561710.035490
\n", + "

5 rows × 96 columns

\n", + "
" + ], + "text/plain": [ + " Bankrupt? ROA(C) before interest and depreciation before interest \\\n", + "0 1 0.370594 \n", + "1 1 0.464291 \n", + "2 1 0.426071 \n", + "3 1 0.399844 \n", + "4 1 0.465022 \n", + "\n", + " ROA(A) before interest and % after tax \\\n", + "0 0.424389 \n", + "1 0.538214 \n", + "2 0.499019 \n", + "3 0.451265 \n", + "4 0.538432 \n", + "\n", + " ROA(B) before interest and depreciation after tax \\\n", + "0 0.405750 \n", + "1 0.516730 \n", + "2 0.472295 \n", + "3 0.457733 \n", + "4 0.522298 \n", + "\n", + " Operating Gross Margin Realized Sales Gross Margin \\\n", + "0 0.601457 0.601457 \n", + "1 0.610235 0.610235 \n", + "2 0.601450 0.601364 \n", + "3 0.583541 0.583541 \n", + "4 0.598783 0.598783 \n", + "\n", + " Operating Profit Rate Pre-tax net Interest Rate \\\n", + "0 0.998969 0.796887 \n", + "1 0.998946 0.797380 \n", + "2 0.998857 0.796403 \n", + "3 0.998700 0.796967 \n", + "4 0.998973 0.797366 \n", + "\n", + " After-tax net Interest Rate Non-industry income and expenditure/revenue \\\n", + "0 0.808809 0.302646 \n", + "1 0.809301 0.303556 \n", + "2 0.808388 0.302035 \n", + "3 0.808966 0.303350 \n", + "4 0.809304 0.303475 \n", + "\n", + " ... Net Income to Total Assets Total assets to GNP price \\\n", + "0 ... 0.716845 0.009219 \n", + "1 ... 0.795297 0.008323 \n", + "2 ... 0.774670 0.040003 \n", + "3 ... 0.739555 0.003252 \n", + "4 ... 0.795016 0.003878 \n", + "\n", + " No-credit Interval Gross Profit to Sales \\\n", + "0 0.622879 0.601453 \n", + "1 0.623652 0.610237 \n", + "2 0.623841 0.601449 \n", + "3 0.622929 0.583538 \n", + "4 0.623521 0.598782 \n", + "\n", + " Net Income to Stockholder's Equity Liability to Equity \\\n", + "0 0.827890 0.290202 \n", + "1 0.839969 0.283846 \n", + "2 0.836774 0.290189 \n", + "3 0.834697 0.281721 \n", + "4 0.839973 0.278514 \n", + "\n", + " Degree of Financial Leverage (DFL) \\\n", + "0 0.026601 \n", + "1 0.264577 \n", + "2 0.026555 \n", + "3 0.026697 \n", + "4 0.024752 \n", + "\n", + " Interest Coverage Ratio (Interest expense to EBIT) Net Income Flag \\\n", + "0 0.564050 1 \n", + "1 0.570175 1 \n", + "2 0.563706 1 \n", + "3 0.564663 1 \n", + "4 0.575617 1 \n", + "\n", + " Equity to Liability \n", + "0 0.016469 \n", + "1 0.020794 \n", + "2 0.016474 \n", + "3 0.023982 \n", + "4 0.035490 \n", + "\n", + "[5 rows x 96 columns]" + ] + }, + "execution_count": 241, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df = pd.read_csv(\"./data.csv\")\n", + "df.head()" + ] + }, + { + "cell_type": "markdown", + "id": "d6002adb", + "metadata": {}, + "source": [ + "Exploration of the dataset:" + ] + }, + { + "cell_type": "code", + "execution_count": 242, + "id": "b6ca4bb0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "RangeIndex: 6819 entries, 0 to 6818\n", + "Data columns (total 96 columns):\n", + " # Column Non-Null Count Dtype \n", + "--- ------ -------------- ----- \n", + " 0 Bankrupt? 6819 non-null int64 \n", + " 1 ROA(C) before interest and depreciation before interest 6819 non-null float64\n", + " 2 ROA(A) before interest and % after tax 6819 non-null float64\n", + " 3 ROA(B) before interest and depreciation after tax 6819 non-null float64\n", + " 4 Operating Gross Margin 6819 non-null float64\n", + " 5 Realized Sales Gross Margin 6819 non-null float64\n", + " 6 Operating Profit Rate 6819 non-null float64\n", + " 7 Pre-tax net Interest Rate 6819 non-null float64\n", + " 8 After-tax net Interest Rate 6819 non-null float64\n", + " 9 Non-industry income and expenditure/revenue 6819 non-null float64\n", + " 10 Continuous interest rate (after tax) 6819 non-null float64\n", + " 11 Operating Expense Rate 6819 non-null float64\n", + " 12 Research and development expense rate 6819 non-null float64\n", + " 13 Cash flow rate 6819 non-null float64\n", + " 14 Interest-bearing debt interest rate 6819 non-null float64\n", + " 15 Tax rate (A) 6819 non-null float64\n", + " 16 Net Value Per Share (B) 6819 non-null float64\n", + " 17 Net Value Per Share (A) 6819 non-null float64\n", + " 18 Net Value Per Share (C) 6819 non-null float64\n", + " 19 Persistent EPS in the Last Four Seasons 6819 non-null float64\n", + " 20 Cash Flow Per Share 6819 non-null float64\n", + " 21 Revenue Per Share (Yuan ¥) 6819 non-null float64\n", + " 22 Operating Profit Per Share (Yuan ¥) 6819 non-null float64\n", + " 23 Per Share Net profit before tax (Yuan ¥) 6819 non-null float64\n", + " 24 Realized Sales Gross Profit Growth Rate 6819 non-null float64\n", + " 25 Operating Profit Growth Rate 6819 non-null float64\n", + " 26 After-tax Net Profit Growth Rate 6819 non-null float64\n", + " 27 Regular Net Profit Growth Rate 6819 non-null float64\n", + " 28 Continuous Net Profit Growth Rate 6819 non-null float64\n", + " 29 Total Asset Growth Rate 6819 non-null float64\n", + " 30 Net Value Growth Rate 6819 non-null float64\n", + " 31 Total Asset Return Growth Rate Ratio 6819 non-null float64\n", + " 32 Cash Reinvestment % 6819 non-null float64\n", + " 33 Current Ratio 6819 non-null float64\n", + " 34 Quick Ratio 6819 non-null float64\n", + " 35 Interest Expense Ratio 6819 non-null float64\n", + " 36 Total debt/Total net worth 6819 non-null float64\n", + " 37 Debt ratio % 6819 non-null float64\n", + " 38 Net worth/Assets 6819 non-null float64\n", + " 39 Long-term fund suitability ratio (A) 6819 non-null float64\n", + " 40 Borrowing dependency 6819 non-null float64\n", + " 41 Contingent liabilities/Net worth 6819 non-null float64\n", + " 42 Operating profit/Paid-in capital 6819 non-null float64\n", + " 43 Net profit before tax/Paid-in capital 6819 non-null float64\n", + " 44 Inventory and accounts receivable/Net value 6819 non-null float64\n", + " 45 Total Asset Turnover 6819 non-null float64\n", + " 46 Accounts Receivable Turnover 6819 non-null float64\n", + " 47 Average Collection Days 6819 non-null float64\n", + " 48 Inventory Turnover Rate (times) 6819 non-null float64\n", + " 49 Fixed Assets Turnover Frequency 6819 non-null float64\n", + " 50 Net Worth Turnover Rate (times) 6819 non-null float64\n", + " 51 Revenue per person 6819 non-null float64\n", + " 52 Operating profit per person 6819 non-null float64\n", + " 53 Allocation rate per person 6819 non-null float64\n", + " 54 Working Capital to Total Assets 6819 non-null float64\n", + " 55 Quick Assets/Total Assets 6819 non-null float64\n", + " 56 Current Assets/Total Assets 6819 non-null float64\n", + " 57 Cash/Total Assets 6819 non-null float64\n", + " 58 Quick Assets/Current Liability 6819 non-null float64\n", + " 59 Cash/Current Liability 6819 non-null float64\n", + " 60 Current Liability to Assets 6819 non-null float64\n", + " 61 Operating Funds to Liability 6819 non-null float64\n", + " 62 Inventory/Working Capital 6819 non-null float64\n", + " 63 Inventory/Current Liability 6819 non-null float64\n", + " 64 Current Liabilities/Liability 6819 non-null float64\n", + " 65 Working Capital/Equity 6819 non-null float64\n", + " 66 Current Liabilities/Equity 6819 non-null float64\n", + " 67 Long-term Liability to Current Assets 6819 non-null float64\n", + " 68 Retained Earnings to Total Assets 6819 non-null float64\n", + " 69 Total income/Total expense 6819 non-null float64\n", + " 70 Total expense/Assets 6819 non-null float64\n", + " 71 Current Asset Turnover Rate 6819 non-null float64\n", + " 72 Quick Asset Turnover Rate 6819 non-null float64\n", + " 73 Working capitcal Turnover Rate 6819 non-null float64\n", + " 74 Cash Turnover Rate 6819 non-null float64\n", + " 75 Cash Flow to Sales 6819 non-null float64\n", + " 76 Fixed Assets to Assets 6819 non-null float64\n", + " 77 Current Liability to Liability 6819 non-null float64\n", + " 78 Current Liability to Equity 6819 non-null float64\n", + " 79 Equity to Long-term Liability 6819 non-null float64\n", + " 80 Cash Flow to Total Assets 6819 non-null float64\n", + " 81 Cash Flow to Liability 6819 non-null float64\n", + " 82 CFO to Assets 6819 non-null float64\n", + " 83 Cash Flow to Equity 6819 non-null float64\n", + " 84 Current Liability to Current Assets 6819 non-null float64\n", + " 85 Liability-Assets Flag 6819 non-null int64 \n", + " 86 Net Income to Total Assets 6819 non-null float64\n", + " 87 Total assets to GNP price 6819 non-null float64\n", + " 88 No-credit Interval 6819 non-null float64\n", + " 89 Gross Profit to Sales 6819 non-null float64\n", + " 90 Net Income to Stockholder's Equity 6819 non-null float64\n", + " 91 Liability to Equity 6819 non-null float64\n", + " 92 Degree of Financial Leverage (DFL) 6819 non-null float64\n", + " 93 Interest Coverage Ratio (Interest expense to EBIT) 6819 non-null float64\n", + " 94 Net Income Flag 6819 non-null int64 \n", + " 95 Equity to Liability 6819 non-null float64\n", + "dtypes: float64(93), int64(3)\n", + "memory usage: 5.0 MB\n" + ] + } + ], + "source": [ + "df.info()" + ] + }, + { + "cell_type": "markdown", + "id": "f1b53b91", + "metadata": {}, + "source": [ + "We look at the number of missing data." + ] + }, + { + "cell_type": "code", + "execution_count": 243, + "id": "ddd375a5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "np.int64(0)" + ] + }, + "execution_count": 243, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "(df.isna().sum() > 0).sum()" + ] + }, + { + "cell_type": "markdown", + "id": "b5d6ae60", + "metadata": {}, + "source": [ + "So no data is missing.\n", + "\n", + "Next, we look at the count for each label (0: healthy, 1: bankrupt)." + ] + }, + { + "cell_type": "code", + "execution_count": 244, + "id": "5ac975f9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Bankrupt?\n", + "0 0.967737\n", + "1 0.032263\n", + "Name: proportion, dtype: float64\n" + ] + }, + { + "data": { + "image/png": "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3mz+W6fLDAQi4P9cunTJTMFXd955p4SEhPDdALe5xx9/XCpXriwjRoy45hgiHSeYkpJyy+uGrIUuM+D/aAAqWLAg3wcQQAYOHCh//PHHNc+XKFFCVq1adUvrhKyJFiIAAGA9bt0BAACsRyACAADWIxABAADrEYgAZAkPP/yw9O3bV25ngfAZAFsRiABc03PPPWcWrXO3fPnySZMmTcz93QLJ6tWrzedLSEjwd1UA+AmBCEC6NAAdPXrUbLp6d3BwsDRr1szv39rFixf9XQUAAYRABCBdYWFh5p5vuukCd//5n/9p7hB+8uRJT5nBgwfLfffdJ+Hh4XLPPffIq6++aha6dA0fPty89n/+53+kWLFiEhkZKW3atJGzZ89e830XLVpkyn300Uee1iq9DcPrr78uhQoVklKlSpnj2rLz1Vdf+bw2KipKZs+ebZ4fOHDAlJk7d6489NBDkiNHDilfvrx8++23nvP169c3z/PmzWvK6ntdy/fff2+6xvSzavnY2Fg5c+ZMmmX181arVk3y5Mljvr927drJiRMnPOf1de3bt5f8+fObGw2XLFlSZs2a5Ql8vXv3NmtjaZ2LFi0qo0aN8rxWW7O6dOliXhsRESENGjQwiwy69Ll+Ln1vPa+rMf/444/X/FyA7ViYEUCGnTt3Tj788EOzmJ12n7n0R1cDiAaVbdu2SdeuXc2xQYMGecrs3bvXBJeFCxeaIKC3RBk9erQJOKl9/PHH0r17d/Po3RqlLVT64758+fJMLdD39ttvS9myZWXcuHHSvHlz2b9/vxQuXFg+//xzadWqlcTHx5vrazi51m0e9J53zz//vEyYMMG0lumifte67YOGwr///e8mvGkQ0lvBaNhavHixOa/BcefOnbJkyRKzOvovv/wi58+fN+cmTpxobkz62WefSZEiRUwI1c319NNPm3rqazU4Tps2zdRt9+7dcscdd5igpSswT5kyxdyKRuvO6utAOhwAuIaOHTs62bNnd3LlymU2/ZNRsGBBZ+PGjel+Z2PHjnWqVq3q2R82bJgTHh7uJCUleY4NHDjQqV69ume/Xr16zosvvuhMmjTJiYyMdFavXn1VXaKjo53k5GSf41qnL7/80ueYvn7WrFnm+f79+02Z0aNHe85funTJufvuu5033njD7K9atcqUOXPmTLqfq23btk6tWrWued79DNeyYcMG8z5nz541+82bN3c6deqUZtkXXnjBadCggZOSknLVuX/9619ORESEc+HCBZ/j9957rzNt2jTzPE+ePM7s2bPT/TwA/j+6zACkS7tdtHVBt/Xr15suokcffVR+/fVXnxvk1qpVy3QL5c6dW1555RU5ePCgz3W0q0xbjVzaFeTdfaT+8Y9/SL9+/UwLUL169a6qS4UKFSQ0NDRT/8Rq1qzpea4tO9qV9fPPP9/QNdwWoozSGwZrS5S28Ohndz+T+9306NHDdOVpd6K2pq1du9bzWm1J0vfT1qU+ffrIP//5T5/uMG2t01Y6/b7dTVu8tCVOaWuUdqk1atTItMS5xwGkjUAEIF25cuUyXWS6PfDAAzJjxgxzb6j33nvPnI+LizPdM4899pjpDtu0aZO8/PLLVw16Tt1do2N1Ut9QU7t4dEzMzJkztfU6zbqkptdJXdZ7/NLNdK2utLTod6ThUbvgdBzUhg0b5MsvvzTn3O/GDZYaAo8cOWLC1oABA8y5KlWqmICjXW7ajaZdjE899ZQ5p2FIA6UbVN1Nu/y0a9Adt7Vjxw5p2rSprFy50nQVuu8P4GoEIgA3RANItmzZPGNdtFVDB/xqCNJWFx0Y7N16dCPuvfdeMybn66+/lhdeeCFDr9EApTPgXHv27JE///zzqnI//PCD5/nly5dN602ZMmXMvtvqdK2xQK6KFSuacUwZsWvXLjl16pRpnalTp46ULl36qhYxt/4dO3Y0Y7N0jNP06dM95zRMtW7d2oRPbYXTsU6nT582YenYsWOmpcsNq+6mY5FcOtBdw5a2LrVs2dIzYBvA1RhUDSBdycnJ5sdX6WDoSZMmmRYK7QpSGoC0C0i7frQFSWeH/ZWWCP0R11CkM7n0B19DQnp0dpXWSbvENNDojLe0Bg+/++67pq4agsaPH28+iw6OVhroNOhpC5e2dGlLkHZBpTZkyBDTbdezZ08z6FuDlNZVBzh7BxGl3WR6/p133jFlt2/fblp7vA0dOtTM/ipXrpz5nvX93ZCmA7+1FUhbzTSAzps3z3RJ6gw67QbTz6uz7saMGWO+M21h0u++RYsW5nraUqQtSsWLF5fffvvNtFDpwHEAaaOFCEC6li5dan6Ydatevbr5YdUfZw0s6vHHHzetEDpFXMfCaIuRzp76K3TcjHbzfPLJJ/LSSy+lW/att94yM8W0FUantWuXk06JT01banSrVKmSfPfdd2YGlxti7rrrLnnttdfMkgLR0dHms6RFg4e2tugYngcffNCEEm3N0uCWVsuPzrzT70q7q/S933zzTZ8yGpg0ZGnLU926dc1sMA2WSsccadjRVjcNmro8gM5O03Ck4U2f62s6depk6qXLGGjLnNZfr6OtUx06dDDntLtNu+f0MwJIW5COrL7GOQC47WmQ0FYSHdukgQ0A0kILEQAAsB6BCAAAWI8uMwAAYD1aiAAAgPUIRAAAwHoEIgAAYD0CEQAAsB6BCAAAWI9ABAAArEcgAgAA1iMQAQAA6xGIAACA2O7/AUBkixDuzkF9AAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "df['Bankrupt?'].value_counts().plot(kind= 'bar', color = ['blue', 'orange'])\n", + "plt.xlabel(\"Bankrupt classes\")\n", + "plt.ylabel(\"Frequency\")\n", + "plt.title(\"Class balance\")\n", + "print(df['Bankrupt?'].value_counts(normalize=True))" + ] + }, + { + "cell_type": "markdown", + "id": "6d204b8a", + "metadata": {}, + "source": [ + "### 2.1 Dataset Splits\n", + "\n", + "Now, we separate the dataset into three sets: the train, validation and the test sets.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 245, + "id": "c98ccaa6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Before splitting: (6819, 95), (6819,)\n", + "X_train shape: (4773, 95), y_train shape: (4773,)\n", + "X_val shape: (1023, 95), y_val shape: (1023,)\n", + "X_test shape: (1023, 95), y_test shape: (1023,)\n" + ] + } + ], + "source": [ + "target = \"Bankrupt?\"\n", + "X = df.drop(columns=[target])\n", + "y = df[target]\n", + "\n", + "print(f\"Before splitting: {X.shape}, {y.shape}\")\n", + "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)\n", + "\n", + "print(f\"X_train shape: {X_train.shape}, y_train shape: {y_train.shape}\")\n", + "\n", + "# We then split test set into validation and test sets\n", + "X_val, X_test, y_val, y_test = train_test_split(X_test, y_test, test_size=0.5, random_state=42)\n", + "print(f\"X_val shape: {X_val.shape}, y_val shape: {y_val.shape}\")\n", + "print(f\"X_test shape: {X_test.shape}, y_test shape: {y_test.shape}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "34844b06", + "metadata": {}, + "source": [ + "### 2.2 Oversampling\n", + "\n", + "We apply oversampling to reduce the effect of the dataset labels imbalancement during training.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 246, + "id": "f3d6abe2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "After resampling: (9262, 95), (9262,)\n", + "Class distribution after resampling:\n", + "Bankrupt?\n", + "0 0.5\n", + "1 0.5\n", + "Name: proportion, dtype: float64\n" + ] + } + ], + "source": [ + "ros = RandomOverSampler(random_state=42)\n", + "X_train, y_train = ros.fit_resample(X_train, y_train)\n", + "print(f\"After resampling: {X_train.shape}, {y_train.shape}\")\n", + "print(f\"Class distribution after resampling:\\n{pd.Series(y_train).value_counts(normalize=True)}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "e8b29d3f", + "metadata": {}, + "source": [ + "### 2.3 Standardization\n", + "\n", + "Next, we want to standardize our data as preprocessing. That is to induce mean = 0 and variance = 1 to every feature of the training set (and applying the same transformation to the validation and test sets).\n" + ] + }, + { + "cell_type": "code", + "execution_count": 247, + "id": "da43a372", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(array([ 1.96392723e-16, -6.87374532e-16, -7.85570893e-16, 2.92134176e-15,\n", + " -3.16683266e-15, -2.22491475e-14, -2.72494904e-15, -4.31757128e-15,\n", + " 4.43724809e-15, 1.51590633e-15, -1.53431815e-17, -9.20590891e-17,\n", + " -2.08667269e-15, 0.00000000e+00, -1.22745452e-16, 9.81963617e-17,\n", + " -4.41883628e-16, -2.45490904e-16, 0.00000000e+00, 8.10119984e-16,\n", + " -6.13727260e-18, -2.45490904e-16, 4.41883628e-16, -8.13188620e-17,\n", + " -1.00774016e-14, 3.35708811e-15, 4.90981808e-15, 2.12656496e-15,\n", + " 2.45490904e-17, -1.53431815e-18, -2.22476132e-15, 8.34669074e-16,\n", + " 1.22745452e-17, 0.00000000e+00, 4.07284753e-15, -1.22745452e-17,\n", + " -9.81963617e-17, 2.94589085e-16, 5.52354534e-17, -8.59218165e-17,\n", + " 9.20590891e-17, 1.71843633e-16, 1.96392723e-16, -3.68236356e-16,\n", + " 1.22745452e-17, -7.47980099e-18, -1.53431815e-18, -1.01264998e-16,\n", + " 4.90981808e-17, 4.29609082e-17, -6.13727260e-18, 6.62825441e-16,\n", + " -3.06863630e-18, -1.47294543e-16, -1.47294543e-16, 7.36472713e-17,\n", + " 0.00000000e+00, 1.53431815e-18, 0.00000000e+00, 4.90981808e-17,\n", + " 3.80510901e-16, -1.38779077e-15, 1.53431815e-18, 3.11274795e-16,\n", + " -2.23396723e-15, 2.94589085e-16, 1.07402271e-17, -1.47294543e-16,\n", + " -7.11923622e-16, -2.20941814e-16, 4.52623855e-17, 1.22745452e-16,\n", + " 5.90443982e-15, 4.90981808e-17, 2.33983518e-15, 4.90981808e-17,\n", + " 3.11274795e-16, 2.94589085e-16, 9.81963617e-17, 2.94589085e-16,\n", + " 1.32565088e-15, -3.19138175e-16, -1.77980906e-15, -9.81963617e-17,\n", + " 0.00000000e+00, 8.83767255e-16, 0.00000000e+00, -8.15336665e-15,\n", + " 1.93937814e-15, 1.04333634e-15, -9.20590891e-16, 1.31184202e-16,\n", + " 1.23926877e-14, 0.00000000e+00, -2.45490904e-17]),\n", + " array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", + " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", + " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", + " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", + " 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,\n", + " 1., 1., 1., 1., 1., 1., 1., 1., 0., 1.]))" + ] + }, + "execution_count": 247, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "scaler = StandardScaler()\n", + "X_train = scaler.fit_transform(X_train)\n", + "X_val = scaler.transform(X_val)\n", + "X_test = scaler.transform(X_test)\n", + "\n", + "X_train.mean(axis=0), X_train.std(axis=0)" + ] + }, + { + "cell_type": "markdown", + "id": "5cd9dc66", + "metadata": {}, + "source": [ + "### 2.4 Build The Dataset\n", + "\n", + "Now that our original features are preprocessed, we will use [Principle Component Analysis](https://en.wikipedia.org/wiki/Principal_component_analysis) to only consider 5 features instead of 18 to mimic the original fallen angel prediction framework which presented 5 features.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 248, + "id": "e7f3e558", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "((9262, 5), (1023, 5), (1023, 5))" + ] + }, + "execution_count": 248, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pca = PCA(n_components=5)\n", + "X_train_pca = pca.fit_transform(X_train)\n", + "X_val_pca = pca.transform(X_val)\n", + "X_test_pca = pca.transform(X_test)\n", + "\n", + "X_train_pca.shape, X_val_pca.shape, X_test_pca.shape" + ] + }, + { + "cell_type": "code", + "execution_count": 249, + "id": "4dd8d8bf", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0.16948571, 0.07214918, 0.0653974 , 0.05641544, 0.04374511])" + ] + }, + "execution_count": 249, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pca.explained_variance_ratio_" + ] + }, + { + "cell_type": "markdown", + "id": "8f93145e", + "metadata": {}, + "source": [ + "Since there are 95 features, it is normal to lose a big part of the original dataset variance by using PCA to reduce dimentionality to 5." + ] + }, + { + "cell_type": "markdown", + "id": "98263250", + "metadata": {}, + "source": [ + "### 2.5 Angle Encoding\n", + "\n", + "In the original implementation, quantum models use an angle-like preprocessing before before entering photonic circuits through angle encoding.\n", + "Each feature is transformed with a sigmoid rescaling into `[0, pi]`, using train-set statistics.\n", + "\n", + "We apply this preprocessing to the dataset now, for later use in the hybrid model.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 250, + "id": "f73461e9", + "metadata": {}, + "outputs": [], + "source": [ + "def transform_features_to_phases(x, means=None, stds=None):\n", + " \"\"\"Feature-to-phase transform: pi / (1 + exp(-(x-mean)/std)).\"\"\"\n", + " x = np.asarray(x, dtype=np.float32)\n", + "\n", + " if means is None:\n", + " means = x.mean(axis=0)\n", + " if stds is None:\n", + " stds = x.std(axis=0)\n", + "\n", + " stds = np.where(stds < 1e-12, 1.0, stds)\n", + " z = (x - means) / stds\n", + " #phases = np.pi / (1.0 + np.exp(-z))\n", + " phases = np.pi * expit(z) # more stable version\n", + " return phases, means, stds\n", + "\n", + "X_train_pca_angle, pca_angle_means, pca_angle_stds = transform_features_to_phases(X_train_pca)\n", + "X_val_pca_angle, _, _ = transform_features_to_phases(X_val_pca, pca_angle_means, pca_angle_stds)\n", + "X_test_pca_angle, _, _ = transform_features_to_phases(X_test_pca, pca_angle_means, pca_angle_stds)\n" + ] + }, + { + "cell_type": "markdown", + "id": "87857804", + "metadata": {}, + "source": [ + "## 3. Model Definitions\n", + "\n", + "We define two models:\n", + "1. **Classical AdaBoost** (baseline).\n", + "2. **Quantum-Enhanced AdaBoost** which comprises of several branches:\n", + " - The first branch send the input through the classical AdaBoost model\n", + " - The remaining branches send the input through k different Quantum Classifiers\n", + "Afterward, the final score is an optimized weighted sum of the obtained scores from every branch.\n", + "\n", + "The quantum classifiers are made of fixed (non-trainable) quantum circuits followed by a trainable linear readout.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 251, + "id": "e45d3008", + "metadata": {}, + "outputs": [], + "source": [ + "class ClassicalAdaBoost:\n", + " def __init__(self, n_estimators=50, max_depth=3, random_state=42):\n", + " base_tree = DecisionTreeClassifier(max_depth=max_depth, random_state=random_state)\n", + " self.model = AdaBoostClassifier(\n", + " estimator=base_tree,\n", + " n_estimators=n_estimators,\n", + " random_state=10,\n", + " )\n", + "\n", + " def fit(self, x_train, y_train):\n", + " self.model.fit(x_train, y_train)\n", + "\n", + " def predict(self, x):\n", + " return self.model.predict(x)\n", + "\n", + " def predict_proba(self, x):\n", + " return self.model.predict_proba(x)[:, 1]" + ] + }, + { + "cell_type": "markdown", + "id": "72cdc1bb", + "metadata": {}, + "source": [ + "Next we define the quantum classifier model class:" + ] + }, + { + "cell_type": "code", + "execution_count": 252, + "id": "e6aeb4cb", + "metadata": {}, + "outputs": [], + "source": [ + "class QuantumClassifier(torch.nn.Module):\n", + " \"\"\"\n", + " A simple quantum classifier model that consists of a trainable quantum layer followed by a trainable linear readout.\n", + " \"\"\"\n", + " def __init__(self, n_features=5):\n", + " super().__init__()\n", + " self.n_features = n_features\n", + " self.quantum_layer = self.create_quantum_layer()\n", + " self.readout = torch.nn.Linear(self.quantum_layer.output_size, 1)\n", + " self.readout\n", + "\n", + " def create_quantum_layer(self):\n", + " builder = ML.CircuitBuilder(n_modes=self.n_features)\n", + " # Start by placing an entangling layer\n", + " builder.add_entangling_layer(modes=[0, self.n_features - 1], trainable=True)\n", + " # Encode the data using angle encoding on all modes\n", + " builder.add_angle_encoding(modes=list(range(self.n_features)), name=\"data\")\n", + " # Add another entangling layer after the encoding\n", + " builder.add_entangling_layer(modes=[0, self.n_features - 1], trainable=True)\n", + "\n", + " # Define quantum layer\n", + " quantum_layer = ML.QuantumLayer(\n", + " builder=builder,\n", + " input_state=[1, 0, 1, 0, 0],\n", + " measurement_strategy=ML.MeasurementStrategy.probs(computation_space=ML.ComputationSpace.FOCK),\n", + " )\n", + " return quantum_layer\n", + "\n", + " def forward(self, x):\n", + " \"\"\"\n", + " It is assumed here that the input x is already \"angle transformed\"\n", + " \"\"\"\n", + " assert len(x.shape) == 2, \"Input must be a 2D tensor of shape (n_samples, n_features)\"\n", + " n_sample = x.shape[0]\n", + " n_features = x.shape[1]\n", + " assert n_features == self.n_features, f\"Expected {self.n_features} features, got {n_features}\"\n", + "\n", + " # Assume x is already in angle-encoded form\n", + " q_out = self.quantum_layer(x)\n", + " q_score = torch.sigmoid(self.readout(q_out))\n", + "\n", + " assert q_score.shape == (n_sample, 1), f\"Expected output shape (n_samples, 1), got {q_score.shape}\"\n", + " return q_score.squeeze()" + ] + }, + { + "cell_type": "code", + "execution_count": 253, + "id": "2b160dd9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Total parameters in QuantumClassifier: 56\n", + "Expected parameters (circuit params + readout weights + bias): 56\n" + ] + } + ], + "source": [ + "# Check that the number of parameters fits the expected number for the defined quantum layer and readout\n", + "qc = QuantumClassifier(n_features=5)\n", + "total_params = sum(p.numel() for p in qc.parameters())\n", + "print(f\"Total parameters in QuantumClassifier: {total_params}\")\n", + "\n", + "circuit_params = sum(p.numel() for p in qc.quantum_layer.parameters())\n", + "expected_params = circuit_params + qc.quantum_layer.output_size + 1 # circuit params +readout weights + bias\n", + "print(f\"Expected parameters (circuit params + readout weights + bias): {expected_params}\")\n", + "assert total_params == expected_params, f\"Expected {expected_params} parameters, but got {total_params}\"" + ] + }, + { + "cell_type": "markdown", + "id": "35c497ef", + "metadata": {}, + "source": [ + "Next we move on to the more general QuantumEnhancedAdaBoost model.\n", + "\n", + "It expects at initialization an already fitted classical AdaBoost model and already trained quantum_estimators. \n", + "\n", + "Some methods for optimization of the ensembling weights are then defined `precision_at_fixed_recall` and `optimize_nonnegative_weights`. These methods are used in the `fit` method which encapsulates all the training for this weight vector. Because the dataset we use here is highly unbalanced, the metric of interest is the precision at a fixed recall of 83%. We can interpet this as:\n", + "- The model correctly identifies 83% of the actual bankruptcy\n", + "- Measure precision: among the bankruptcy predictions, how many are correct?\n", + "\n", + "The weight vector (which combines outputs from every submodel) is optimized with regard to this metric." + ] + }, + { + "cell_type": "code", + "execution_count": 254, + "id": "9dc7be1b", + "metadata": {}, + "outputs": [], + "source": [ + "def precision_at_fixed_recall(y_true, y_scores, target_recall=0.83):\n", + " \"\"\"\n", + " Get maximum precision at recall >= target_recall.\n", + " If no recall value is above target_recall, return NaN.\n", + " \"\"\"\n", + " precision, recall, _ = precision_recall_curve(y_true, y_scores)\n", + "\n", + " mask = recall >= target_recall\n", + " if not np.any(mask):\n", + " return np.nan\n", + "\n", + " return float(np.max(precision[mask]))" + ] + }, + { + "cell_type": "markdown", + "id": "9ca131a8", + "metadata": {}, + "source": [ + "In the hybrid model (QuantumEnhancedAdaBoost), we enforce two constraints on the unifying weights:\n", + "1. The sum of the weigth vector must equal 1 to ensure a fusion of scores that does not scale.\n", + "2. Each weight value must be positive to ensure every submodel is used as intended for the final prediction.\n", + "3. The final optimized value of the weights must be superior to their initial value divised by the number of submodels. That is to ensure no submodel is completelly ignored while giving the complete hybrid model a margin that scales with the number of submodels to choose which submodel to consider with more importance." + ] + }, + { + "cell_type": "code", + "execution_count": 255, + "id": "67821e7f", + "metadata": {}, + "outputs": [], + "source": [ + "class QuantumEnhancedAdaBoost(BaseEstimator, ClassifierMixin):\n", + " def __init__(self, classical_model, quantum_estimators):\n", + " \"\"\"\n", + " Initializes the QuantumEnhancedAdaBoost model.\n", + "\n", + " Args:\n", + " classical_model: The fitted classical AdaBoost model.\n", + " quantum_estimators: A list of already trained quantum estimators to be used.\n", + " \"\"\"\n", + " self.classical_model = classical_model\n", + " self.quantum_estimators = quantum_estimators\n", + " self.n_quantum = len(quantum_estimators)\n", + " self.weights = np.ones(self.n_quantum + 1) / (self.n_quantum + 1) # Initialize weights equally\n", + "\n", + " def optimize_nonnegative_weights(self, scores, y_true, target_recall=0.83):\n", + " \"\"\"\n", + " Optimize non-negative ensemble weights to maximize precision at fixed recall,\n", + " while enforcing a minimum per-component contribution.\n", + "\n", + " scores shape: (n_components, n_samples)\n", + " \"\"\"\n", + " n_components = scores.shape[0]\n", + " init = self.weights.copy()\n", + "\n", + " # Constraint: each weight >= initial_value / n_components.\n", + " # With equal initialization, initial_value = 1 / n_components, so min_weight = 1 / n_components^2.\n", + " initial_value = float(init[0])\n", + " min_weight = initial_value / n_components\n", + "\n", + " # Keep a small safety margin from exact bounds for numerical stability.\n", + " eps = 1e-12\n", + " min_weight = max(0.0, min(min_weight, 1.0 / n_components - eps))\n", + "\n", + " def objective(w):\n", + " combined_scores = w @ scores\n", + " precision = precision_at_fixed_recall(y_true, combined_scores, target_recall=target_recall)\n", + " if np.isnan(precision):\n", + " return 1e6\n", + " return -precision\n", + "\n", + " # Feasible initialization inside simplex with lower-bounded components.\n", + " init = np.clip(init, min_weight, None)\n", + " init = init / init.sum()\n", + " if np.any(init < min_weight):\n", + " # If clipping + normalization drifts below bound, rebuild from slack allocation.\n", + " slack = 1.0 - n_components * min_weight\n", + " if slack <= 0:\n", + " init = np.ones(n_components) / n_components\n", + " else:\n", + " extra = np.maximum(self.weights - self.weights.min(), 0.0)\n", + " if extra.sum() == 0:\n", + " extra = np.ones(n_components)\n", + " extra = extra / extra.sum()\n", + " init = min_weight + slack * extra\n", + "\n", + " # COBYLA supports inequality constraints only.\n", + " # Enforce sum(w)=1 with two inequalities using a small tolerance.\n", + " eq_tol = 1e-9\n", + " constraints = [\n", + " {\"type\": \"ineq\", \"fun\": lambda w: w - min_weight},\n", + " {\"type\": \"ineq\", \"fun\": lambda w: np.sum(w) - (1.0 - eq_tol)},\n", + " {\"type\": \"ineq\", \"fun\": lambda w: (1.0 + eq_tol) - np.sum(w)},\n", + " ]\n", + "\n", + " result = minimize(\n", + " objective,\n", + " x0=init,\n", + " method=\"COBYLA\",\n", + " constraints=constraints,\n", + " options={\"maxiter\": 2000, \"tol\": 1e-9, \"catol\": 1e-9},\n", + " )\n", + "\n", + " if result.success:\n", + " weights = result.x\n", + " else:\n", + " weights = init\n", + "\n", + " # Final projection for robustness against tiny numerical violations.\n", + " weights = np.clip(weights, min_weight, None)\n", + " weights = weights / weights.sum()\n", + " self.weights = weights\n", + " return\n", + "\n", + " def fit(self, X, X_angle, y):\n", + " classical_score = self.classical_model.predict_proba(X)\n", + " quantum_scores = [quantum_estimator(torch.tensor(X_angle, dtype=torch.float32)).detach().numpy() for quantum_estimator in self.quantum_estimators]\n", + " scores = np.vstack([classical_score] + quantum_scores)\n", + " assert scores.shape == (self.n_quantum + 1, X.shape[0]), f\"Expected scores shape ({self.n_quantum + 1}, n_samples), got {scores.shape}\"\n", + " self.optimize_nonnegative_weights(scores, y)\n", + "\n", + " def predict(self, X, X_angle):\n", + " classical_score = self.classical_model.predict_proba(X)\n", + " quantum_scores = [quantum_estimator(torch.tensor(X_angle, dtype=torch.float32)).detach().numpy() for quantum_estimator in self.quantum_estimators]\n", + " scores = np.vstack([classical_score] + quantum_scores)\n", + " assert scores.shape == (self.n_quantum + 1, X.shape[0]), f\"Expected scores shape ({self.n_quantum + 1}, n_samples), got {scores.shape}\"\n", + " combined_scores = self.weights @ scores\n", + " return (combined_scores >= 0.5).astype(float)\n", + "\n", + " def predict_proba(self, X, X_angle):\n", + " classical_score = self.classical_model.predict_proba(X)\n", + " quantum_scores = [quantum_estimator(torch.tensor(X_angle, dtype=torch.float32)).detach().numpy() for quantum_estimator in self.quantum_estimators]\n", + " scores = np.vstack([classical_score] + quantum_scores)\n", + " assert scores.shape == (self.n_quantum + 1, X.shape[0]), f\"Expected scores shape ({self.n_quantum + 1}, n_samples), got {scores.shape}\"\n", + " combined_scores = self.weights @ scores\n", + "\n", + " return combined_scores\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "7ea8a2f1", + "metadata": {}, + "source": [ + "## 4. Baseline Models\n", + "\n", + "### 4.1 Neural Network\n", + "Let us also define a classical Neural Network baseline.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 256, + "id": "99648bb3", + "metadata": {}, + "outputs": [], + "source": [ + "class NeuralNetWork(torch.nn.Module):\n", + " def __init__(self, n_features=5, hidden_size=4):\n", + " super().__init__()\n", + " self.model = torch.nn.Sequential(\n", + " torch.nn.Linear(n_features, hidden_size),\n", + " torch.nn.ReLU(),\n", + " torch.nn.Linear(hidden_size, 1),\n", + " torch.nn.Sigmoid()\n", + " )\n", + "\n", + " def forward(self, x):\n", + " return self.model(x).squeeze()" + ] + }, + { + "cell_type": "markdown", + "id": "4f935561", + "metadata": {}, + "source": [ + "### 4.2 K-NN\n", + "\n", + "We define a k-nearest-neighbours classifier.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 257, + "id": "5b9ebe64", + "metadata": {}, + "outputs": [], + "source": [ + "knn_pca = KNeighborsClassifier(n_neighbors=5)\n" + ] + }, + { + "cell_type": "markdown", + "id": "0918b4f1", + "metadata": {}, + "source": [ + "## 5. Training\n", + "\n", + "The training will be done in several steps.\n", + "\n", + "1. Train the classical AdaBoost model (on the training set)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 258, + "id": "06ec18f5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Fitting classical AdaBoost model on PCA dataset...\n" + ] + } + ], + "source": [ + "adaboost_pca = ClassicalAdaBoost()\n", + "\n", + "print(\"Fitting classical AdaBoost model on PCA dataset...\")\n", + "adaboost_pca.fit(X_train_pca, y_train)" + ] + }, + { + "cell_type": "markdown", + "id": "1e6192d5", + "metadata": {}, + "source": [ + "We define a function to analyze performance of an sklearn model on the train set and a test set." + ] + }, + { + "cell_type": "code", + "execution_count": 259, + "id": "90d15534", + "metadata": {}, + "outputs": [], + "source": [ + "def sklearn_model_analysis(model, X_train, y_train, X_test, y_test):\n", + " train_preds = model.predict(X_train)\n", + " test_preds = model.predict(X_test)\n", + "\n", + " train_probas = model.predict_proba(X_train)\n", + " test_probas = model.predict_proba(X_test)\n", + "\n", + " # Handle binary classification case where predict_proba returns shape (n_samples, 2)\n", + " if train_probas.ndim > 1 and train_probas.shape[1] > 1:\n", + " train_probas = train_probas[:, 1]\n", + " if test_probas.ndim > 1 and test_probas.shape[1] > 1:\n", + " test_probas = test_probas[:, 1]\n", + "\n", + " train_precision = precision_at_fixed_recall(y_train, train_probas, target_recall=0.83)\n", + " test_precision = precision_at_fixed_recall(y_test, test_probas, target_recall=0.83)\n", + "\n", + " print(f\"MODEL OUTPUT ANALYSIS:#####################################\")\n", + " print(f\"Number of positive predictions (class 1.0) on train set: {(train_preds == 1.0).sum()} out of {len(train_preds)}\")\n", + " print(f\"Number of positive predictions (class 1.0) on test set: {(test_preds == 1.0).sum()} out of {len(test_preds)}\")\n", + " print(f\"\\nTRAIN METRICS:#####################################\")\n", + " print(f\"Accuracy: {(train_preds == y_train).mean():.4f}, Precision at 83% recall: {train_precision:.4f}\")\n", + " print(f\"\\nTEST METRICS:#####################################\")\n", + " print(f\"Accuracy: {(test_preds == y_test).mean():.4f}, Precision at 83% recall: {test_precision:.4f}\")\n", + "\n", + " only_zero = np.array([0.0] * len(y_test))\n", + " only_one = np.array([1.0] * len(y_test))\n", + " random_outputs = np.random.rand(len(y_test))\n", + " random_preds = (random_outputs >= 0.5).astype(float)\n", + "\n", + " accuracy_only_zero = np.mean(only_zero == y_test)\n", + " accuracy_only_one = np.mean(only_one == y_test)\n", + " accuracy_random = np.mean(random_preds == y_test)\n", + "\n", + " precision_random = precision_at_fixed_recall(y_test, random_outputs, target_recall=0.83)\n", + " print(f\"\\nTRIVIAL BASELINE TEST METRICS:#####################################\")\n", + " print(f\"Test accuracy (only zeros): {accuracy_only_zero:.4f}\")\n", + " print(f\"Test accuracy (only ones): {accuracy_only_one:.4f}\")\n", + " print(f\"Test accuracy (random): {accuracy_random:.4f}, Precision at 83% recall (random) on test: {precision_random:.4f}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "5e398b38", + "metadata": {}, + "source": [ + "### 5.1 AdaBoost Output And Performance Analysis (Train And Validation)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 260, + "id": "561f8b46", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MODEL OUTPUT ANALYSIS:#####################################\n", + "Number of positive predictions (class 1.0) on train set: 5010 out of 9262\n", + "Number of positive predictions (class 1.0) on test set: 133 out of 1023\n", + "\n", + "TRAIN METRICS:#####################################\n", + "Accuracy: 0.9496, Precision at 83% recall: 0.9706\n", + "\n", + "TEST METRICS:#####################################\n", + "Accuracy: 0.8847, Precision at 83% recall: 0.1162\n", + "\n", + "TRIVIAL BASELINE TEST METRICS:#####################################\n", + "Test accuracy (only zeros): 0.9619\n", + "Test accuracy (only ones): 0.0381\n", + "Test accuracy (random): 0.5024, Precision at 83% recall (random) on test: 0.0397\n" + ] + } + ], + "source": [ + "sklearn_model_analysis(adaboost_pca, X_train_pca, y_train, X_val_pca, y_val)" + ] + }, + { + "cell_type": "markdown", + "id": "ba46a0a9", + "metadata": {}, + "source": [ + "2. Train the quantum classifiers (also on the training set after angle transform)\n", + "\n", + "We start off by defining a function to train torch models." + ] + }, + { + "cell_type": "code", + "execution_count": 261, + "id": "e2e6f49a", + "metadata": {}, + "outputs": [], + "source": [ + "def train_torch_model(model, X_angle, y, n_epochs=50, lr=1e-2, batch_size=64):\n", + " optimizer = torch.optim.Adam(model.parameters(), lr=lr)\n", + " scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=25, gamma=0.1)\n", + " criterion = torch.nn.BCELoss()\n", + " \n", + " dataset = torch.utils.data.TensorDataset(torch.tensor(X_angle, dtype=torch.float32), torch.tensor(y, dtype=torch.float32))\n", + " dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True)\n", + "\n", + " model.train()\n", + "\n", + " train_losses = []\n", + " train_accuracies = []\n", + "\n", + " progress = tqdm(range(n_epochs), desc=\"Training\", leave=False)\n", + " for epoch in progress:\n", + " epoch_loss = 0.0\n", + " epoch_correct = 0\n", + " epoch_total = 0\n", + "\n", + " for batch_X, batch_y in dataloader:\n", + " optimizer.zero_grad()\n", + " outputs = model(batch_X)\n", + " loss = criterion(outputs, batch_y)\n", + " loss.backward()\n", + " optimizer.step()\n", + "\n", + " epoch_loss += loss.item() * batch_X.size(0)\n", + " preds = (outputs >= 0.5).float()\n", + " epoch_correct += (preds == batch_y).sum().item()\n", + " epoch_total += batch_y.size(0)\n", + "\n", + " scheduler.step()\n", + "\n", + " epoch_loss /= len(dataset)\n", + " epoch_acc = epoch_correct / epoch_total if epoch_total > 0 else 0.0\n", + "\n", + " train_losses.append(epoch_loss)\n", + " train_accuracies.append(epoch_acc)\n", + "\n", + " progress.set_postfix(loss=f\"{epoch_loss:.4f}\", acc=f\"{epoch_acc:.4f}\")\n", + " if epoch % 10 == 0 or epoch == n_epochs - 1:\n", + " print(f\"Epoch {epoch + 1:03d}/{n_epochs} | Train Loss: {epoch_loss:.4f} | Train Acc: {epoch_acc:.4f}\")\n", + "\n", + " plt.figure(figsize=(10, 4))\n", + "\n", + " plt.subplot(1, 2, 1)\n", + " plt.plot(train_losses, label=\"Train Loss\")\n", + " plt.xlabel(\"Epoch\")\n", + " plt.ylabel(\"Loss\")\n", + " plt.title(\"Training Loss\")\n", + " plt.legend()\n", + "\n", + " plt.subplot(1, 2, 2)\n", + " plt.plot(train_accuracies, label=\"Train Accuracy\")\n", + " plt.xlabel(\"Epoch\")\n", + " plt.ylabel(\"Accuracy\")\n", + " plt.title(\"Training Accuracy\")\n", + " plt.legend()\n", + "\n", + " plt.tight_layout()\n", + " plt.show()\n", + "\n", + " return {\"train_loss\": train_losses, \"train_accuracy\": train_accuracies}" + ] + }, + { + "cell_type": "code", + "execution_count": 262, + "id": "d71ededa", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Training quantum classifiers on PCA dataset...\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "bb177c2e4c004bf9b480b1b587d56ae4", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Training: 0%| | 0/50 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Trained quantum classifier 1/3 on PCA dataset.\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "2df0bc3d03984b788dd0002ac00a908f", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Training: 0%| | 0/50 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Trained quantum classifier 2/3 on PCA dataset.\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "4b7cf418a18d4d7aacdde0b9d736984c", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Training: 0%| | 0/50 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Trained quantum classifier 3/3 on PCA dataset.\n" + ] + } + ], + "source": [ + "# Choose number of quantum classifiers\n", + "n_quantum_classifiers = 3\n", + "# Initialize quantum classifiers\n", + "quantum_classifiers_pca = [QuantumClassifier() for _ in range(n_quantum_classifiers)]\n", + "\n", + "# Select datasets\n", + "X_train_pca_quantum = X_train_pca_angle\n", + "X_val_pca_quantum = X_val_pca_angle\n", + "X_test_pca_quantum = X_test_pca_angle\n", + "y_train = np.array(y_train, dtype=np.float32)\n", + "y_test = np.array(y_test, dtype=np.float32)\n", + "\n", + "# Train quantum classifiers on PCA dataset\n", + "print(\"Training quantum classifiers on PCA dataset...\")\n", + "for i, quantum_classifier_pca in enumerate(quantum_classifiers_pca):\n", + " train_torch_model(quantum_classifier_pca, X_train_pca_quantum, y_train)\n", + " print(f\"Trained quantum classifier {i+1}/{n_quantum_classifiers} on PCA dataset.\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "0a11ea83", + "metadata": {}, + "source": [ + "We define a function to print an analysis of a torch model's output and performance metrics." + ] + }, + { + "cell_type": "code", + "execution_count": 263, + "id": "98942be4", + "metadata": {}, + "outputs": [], + "source": [ + "def torch_model_analysis(torch_model, X_train, y_train, X_test, y_test):\n", + " torch_model.eval()\n", + " with torch.no_grad():\n", + " train_outputs = torch_model(torch.tensor(X_train, dtype=torch.float32)).numpy()\n", + " test_outputs = torch_model(torch.tensor(X_test, dtype=torch.float32)).numpy()\n", + "\n", + " print(\"MODEL OUTPUTS ANALYSIS: ###################################################\")\n", + " print(f\"Train outputs - min: {train_outputs.min():.4f}, max: {train_outputs.max():.4f}, mean: {train_outputs.mean():.4f}, std: {train_outputs.std():.4f}\")\n", + " print(f\"Number of unique output values on train set of length {len(train_outputs)}: {len(np.unique(train_outputs))}\")\n", + " if len(np.unique(train_outputs)) < 6:\n", + " print(f\"Warning: Low number of unique output values; output values all in {np.unique(train_outputs)}\")\n", + " print(f\"Test outputs - min: {test_outputs.min():.4f}, max: {test_outputs.max():.4f}, mean: {test_outputs.mean():.4f}, std: {test_outputs.std():.4f}\")\n", + " print(f\"Number of unique output values on test set of length {len(test_outputs)}: {len(np.unique(test_outputs))}\")\n", + " if len(np.unique(test_outputs)) < 6:\n", + " print(f\"Warning: Low number of unique output values; output values all in {np.unique(test_outputs)}\")\n", + "\n", + " train_predictions = (train_outputs >= 0.5).astype(float)\n", + " test_predictions = (test_outputs >= 0.5).astype(float)\n", + "\n", + " train_accuracy = np.mean(train_predictions == y_train)\n", + " test_accuracy = np.mean(test_predictions == y_test)\n", + "\n", + " train_precision = precision_at_fixed_recall(y_train, train_outputs, target_recall=0.83)\n", + " test_precision = precision_at_fixed_recall(y_test, test_outputs, target_recall=0.83)\n", + "\n", + " print(\"\\nMODEL PERFORMANCE ANALYSIS: ###################################################\")\n", + " print(f\"Training accuracy: {train_accuracy:.4f}, Precision at 83% recall: {train_precision:.4f}\")\n", + " print(f\"Test accuracy: {test_accuracy:.4f}, Precision at 83% recall: {test_precision:.4f}\")\n", + "\n", + " only_zero = np.array([0.0] * len(y_test))\n", + " only_one = np.array([1.0] * len(y_test))\n", + " random_outputs = np.random.rand(len(y_test))\n", + " random_preds = (random_outputs >= 0.5).astype(float)\n", + "\n", + " accuracy_only_zero = np.mean(only_zero == y_test)\n", + " accuracy_only_one = np.mean(only_one == y_test)\n", + " accuracy_random = np.mean(random_preds == y_test)\n", + "\n", + " precision_random = precision_at_fixed_recall(y_test, random_outputs, target_recall=0.83)\n", + " print(f\"\\nTRIVIAL PREDICTORS TEST PERFORMANCE: ###################################################\")\n", + " print(f\"Only zero predictor - Test accuracy: {accuracy_only_zero:.4f}\")\n", + " print(f\"Only one predictor - Test accuracy: {accuracy_only_one:.4f}\")\n", + " print(f\"Random predictor - Test accuracy: {accuracy_random:.4f}, Precision at 83% recall (on test): {precision_random:.4f}\")" + ] + }, + { + "cell_type": "markdown", + "id": "141eec37", + "metadata": {}, + "source": [ + "### 5.2 First Quantum Classifier Output And Performance Analysis (Train And Validation)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 264, + "id": "ec69580a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MODEL OUTPUTS ANALYSIS: ###################################################\n", + "Train outputs - min: 0.0061, max: 0.9917, mean: 0.5024, std: 0.3591\n", + "Number of unique output values on train set of length 9262: 4773\n", + "Test outputs - min: 0.0072, max: 0.9904, mean: 0.2637, std: 0.2732\n", + "Number of unique output values on test set of length 1023: 1023\n", + "\n", + "MODEL PERFORMANCE ANALYSIS: ###################################################\n", + "Training accuracy: 0.8538, Precision at 83% recall: 0.8705\n", + "Test accuracy: 0.8221, Precision at 83% recall: 0.2481\n", + "\n", + "TRIVIAL PREDICTORS TEST PERFORMANCE: ###################################################\n", + "Only zero predictor - Test accuracy: 0.9619\n", + "Only one predictor - Test accuracy: 0.0381\n", + "Random predictor - Test accuracy: 0.4966, Precision at 83% recall (on test): 0.0418\n" + ] + } + ], + "source": [ + "torch_model_analysis(quantum_classifiers_pca[0], X_train_pca_quantum, y_train, X_val_pca_quantum, y_val)" + ] + }, + { + "cell_type": "markdown", + "id": "c6d388fd", + "metadata": {}, + "source": [ + "### 5.3 Train The NN Baseline\n" + ] + }, + { + "cell_type": "code", + "execution_count": 265, + "id": "90d690f4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of parameters in NN: 29\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "abe7e996d0144a3bb5717d755aaf6957", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Training: 0%| | 0/50 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Training of NN baseline completed on PCA dataset.\n" + ] + } + ], + "source": [ + "train_pca_nn = X_train_pca\n", + "val_pca_nn = X_val_pca\n", + "test_pca_nn = X_test_pca\n", + "\n", + "nn_pca = NeuralNetWork(n_features=train_pca_nn.shape[1], hidden_size=4)\n", + "print(\"Number of parameters in NN:\", sum(p.numel() for p in nn_pca.parameters()))\n", + "train_torch_model(nn_pca, train_pca_nn, y_train)\n", + "print(\"Training of NN baseline completed on PCA dataset.\")" + ] + }, + { + "cell_type": "markdown", + "id": "38eec1ac", + "metadata": {}, + "source": [ + "### 5.4 Analysis Of NN Baseline Outputs And Performance Metrics\n" + ] + }, + { + "cell_type": "code", + "execution_count": 266, + "id": "254f8aa6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MODEL OUTPUTS ANALYSIS: ###################################################\n", + "Train outputs - min: 0.0000, max: 1.0000, mean: 0.4999, std: 0.3691\n", + "Number of unique output values on train set of length 9262: 4773\n", + "Test outputs - min: 0.0000, max: 1.0000, mean: 0.2602, std: 0.2874\n", + "Number of unique output values on test set of length 1023: 1023\n", + "\n", + "MODEL PERFORMANCE ANALYSIS: ###################################################\n", + "Training accuracy: 0.8540, Precision at 83% recall: 0.8579\n", + "Test accuracy: 0.8094, Precision at 83% recall: 0.2171\n", + "\n", + "TRIVIAL PREDICTORS TEST PERFORMANCE: ###################################################\n", + "Only zero predictor - Test accuracy: 0.9619\n", + "Only one predictor - Test accuracy: 0.0381\n", + "Random predictor - Test accuracy: 0.5054, Precision at 83% recall (on test): 0.0438\n" + ] + } + ], + "source": [ + "torch_model_analysis(nn_pca, train_pca_nn, y_train, val_pca_nn, y_val)" + ] + }, + { + "cell_type": "markdown", + "id": "072aee88", + "metadata": {}, + "source": [ + "### 5.5 Training Of KNN Performance And Analysis Of Performance Metrics\n" + ] + }, + { + "cell_type": "code", + "execution_count": 267, + "id": "75edf8aa", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "MODEL OUTPUT ANALYSIS:#####################################\n", + "Number of positive predictions (class 1.0) on train set: 4834 out of 9262\n", + "Number of positive predictions (class 1.0) on test set: 77 out of 1023\n", + "\n", + "TRAIN METRICS:#####################################\n", + "Accuracy: 0.9781, Precision at 83% recall: 1.0000\n", + "\n", + "TEST METRICS:#####################################\n", + "Accuracy: 0.9159, Precision at 83% recall: 0.0381\n", + "\n", + "TRIVIAL BASELINE TEST METRICS:#####################################\n", + "Test accuracy (only zeros): 0.9619\n", + "Test accuracy (only ones): 0.0381\n", + "Test accuracy (random): 0.4917, Precision at 83% recall (random) on test: 0.0389\n" + ] + } + ], + "source": [ + "# Training KNN on PCA dataset\n", + "knn_pca.fit(X_train_pca, y_train)\n", + "\n", + "# Analyze KNN predictions on PCA dataset\n", + "sklearn_model_analysis(knn_pca, X_train_pca, y_train, X_test_pca, y_test)" + ] + }, + { + "cell_type": "markdown", + "id": "57f1ea94", + "metadata": {}, + "source": [ + "### 5.6 Training of the hybrid model\n", + "\n", + "Now that all subsidiary models have been trained, we can optimize the complete QuantumEnhancedAdaBoost\n", + "\n", + "3. Train complete QuantumEnhancedAdaBoost model (on training set)" + ] + }, + { + "cell_type": "code", + "execution_count": 268, + "id": "3d4ffd79", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Precision at 83% recall for QuantumEnhancedAdaBoost on PCA validation dataset before weight optimization: 0.2973\n", + "Optimizing ensemble weights for QuantumEnhancedAdaBoost on PCA validation dataset...\n", + "Initial ensemble weights for QuantumEnhancedAdaBoost on PCA validation dataset: [0.25 0.25 0.25 0.25]\n", + "Optimal ensemble weights for QuantumEnhancedAdaBoost on PCA validation dataset: [0.0625 0.06320711 0.81179289 0.0625 ]\n", + "Precision at 83% recall for QuantumEnhancedAdaBoost on PCA validation dataset after weight optimization: 0.3084\n", + "\n" + ] + } + ], + "source": [ + "quantum_enhanced_adaboost_pca = QuantumEnhancedAdaBoost(classical_model=adaboost_pca, quantum_estimators=quantum_classifiers_pca)\n", + "\n", + "precision_before = precision_at_fixed_recall(y_val, quantum_enhanced_adaboost_pca.predict_proba(X_val_pca, X_val_pca_angle), target_recall=0.83)\n", + "print(f\"Precision at 83% recall for QuantumEnhancedAdaBoost on PCA validation dataset before weight optimization: {precision_before:.4f}\")\n", + "\n", + "print(\"Optimizing ensemble weights for QuantumEnhancedAdaBoost on PCA validation dataset...\")\n", + "print(f\"Initial ensemble weights for QuantumEnhancedAdaBoost on PCA validation dataset: {quantum_enhanced_adaboost_pca.weights}\")\n", + "quantum_enhanced_adaboost_pca.fit(X_val_pca, X_val_pca_angle, y_val)\n", + "print(f\"Optimal ensemble weights for QuantumEnhancedAdaBoost on PCA validation dataset: {quantum_enhanced_adaboost_pca.weights}\")\n", + "\n", + "precision_after = precision_at_fixed_recall(y_val, quantum_enhanced_adaboost_pca.predict_proba(X_val_pca, X_val_pca_angle), target_recall=0.83)\n", + "print(f\"Precision at 83% recall for QuantumEnhancedAdaBoost on PCA validation dataset after weight optimization: {precision_after:.4f}\\n\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "f480e27a", + "metadata": {}, + "source": [ + "## 6. Evaluation (Test Set)\n", + "\n", + "For evaluation, we will consider two metrics: accuracy and precision at 83% recall on the test set.\n", + "\n", + "1. Evaluation of classical baseline\n" + ] + }, + { + "cell_type": "code", + "execution_count": 269, + "id": "bd7e12fc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
PCA
Accuracy0.891496
Precision @ Recall=0.830.157895
\n", + "
" + ], + "text/plain": [ + " PCA\n", + "Accuracy 0.891496\n", + "Precision @ Recall=0.83 0.157895" + ] + }, + "execution_count": 269, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "test_predictions_pca_adaboost = adaboost_pca.predict(X_test_pca)\n", + "\n", + "adaboost_accuracy_pca = (test_predictions_pca_adaboost == y_test).mean()\n", + "\n", + "adaboost_precision_pca = precision_at_fixed_recall(y_test, adaboost_pca.predict_proba(X_test_pca), target_recall=0.83)\n", + "\n", + "results = pd.DataFrame({\n", + " \"PCA\": [adaboost_accuracy_pca, adaboost_precision_pca],\n", + "}, index=[\"Accuracy\", \"Precision @ Recall=0.83\"])\n", + "\n", + "results" + ] + }, + { + "cell_type": "code", + "execution_count": 270, + "id": "0d6e371b", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AUC: 0.9028\n" + ] + } + ], + "source": [ + "probas = adaboost_pca.predict_proba(X_test_pca)\n", + "fpr, tpr, thresholds = roc_curve(y_test, probas)\n", + "plt.figure(figsize=(6, 4))\n", + "plt.plot(fpr, tpr, marker='.')\n", + "plt.plot([0, 1], [0, 1], linestyle='--', color='gray') # Add diagonal line for reference\n", + "plt.xlabel('False Positive Rate')\n", + "plt.ylabel('True Positive Rate')\n", + "plt.title('ROC Curve for AdaBoost on PCA Dataset')\n", + "plt.show()\n", + "\n", + "print(f\"AUC: {auc(fpr, tpr):.4f}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "a51573a2", + "metadata": {}, + "source": [ + "### 6.1 Evaluation Of The QuantumEnhancedAdaBoost\n" + ] + }, + { + "cell_type": "code", + "execution_count": 271, + "id": "b2b11e77", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
PCA
Accuracy0.847507
Precision @ Recall=0.830.186441
\n", + "
" + ], + "text/plain": [ + " PCA\n", + "Accuracy 0.847507\n", + "Precision @ Recall=0.83 0.186441" + ] + }, + "execution_count": 271, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "test_predictions_pca_hybrid = quantum_enhanced_adaboost_pca.predict(X_test_pca, X_test_pca_angle)\n", + "\n", + "hybrid_accuracy_pca = (test_predictions_pca_hybrid == y_test).mean()\n", + "\n", + "hybrid_precision_pca = precision_at_fixed_recall(y_test, quantum_enhanced_adaboost_pca.predict_proba(X_test_pca, X_test_pca_angle), target_recall=0.83)\n", + "\n", + "results_hybrid = pd.DataFrame({\n", + " \"PCA\": [hybrid_accuracy_pca, hybrid_precision_pca],\n", + "}, index=[\"Accuracy\", \"Precision @ Recall=0.83\"])\n", + "\n", + "results_hybrid" + ] + }, + { + "cell_type": "code", + "execution_count": 272, + "id": "876a16be", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AUC: 0.9209\n" + ] + } + ], + "source": [ + "probas = quantum_enhanced_adaboost_pca.predict_proba(X_test_pca, X_test_pca_angle)\n", + "fpr, tpr, thresholds = roc_curve(y_test, probas)\n", + "plt.figure(figsize=(6, 4))\n", + "plt.plot(fpr, tpr, marker='.')\n", + "plt.plot([0, 1], [0, 1], linestyle='--', color='gray') # Add diagonal line for reference\n", + "plt.xlabel('False Positive Rate')\n", + "plt.ylabel('True Positive Rate')\n", + "plt.title('ROC Curve for QuantumEnhancedAdaBoost on PCA Dataset')\n", + "plt.show()\n", + "\n", + "print(f\"AUC: {auc(fpr, tpr):.4f}\")" + ] + }, + { + "cell_type": "markdown", + "id": "8103ac85", + "metadata": {}, + "source": [ + "### 6.2 Final Look At Our Models\n" + ] + }, + { + "cell_type": "code", + "execution_count": 273, + "id": "7c6e281b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of samples predicted as positive (bankrupt) by the PCA AdaBoost model: 126\n", + "Number of samples predicted as positive (bankrupt) by the PCA hybrid model: 183\n", + "Total number of positive samples in test set: 39\n", + "Total number of samples in test set: 1023\n" + ] + } + ], + "source": [ + "num_positives_pca = (test_predictions_pca_adaboost == 1.0).sum()\n", + "print(f\"Number of samples predicted as positive (bankrupt) by the PCA AdaBoost model: {num_positives_pca}\")\n", + "num_positives_pca = (test_predictions_pca_hybrid == 1.0).sum()\n", + "print(f\"Number of samples predicted as positive (bankrupt) by the PCA hybrid model: {num_positives_pca}\")\n", + "print(f\"Total number of positive samples in test set: {(y_test == 1.0).sum()}\")\n", + "\n", + "total_samples = len(y_test)\n", + "print(f\"Total number of samples in test set: {total_samples}\")" + ] + }, + { + "cell_type": "markdown", + "id": "86dc8f98", + "metadata": {}, + "source": [ + "## 7 Conclusion\n", + "\n", + "We have seen, through the hybrid model's weights, that this model indeed gives more importance to submodels that reach high precision at 83% recall on the validation set. We see now how this reflects on the test set; a lower accuracy and a tendency to over-predict the positive class, but a higher precision at 83% recall and a higher Area Under the Curve (AUC). \n", + "\n", + "We can now justify the use of a QuantumEnhancedAdaBoost optimized on the precision for a given recall based on which metric is most important for a given task. We have also seen how oversampling can help in imbalanced dataset settings." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "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.13.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 3cd8a78f35b44eb7d365f4bd0db15120436a4162 Mon Sep 17 00:00:00 2001 From: philippeschoeb Date: Thu, 9 Apr 2026 12:44:30 -0400 Subject: [PATCH 2/2] Fixed dataset fetching doc --- .../CACIB_project_Bankruptcy_Prediction_tutorial.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/QML_with_MerLin/CACIB_project_Bankruptcy_Prediction_tutorial.ipynb b/QML_with_MerLin/CACIB_project_Bankruptcy_Prediction_tutorial.ipynb index 5ea5bf9..8a1c464 100644 --- a/QML_with_MerLin/CACIB_project_Bankruptcy_Prediction_tutorial.ipynb +++ b/QML_with_MerLin/CACIB_project_Bankruptcy_Prediction_tutorial.ipynb @@ -98,12 +98,12 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "32f5ac94", "metadata": {}, "outputs": [], "source": [ - "KAGGLE_API_TOKEN=\"KGAT_4accc1301911b126b14cbdc4ab59fb28\"" + "KAGGLE_API_TOKEN=\"{YOUR_KAGGLE_API_TOKEN}\"" ] }, {