Guía de Migración de Qiskit Machine Learning v0.5#

Este tutorial te guiará a través del proceso de migración de tu código de Qiskit Machine Learning v0.4 a v0.5.

Introducción#

El objetivo principal de la versión 0.5 de Qiskit Machine Learning es la migración de los bloques computacionales básicos, como los kernels cuánticos y las redes neuronales cuánticas, a las primitivas introducidas en Qiskit, así como la compatibilidad ampliada con las primitivas en los algoritmos.

Contenido:

  • Descripción general de las primitivas

  • Nuevo kernel cuántico

  • Nuevas redes neuronales cuánticas

  • Otros casos de obsolescencia notables

Descripción general de las primitivas#

La capacidad central de las computadoras cuánticas que las distingue de las computadoras clásicas es su capacidad para generar distribuciones de probabilidad no clásicas en sus salidas. Las operaciones nativas que uno puede hacer con una distribución de probabilidad es tomar muestras de ella o estimar cantidades en ella. En consecuencia, estas operaciones de muestreo y estimación forman los bloques de construcción fundamentales del desarrollo de algoritmos cuánticos. Así, como fue anunciado, se introdujeron dos primitivas básicas, Sampler y Estimator, respectivamente, que implementan estas dos operaciones:

Qiskit Terra proporciona interfaces centrales y dos implementaciones:

  • La implementación de referencia que está basada en el vector de estado. Esta implementación requiere un backend o un simulador, depende de las clases del paquete quantum_info.

  • Las primitivas basadas en backend son para soportar proveedores/backends que no admiten primitivas directamente. Esta implementación requiere que se pase una instancia de un backend a una primitiva.

Se puede encontrar más información sobre las primitivas de Qiskit Terra en la documentación.

Vale la pena mencionar otras implementaciones también:

  • Las primitivas Aer deben usarse para el simulador Aer. Extienden las interfaces correspondientes de Terra y se pueden usar de la misma manera que las primitivas de Terra. Consulta la documentación para obtener más información.

  • Las primitivas de runtime a usar con los dispositivos de IBM. Esta es una implementación que se centra en la computación en la nube en hardware real. Consulta aquí.

Junto con las primitivas, Terra tiene algunos algoritmos de tipo primitivo que son muy útiles en QML y utilizados por las nuevas funciones en 0.5:

  • Algoritmos para calcular el gradiente de un circuito cuántico. Para cada núcleo primitivo hay una interfaz base correspondiente que define el gradiente del circuito cuántico. La documentación sobre gradientes está aquí.

  • Algoritmos que calculan la fidelidad o “cercanía” de pares de estados cuánticos. Actualmente, solo hay disponible una implementación que requiere una primitiva sampler y se basa en el método de computación-descomputación. La documentación está aquí.

Los dos nuevos algoritmos son muy similares a las primitivas centrales, comparten las mismas firmas de método, por lo que pueden llamarse primitivas de alto nivel a pesar de que no están en el paquete de primitivas.

Nuevo kernel cuántico#

La implementación anterior consistía en una sola clase QuantumKernel que hacía todo:

  • Circuitos construidos

  • Circuitos ejecutados y traslape evaluado entre circuitos

  • Parámetros de entrenamiento proporcionados

  • Seguimiento de los valores asignados a los parámetros.

La implementación se volvió sofisticada e inflexible y agregar soporte para las nuevas primitivas podría ser complicado. Para abordar los problemas, se introdujo un nuevo diseño flexible y extensible de kernels cuánticos. Los objetivos del nuevo diseño son:

  • Migrar a las primitivas y aprovechar el algoritmo de fidelidad. Ahora los usuarios pueden conectar sus propias implementaciones de cálculos de fidelidad.

  • Extraer la característica de entrenabilidad a una clase dedicada.

  • Introducir una clase base que puede ser extendida por otras implementaciones de kernel.

El nuevo diseño del kernel cuántico se muestra en el siguiente diagrama.

Diagrama de Kernel Cuántico

Los nuevos kernels exponen la misma interfaz y los mismos parámetros, excepto el parámetro quantum_instance. Este parámetro no tiene un reemplazo directo y en su lugar se debe usar el parámetro fidelity. El manejo/selección del backend, que anteriormente se realizaba con quantum_instance, ahora se realiza a través de la primitiva Sampler asignada a la fidelity.

Una nueva jerarquía que se muestra en el diagrama introduce:

  • Se introduce una clase base y abstracta BaseKernel. Toda implementación concreta debe heredar de esta clase.

  • Se agrega un kernel cuántico basado en la fidelidad FidelityQuantumKernel. Este es un reemplazo directo de la implementación anterior del kernel cuántico. La diferencia es que la nueva clase toma una instancia de fidelidad para estimar las superposiciones y construir la matriz del kernel.

  • Se introduce una nueva clase abstracta TrainableKernel para generalizar la capacidad de entrenar kernels cuánticos.

  • Se presenta un kernel cuántico entrenable basado en la fidelidad TrainableFidelityQuantumKernel. Este es un reemplazo del kernel cuántico anterior si se requiere un kernel entrenable. El entrenador QuantumKernelTrainer ahora acepta implementaciones de kernel cuántico, la nueva y la anterior.

