# Quantum GAN implementation

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.

• 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. Commented Jan 16, 2022 at 3:16
• 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. Commented Jan 16, 2022 at 12:50
• 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. Commented Jan 17, 2022 at 11:38
• github.com/Classiq/classiq-library/tree/main/algorithms/qml/… You can check how to do pytroch QGAN with Classiq QMOD language. There is a very nice tutorial over there, and I think it explains qGan very well over there Commented Jul 25 at 7:46

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:

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)
qc.h(range(num_qubits))

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


#### Generator:

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(
circuit=qc,
sampler=sampler,
input_params=[],
weight_params=qc.parameters,
sparse=False,
)

intial_weights = algorithm_globals.random.random(qc.num_parameters)



#### Classical Discriminator

from torch import nn

class Discriminator(nn.Module):
"""Discriminator."""

def __init__(self, input_size):
"""Initialize discriminator."""
super(Discriminator,self).__init__()
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)
bce_loss = target * torch.log(input) + (1-target) * torch.log(1-input)
weigted_loss = w * bce_loss
total_loss = -torch.sum(weigted_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:
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="blue")
ax1.plot(discriminator_loss_values,label="Discriminator_loss",color="red")
ax1.legend(loc='best')
ax1.set_xlabel("Iterations")
ax1.set_ylabel("Loss")
ax1.grid()

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

plt.show()


## 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

# store for plotting
generator_loss_values.append(generator_loss.detach().item())

generator_loss.backward(retain_graph=True)
generator_optimizer.step()

# train discriminator
discriminator_loss = (real_loss + fake_loss) / 2

# store for plotting
discriminator_loss_values.append(discriminator_loss.detach().item())

discriminator_loss.backward()
discriminator_optimizer.step()

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

plot_training_progress()
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")
plt.legend(loc='best')
plt.xlabel("Data")
plt.ylabel("Probabilities")
plt.grid()


which will look something like this:

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.