Variational quantum classifier#

  • Trains a VQCClassifier on a synthetic dataset, on the torch backend.

  • Then VQCRegressor, the same circuit read as a value.

1. Data#

  • make_classification, 200 samples, 4 features, 2 classes.

  • normalize="minmax" is fit on the train split only.

  • AngleEmbedding prescaling maps it to [0, pi] during setup().

[9]:
import numpy as np
from sklearn.datasets import make_classification

X, y = make_classification(
    n_samples=200,
    n_features=4,
    n_informative=4,
    n_redundant=0,
    n_classes=2,
    random_state=42,
)
[10]:
from pyqit import DataModule, set_backend

set_backend("torch")

dm = DataModule(
    X=X,
    y=y,
    normalize="minmax",
    batch_size=16,
    split=(0.7, 0.15, 0.15),
    seed=42,
)

2. Model and training#

  • 4 qubits, 3 layers, SELAnsatz, AngleEmbedding.

  • 15 epochs at lr 0.05.

  • fit returns a TrainingHistory.

[11]:
from pyqit import Trainer
from pyqit.ansatzes import SELAnsatz
from pyqit.core import AngleEmbedding
from pyqit.models import VQCClassifier

# Initialize the VQC
model = VQCClassifier(
    n_qubits=4,
    n_layers=3,
    n_classes=2,
    ansatz=SELAnsatz,
    encoder=AngleEmbedding,
)

# Set up the Trainer
trainer = Trainer(max_epochs=15, learning_rate=0.05)

history = trainer.fit(model, datamodule=dm)
 Parameter             Value          
 Model Name            VQCClassifier  
 Backend               Torch          
 Qubits                4              
 Ansatz                SELAnsatz      
 Encoder               AngleEmbedding 
 Trainable Params      36             
 Device                default.qubit  
 Diff Method           backprop       
 Optimizer             ADAM           
 Learning Rate         0.05           
 Train / Val Samples   140 / 30       
GPU available: True (cuda), used: False
TPU available: False, using: 0 TPU cores
/home/aryan/PyQit/.venv/lib/python3.12/site-packages/lightning/pytorch/trainer/setup.py:175: GPU available but not used. You can set it by doing `Trainer(accelerator='gpu')`.
💡 Tip: For seamless cloud logging and experiment tracking, try installing [litlogger](https://pypi.org/project/litlogger/) to enable LitLogger, which logs metrics and artifacts automatically to the Lightning Experiments platform.
`Trainer(limit_val_batches=1.0)` was configured so 100% of the batches will be used..
/home/aryan/PyQit/.venv/lib/python3.12/site-packages/lightning/pytorch/utilities/_pytree.py:21:
`isinstance(treespec, LeafSpec)` is deprecated, use `isinstance(treespec, TreeSpec) and treespec.is_leaf()`
instead.
/home/aryan/PyQit/.venv/lib/python3.12/site-packages/lightning/pytorch/trainer/connectors/data_connector.py:434:
The 'val_dataloader' does not have many workers which may be a bottleneck. Consider increasing the value of the
`num_workers` argument` to `num_workers=15` in the `DataLoader` to improve performance.
/home/aryan/PyQit/.venv/lib/python3.12/site-packages/lightning/pytorch/trainer/connectors/data_connector.py:434:
The 'train_dataloader' does not have many workers which may be a bottleneck. Consider increasing the value of the
`num_workers` argument` to `num_workers=15` in the `DataLoader` to improve performance.
`Trainer.fit` stopped: `max_epochs=15` reached.
[Trainer] Training complete.

3. Loss curve#

  • history.as_dict() returns the per-epoch metrics.

[12]:
import matplotlib.pyplot as plt

# Extract metrics from the history
train_loss = history.as_dict()["train_loss"]
val_loss = history.as_dict()["val_loss"]
epochs = range(1, len(train_loss) + 1)

plt.figure(figsize=(8, 5))
plt.plot(epochs, train_loss, label="Train Loss", marker="o")
plt.plot(epochs, val_loss, label="Validation Loss", marker="o")
plt.title("VQC Training History")
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.legend()
plt.grid(True)
plt.show()
../_images/tutorials_vqc_7_0.png

4. Evaluation#

  • predict runs on the test split.

  • return_format="numpy" forces numpy output on the torch backend.

[13]:
from sklearn.metrics import accuracy_score, confusion_matrix

# Predict
predictions = trainer.predict(model, datamodule=dm, return_format="numpy")
actuals = dm.y_test.astype(int)

# Calculate Accuracy
acc = accuracy_score(actuals, predictions)
print(f"Standalone VQC Test Accuracy: {acc * 100:.2f}%")

# Plot Confusion Matrix
cm = confusion_matrix(actuals, predictions)
plt.figure(figsize=(5, 4))
plt.imshow(cm)
plt.title("Standalone VQC Confusion Matrix")
plt.xlabel("Predicted Label")
plt.ylabel("True Label")

for i in range(cm.shape[0]):
    for j in range(cm.shape[1]):
        plt.text(j, i, cm[i, j], ha="center", va="center")
plt.xticks(np.arange(cm.shape[1]))
plt.yticks(np.arange(cm.shape[0]))

plt.colorbar()
plt.tight_layout()
plt.show()
Standalone VQC Test Accuracy: 83.33%
../_images/tutorials_vqc_9_1.png

Variational quantum regressor#

  • VQCRegressor reads the same circuit as one value: the parity <Z...Z> in [-1, 1], then a trained scale * <Z> + offset.

  • The default loss_fn="mse" applies. Accuracy is recorded as NaN for a regressor.

  • Target: sin(pi * x) on one feature. minmax and AngleEmbedding prescaling map x to [0, pi].

[ ]:
from pyqit.models import VQCRegressor

X_r = np.linspace(-1, 1, 80).reshape(-1, 1)
y_r = np.sin(np.pi * X_r).ravel()

dm_r = DataModule(
    X=X_r, y=y_r, normalize="minmax", batch_size=16, split=(0.7, 0.15, 0.15), seed=42
)

regressor = VQCRegressor(n_qubits=2, n_layers=3)
trainer_r = Trainer(max_epochs=30, learning_rate=0.05)
history_r = trainer_r.fit(regressor, datamodule=dm_r)
  • predict returns one value per row, no thresholding.

  • dm_r.X_test is the prescaled input, so the x axis is in [0, pi].

[ ]:
preds_r = trainer_r.predict(regressor, datamodule=dm_r, return_format="numpy")
print(f"Test MSE: {np.mean((preds_r - dm_r.y_test) ** 2):.4f}")

order = np.argsort(dm_r.X_test[:, 0])
plt.plot(dm_r.X_test[order, 0], dm_r.y_test[order], "o", label="target")
plt.plot(dm_r.X_test[order, 0], preds_r[order], "x", label="prediction")
plt.xlabel("x (prescaled)")
plt.ylabel("y")
plt.legend()
plt.show()