{ "cells": [ { "cell_type": "markdown", "id": "7fb27b941602401d91542211134fc71a", "metadata": {}, "source": [ "# Quantum pipelines\n", "\n", "- `QuantumPipeline` composes stages; the same `Trainer` fits and predicts it.\n", "- `fit_mode=\"frozen_backbone\"`: a fixed 4-class model feeds a trainable head.\n", "- `fit_mode=\"joint\"`: dense, circuit, dense trained end to end as one model.\n", "- Pennylane backend throughout." ] }, { "cell_type": "markdown", "id": "acae54e37e7d407bbb7b55eff062a284", "metadata": {}, "source": [ "### 1. Data\n", "\n", "- `make_classification`, 200 samples, 4 features, 2 classes.\n", "- `normalize=\"minmax\"`, fit on train only.\n", "- A pipeline prescales each stage's input itself, so the DataModule only normalizes." ] }, { "cell_type": "code", "id": "9a63283cbaf04dbcab1f6479b197f3a8", "metadata": { "ExecuteTime": { "end_time": "2026-09-19T12:18:04.678989626Z", "start_time": "2026-09-19T12:18:04.561371802Z" } }, "source": [ "from sklearn.datasets import make_classification\n", "from sklearn.metrics import accuracy_score\n", "\n", "import pyqit\n", "from pyqit import DataModule, Trainer\n", "from pyqit.core import PipelineStage, QuantumPipeline\n", "from pyqit.models import VQCClassifier\n", "\n", "pyqit.set_backend(\"pennylane\")\n", "pyqit.set_seed(42)\n", "\n", "X, y = make_classification(\n", " n_samples=200, n_features=4, n_informative=4, n_redundant=0, random_state=42\n", ")\n", "dm = DataModule(\n", " X, y, normalize=\"minmax\", batch_size=16, split=(0.7, 0.15, 0.15), seed=42\n", ")" ], "outputs": [], "execution_count": 7 }, { "cell_type": "markdown", "id": "8dd0d8092fe74a7c96281538738b07e2", "metadata": {}, "source": [ "### 2. Frozen backbone, trainable head\n", "\n", "- Stage 1: 4-class `VQCClassifier`, `trainable=False`.\n", "- Stage 2: 2-class `VQCClassifier` head.\n", "- `mode=\"sequential\"` feeds each stage's output to the next; `fit_mode=\"frozen_backbone\"` trains only the head.\n", "- `fit` returns one `TrainingHistory` per trained stage." ] }, { "cell_type": "code", "id": "72eea5119410473aa328ad9291626812", "metadata": { "ExecuteTime": { "end_time": "2026-09-19T12:18:06.118730013Z", "start_time": "2026-09-19T12:18:04.779323666Z" } }, "source": [ "backbone = PipelineStage(\n", " VQCClassifier(n_qubits=4, n_layers=4, n_classes=4),\n", " name=\"feature_extractor\",\n", " trainable=False,\n", ")\n", "head = PipelineStage(VQCClassifier(n_qubits=4, n_layers=1), name=\"classifier\")\n", "\n", "pipeline = QuantumPipeline(\n", " [backbone, head], mode=\"sequential\", fit_mode=\"frozen_backbone\"\n", ")\n", "trainer = Trainer(max_epochs=15, learning_rate=0.2)\n", "histories = trainer.fit(pipeline, datamodule=dm)\n", "\n", "preds = trainer.predict(pipeline, datamodule=dm, return_format=\"numpy\")\n", "acc = accuracy_score(dm.y_test, preds)\n", "print(f\"Frozen backbone test accuracy: {acc * 100:.2f}%\")" ], "outputs": [ { "data": { "text/plain": [ "\u001B[1;36m[\u001B[0m\u001B[1;36mPipeline\u001B[0m\u001B[1;36m]\u001B[0m QuantumPipeline | \u001B[33mmode\u001B[0m=\u001B[35msequential\u001B[0m | \u001B[33mstages\u001B[0m=\u001B[1;36m2\u001B[0m | \u001B[33mfit_mode\u001B[0m=\u001B[35mfrozen_backbone\u001B[0m\n" ], "text/html": [ "
[Pipeline] QuantumPipeline | mode=sequential | stages=2 | fit_mode=frozen_backbone\n", "\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "\u001B[1;36m \u001B[0m\u001B[1;36m#\u001B[0m\u001B[1;36m \u001B[0m\u001B[1;36m \u001B[0m\u001B[1;36mStage \u001B[0m\u001B[1;36m \u001B[0m\u001B[1;36m \u001B[0m\u001B[1;36mModel \u001B[0m\u001B[1;36m \u001B[0m\u001B[1;36m \u001B[0m\u001B[1;36mQubits\u001B[0m\u001B[1;36m \u001B[0m\u001B[1;36m \u001B[0m\u001B[1;36mParams\u001B[0m\u001B[1;36m \u001B[0m\u001B[1;36m \u001B[0m\u001B[1;36mStatus \u001B[0m\u001B[1;36m \u001B[0m\n", " 1 \u001B[1m \u001B[0m\u001B[1mfeature_extractor\u001B[0m\u001B[1m \u001B[0m VQCClassifier 4 48 frozen \n", " 2 \u001B[1m \u001B[0m\u001B[1mclassifier \u001B[0m\u001B[1m \u001B[0m VQCClassifier 4 12 trainable \n" ], "text/html": [ "
# Stage Model Qubits Params Status \n", " 1 feature_extractor VQCClassifier 4 48 frozen \n", " 2 classifier VQCClassifier 4 12 trainable \n", "\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "\n" ], "text/html": [ "
\n",
"\n"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/plain": [
"\u001B[1;36m[\u001B[0m\u001B[1;36m2\u001B[0m\u001B[1;36m/\u001B[0m\u001B[1;36m2\u001B[0m\u001B[1;36m]\u001B[0m\u001B[1;36m Fitting stage \u001B[0m\u001B[1;36m'classifier'\u001B[0m\n"
],
"text/html": [
"[2/2] Fitting stage 'classifier'\n", "\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "Output()" ], "application/vnd.jupyter.widget-view+json": { "version_major": 2, "version_minor": 0, "model_id": "81d1bf1f773a4012a6de70abbc395f10" } }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [], "text/html": [ "\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "\u001B[1;32m[\u001B[0m\u001B[1;32mTrainer\u001B[0m\u001B[1;32m]\u001B[0m Training complete.\n" ], "text/html": [ "
[Trainer] Training complete.\n",
"\n"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Frozen backbone test accuracy: 60.00%\n"
]
}
],
"execution_count": 8
},