Kernels cuánticos
Introducción a los kernels cuánticos
El "método de kernel cuántico" designa cualquier método que utiliza computadoras cuánticas para estimar un kernel. En este contexto, "kernel" se refiere a la matriz kernel o a entradas individuales de la misma. Como recordatorio: un mapeo de características es un mapeo de a donde generalmente y el objetivo de este mapeo es hacer que las categorías de datos sean separables por un hiperplano. La función kernel toma vectores en el espacio mapeado por características como argumentos y devuelve su producto interno, es decir, con . Clásicamente, nos interesan mapeos de características para los cuales la función kernel es fácil de evaluar. Esto a menudo significa encontrar una función kernel donde el producto interno en el espacio mapeado por características pueda expresarse en términos de los vectores de datos originales, sin necesidad de construir explícitamente y . En el método de kernel cuántico, el mapeo de características se realiza mediante un circuito cuántico, y el kernel se estima a partir de las mediciones en este circuito y las probabilidades de medición relativas.
En esta lección investigamos las profundidades de circuitos de codificación preconstruidos que utilizan entrelazamiento extensivo, y las comparamos con las profundidades de circuitos que programamos nosotros mismos. Esto no es una recomendación de un método sobre otro. Quizás descubras que los circuitos preconstruidos son demasiado profundos y que el entrelazamiento en el circuito auto-construido no es suficiente para un uso útil. Estos ejemplos sirven únicamente para posibilitar tu propia exploración.
Antes de recorrer en detalle una estimación de matriz kernel, esbozamos el flujo de trabajo utilizando el lenguaje de Qiskit Patterns.
Paso 1: Mapear entradas clásicas a un problema cuántico
- Entrada: Conjunto de datos de entrenamiento
- Salida: Circuito abstracto para calcular una entrada de la matriz kernel
Partiendo del conjunto de datos, el primer paso es codificar los datos en un circuito cuántico. Dicho de otro modo: debemos mapear nuestros datos al espacio de Hilbert de los estados de nuestra computadora cuántica. Esto lo hacemos construyendo un circuito dependiente de los datos. Hay muchas formas de hacerlo, y la lección anterior presentó varias opciones. Puedes construir tu propio circuito para codificar tus datos o usar un mapa de características preconstruido como zz_feature_map. En esta lección haremos ambas cosas.
Para calcular un único elemento de la matriz kernel, queremos codificar dos puntos diferentes para poder estimar su producto interno. Un flujo de trabajo completo de kernel cuántico incluye, por supuesto, muchos de estos productos internos entre vectores de datos mapeados, así como métodos clásicos de Machine Learning. Sin embargo, el paso central que se itera es la estimación de un único elemento de la matriz kernel. Para ello, elegimos un circuito cuántico dependiente de los datos y mapeamos dos vectores de datos al espacio de características.

