Use Qiskit ML plugin for Quantum Machine Learning
Qiskit is a comprehensive, open-source SDK for working with quantum computers. Its dedicated plugin, qiskit-machine-learning, introduces foundational quantum machine learning (QML) algorithms, neural networks, and tools to integrate quantum circuits seamlessly into classic machine learning workflows.
Scaleway offers access to the qiskit-scaleway provider, enabling you to run your Qiskit circuits and QML models directly on Scaleway's QaaS infrastructure with minimal configuration.
In this practical example, we will build a Quantum Autoencoder using qiskit-machine-learning to compress and reconstruct a custom toy dataset of digital numbers (zeros and ones) on Scaleway's infrastructure. This example is freely adapted from Qiskit's Quantum Autoencoder tutorial.
How to perform Quantum Neural Network training on Scaleway using Qiskit-ML
Before you start
To complete the actions presented below, you must have:
- A Scaleway account with a valid Project ID
- A Scaleway API key (Secret Key)
- Python installed on your local machine
-
Install the
qiskit-scalewayprovider alongside the necessary machine learning and visualization packages:pip install qiskit-machine-learning qiskit-scaleway pylatexenc matplotlib -
Create a helper file named
digits_dataset.pyin your working directory, and add the helper script shown below. This script generates a basic 5x3 grid representing handwritten "zeros" and "ones" that we will use to train our model.import matplotlib.pyplot as plt import numpy as np from qiskit_machine_learning.utils import algorithm_globals algorithm_globals.random_seed = 42 def zero_idx(): # Index for zero pixels (Perimeter of a 5x3 grid) return [[0,1], [1,0], [1,2], [2,0], [2,2], [3,0], [3,2], [4,1]] def one_idx(): # Index for one pixels (Middle column of a 5x3 grid) return [[0,1], [1,0], [1,1], [2,1], [3,1], [4,1]] def get_dataset_digits(num, draw=True): train_images = [] train_labels = [] for i in range(int(num / 2)): empty = np.array([algorithm_globals.random.uniform(0, 0.1) for _ in range(15)]).reshape(5, 3) for r, c in one_idx(): empty[r][c] = algorithm_globals.random.uniform(0.9, 1) train_images.append(empty) train_labels.append(1) if draw: plt.title("This is a One") plt.imshow(train_images[-1], vmin=0, vmax=1) plt.show(block=True) for i in range(int(num / 2)): empty = np.array([algorithm_globals.random.uniform(0, 0.1) for _ in range(15)]).reshape(5, 3) for r, c in zero_idx(): empty[r][c] = algorithm_globals.random.uniform(0.9, 1) train_images.append(empty) train_labels.append(0) if draw: plt.imshow(train_images[-1], vmin=0, vmax=1) plt.title("This is a Zero") plt.show(block=True) train_images = np.array(train_images) # Flatten to 15-dimensional vectors for the 15 data qubits train_images = train_images.reshape(len(train_images), 15) return train_images, train_labels -
Create the main training script. Save the following code as
qml_tutorial.py. Make sure to define yourSCW_PROJECT_IDandSCW_SECRET_KEYas environment variables (or via an.envfile) before running it. Notice how theScalewayProvideris used to fetch theEMU-AER-2L4backend and initiate a dedicated session for the iterativeCOBYLAoptimizer.import os import time import matplotlib.pyplot as plt import numpy as np from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit from qiskit.circuit import ParameterVector from qiskit.circuit.library import real_amplitudes from qiskit.quantum_info import Statevector from qiskit_scaleway import ScalewayProvider from qiskit_scaleway.primitives import Sampler as ScwSampler from qiskit_machine_learning.optimizers import COBYLA from qiskit_machine_learning.utils import algorithm_globals from qiskit_machine_learning.neural_networks import SamplerQNN from digits_dataset import get_dataset_digits algorithm_globals.random_seed = 42 ### Instantiate Scaleway provider ### PROJECT_ID = os.getenv("SCW_PROJECT_ID") SECRET_KEY = os.getenv("SCW_SECRET_KEY") provider = ScalewayProvider( project_id=PROJECT_ID, secret_key=SECRET_KEY, ) backend = provider.get_backend("EMU-AER-2L4") session_id = backend.start_session() sampler = ScwSampler(backend, session_id, options={"default_shots": 100}) print(sampler) ### Helpers ### def ansatz(num_qubits: int): return real_amplitudes(num_qubits, reps=3) def encoder_circuit(num_latent: int, num_trash: int) -> QuantumCircuit: qr = QuantumRegister(num_latent + 2 * num_trash + 1, "q") cr = ClassicalRegister(1, "c") circuit = QuantumCircuit(qr, cr) circuit.compose(ansatz(num_latent + num_trash), range(0, num_latent + num_trash), inplace=True) circuit.barrier() auxiliary_qubit = num_latent + 2 * num_trash # swap test circuit.h(auxiliary_qubit) for i in range(num_trash): circuit.cswap(auxiliary_qubit, num_latent + i, num_latent + num_trash + i) circuit.h(auxiliary_qubit) circuit.measure(auxiliary_qubit, cr[0]) return circuit fig, ax, line = None, None, None def cost_func_digits(params_values) -> float: global fig, ax, line probabilities = qnn.forward(train_images, params_values) cost = np.sum(probabilities[:, 1]) / train_images.shape[0] if not objective_func_vals: plt.ion() fig, ax = plt.subplots() ax.set_title("Objective function value against iteration") ax.set_xlabel("Iteration") ax.set_ylabel("Objective function value") line, = ax.plot([], [], color='blue', marker='o', markersize=4) plt.show(block=False) objective_func_vals.append(cost) line.set_data(range(len(objective_func_vals)), objective_func_vals) ax.relim() ax.autoscale_view() fig.canvas.draw() fig.canvas.flush_events() return cost ### MAIN SCRIPT ### num_latent = 9 num_trash = 6 train_images, __ = get_dataset_digits(4, draw=False) # Custom Unitary Feature Map (Avoids OpenQASM 3 Serialization errors) num_features = num_latent + num_trash feature_map = QuantumCircuit(num_features) inputs = ParameterVector("x", num_features) for i in range(num_features): # Mapping [0, 1] data into RY rotation probability. feature_map.ry(np.pi * inputs[i], i) ae = encoder_circuit(num_latent, num_trash) qc = QuantumCircuit(num_latent + 2 * num_trash + 1, 1) qc = qc.compose(feature_map, range(num_latent + num_trash)) qc = qc.compose(ae) qc.draw(output="mpl", style="clifford") plt.show(block=True) qnn = SamplerQNN( circuit=qc, input_params=feature_map.parameters, weight_params=ae.parameters, interpret=lambda x: x, output_shape=2, sampler=sampler, ) opt = COBYLA(maxiter=150) initial_point = algorithm_globals.random.random(ae.num_parameters) objective_func_vals = [] plt.rcParams["figure.figsize"] = (12, 6) start = time.time() opt_result = opt.minimize(fun=cost_func_digits, x0=initial_point) elapsed = time.time() - start print(f"Fit in {elapsed:0.2f} seconds") plt.ioff() plt.show(block=True) # Test our circuit test_qc = QuantumCircuit(num_latent + num_trash) test_qc = test_qc.compose(feature_map) ansatz_qc = ansatz(num_latent + num_trash) test_qc = test_qc.compose(ansatz_qc) test_qc.barrier() # Dynamically reset the designated trash qubits for i in range(num_latent, num_latent + num_trash): test_qc.reset(i) test_qc.barrier() test_qc = test_qc.compose(ansatz_qc.inverse()) # Helper to visually decode our RY probabilities back into images def extract_image(state_circ): sv = Statevector(state_circ) pixels = np.zeros(15) for i in range(15): # Marginal probability of measuring |1> on qubit i pixels[i] = sv.probabilities([i])[1] return pixels.reshape(5, 3) # Sample new images and reconstruct test_images, test_labels = get_dataset_digits(2, draw=False) for image, label in zip(test_images, test_labels): original_qc = feature_map.assign_parameters(image) original_img = extract_image(original_qc) param_values = np.concatenate((image, opt_result.x)) output_qc = test_qc.assign_parameters(param_values) output_img = extract_image(output_qc) fig, (ax1, ax2) = plt.subplots(1, 2) ax1.imshow(original_img, vmin=0, vmax=1) ax1.set_title("Input Data") ax2.imshow(output_img, vmin=0, vmax=1) ax2.set_title("Output Data") plt.show(block=True) -
Run the script.
python ~/qml_tutorial.py
Executing this code will iteratively train the parameters of the quantum autoencoder, visually plotting the decreasing cost function in real-time. Once convergence is reached, the script will output a comparison of the newly reconstructed digits against the original input data for comparison.
Understanding session management
Because quantum machine learning algorithms often require hundreds of iterative steps (like the COBYLA optimization in the script above), submitting each circuit independently can introduce heavy network overhead.
With qiskit-scaleway, your iterative workloads are grouped via a session. By explicitly calling start_session() on your backend and passing that session_id to your ScwSampler primitive, you guarantee that Scaleway's QaaS infrastructure processes your training loop in the same session.