
{
"cell_type": "markdown",
"id": "8edb47106e1a46a883d545849b8ab81b",
"metadata": {},
"source": [
"### 3. Joint mode\n",
"\n",
"- `DenseLayer` and `QuantumLayer` emit features, `DenseClassifier` is the head.\n",
"- `fit_mode=\"joint\"` hands the pipeline to the loop as one model, so the loss at the head trains every stage.\n",
"- `weights` is one flat dict, keyed `[Pipeline] QuantumPipeline | mode=sequential | stages=3 | fit_mode=joint\n", "\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "\u001B[1;36m \u001B[0m\u001B[1;36m#\u001B[0m\u001B[1;36m \u001B[0m\u001B[1;36m \u001B[0m\u001B[1;36mStage \u001B[0m\u001B[1;36m \u001B[0m\u001B[1;36m \u001B[0m\u001B[1;36mModel \u001B[0m\u001B[1;36m \u001B[0m\u001B[1;36m \u001B[0m\u001B[1;36mQubits\u001B[0m\u001B[1;36m \u001B[0m\u001B[1;36m \u001B[0m\u001B[1;36mParams\u001B[0m\u001B[1;36m \u001B[0m\u001B[1;36m \u001B[0m\u001B[1;36mStatus \u001B[0m\u001B[1;36m \u001B[0m\n", " 1 \u001B[1m \u001B[0m\u001B[1mpre \u001B[0m\u001B[1m \u001B[0m DenseLayer N/A 20 trainable \n", " 2 \u001B[1m \u001B[0m\u001B[1mquantum\u001B[0m\u001B[1m \u001B[0m QuantumLayer 4 24 trainable \n", " 3 \u001B[1m \u001B[0m\u001B[1mhead \u001B[0m\u001B[1m \u001B[0m DenseClassifier N/A 5 trainable \n" ], "text/html": [ "
# Stage Model Qubits Params Status \n", " 1 pre DenseLayer N/A 20 trainable \n", " 2 quantum QuantumLayer 4 24 trainable \n", " 3 head DenseClassifier N/A 5 trainable \n", "\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "\n" ], "text/html": [ "
\n",
"\n"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/plain": [],
"text/html": [
"\n"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/plain": [
"Output()"
],
"application/vnd.jupyter.widget-view+json": {
"version_major": 2,
"version_minor": 0,
"model_id": "505b486423124e4f9ac0c1df3042c093"
}
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"\u001B[3m BP Diagnostic Result : \u001B[0m\u001B[1;3;31mBARREN PLATEAU\u001B[0m\u001B[3m \u001B[0m\n",
"┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓\n",
"┃\u001B[1;36m \u001B[0m\u001B[1;36mMetric / Layer \u001B[0m\u001B[1;36m \u001B[0m┃\u001B[1;36m \u001B[0m\u001B[1;36m Value\u001B[0m\u001B[1;36m \u001B[0m┃\u001B[1;36m \u001B[0m\u001B[1;36m Status\u001B[0m\u001B[1;36m \u001B[0m┃\n",
"┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩\n",
"│\u001B[1m \u001B[0m\u001B[1mQubits \u001B[0m\u001B[1m \u001B[0m│ 4 │ │\n",
"│\u001B[1m \u001B[0m\u001B[1mSamples \u001B[0m\u001B[1m \u001B[0m│ 200 │ │\n",
"│\u001B[1m \u001B[0m\u001B[1mCircuit Executions \u001B[0m\u001B[1m \u001B[0m│ 200 │ │\n",
"│\u001B[1m \u001B[0m\u001B[1mExpected Variance \u001B[0m\u001B[1m \u001B[0m│ 5.21e-03 │ \u001B[2mBaseline\u001B[0m │\n",
"│\u001B[1m \u001B[0m\u001B[1mQuantum Variance \u001B[0m\u001B[1m \u001B[0m│ \u001B[31m7.39e-04\u001B[0m │ \u001B[1;31mBARREN PLATEAU\u001B[0m │\n",
"│\u001B[1m \u001B[0m\u001B[1mClassical Variance \u001B[0m\u001B[1m \u001B[0m│ 7.44e-03 │ \u001B[2mN/A\u001B[0m │\n",
"├─────────────────────────────────────┼──────────┼────────────────┤\n",
"│\u001B[1m \u001B[0m\u001B[1mLayer: quantum.main_circuit.weights\u001B[0m\u001B[1m \u001B[0m│ \u001B[31m0.142x\u001B[0m │ \u001B[1;31m← plateau\u001B[0m │\n",
"│\u001B[1m \u001B[0m\u001B[1mLayer: pre.dense.weight \u001B[0m\u001B[1m \u001B[0m│ \u001B[31m0.810x\u001B[0m │ \u001B[1;31m← plateau\u001B[0m │\n",
"│\u001B[1m \u001B[0m\u001B[1mLayer: pre.dense.bias \u001B[0m\u001B[1m \u001B[0m│ \u001B[32m2.781x\u001B[0m │ \u001B[32mHealthy\u001B[0m │\n",
"│\u001B[1m \u001B[0m\u001B[1mLayer: head.dense.weight \u001B[0m\u001B[1m \u001B[0m│ \u001B[32m1.997x\u001B[0m │ \u001B[32mHealthy\u001B[0m │\n",
"│\u001B[1m \u001B[0m\u001B[1mLayer: head.dense.bias \u001B[0m\u001B[1m \u001B[0m│ \u001B[31m0.131x\u001B[0m │ \u001B[1;31m← plateau\u001B[0m │\n",
"└─────────────────────────────────────┴──────────┴────────────────┘\n"
]
},
{
"data": {
"text/plain": [],
"text/html": [
"\n"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/plain": [
"\u001B[1;32m[\u001B[0m\u001B[1;32mTrainer\u001B[0m\u001B[1;32m]\u001B[0m Training complete.\n"
],
"text/html": [
"[Trainer] Training complete.\n",
"\n"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"['pre.dense.weight', 'pre.dense.bias', 'quantum.main_circuit.weights', 'head.dense.weight', 'head.dense.bias']\n"
]
}
],
"execution_count": 9
},