Para la tarea de generar una matriz kernel, nos interesa particularmente la probabilidad de medir el estado , en el que todos los qubits están en el estado . Para entender esto: el circuito responsable de la codificación y mapeo de un vector de datos puede escribirse como , y el de como . Los estados mapeados son entonces
Estos estados son el mapeo de los datos a dimensiones más altas, por lo que nuestra entrada de kernel deseada es el producto interno
Si aplicamos al estado inicial estándar ambos circuitos y , la probabilidad de medir después el estado es
Este es exactamente el valor que buscamos (salvo ). La capa de medición de nuestro circuito proporciona probabilidades de medición (o las llamadas "cuasi-probabilidades", si se utilizan ciertos métodos de mitigación de errores). La probabilidad que nos interesa es la del estado cero, .
Paso 2: Optimizar el problema para la ejecución cuántica
- Entrada: Circuito abstracto, no optimizado para un backend específico
- Salida: Circuito objetivo y observable, optimizados para el QPU seleccionado
En este paso utilizamos la función generate_preset_pass_manager de Qiskit para establecer una rutina de optimización para nuestro circuito respecto a la computadora cuántica real en la que queremos ejecutar el experimento. Establecemos optimization_level=3, lo que significa que usamos el pass manager preestablecido con el nivel de optimización más alto. "Optimización" aquí se refiere a la optimización de la implementación del circuito en una computadora cuántica real. Esto incluye consideraciones como la selección de qubits físicos que corresponden a los qubits en el circuito cuántico abstracto y minimizan la profundidad de puertas, o la selección de qubits físicos con las tasas de error más bajas disponibles. Esto no tiene relación directa con la optimización del problema de Machine Learning (como con optimizadores clásicos como COBYLA).
Dependiendo de la implementación del Paso 2, es posible que necesites optimizar el circuito más de una vez, ya que cada par de puntos involucrado en un elemento de la matriz produce un circuito diferente para medir.
Paso 3: Ejecución con Qiskit Runtime Primitives
- Entrada: Circuito objetivo
- Salida: Distribución de probabilidad
Usa el primitivo Sampler de Qiskit Runtime para reconstruir una distribución de probabilidad de los estados producidos al muestrear el circuito. Ten en cuenta que esto a veces se denomina "distribución de cuasi-probabilidad", un término que se aplica cuando el ruido es un problema y se introducen pasos adicionales, como en la mitigación de errores. En tales casos, la suma de todas las probabilidades no necesariamente es exactamente 1; de ahí "cuasi-probabilidad".
Paso 4: Post-procesamiento, devolver resultado en formato clásico
- Entrada: Distribución de probabilidad
- Salida: Un único elemento de la matriz kernel o una matriz kernel si se repite
Calcula la probabilidad de medir en el circuito cuántico y llena la matriz kernel en la posición correspondiente a los dos vectores de datos utilizados. Para llenar toda la matriz kernel, debemos realizar un experimento cuántico para cada entrada. Una vez que tenemos una matriz kernel, podemos usarla en muchos algoritmos clásicos de Machine Learning que aceptan pre-calculated kernels. Por ejemplo: qml_svc = SVC(kernel="precomputed"). Entonces podemos usar flujos de trabajo clásicos para aplicar nuestro modelo a nuestros datos de prueba y obtener una puntuación de precisión. Dependiendo de la satisfacción con nuestra precisión, es posible que necesitemos revisar aspectos de nuestro cálculo, como nuestro mapa de características.
Esquema de la lección
En esta lección realizamos estos pasos de diferentes maneras para optimizar tu tiempo en computadoras cuánticas reales. Aplicamos un método de kernel cuántico a:
- Un único elemento de la matriz kernel para datos con relativamente pocas características, en un backend real, para que podamos seguir fácilmente lo que sucede en cada paso.
- Un conjunto de datos completo con relativamente pocas características, en un backend simulado, para que podamos ver cómo el flujo de trabajo cuántico se conecta con métodos clásicos de Machine Learning.
- Un único elemento de la matriz kernel para datos con muchas características, en una computadora cuántica real. No estimamos una matriz kernel completa para un conjunto de datos grande, para respetar el tiempo en las computadoras cuánticas de IBM®.
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy pandas qiskit qiskit-ibm-runtime scikit-learn
# If you have not already, install scikit learn
#!pip install scikit-learn
Elemento único de la matriz kernel
Paso 1: Mapear entradas clásicas a un problema cuántico
Consideremos primero un conjunto de datos con pocas características, digamos 10. El conjunto de datos puede ser de cualquier tamaño, ya que calculamos los elementos de la matriz kernel individualmente. Necesitamos al menos dos puntos, así que comencemos con eso (en el siguiente ejemplo importaremos un conjunto de datos completo). Importemos algunos paquetes necesarios:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# Two mock data points, including category labels, as in training
small_data = [
[-0.194, 0.114, -0.006, 0.301, -0.359, -0.088, -0.156, 0.342, -0.016, 0.143, 1],
[-0.1, 0.002, 0.244, 0.127, -0.064, -0.086, 0.072, 0.043, -0.053, 0.02, -1],
]
# Data points with labels removed, for inner product
train_data = [small_data[0][:-1], small_data[1][:-1]]
Podemos probar la z_feature_map.
# from qiskit.circuit.library import zz_feature_map
# fm = zz_feature_map(feature_dimension=np.shape(train_data)[1], entanglement='linear', reps=1)
from qiskit.circuit.library import z_feature_map
fm = z_feature_map(feature_dimension=np.shape(train_data)[1])
unitary1 = fm.assign_parameters(train_data[0])
unitary2 = fm.assign_parameters(train_data[1])
Las dos unitarias anteriores corresponden exactamente a y de la introducción. Podemos combinarlas usando unitary_overlap. Como siempre, debemos vigilar la profundidad del circuito.
from qiskit.circuit.library import unitary_overlap
overlap_circ = unitary_overlap(unitary1, unitary2)
overlap_circ.measure_all()
print("circuit depth = ", overlap_circ.decompose().depth())
overlap_circ.decompose().draw("mpl", scale=0.6, style="iqp")
circuit depth = 9
Paso 2: Optimizar el problema para la ejecución cuántica
Comenzamos seleccionando el backend menos ocupado y luego optimizamos nuestro circuito para la ejecución en ese backend.
# Import needed packages
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
# Get the least busy backend
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=fm.num_qubits
)
print(backend)
<IBMBackend('ibm_brisbane')>
# Apply level 3 optimization to our overlap circuit
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
overlap_ibm = pm.run(overlap_circ)
Para circuitos complejos, este paso aumenta significativamente la profundidad del circuito, ya que se realiza el mapeo a puertas nativas para computadoras cuánticas reales y la información debe moverse de qubit a qubit. En este caso sencillo, la profundidad apenas se ve afectada.
print("circuit depth = ", overlap_ibm.decompose().depth())
overlap_ibm.decompose().depth(lambda instr: len(instr.qubits) > 1)
circuit depth = 10
1
Paso 3: Ejecución con Qiskit Runtime Primitives
La sintaxis para la ejecución en un simulador está comentada abajo. Para este conjunto de datos con un número reducido de características, la ejecución en un simulador sigue siendo una opción. Para cálculos a escala de utilidad, la simulación generalmente no es practicable. Los simuladores solo deben usarse para depurar código simplificado.
# Run this for a simulator
# from qiskit.primitives import StatevectorSampler
# from qiskit_ibm_runtime import Options, Session, Sampler
# num_shots = 10000
# Evaluate the problem using state vector-based primitives from Qiskit
# sampler = StatevectorSampler()
# results = sampler.run([overlap_circ], shots=num_shots).result()
# .get_counts() returns counts associated with a state labeled by bit results such as |001101...01>.
# counts_bit = results[0].data.meas.get_counts()
# .get_int_counts returns the same counts, but labeled by integer equivalent of the above bit string.
# counts = results[0].data.meas.get_int_counts()
# Benchmarked on an Eagle processor, 7-11-24, took 4 sec.
# Import our runtime primitive
from qiskit_ibm_runtime import Session, SamplerV2 as Sampler
num_shots = 10000
# Use sampler and get the counts
sampler = Sampler(mode=backend)
results = sampler.run([overlap_ibm], shots=num_shots).result()
# .get_counts() returns counts associated with a state labeled by bit results such as |001101...01>.
counts_bit = results[0].data.meas.get_counts()
# .get_int_counts returns the same counts, but labeled by integer equivalent of the above bit string.
counts = results[0].data.meas.get_int_counts()
Paso 4: Post-procesamiento, devolver resultado en formato clásico
Como se describe en la introducción, la medición más útil aquí es la probabilidad de medir el estado cero