Por conveniencia, la implementación anterior del kernel cuántico, QuantumKernel, ahora extiende ambas clases abstractas nuevas y, por lo tanto, es compatible con las nuevas interfaces introducidas. Esta implementación ahora está pendiente de obsolescencia, quedará obsoleta en una versión futura y posteriormente se eliminará después de eso. En su lugar, se deben usar los nuevos kernels cuánticos basados en primitivas.

Los algoritmos existentes como QSVC, QSVR y otros algoritmos basados en kernel están actualizados y funcionan con ambas implementaciones.

Por ejemplo, un clasificador QSVM se puede entrenar de la siguiente manera.

Crear un conjunto de datos#

Fija la aleatorización.

from qiskit.utils import algorithm_globals

algorithm_globals.random_seed = 123456

Genera un conjunto de datos simple usando scikit-learn.

from sklearn.datasets import make_blobs

features, labels = make_blobs(
    n_samples=20,
    centers=2,
    center_box=(-1, 1),
    cluster_std=0.1,
    random_state=algorithm_globals.random_seed,
)

Implementación previa del kernel cuántico#

En la implementación anterior comenzamos creando una instancia de QuantumInstance. Esta clase define dónde se ejecutan nuestros circuitos cuánticos. En este caso encapsulamos un simulador de vector de estado en la instancia cuántica.

from qiskit import BasicAer
from qiskit.utils import QuantumInstance

sv_qi = QuantumInstance(
    BasicAer.get_backend("statevector_simulator"),
    seed_simulator=algorithm_globals.random_seed,
    seed_transpiler=algorithm_globals.random_seed,
)

Luego crea un núcleo cuántico.

from qiskit.circuit.library import ZZFeatureMap
from qiskit_machine_learning.kernels import QuantumKernel

feature_map = ZZFeatureMap(2)
previous_kernel = QuantumKernel(feature_map=feature_map, quantum_instance=sv_qi)

Y finalmente ajustamos un clasificador SVM.

from qiskit_machine_learning.algorithms import QSVC

qsvc = QSVC(quantum_kernel=previous_kernel)
qsvc.fit(features, labels)
qsvc.score(features, labels)
0.95

Nueva implementación del kernel cuántico#

En la nueva implementación, comenzamos con la creación de una instancia de Fidelity. La fidelidad es opcional y el kernel cuántico la creará automáticamente si no se pasa ninguna. Pero aquí, la creamos manualmente con fines ilustrativos. Para crear una instancia de fidelidad pasamos un sampler. El sampler es la implementación de referencia y define dónde se ejecutan nuestros circuitos cuánticos. Puedes crear una instancia de sampler desde QiskitRuntimeService para aprovechar los servicios de ejecución de Qiskit.

from qiskit.algorithms.state_fidelities import ComputeUncompute
from qiskit.primitives import Sampler

fidelity = ComputeUncompute(sampler=Sampler())

A continuación, creamos un nuevo kernel cuántico con la instancia de fidelity.

from qiskit_machine_learning.kernels import FidelityQuantumKernel

feature_map = ZZFeatureMap(2)
new_kernel = FidelityQuantumKernel(feature_map=feature_map, fidelity=fidelity)

Luego ajustamos un clasificador SVM de la misma manera que antes.

from qiskit_machine_learning.algorithms import QSVC

qsvc = QSVC(quantum_kernel=new_kernel)
qsvc.fit(features, labels)
qsvc.score(features, labels)
0.95

Nuevas redes neuronales cuánticas#

Los cambios en las redes neuronales cuánticas no son tan dramáticos como en los kernels cuánticos. Además, y como sustitución de las redes neuronales existentes, se introducen dos nuevas redes. Las nuevas redes introducidas son SamplerQNN y EstimatorQNN que se detallan a continuación y reemplazan a las preexistentes CircuitQNN, OpflowQNN y TwoLayerQNN que ahora están pendientes de ser obsoletas.

SamplerQNN#

Una nueva Red Neuronal Cuántica de Sampler aprovecha la primitiva sampler, los gradientes de sampler y es un reemplazo directo de CircuitQNN.

La nueva SamplerQNN expone una interfaz similar a la existente CircuitQNN, con algunas diferencias. Una es el parámetro quantum_instance. Este parámetro no tiene un reemplazo directo, y en su lugar se debe usar el parámetro sampler. El parámetro gradient mantiene el mismo nombre que en la implementación CircuitQNN, pero ya no acepta clases de gradiente Opflow como entradas; en cambio, este parámetro espera un gradiente primitivo (opcionalmente personalizado). La opción sampling se ha eliminado por el momento, ya que esta información no está expuesta actualmente por el sampler y podría corresponder a futuras primitivas de nivel inferior.

