Idiomas
English
Bengali
French
Hindi
Italian
Japanese
Korean
Malayalam
Russian
Spanish
Tamil
Turkish
Vietnamese

Nota

Esta página fue generada a partir de docs/tutorials/12_quantum_autoencoder.ipynb.

El Autocodificador Cuántico

El objetivo de este tutorial es construir un Autocodificador Cuántico, un circuito que puede comprimir un estado cuántico en una cantidad más pequeña de qubits, mientras retiene la información del estado inicial.

A lo largo de este tutorial, explicamos la arquitectura de un Autocodificador Cuántico y cómo se puede diseñar y entrenar dicho sistema para comprimir y codificar información. Después de esta discusión, damos dos ejemplos para demostrar las capacidades de dicho sistema para comprimir diferentes estados cuánticos, así como la capacidad de comprimir imágenes de ceros y unos.

Contenido

El siguiente tutorial se desglosa de la siguiente manera:

  1. ¿Qué es un Autocodificador?

  2. El Autocodificador Cuántico

  3. Componentes de un Autocodificador Cuántico

  4. Elegir una Función de Pérdida

  5. Construir nuestro Autocodificador

  6. Un Ejemplo Simple: La Pared de Dominio

  7. Un Autocodificador Cuántico para Imágenes Ruidosas de Dígitos

  8. Aplicaciones de un Autocodificador Cuántico

  9. Referencias

1. ¿Qué es un Autocodificador?

Un autocodificador clásico (classical autoencoder, CAE) es un tipo de arquitectura de red neuronal que se usa comúnmente para comprimir y codificar de manera eficiente la información de la entrada mediante el aprendizaje de representación. Después de la compresión, se pueden descomprimir los datos mediante el uso de un decodificador.

Los autocodificadores típicos se dividen comúnmente en tres capas, como se ve en la Figura 1.

qae_fig1_wide.png Figura 1: Ejemplo de un Autocodificador Clásico que incluye la capa de entrada, cuello de botella y salida.

La primera capa se llama Capa de Entrada (1) y es la capa en la que ingresamos nuestros datos de longitud \(n\).

Luego, los datos de entrada pasan a través de un codificador y viajan a la siguiente capa, que tiene menos nodos o tiene dimensiones reducidas y se conoce como la capa de Cuello de Botella (2). La capa de entrada se comprime a través de este proceso. Los CAE comunes pueden tener varias capas.

La capa final se llama Capa de Salida (3). Aquí, los datos comprimidos se reconstruyen a su tamaño original, \(n\), a partir de los datos comprimidos mediante el proceso de un decodificador.

Al pasar nuestros datos de entrada a través de un CAE, podemos reducir la dimensionalidad de nuestros datos de entrada, como se ve en la capa de cuello de botella, mientras retenemos la mayor cantidad de información posible de los datos de entrada. Debido a esta característica, los usos comunes del CAE son dispositivos de eliminación de ruido de imagen, detección de anomalías y reconocimiento facial. Para obtener más información sobre los autocodificadores clásicos, consulta [1].

2. El Autocodificador Cuántico

También podemos definir una contraparte cuántica del CAE, el Autocodificador Cuántico (Quantum Autoencoder). Al igual que el CAE, el Autocodificador Cuántico tiene como objetivo reducir la dimensionalidad de la entrada de la red neuronal, en este caso un estado cuántico. Una representación pictórica de esto se puede ver en la Figura 2.

qae_fig2_wide.png Figura 2: Representación pictórica de un Autocodificador Cuántico. Aquí se pueden ver las similitudes con el CAE, teniendo el circuito un estado de entrada, un estado de cuello de botella y un estado de salida.

Al igual que su contraparte clásica, nuestro circuito contiene tres capas. Primero ingresamos nuestro estado \(|\psi>\) (que contiene \(n\) qubits), el cual deseamos comprimir. Esta es nuestra capa de entrada (1).

