Can anyone provide a good link to understand how to implement qgan using pytorch in qiskit. Trying to understand this ( https://qiskit.org/documentation/machine-learning/tutorials/04_qgans_for_loading_random_distributions.html) but being a beginner not able to understand much. Thanks in adv.

  • $\begingroup$ I am afraid you have to share a bit more with us. What have you tried so far? Are you familiar with classical GANs? Are you familiar with QNNs? Both are relatively broad topics and how to implement a GAN is not necessarily something you'd describe from scratch in a SO answer. $\endgroup$
    – 3yakuya
    Jan 16, 2022 at 3:16
  • $\begingroup$ I have tried quantum NN using pytorch and have studied about classical GAN. I don't have much idea about QGAN. I wanted to implement the QGAN technique to increase the dataset size. But not sure how to go about it. $\endgroup$
    – user14924
    Jan 16, 2022 at 12:50
  • 1
    $\begingroup$ This course in the Qiskit Textbook has a page on QGANs (although using tensorflow instead of pytorch). If you need more background, you can start from the beginning of the course. $\endgroup$
    – Frank
    Jan 17, 2022 at 11:38

2 Answers 2


qGAN is a hybrid quantum + classical algorithm for generative modeling. We make one quantum generator ( which is a quantum circuit i.e. parameterized), which you can take from qiskit.circuit.library. There are options of RealAmplitues, EfficientSU2, and PauliTwoDesign. The discriminator on the other hand is a classical neural network, made using pytorch.

Let's suppose you want to load a normal distribution, with a mean of $4$ and std dev of $1$ and your data ranges from $0$ to $7$. Basically, you want to load a distribution that looks like this:

Distribution you want to load on Quantum Circuit

The code is updated with the current Qiskit version (0.45)

# these are necessary packages
import torch
import numpy as np
import matplotlib.pyplot as plt
from qiskit_algorithms.utils import algorithm_globals

Then see how many qubits you require for your distribution:

num_dim = 1  # you can set this to 2 if you have a multivariate distribution
num_discrete_values = 8 #how many discrete values your distribution has

num_qubits = num_dim * int(np.log2(num_discrete_values))

print("Number of qubits required: ", num_qubits)

and define the distribution you want:

from scipy.stats import norm

coords = np.linspace(0,7, num_discrete_values)
rv = norm(loc= 4.0, scale=1.0) # mean and the std dev

prob_data = rv.pdf(coords)
prob_data = prob_data / np.sum(prob_data)

Making of the QNN

from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.circuit.library import EfficientSU2

qc = QuantumCircuit(num_qubits)

ansatz = EfficientSU2(num_qubits, reps=5) # you can use other ansatz as well
qc.compose(ansatz, inplace=True)


from qiskit.primitives import Sampler
from qiskit_machine_learning.connectors import TorchConnector   
from qiskit_machine_learning.neural_networks import SamplerQNN

shots = 10000
sampler = Sampler(options={"shots":shots,"seed":algorithm_globals.random_seed})

def create_generator() -> TorchConnector:
    """Generate a TorchConnector."""
    qnn = SamplerQNN(
    intial_weights = algorithm_globals.random.random(qc.num_parameters)
    return TorchConnector(qnn, intial_weights)

Classical Discriminator

from torch import nn

class Discriminator(nn.Module):

    def __init__(self, input_size):
        """Initialize discriminator."""
        self.linear_input = nn.Linear(input_size, 8)
        self.leaky_relu = nn.LeakyReLU(0.2)
        self.linear20 = nn.Linear(8, 1)
        self.sigmoid = nn.Sigmoid()

    def forward(self, input: torch.Tensor) -> torch.Tensor:
        x = self.linear_input(input)
        x = self.leaky_relu(x)
        x = self.linear20(x)
        x = self.sigmoid(x)
        """Compute discriminator output."""
        return x

Loss Function

$$ L(\theta) = \sum_j p_j(\theta) [y_j \log(x_j) + (1-y_j) \log(1-x_j)]$$

generator = create_generator()
discriminator = Discriminator(num_dim)
def adversarial_loss(input,target,w):
    bce_loss = target * torch.log(input) + (1-target) * torch.log(1-input)
    weigted_loss = w * bce_loss
    total_loss = -torch.sum(weigted_loss)
    return total_loss

and bring on the optimizer

from torch.optim import Adam

lr = 0.01 # learning rate
b1 = 0.7 # beta 1
b2 = 0.999 # beta 2

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)

If you want to, you can also visualize the entire training process:

from IPython.display import clear_output

def plot_training_progress():
    if len(generator_loss_values) < 2:
    fig, (ax1,ax2) = plt.subplots(1,2,figsize=(18,9))

    #generator loss


    # relative entropy
    ax2.set_title("Relative Entropy")
    ax2.plot(entropy_values,label="Relative Entropy",color="green")
    ax2.set_ylabel("Relative Entropy")


Training Process

import time
from scipy.stats import entropy

n_epochs = 50
num_qnn_outputs = num_discrete_values**num_dim

generator_loss_values = []
discriminator_loss_values = []
entropy_values = []

start = time.time()

for epoch in range(n_epochs):
    valid = torch.ones(num_qnn_outputs,1,dtype=torch.float)
    fake = torch.zeros(num_qnn_outputs,1,dtype=torch.float)

    # configure input
    real_dist =torch.tensor(prob_data, dtype=torch.float32).reshape(-1,1)

    # configure samples
    samples = torch.tensor(coords, dtype=torch.float32).reshape(-1,1)
    disc_value = discriminator(samples)

    # generate data

    gen_dist = generator(torch.tensor([])).reshape(-1,1)

    # train generator
    generator_loss = adversarial_loss(disc_value,valid,gen_dist)

    # store for plotting


    # train discriminator   
    real_loss = adversarial_loss(disc_value,valid,real_dist)
    fake_loss = adversarial_loss(disc_value,fake,gen_dist.detach())
    discriminator_loss = (real_loss + fake_loss) / 2

    # store for plotting


    entropy_value = entropy(gen_dist.detach().squeeze().numpy(),prob_data)

elapsed = time.time() - start
print(f"Fit in {elapsed:.2f}s.")

This code will give error if your dimensions of the matrices are not correct, so recheck them.

and you can see the final result as:

with torch.no_grad():
    samples = torch.tensor(coords, dtype=torch.float32).reshape(-1,1)
    disc_value = discriminator(samples)
    gen_dist = generator(torch.tensor([])).reshape(-1,1)
    generated_probabilities = generator().numpy()
    print("Discriminator output for real data: ", disc_value)
    print("Discriminator output for generated data: ", gen_dist)

or if you want to plot it:

fig = plt.figure(figsize=(18,9))

plt.plot(coords, generated_probabilities, label="Generated", color="red",marker="o")
plt.bar(coords, prob_data, label="Real", color="blue")

which will look something like this:

Final Result

You can also play with different hyperparameters and fine-tune your results, increase or decrease learning rate and get better result.


Maybe you can refer to this site, it is a site that converts papers into code, you can either understand the theory or try the demos they provide.



Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.