Los algoritmos de entrenamiento existentes como VQC que se basaron en CircuitQNN, son actualizados para aceptar ambas implementaciones. La implementación de NeuralNetworkClassifier no ha cambiado.

El CircuitQNN existente ahora está pendiente de obsolescencia, quedará obsoleto en una versión futura y subsecuentemente eliminado después de eso.

Mostraremos cómo entrenar un clasificador cuántico variacional utilizando ambas redes. Para estos fines, reutilizamos el conjunto de datos generado para el kernel cuántico. Para ambas redes neuronales cuánticas todavía tenemos que construir un mapa de características, un ansatz y combinarlos en un solo circuito cuántico.

from qiskit import QuantumCircuit
from qiskit.circuit.library import RealAmplitudes

num_inputs = 2
feature_map = ZZFeatureMap(num_inputs)
ansatz = RealAmplitudes(num_inputs, reps=1)

circuit = QuantumCircuit(num_inputs)
circuit.compose(feature_map, inplace=True)
circuit.compose(ansatz, inplace=True)

Necesitamos una función de interpretación también. Definimos nuestra función de paridad habitual que asigna cadenas de bits a \(0\) o \(1\).

def parity(x):
    return "{:b}".format(x).count("1") % 2

Fijamos el punto inicial para obtener los mismos resultados de ambas redes.

initial_point = algorithm_globals.random.random(ansatz.num_parameters)

Construcción de un clasificador usando CircuitQNN#

Creamos una instancia de CircuitQNN y reutilizamos la quantum instance creada para el kernel cuántico.

from qiskit_machine_learning.neural_networks import CircuitQNN

circuit_qnn = CircuitQNN(
    circuit=circuit,
    input_params=feature_map.parameters,
    weight_params=ansatz.parameters,
    interpret=parity,
    output_shape=2,
    quantum_instance=sv_qi,
)

Construye un clasificador a partir de la red, entrénalo y puntúalo. No buscamos buenos resultados, por lo que el número de iteraciones se establece en un número pequeño para reducir el tiempo de ejecución total.

from qiskit.algorithms.optimizers import COBYLA
from qiskit_machine_learning.algorithms import NeuralNetworkClassifier

classifier = NeuralNetworkClassifier(
    neural_network=circuit_qnn,
    loss="cross_entropy",
    one_hot=True,
    optimizer=COBYLA(maxiter=40),
    initial_point=initial_point,
)
classifier.fit(features, labels)
classifier.score(features, labels)
0.6

Construcción de un clasificador usando SamplerQNN#

En lugar de QuantumInstance, crea una instancia de la referencia Sampler.

from qiskit.primitives import Sampler

sampler = Sampler()

Ahora, creamos una instancia de SamplerQNN. La diferencia con CircuitQNN es que pasamos un sampler en lugar de una quantum instance.

from qiskit_machine_learning.neural_networks import SamplerQNN

sampler_qnn = SamplerQNN(
    circuit=circuit,
    input_params=feature_map.parameters,
    weight_params=ansatz.parameters,
    interpret=parity,
    output_shape=2,
    sampler=sampler,
)

Construye un clasificador y ajústalo como de costumbre. Como es una neural_network pasamos un SamplerQNN creado y esta es la única diferencia.

classifier = NeuralNetworkClassifier(
    neural_network=sampler_qnn,
    loss="cross_entropy",
    one_hot=True,
    optimizer=COBYLA(maxiter=40),
    initial_point=initial_point,
)
classifier.fit(features, labels)
classifier.score(features, labels)
0.6

En lugar de construir una red neuronal cuántica manualmente, puedes entrenar un VQC. Toma una quantum instance o un sampler, dependiendo de lo que se pase, construye automáticamente CircuitQNN o SamplerQNN respectivamente.

EstimatorQNN#

Una nueva red neuronal cuántica de Estimator aprovecha la primitiva estimator, los gradientes del estimator y es un reemplazo directo de OpflowQNN.

La nueva EstimatorQNN expone una interfaz similar a la existente OpflowQNN, con algunas diferencias. Una es el parámetro quantum_instance. Este parámetro no tiene un reemplazo directo, y en su lugar se debe usar el parámetro estimator. El parámetro gradient mantiene el mismo nombre que en la implementación OpflowQNN, pero ya no acepta clases de gradiente Opflow como entradas; en cambio, este parámetro espera un gradiente primitivo (opcionalmente personalizado).

Los algoritmos de entrenamiento existentes como VQR que están basados en TwoLayerQNN, son actualizados para aceptar ambas implementaciones. La implementación de NeuralNetworkRegressor no ha cambiado.