We then apply our parametrized circuit on our input state, which will act as our encoder and “compresses” our quantum state, reducing the dimensionality of our state to \(n-k\) qubits. Our new compressed state is of the form \(|\psi_{comp}> \otimes |0>^{\otimes k}\), where \(|\psi_{comp}>\) contains \(n-k\) qubits.

Este circuito parametrizado dependerá de un conjunto de parámetros, que serán los nodos de nuestro Autocodificador Cuántico. A lo largo del proceso de entrenamiento, estos parámetros se actualizarán para optimizar la función de pérdida.

Ignoramos los \(k\) qubits restantes para el resto del circuito. Esta es nuestra capa de cuello de botella (2) y nuestro estado de entrada ahora está comprimido.

La capa final consiste en la adición de \(k\) qubits (todos en el estado \(|0\rangle\)) y aplicar otro circuito parametrizado entre el estado comprimido y los nuevos qubits. Este circuito parametrizado actúa como nuestro decodificador y reconstruye el estado de entrada a partir del estado comprimido usando los nuevos qubits. Después del decodificador, retenemos el estado original a medida que el estado viaja a la capa de salida (3).

3. Componentes de un Autocodificador Cuántico

Antes de construir nuestro Autocodificador Cuántico, debemos tener en cuenta algunas sutilezas.

Primero notamos que no podemos introducir o descartar qubits en medio de un circuito cuántico cuando implementamos un autocodificador usando Qiskit.

Por eso, debemos incluir nuestro estado de referencia, así como nuestros qubits auxiliares (cuya función se describirá en secciones posteriores) al comienzo del circuito.

Por lo tanto, nuestra capa de entrada consistirá en nuestro estado de entrada, el estado de referencia y un qubit auxiliar, así como un registro clásico para realizar mediciones (que se describirá en la siguiente sección). Una representación pictórica de esto se puede ver en la Figura 3.

qae_fig3_wide.png Figura 3: Representación pictórica del estado de entrada de un Autocodificador Cuántico. Ten en cuenta que también debemos incluir un qubit auxiliar, el estado de referencia y el registro clásico al comienzo del circuito, aunque no se usan hasta más adelante en el circuito.

4. Elegir una Función de Pérdida

We now define our cost function, which we will use to train our Quantum Autoencoder, to return the input state. There’s a bit of math involved here, so skip this section if you’re not interested!

Tomamos la función de costo como se define en [2], que trata de maximizar la fidelidad entre el estado de entrada y salida de nuestro Autocodificador Cuántico.

Primero definimos los subsistemas \(A\) y \(B\) para contener \(n\) y \(k\) qubits respectivamente, mientras que \(B'\) es el espacio que contendrá nuestro espacio de referencia. Llamamos al subsistema \(A\) nuestro espacio latente, que contendrá el estado de qubit comprimido, y \(B\) nuestro espacio basura, que contiene los qubits que ignoramos durante la compresión.

Por lo tanto, nuestro estado de entrada \(|\psi_{AB}>\) contiene \(n + k\) qubits. Definimos el espacio de referencia \(B'\) que contiene el estado de referencia \(|a>_{B'}\). Este espacio contendrá los \(k\) qubits adicionales que usamos en el decodificador. Todos estos subsistemas se pueden ver en la Figura 3.

Definimos el circuito parametrizado como \(U(\theta)\) que usaremos como nuestro codificador. Sin embargo, la estructura y los parámetros de nuestro circuito parametrizado son actualmente desconocidos para nosotros y pueden variar para diferentes estados de entrada. Para determinar los parámetros para comprimir nuestro estado de entrada, debemos entrenar nuestro dispositivo para comprimir al máximo el estado ajustando los valores de los parámetros \(\theta\). Para el decodificador usaremos \(U^{\dagger}(\theta)\).

Por lo tanto, nuestro objetivo es maximizar la fidelidad entre los estados de entrada y salida, es decir,

