This Qiskit Machine Learning tutorial implements a quantum Generative Adversarial Network (qGAN) by combining Qiskit's quantum neural networks with PyTorch's deep-learning machinery. A GAN trains two networks against each other, a generator that learns to produce samples resembling a target distribution and a discriminator that learns to tell real samples from generated ones, and in a qGAN the generator is a parameterized quantum circuit whose measurement distribution is the learned generative model. The tutorial shows how to build the quantum generator as a SamplerQNN, wrap it with the TorchConnector so it integrates into a PyTorch training loop, pair it with a classical discriminator network, and train the adversarial pair with PyTorch optimizers and automatic differentiation, with quantum gradients supplied by Qiskit. It demonstrates learning to load a probability distribution into a quantum state, a useful subroutine for quantum algorithms that require state preparation from data. By marrying quantum circuits with the mature PyTorch ecosystem, the tutorial shows a practical path to hybrid quantum-classical generative modeling in Qiskit.
# --- Setup cell added by QCR (not part of the original tutorial) ---# Source: qiskit-community/qiskit-machine-learning @ 0.9.0, Apache License 2.0.# Installs the example's dependencies. If a later cell still reports a missing# package, restart the runtime/kernel and run again from the top.
%pip install -q qiskit-machine-learning==0.9.0 torch matplotlib pylatexenc
PyTorch qGAN Implementation
Overview
This tutorial introduces step-by-step how to build a PyTorch-based Quantum Generative Adversarial Network algorithm.
The qGAN [1] is a hybrid quantum-classical algorithm used for generative modeling tasks. The algorithm uses the interplay of a quantum generator , i.e., an ansatz (parametrized quantum circuit), and a classical discriminator , a neural network, to learn the underlying probability distribution given training data.
The generator and discriminator are trained in alternating optimization steps, where the generator aims at generating probabilities that will be classified by the discriminator as training data values (i.e, probabilities from the real training distribution), and the discriminator tries to differentiate between original distribution and probabilities from the generator (in other words, telling apart the real and generated distributions). The final goal is for the quantum generator to learn a representation for the target probability distribution.
The trained quantum generator can, thus, be used to load a quantum state which is an approximate model of the target distribution.
Given -dimensional data samples, we employ a quantum Generative Adversarial Network (qGAN) to learn a random distribution and to load it directly into a quantum state:
where describe the occurrence probabilities of the basis states .
The aim of the qGAN training is to generate a state where , for , describe a probability distribution that is close to the distribution underlying the training data .
For an example of how to use a trained qGAN in an application, the pricing of financial derivatives, please see the
Option Pricing with qGANs tutorial.
2. Data and Representation
First, we need to load our training data .
In this tutorial, the training data is given by a 2D multivariate normal distribution.
The goal of the generator is to learn how to represent such distribution, and the trained generator should correspond to an -qubit quantum state
where the basis states represent the data items in the training data set
with and refers to the sampling probability
of .
To facilitate this representation, we need to map the samples from the multivariate
normal distribution to discrete values. The number of values that can be represented
depends on the number of qubits used for the mapping.
Hence, the data resolution is defined by the number of qubits.
If we use qubits to represent one feature, we have discrete values.
We first begin by fixing seeds in the random number generators for reproducibility of the outcome in this tutorial.
Then, we prepare a discrete distribution from the continuous 2D normal distribution. We evaluate the continuous probability density function (PDF) on the grid with a discretization of values per feature. Thus, we have values of the PDF. Since this will be a discrete distribution we normalize the obtained probabilities.
In this section we define two neural networks as described above:
A quantum generator as a quantum neural network.
A classical discriminator as a PyTorch-based neural network.
3.1. Definition of the quantum neural network ansatz
Now, we define the parameterized quantum circuit with which will be used in our quantum generator.
To implement the quantum generator, we choose a hardware efficient ansatz with repetitions. The ansatz implements , rotations and gates which takes a uniform distribution as an input state. Notably, for the generator's parameters must be chosen carefully. For example, the circuit depth should be more than because higher circuit depths enable the representation of more complex structures. Here, we construct quite a deep circuit with a large number of parameters to be able to adequately capture and represent the distribution.
In [5]:
from qiskit import QuantumCircuit
from qiskit.circuit.library import efficient_su2
qc = QuantumCircuit(num_qubits)
qc.h(qc.qubits)
ansatz = efficient_su2(num_qubits, reps=6)
qc.compose(ansatz, inplace=True)
Let's draw our circuit and see what it looks like. On the plot we may notice a pattern that appears times.
We start defining the generator by creating a sampler for the ansatz. The reference implementation is a statevector-based implementation, thus it returns exact probabilities as a result of circuit execution. In this case the implementation samples probabilities from the multinomial distribution constructed from the measured quasi probabilities.
In [8]:
from qiskit.primitives import StatevectorSampler as Sampler
sampler = Sampler()
Next, we define a function that creates the quantum generator from a given parameterized quantum circuit. Inside this function we create a neural network that returns the quasi probability distribution evaluated by the underlying Sampler. We fix initial_weights for reproducibility purposes. In the end we wrap the created quantum neural network in TorchConnector to make use of PyTorch-based training.
Next, we define a PyTorch-based classical neural network that represents the classical discriminator. The underlying gradients can be automatically computed with PyTorch.
In [10]:
from torch import nn
classDiscriminator(nn.Module):
def__init__(self, input_size):
super(Discriminator, self).__init__()
self.linear_input = nn.Linear(input_size, 20)
self.leaky_relu = nn.LeakyReLU(0.2)
self.linear20 = nn.Linear(20, 1)
self.sigmoid = nn.Sigmoid()
defforward(self, input: torch.Tensor) -> torch.Tensor:
x = self.linear_input(input)
x = self.leaky_relu(x)
x = self.linear20(x)
x = self.sigmoid(x)
return x
No gradient function provided, creating a gradient function. If your Sampler requires transpilation, please provide a pass manager.
4. Setting up the Training Loop
In this section we set up:
A loss function for the generator and discriminator.
Optimizers for both.
A utility plotting function to visualize training process.
4.1. Definition of the loss functions
We want to train the generator and the discriminator with binary cross entropy as the loss function:
where refers to a data sample and to the corresponding label.
Since PyTorch's binary_cross_entropy is not differentiable with respect to weights, we implement the loss function manually to be able to evaluate gradients.
In order to train the generator and discriminator, we need to define optimization schemes. In the following, we employ a momentum based optimizer called Adam, see Kingma et al., Adam: A method for stochastic optimization for more details.
In [13]:
from torch.optim import Adam
lr = 0.01# learning rate
b1 = 0.7# first momentum parameter
b2 = 0.999# second momentum parameter
generator_optimizer = Adam(generator.parameters(), lr=lr, betas=(b1, b2), weight_decay=0.005)
discriminator_optimizer = Adam(
discriminator.parameters(), lr=lr, betas=(b1, b2), weight_decay=0.005
)
4.3. Visualization of the training process
We will visualize what is happening during the training by plotting the evolution of the generator's and the discriminator's loss functions during the training, as well as the progress in the relative entropy between the trained and the target distribution. We define a function that plots the loss functions and relative entropy. We call this function once an epoch of training is complete.
Visualization of the training process begins when training data is collected across two epochs.
In [14]:
from IPython.display import clear_output
defplot_training_progress():
# we don't plot if we don't have enough dataiflen(generator_loss_values) < 2:
return
clear_output(wait=True)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(18, 9))
# Generator Loss
ax1.set_title("Loss")
ax1.plot(generator_loss_values, label="generator loss", color="royalblue")
ax1.plot(discriminator_loss_values, label="discriminator loss", color="magenta")
ax1.legend(loc="best")
ax1.set_xlabel("Iteration")
ax1.set_ylabel("Loss")
ax1.grid()
# Relative Entropy
ax2.set_title("Relative entropy")
ax2.plot(entropy_values)
ax2.set_xlabel("Iteration")
ax2.set_ylabel("Relative entropy")
ax2.grid()
plt.show()
5. Model Training
In the training loop we monitor not only loss functions, but relative entropy as well. The relative entropy describes a distance metric for distributions. Hence, we can use it to benchmark how close/far away the trained distribution is from the target distribution.
Now, we are ready to train our model. It may take some time to train the model so be patient.
In this section we compare the cumulative distribution function (CDF) of the trained distribution to the CDF of the target distribution.
First, we generate a new probability distribution with PyTorch autograd turned off as we are not going to train the model anymore.
In [16]:
with torch.no_grad():
generated_probabilities = generator().numpy()
And then, we plot the cumulative distribution functions of the generated distribution, original distribution, and the difference between them. Please, be careful, the scale on the third plot is not the same as on the first and second plot, and the actual difference between the two plotted CDFs is pretty small.
Quantum generative adversarial networks employ the interplay of a generator and discriminator to map an approximate representation of a probability distribution underlying given data samples into a quantum channel. This tutorial presents a self-standing PyTorch-based qGAN implementation where the generator is given by a quantum channel, i.e., a variational quantum circuit, and the discriminator by a classical neural network, and discusses the application of efficient learning and loading of generic probability distributions into quantum states. The loading requires gates and can thus enable the use of potentially advantageous quantum algorithms.
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.
In [ ]:
Join the Discussion
Comments (0)
No comments yet. Be the first to share your thoughts!
Indexed by QCR Librarian
This entry was created automatically from publicly available records. QCR links to public sources and only stores repository content where the license permits redistribution.
Join the Discussion
Comments (0)
No comments yet. Be the first to share your thoughts!