El OpflowQNN existente ahora está pendiente de obsolescencia, quedará obsoleto en una versión futura y subsecuentemente eliminado después de eso.

Mostraremos cómo entrenar un regresor cuántico variacional usando ambas redes. Comenzamos generando un conjunto de datos de regresión simple.

import numpy as np

num_samples = 20
eps = 0.2
lb, ub = -np.pi, np.pi
features = (ub - lb) * np.random.rand(num_samples, 1) + lb
labels = np.sin(features[:, 0]) + eps * (2 * np.random.rand(num_samples) - 1)

Todavía tenemos que construir un mapa de características, un ansatz y combinarlos en un solo circuito cuántico para ambas redes neuronales cuánticas.

from qiskit.circuit import Parameter

num_inputs = 1
feature_map = QuantumCircuit(1)
feature_map.ry(Parameter("input"), 0)

ansatz = QuantumCircuit(1)
ansatz.ry(Parameter("weight"), 0)

circuit = QuantumCircuit(num_inputs)
circuit.compose(feature_map, inplace=True)
circuit.compose(ansatz, inplace=True)

Fijamos el punto inicial para obtener los mismos resultados de ambas redes.

initial_point = algorithm_globals.random.random(ansatz.num_parameters)

Construcción de un regresor usando OpflowQNN#

Creamos una instancia de OpflowQNN y reutilizamos la quantum instance creada para el kernel cuántico.

from qiskit.opflow import PauliSumOp, StateFn
from qiskit_machine_learning.neural_networks import OpflowQNN

observable = PauliSumOp.from_list([("Z", 1)])
operator = StateFn(observable, is_measurement=True) @ StateFn(circuit)

opflow_qnn = OpflowQNN(
    operator=operator,
    input_params=feature_map.parameters,
    weight_params=ansatz.parameters,
    quantum_instance=sv_qi,
)

Construye un regresor a partir de la red, entrénalo y puntúalo. En este caso, utilizamos un optimizador basado en gradientes, por lo que la red utiliza el framework de gradiente y, debido a la naturaleza del conjunto de datos, converge muy rápidamente.

from qiskit.algorithms.optimizers import L_BFGS_B
from qiskit_machine_learning.algorithms import NeuralNetworkRegressor

regressor = NeuralNetworkRegressor(
    neural_network=opflow_qnn,
    optimizer=L_BFGS_B(maxiter=5),
    initial_point=initial_point,
)
regressor.fit(features, labels)
regressor.score(features, labels)
0.9681198723451012

Construcción de un regresor usando EstimatorQNN#

Crea una instancia de Estimator de referencia. Puedes crear una instancia de estimator desde QiskitRuntimeService para aprovechar los servicios de Qiskit runtime.

from qiskit.primitives import Estimator

estimator = Estimator()

Ahora, creamos una instancia de EstimatorQNN. La red crea un observable como \(Z^{\otimes n}\), donde \(n\) es el número de qubits, si no se especifica.

from qiskit_machine_learning.neural_networks import EstimatorQNN

estimator_qnn = EstimatorQNN(
    circuit=circuit,
    input_params=feature_map.parameters,
    weight_params=ansatz.parameters,
    estimator=estimator,
)

Construye un regresor cuántico variacional y ajústalo. En este caso, usamos un optimizador basado en gradientes, por lo que la red utiliza el gradiente de estimator predeterminado que es creado automáticamente.

from qiskit.algorithms.optimizers import L_BFGS_B
from qiskit_machine_learning.algorithms import VQR

regressor = NeuralNetworkRegressor(
    neural_network=estimator_qnn,
    optimizer=L_BFGS_B(maxiter=5),
    initial_point=initial_point,
)
regressor.fit(features, labels)
regressor.score(features, labels)
0.9681198723451012

En lugar de construir una red neuronal cuántica manualmente, puedes entrenar un VQR. Toma una quantum instance o un estimator, dependiendo de lo que se pase, automáticamente construye TwoLayerQNN o EstimatorQNN respectivamente.

Otros casos de obsolescencia notables#

Algunos otros componentes, no mencionados explícitamente anteriormente, también están obsoletos o están pendientes de obsolescencia:

import qiskit.tools.jupyter

%qiskit_version_table
%qiskit_copyright

Version Information

Qiskit SoftwareVersion
qiskit-terra0.25.0
qiskit-aer0.13.0
qiskit-machine-learning0.7.0
System information
Python version3.8.13
Python compilerClang 12.0.0
Python builddefault, Oct 19 2022 17:54:22
OSDarwin
CPUs10
Memory (Gb)64.0
Thu Sep 14 13:57:31 2023 IST

This code is a part of Qiskit

© Copyright IBM 2017, 2023.

This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.

Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.