\[\text{max }F(\psi_{AB}, \rho_{out})\]

donde

\[\rho_{out} = U^{\dagger}(\theta)_{AB'} \text{Tr}_{B} [U(\theta)_{AB}[\psi_{AB} \otimes a_{B'}]U^{\dagger}(\theta)_{AB}]U(\theta)_{AB'}\]

We can maximize this fidelity by tuning the parameters \(\theta\) in our parametrized circuit. However, this fidelity can at times be complicated to determine and may require a large amount of gates needed to calculate the fidelity between two states, i.e. the larger the number of qubits, the more gates required which results to deeper circuits. Therefore we look for alternative means of comparing the input and output states.

Como se muestra en [2], una forma más sencilla de determinar un estado comprimido de manera óptima es realizar una compuerta de intercambio entre el estado basura y el estado de referencia. Estos estados suelen tener una menor cantidad de qubits y, por lo tanto, son más fáciles de comparar debido a la menor cantidad de compuertas requeridas. Como se muestra en [2], maximizar la fidelidad de estos dos estados es equivalente a maximizar la fidelidad del estado de entrada y salida y, en consecuencia, determinar una compresión óptima de nuestro circuito de entrada.

Manteniendo fijo nuestro estado de referencia, nuestra función de costo ahora será una función del estado basura y se denota como;

\[\text{max }F(\text{Tr}_{A} [ U(\theta)_{AB}\psi_{AB} U^{\dagger}(\theta)_{AB}], a_{B'})\]

A lo largo del proceso de entrenamiento, ajustamos los parámetros \(\theta\) en nuestro codificador y realizamos una prueba swap o de intercambio (como se describe a continuación) para determinar la fidelidad entre estos estados basura y de referencia. Al hacerlo, debemos incluir un qubit adicional, nuestro qubit auxiliar, que se usará a lo largo de la prueba swap y se medirá para determinar la fidelidad general de los estados basura y de referencia. Esta es la razón por la que incluimos un qubit auxiliar y un registro clásico en la sección anterior al inicializar nuestro circuito.

La Prueba SWAP

La Prueba SWAP (prueba de intercambio) es un procedimiento comúnmente utilizado para comparar dos estados mediante la aplicación de compuertas CNOT a cada qubit (para obtener más información, consulta [3]). Al ejecutar el circuito \(M\) veces y aplicar la prueba SWAP, medimos el qubit auxiliar. Usamos el número de estados en el estado \(|1\rangle\) a calcular:

\[S = 1 - \frac{2}{M}L\]

where \(L\) is the count for the states in the \(|1\rangle\) state. As shown in [3], maximizing this function corresponds to the two states of which we are comparing being identical. We therefore aim to maximize this function, i.e. minimize \(\frac{2}{M}L\). This value will be therefore be our cost function.

5. Construir el Ansatz del Autocodificador Cuántico

First, we implement IBM’s Qiskit to build our Quantum Autoencoder. We first begin by importing in the necessary libraries and fixing the seed.

[1]:
import json
import time
import warnings

import matplotlib.pyplot as plt
import numpy as np
from IPython.display import clear_output
from qiskit import ClassicalRegister, QuantumRegister
from qiskit import QuantumCircuit
from qiskit.algorithms.optimizers import COBYLA
from qiskit.circuit.library import RealAmplitudes
from qiskit.quantum_info import Statevector
from qiskit.utils import algorithm_globals

from qiskit_machine_learning.circuit.library import RawFeatureVector
from qiskit_machine_learning.neural_networks import SamplerQNN

algorithm_globals.random_seed = 42

Comenzamos definiendo nuestro ansatz parametrizado para el Autocodificador Cuántico. Este será nuestro circuito parametrizado donde podemos ajustar los parámetros para maximizar la fidelidad entre los estados basura y de referencia.

El Circuito Parametrizado

El circuito parametrizado que usaremos a continuación para nuestro codificador es el Ansatz RealAmplitude disponible en Qiskit. Una de las razones por las que hemos elegido este ansatz es porque es un circuito 2-local, los estados cuánticos preparados solo tendrán amplitudes reales y no dependen de la conectividad completa entre cada qubit, lo cual es difícil de implementar o puede conducir a circuitos profundos.

Definimos nuestro circuito parametrizado para nuestro codificador a continuación, donde configuramos el parámetro de repetición en reps=5, para aumentar la cantidad de parámetros en nuestro circuito y permitir una mayor flexibilidad.

[2]:
def ansatz(num_qubits):
    return RealAmplitudes(num_qubits, reps=5)

Let’s draw this ansatz with \(5\) qubits and see what it looks like.

[3]:
num_qubits = 5
circ = ansatz(num_qubits)
circ.decompose().draw("mpl")
[3]:
../_images/tutorials_12_quantum_autoencoder_25_0.png

Ahora aplicamos este Codificador al estado que deseamos comprimir. En este ejemplo, dividimos nuestro estado inicial de \(5\) qubits en un estado latente de \(3\) qubits (\(n = 3\)) y un espacio basura de \(2\) qubits (\(k = 2\)).

Como se explicó en la sección anterior, también debemos incluir un espacio de referencia de \(2\) qubits en nuestro circuito, así como un qubit auxiliar para realizar la prueba swap entre los estados de referencia y basura. Por lo tanto, tendremos un total de \(2 + 3 + 2 + 1 = 8\) qubits y \(1\) registro clásico en nuestro circuito.

Después de inicializar nuestro estado, aplicamos nuestro circuito parametrizado.

Después de esto, dividimos nuestro estado inicial en el espacio latente (el estado comprimido) y el espacio basura (la parte del estado que descartaremos) y realizamos la prueba swap entre el estado de referencia y el espacio basura. Luego se mide el último qubit para determinar la fidelidad entre los estados de referencia y basura. Una representación pictórica de esto se da a continuación en la Figura 4.

qae_fig4_wide.png

Figura 4: Ejemplo de un Autocodificador Cuántico en proceso de entrenamiento. Usamos la prueba swap para determinar la fidelidad entre los espacios basura y de referencia.

Definimos una función a continuación para implementar la configuración del circuito anterior en el estado de pared de dominio \(|00111\rangle\) de \(5\) qubits y graficar un ejemplo a continuación. Aquí los qubits \(5\) y \(6\) son el estado de referencia, \(0, 1, 2, 3, 4\) son el estado inicial que deseamos comprimir y el qubit \(7\) es nuestro qubit auxiliar que se usa en la prueba swap. También incluimos un registro clásico para medir los resultados del qubit \(7\) en la prueba swap.

[4]:
def auto_encoder_circuit(num_latent, num_trash):
    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


num_latent = 3
num_trash = 2
circuit = auto_encoder_circuit(num_latent, num_trash)
circuit.draw("mpl")
[4]:
../_images/tutorials_12_quantum_autoencoder_30_0.png

Para reconstruir el estado de entrada original, debemos aplicar el adjunto de nuestro circuito parametrizado después de la prueba swap. Sin embargo, durante el entrenamiento, solo nos interesa el estado basura y el estado de referencia. Por lo tanto, podemos excluir las compuertas que siguen a la compresión hasta que deseemos reconstruir nuestra entrada inicial.

Después de construir nuestro Autocodificador Cuántico, el siguiente paso es entrenarlo para comprimir el estado y maximizar la función de costo y determinar los parámetros \(\theta\).

6. Un Ejemplo Simple: El Autocodificador de la Pared de Dominio

Let’s first begin with a simple example, a state known as the Domain Wall, which for \(5\) qubits is given by \(|00111\rangle\). Here we will try and compress this state from \(5\) qubits to \(3\) qubits, with the remaining qubits in the trash space, in the state \(|00\rangle\). We can create a function to build the domain wall state below.

[5]:
def domain_wall(circuit, a, b):
    # Here we place the Domain Wall to qubits a - b in our circuit
    for i in np.arange(int(b / 2), int(b)):
        circuit.x(i)
    return circuit


domain_wall_circuit = domain_wall(QuantumCircuit(5), 0, 5)
domain_wall_circuit.draw("mpl")
[5]:
../_images/tutorials_12_quantum_autoencoder_34_0.png

Now let’s train our Autoencoder to compress this state from 5 qubits to 3 qubits (qubits 0,1 and 2), with the remaining qubits in the trash space (qubits 3 and 4) being in the |00> state.

Creamos un circuito para usar en la función de pérdida, como se describe en la Sección 4, que determina la fidelidad entre los dos estados a continuación usando la prueba swap para nuestra función particular de Autocodificador. Para obtener más información sobre la prueba swap, consulta [1].

[6]:
ae = auto_encoder_circuit(num_latent, num_trash)
qc = QuantumCircuit(num_latent + 2 * num_trash + 1, 1)
qc = qc.compose(domain_wall_circuit, range(num_latent + num_trash))
qc = qc.compose(ae)
qc.draw("mpl")
[6]:
../_images/tutorials_12_quantum_autoencoder_37_0.png

Luego, creamos una red neuronal cuántica y pasamos el circuito como parámetro. Notamos que esta red debe tomar una función de interpretación, que determina cómo mapeamos la salida de la red a la forma de salida. Dado que medimos solo un qubit, la salida de la red es una cadena de bits, ya sea \(0\) o \(1\), por lo que la forma de salida es \(2\), el número de resultados posibles. Luego, introducimos un mapeo de identidad. La salida de la red es un vector de probabilidades de obtener cadenas de bits interpretadas. Por lo tanto, obtenemos las probabilidades de obtener \(0\) o \(1\) y esto es exactamente lo que estamos buscando. En la función de costo hacemos uso de la probabilidad de obtener \(1\) y penalizamos los resultados que llevan a \(1\), maximizando así la fidelidad entre el espacio basura y el espacio de referencia.

[7]:
# Here we define our interpret for our SamplerQNN
def identity_interpret(x):
    return x


qnn = SamplerQNN(
    circuit=qc,
    input_params=[],
    weight_params=ae.parameters,
    interpret=identity_interpret,
    output_shape=2,
)

A continuación, creamos nuestra función de costo. Como se describe en la sección anterior, nuestro objetivo es minimizar \(\frac{2}{M}L\), que es el doble de probabilidad de obtener el qubit final en el estado \(|1\rangle\). Por lo tanto, deseamos minimizar la posibilidad de obtener un \(|1\rangle\) en el qubit 7.

La función de costo también graficará el valor objetivo en cada evaluación de la función de costo.

[8]:
def cost_func_domain(params_values):
    probabilities = qnn.forward([], params_values)
    # we pick a probability of getting 1 as the output of the network
    cost = np.sum(probabilities[:, 1])

    # plotting part
    clear_output(wait=True)
    objective_func_vals.append(cost)
    plt.title("Objective function value against iteration")
    plt.xlabel("Iteration")
    plt.ylabel("Objective function value")
    plt.plot(range(len(objective_func_vals)), objective_func_vals)
    plt.show()
    return cost

Ahora entrenaremos nuestro Autocodificador para reducir la dimensión del espacio de Hilbert de \(5\) qubits a \(3\), dejando el espacio basura en el estado \(|00\rangle\). Inicialmente, establecemos los parámetros \(\theta\) en valores aleatorios y ajustamos estos parámetros para minimizar nuestra función de costo mediante el uso del optimizador COBYLA.

[9]:
opt = COBYLA(maxiter=150)
initial_point = algorithm_globals.random.random(ae.num_parameters)

objective_func_vals = []
# make the plot nicer
plt.rcParams["figure.figsize"] = (12, 6)

start = time.time()
opt_result = opt.minimize(cost_func_domain, initial_point)
elapsed = time.time() - start

print(f"Fit in {elapsed:0.2f} seconds")
../_images/tutorials_12_quantum_autoencoder_43_0.png
Fit in 18.48 seconds

Looks like it has converged! After training our Quantum Autoencoder, let’s build it and see how well it compresses the state!

Para hacer esto, primero aplicamos nuestro autocodificador a un estado de pared de dominio de \(5\) qubits. Después de aplicar este estado, el estado comprimido debe tener la forma \(|00\rangle\). Por lo tanto, restablecer los dos últimos qubits no debería afectar a nuestro estado general.

Después de restablecer, aplicamos nuestro decodificador (el conjugado hermitiano de nuestro codificador) y lo comparamos con el estado inicial determinando la fidelidad. Si nuestra fidelidad es uno, entonces nuestro Autocodificador ha codificado toda la información de la pared de dominio de manera eficiente en un conjunto más pequeño de qubits y al decodificar, ¡retenemos el estado original!

Let’s first apply our circuit to the Domain Wall State, using the parameters we obtained when training our Quantum Autoencoder. (Note we have included barriers in our circuit below, however these are not necessary for the implementation of the Quantum Autoencoder and are used to determine between different sections of our circuit).

[10]:
test_qc = QuantumCircuit(num_latent + num_trash)
test_qc = test_qc.compose(domain_wall_circuit)
ansatz_qc = ansatz(num_latent + num_trash)
test_qc = test_qc.compose(ansatz_qc)
test_qc.barrier()
test_qc.reset(4)
test_qc.reset(3)
test_qc.barrier()
test_qc = test_qc.compose(ansatz_qc.inverse())

test_qc.draw("mpl")
[10]:
../_images/tutorials_12_quantum_autoencoder_46_0.png

Ahora asignamos los valores de los parámetros obtenidos en el entrenamiento.

[11]:
test_qc = test_qc.assign_parameters(opt_result.x)

Now let’s get the statevectors of our Domain Wall state and output circuit and calculate the fidelity!

[12]:
domain_wall_state = Statevector(domain_wall_circuit).data
output_state = Statevector(test_qc).data

fidelity = np.sqrt(np.dot(domain_wall_state.conj(), output_state) ** 2)
print("Fidelity of our Output State with our Input State: ", fidelity.real)
Fidelity of our Output State with our Input State:  0.9832814006314854

Como puedes ver, nuestra fidelidad es bastante alta y, por lo tanto, nuestro autocodificador ha comprimido nuestro conjunto de datos mientras ¡retiene toda la información del estado de entrada!

Ahora veremos si podemos aplicar dicho Autocodificador Cuántico a conjuntos de datos más complicados que contienen ruido, como imágenes de los números cero y uno.

7. Un Autocodificador Cuántico para Compresión Digital

También se puede aplicar un Autocodificador Cuántico a ejemplos más complicados, como un conjunto de dígitos escritos a mano para comprimir el conjunto de datos. A continuación, mostraremos que, de hecho, podemos entrenar un Autocodificador Cuántico para comprimir un ejemplo de este tipo, lo que nos brinda la capacidad de almacenar datos de manera más eficiente en una computadora cuántica.

Para este tutorial, crearemos un Autocodificador Cuántico para un conjunto de datos ruidoso que contiene ceros y unos, que se puede ver a continuación.

Each image contains \(32\) pixels of which can be encoded into \(5\) qubits by Amplitude Encoding. This can be done using Qiskit’s RawFeatureVector feature map.

[13]:
def zero_idx(j, i):
    # Index for zero pixels
    return [
        [i, j],
        [i - 1, j - 1],
        [i - 1, j + 1],
        [i - 2, j - 1],
        [i - 2, j + 1],
        [i - 3, j - 1],
        [i - 3, j + 1],
        [i - 4, j - 1],
        [i - 4, j + 1],
        [i - 5, j],
    ]


def one_idx(i, j):
    # Index for one pixels
    return [[i, j - 1], [i, j - 2], [i, j - 3], [i, j - 4], [i, j - 5], [i - 1, j - 4], [i, j]]


def get_dataset_digits(num, draw=True):
    # Create Dataset containing zero and one
    train_images = []
    train_labels = []
    for i in range(int(num / 2)):
        # First we introduce background noise
        empty = np.array([algorithm_globals.random.uniform(0, 0.1) for i in range(32)]).reshape(
            8, 4
        )

        # Now we insert the pixels for the one
        for i, j in one_idx(2, 6):
            empty[j][i] = 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])
            plt.show()

    for i in range(int(num / 2)):
        empty = np.array([algorithm_globals.random.uniform(0, 0.1) for i in range(32)]).reshape(
            8, 4
        )

        # Now we insert the pixels for the zero
        for k, j in zero_idx(2, 6):
            empty[k][j] = algorithm_globals.random.uniform(0.9, 1)

        train_images.append(empty)
        train_labels.append(0)
        if draw:
            plt.imshow(train_images[-1])
            plt.title("This is a Zero")
            plt.show()

    train_images = np.array(train_images)
    train_images = train_images.reshape(len(train_images), 32)

    for i in range(len(train_images)):
        sum_sq = np.sum(train_images[i] ** 2)
        train_images[i] = train_images[i] / np.sqrt(sum_sq)

    return train_images, train_labels


train_images, __ = get_dataset_digits(2)
../_images/tutorials_12_quantum_autoencoder_54_0.png
../_images/tutorials_12_quantum_autoencoder_54_1.png

Después de codificar nuestra imagen en \(5\) qubits, comenzamos a entrenar nuestro Autocodificador Cuántico para comprimir este estado en \(3\) qubits.

Repetimos los pasos del ejemplo anterior y escribimos una función de costo, nuevamente basada en la Prueba Swap entre los espacios basura y latente. También podemos usar la misma función de Autocodificador que se proporciona en el ejemplo anterior, ya que el estado de entrada y el espacio basura contienen la misma cantidad de qubits.

Let’s input one of our digits and see our circuit for the Autoencoder below.

[14]:
num_latent = 3
num_trash = 2

fm = RawFeatureVector(2 ** (num_latent + num_trash))

ae = auto_encoder_circuit(num_latent, num_trash)

qc = QuantumCircuit(num_latent + 2 * num_trash + 1, 1)
qc = qc.compose(fm, range(num_latent + num_trash))
qc = qc.compose(ae)

qc.draw("mpl")
[14]:
../_images/tutorials_12_quantum_autoencoder_56_0.png

Nuevamente, podemos ver la prueba swap que se realiza en los qubits \(3\), \(4\), \(5\) y \(6\), que determinarán el valor de nuestra función de costo.

[15]:
def identity_interpret(x):
    return x


qnn = SamplerQNN(
    circuit=qc,
    input_params=fm.parameters,
    weight_params=ae.parameters,
    interpret=identity_interpret,
    output_shape=2,
)

We build our cost function, based on the swap test between the reference and trash space for the digit dataset. To do this, we again use Qiskit’s CircuitQNN network and use the same interpret function as we are measuring the probability of getting the final qubit in the \(|1\rangle\) state.

[16]:
def cost_func_digits(params_values):
    probabilities = qnn.forward(train_images, params_values)
    cost = np.sum(probabilities[:, 1]) / train_images.shape[0]

    # plotting part
    clear_output(wait=True)
    objective_func_vals.append(cost)
    plt.title("Objective function value against iteration")
    plt.xlabel("Iteration")
    plt.ylabel("Objective function value")
    plt.plot(range(len(objective_func_vals)), objective_func_vals)
    plt.show()

    return cost

Since model training may take a long time we have already pre-trained the model for some iterations and saved the pre-trained weights. We’ll continue training from that point by setting initial_point to a vector of pre-trained weights.

[17]:
with open("12_qae_initial_point.json", "r") as f:
    initial_point = json.load(f)

By minimizing this cost function, we can thus determine the required parameters to compress our noisy images. Let’s see if we can encode our images!

[18]:
opt = COBYLA(maxiter=150)

objective_func_vals = []
# make the plot nicer
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")
../_images/tutorials_12_quantum_autoencoder_64_0.png
Fit in 40.59 seconds

¡Parece que ha convergido!

Now let’s build our Encoder and Decoder using the parameters obtained from the training period. After applying this circuit to our new dataset, we can then compare our input and output data and see if we were able to retain the images efficiently throughout the compression!

[19]:
# Test
test_qc = QuantumCircuit(num_latent + num_trash)
test_qc = test_qc.compose(fm)
ansatz_qc = ansatz(num_latent + num_trash)
test_qc = test_qc.compose(ansatz_qc)
test_qc.barrier()
test_qc.reset(4)
test_qc.reset(3)
test_qc.barrier()
test_qc = test_qc.compose(ansatz_qc.inverse())

# sample new images
test_images, test_labels = get_dataset_digits(2, draw=False)
for image, label in zip(test_images, test_labels):
    original_qc = fm.assign_parameters(image)
    original_sv = Statevector(original_qc).data
    original_sv = np.reshape(np.abs(original_sv) ** 2, (8, 4))

    param_values = np.concatenate((image, opt_result.x))
    output_qc = test_qc.assign_parameters(param_values)
    output_sv = Statevector(output_qc).data
    output_sv = np.reshape(np.abs(output_sv) ** 2, (8, 4))

    fig, (ax1, ax2) = plt.subplots(1, 2)
    ax1.imshow(original_sv)
    ax1.set_title("Input Data")
    ax2.imshow(output_sv)
    ax2.set_title("Output Data")
    plt.show()
../_images/tutorials_12_quantum_autoencoder_66_0.png
../_images/tutorials_12_quantum_autoencoder_66_1.png

It looks like our Quantum Autoencoder can be trained to encode digits as well! Now it’s your turn to build your own Quantum Autoencoder and come up with ideas and datasets to compress!

8. Aplicaciones de un Autocodificador Cuántico

Quantum Autoencoder’s can be used for various different applications, including

  1. Compresión digital: donde la información se puede codificar en una cantidad menor de qubits. Esto puede ser muy beneficioso para los dispositivos cuánticos de término cercano, ya que los sistemas de qubits más pequeños son menos propensos al ruido.

  2. Eliminación de ruido: donde se puede usar Autocodificación Cuántica para extraer características relevantes del estado cuántico inicial o datos codificados, mientras se ignora cualquier ruido adicional.

  3. Química Cuántica: en la que se puede utilizar un Autocodificador Cuántico como ansatz para sistemas, como el Modelo Hubbard. Esto se usa comúnmente para describir las interacciones electrón-electrón en las moléculas.

9. Referencias

  1. Una página de wikipedia sobre el Autocodificador: https://en.wikipedia.org/wiki/Autoencoder

  2. Romero, Jonathan, Jonathan P. Olson, and Alan Aspuru-Guzik. «Quantum autoencoders for efficient compression of quantum data.» Quantum Science and Technology 2.4 (2017): 045001.

  3. Algoritmo de la Prueba de Swap: https://en.wikipedia.org/wiki/Swap_test

[20]:
import qiskit.tools.jupyter

%qiskit_version_table
%qiskit_copyright

Version Information

Qiskit SoftwareVersion
qiskit-terra0.22.2
qiskit-aer0.11.1
qiskit-machine-learning0.6.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 Nov 10 23:26:05 2022 GMT

This code is a part of Qiskit

© Copyright IBM 2017, 2022.

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.