
{
"cell_type": "code",
"id": "8763a12b2bbd4a93a75aff182afb95dc",
"metadata": {
"ExecuteTime": {
"end_time": "2026-09-19T12:18:13.789118811Z",
"start_time": "2026-09-19T12:18:13.758169238Z"
}
},
"source": [
"preds_new = trainer_new.predict(hybrid, datamodule=dm_new, return_format=\"numpy\")\n",
"acc_new = accuracy_score(dm_new.y_test, preds_new)\n",
"print(f\"Joint pipeline test accuracy: {acc_new * 100:.2f}%\")"
],
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Joint pipeline test accuracy: 66.67%\n"
]
}
],
"execution_count": 10
},
{
"cell_type": "markdown",
"id": "b118ea5561624da68c537baed56e602f",
"metadata": {},
"source": [
"### 4. The same network as a hybrid model\n",
"\n",
"- `DressedQuantumClassifier` (Mari et al. 2020) is this dense, circuit, dense network prebuilt, with the pipeline inside `forward`.\n",
"- It takes `n_features` instead of an encoder and is fit like any other model.\n",
"- Its weights are the layers', under `pre_net.*`, `quantum.*` and `post_net.*`."
]
},
{
"cell_type": "code",
"id": "938c804e27f84196a10c8828c723f798",
"metadata": {
"ExecuteTime": {
"end_time": "2026-09-19T12:18:14.910950801Z",
"start_time": "2026-09-19T12:18:13.815775062Z"
}
},
"source": [
"from pyqit.models import DressedQuantumClassifier\n",
"\n",
"dressed = DressedQuantumClassifier(n_features=4, n_qubits=4, n_layers=2)\n",
"history_dressed = Trainer(\n",
" max_epochs=15, learning_rate=0.05, loss_fn=\"cross_entropy\"\n",
").fit(dressed, datamodule=DataModule(X, y, normalize=\"minmax\", batch_size=16, seed=42))\n",
"\n",
"print(list(dressed.weights))"
],
"outputs": [
{
"data": {
"text/plain": [
"\u001B[1;36m \u001B[0m\u001B[1;36mParameter \u001B[0m\u001B[1;36m \u001B[0m\u001B[1;36m \u001B[0m\u001B[1;36mValue \u001B[0m\u001B[1;36m \u001B[0m\n",
"\u001B[2m \u001B[0m\u001B[2mModel Name \u001B[0m\u001B[2m \u001B[0m\u001B[1m \u001B[0m\u001B[1mDressedQuantumClassifier\u001B[0m\u001B[1m \u001B[0m\n",
"\u001B[2m \u001B[0m\u001B[2mType \u001B[0m\u001B[2m \u001B[0m\u001B[1m \u001B[0m\u001B[1mhybrid classifier \u001B[0m\u001B[1m \u001B[0m\n",
"\u001B[2m \u001B[0m\u001B[2mBackend \u001B[0m\u001B[2m \u001B[0m\u001B[1m \u001B[0m\u001B[1mPennylane \u001B[0m\u001B[1m \u001B[0m\n",
"\u001B[2m \u001B[0m\u001B[2mQubits \u001B[0m\u001B[2m \u001B[0m\u001B[1m \u001B[0m\u001B[1m4 \u001B[0m\u001B[1m \u001B[0m\n",
"\u001B[2m \u001B[0m\u001B[2mAnsatz \u001B[0m\u001B[2m \u001B[0m\u001B[1m \u001B[0m\u001B[1mN/A \u001B[0m\u001B[1m \u001B[0m\n",
"\u001B[2m \u001B[0m\u001B[2mEncoder \u001B[0m\u001B[2m \u001B[0m\u001B[1m \u001B[0m\u001B[1mN/A \u001B[0m\u001B[1m \u001B[0m\n",
"\u001B[2m \u001B[0m\u001B[2mTrainable Params \u001B[0m\u001B[2m \u001B[0m\u001B[1m \u001B[0m\u001B[1m33 \u001B[0m\u001B[1m \u001B[0m\n",
"\u001B[2m \u001B[0m\u001B[2mDevice \u001B[0m\u001B[2m \u001B[0m\u001B[1m \u001B[0m\u001B[1mdefault.qubit \u001B[0m\u001B[1m \u001B[0m\n",
"\u001B[2m \u001B[0m\u001B[2mDiff Method \u001B[0m\u001B[2m \u001B[0m\u001B[1m \u001B[0m\u001B[1mbackprop \u001B[0m\u001B[1m \u001B[0m\n",
"\u001B[2m \u001B[0m\u001B[2mOptimizer \u001B[0m\u001B[2m \u001B[0m\u001B[1m \u001B[0m\u001B[1mADAM \u001B[0m\u001B[1m \u001B[0m\n",
"\u001B[2m \u001B[0m\u001B[2mLearning Rate \u001B[0m\u001B[2m \u001B[0m\u001B[1m \u001B[0m\u001B[1m0.05 \u001B[0m\u001B[1m \u001B[0m\n",
"\u001B[2m \u001B[0m\u001B[2mTrain / Val Samples \u001B[0m\u001B[2m \u001B[0m\u001B[1m \u001B[0m\u001B[1m140 / 30 \u001B[0m\u001B[1m \u001B[0m\n"
],
"text/html": [
"Parameter Value \n", " Model Name DressedQuantumClassifier \n", " Type hybrid classifier \n", " Backend Pennylane \n", " Qubits 4 \n", " Ansatz N/A \n", " Encoder N/A \n", " Trainable Params 33 \n", " Device default.qubit \n", " Diff Method backprop \n", " Optimizer ADAM \n", " Learning Rate 0.05 \n", " Train / Val Samples 140 / 30 \n", "\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/plain": [ "\n" ], "text/html": [ "
\n",
"\n"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/plain": [
"Output()"
],
"application/vnd.jupyter.widget-view+json": {
"version_major": 2,
"version_minor": 0,
"model_id": "5db71429ce084671bcfa3cced3548a4d"
}
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/plain": [],
"text/html": [
"\n"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/plain": [
"\u001B[1;32m[\u001B[0m\u001B[1;32mTrainer\u001B[0m\u001B[1;32m]\u001B[0m Training complete.\n"
],
"text/html": [
"[Trainer] Training complete.\n",
"\n"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"['pre_net.weight', 'pre_net.bias', 'quantum.weights', 'post_net.weight', 'post_net.bias']\n"
]
}
],
"execution_count": 11
},
{
"cell_type": "markdown",
"id": "7623eae2785240b9bd12b16a66d81610",
"metadata": {},
"source": [
"### 5. Loss curves"
]
},
{
"cell_type": "code",
"id": "7cdc8c89c7104fffa095e18ddfef8986",
"metadata": {
"ExecuteTime": {
"end_time": "2026-09-19T12:18:15.015203248Z",
"start_time": "2026-09-19T12:18:14.923552320Z"
}
},
"source": [
"import matplotlib.pyplot as plt\n",
"\n",
"plt.plot(histories[\"classifier\"].train_loss, label=\"frozen backbone: head\")\n",
"plt.plot(history_new.train_loss, label=\"joint\")\n",
"plt.plot(history_dressed.train_loss, label=\"dressed model\")\n",
"plt.xlabel(\"epoch\")\n",
"plt.ylabel(\"train loss\")\n",
"plt.legend()\n",
"plt.show()"
],
"outputs": [
{
"data": {
"text/plain": [
"