Kernel Methods with MerLin
This notebook illustrates how to build photonic feature maps and quantum kernels using MerLin. We cover the quickstart factory, custom circuits, and how to plug the resulting Gram matrices into classical machine-learning pipelines built around the Iris dataset.
Setup
We standardise the Iris features, split train/test partitions, and rely on scikit-learn to train classical models on the precomputed kernel matrices.
[1]:
import numpy as np
import perceval as pcvl
import torch
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from merlin.algorithms.kernels import FeatureMap, FidelityKernel, KernelCircuitBuilder
torch.manual_seed(0)
np.random.seed(0)
iris = load_iris()
X = iris.data.astype(np.float32)
y = iris.target
X = StandardScaler().fit_transform(X)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, stratify=y, random_state=12
)
X_train = torch.tensor(X_train)
X_test = torch.tensor(X_test)
Fidelity kernel in a few lines
FidelityKernel.simple constructs a kernel-ready photonic circuit and feature map. It encodes real inputs into a multi-mode Fock space, evaluates their overlaps, and returns a positive-semidefinite Gram matrix.
[2]:
kernel = FidelityKernel.simple(
input_size=4,
n_modes=6,
shots=0, # exact probabilities
no_bunching=False,
dtype=torch.float32,
device=torch.device("cpu"),
)
K_train = kernel(X_train)
K_test = kernel(X_test, X_train)
print("Train Gram shape:", K_train.shape)
print("Test Gram shape:", K_test.shape)
Train Gram shape: torch.Size([105, 105])
Test Gram shape: torch.Size([45, 105])
Use with scikit-learn
We can train any estimator that accepts precomputed kernels. Below we use an SVM to distinguish the three Iris species from the quantum kernel features.
[3]:
svc = SVC(kernel="precomputed")
svc.fit(K_train.detach().numpy(), y_train)
test_accuracy = svc.score(K_test.detach().numpy(), y_test)
print(f"SVM accuracy (precomputed kernel): {test_accuracy:.3f}")
SVM accuracy (precomputed kernel): 1.000
Custom feature maps and experiments
For full control over the optical layout, detectors, and noise, build a perceval.Circuit and wrap it with FeatureMap. Fidelity kernels then evaluate overlaps between the resulting states.
[4]:
circuit = pcvl.Circuit(4)
circuit.add((0, 1), pcvl.BS())
circuit.add(0, pcvl.PS(pcvl.P("phi0")))
circuit.add(1, pcvl.PS(pcvl.P("phi1")))
circuit.add(2, pcvl.PS(pcvl.P("phi2")))
circuit.add(3, pcvl.PS(pcvl.P("phi3")))
circuit.add((2, 3), pcvl.BS())
experiment = pcvl.Experiment(circuit)
experiment.noise = pcvl.NoiseModel(brightness=0.93)
feature_map = FeatureMap(
experiment=experiment,
input_size=4,
input_parameters="phi",
dtype=torch.float32,
)
custom_kernel = FidelityKernel(
feature_map=feature_map,
input_state=[1, 0, 1, 0],
no_bunching=True,
)
K_custom = custom_kernel(X_train[:20])
print("Custom kernel Gram shape:", K_custom.shape)
Custom kernel Gram shape: torch.Size([20, 20])
Declarative kernel circuits
KernelCircuitBuilder offers a fluent API to assemble reusable feature maps programmatically.
[5]:
builder = (
KernelCircuitBuilder()
.input_size(4)
.n_modes(6)
.n_photons(2)
.angle_encoding(scale=0.7)
.trainable(enabled=True, prefix="theta")
)
builder_kernel = builder.build_fidelity_kernel(
input_state=[1, 1, 0, 0, 0, 0],
shots=512,
no_bunching=False,
)
K_builder = builder_kernel(X_train[:15])
print("Builder kernel Gram shape:", K_builder.shape)
Builder kernel Gram shape: torch.Size([15, 15])
Conclusion
Use
FidelityKernel.simplefor quick experiments where a default photonic layout suffices.Wrap custom
perceval.Experimentobjects withFeatureMapto reflect hardware noise and detector choices.KernelCircuitBuilderhelps script repeatable feature maps with entangling blocks and measurement strategies.The resulting Gram matrices integrate with classical ML libraries by choosing the
precomputedkernel option.