Problem on running quantumGAN

This is the link of my notebook code, it is a quantum GAN for images:

This is the error on google colab: I tried to set the max value of recursion limit and if I increase it over 10000, the session will crash.
RecursionError Traceback (most recent call last)
in <cell line: 1>()
----> 1 discriminator = Discriminator().to(device)
2 generator = PatchQuantumGenerator(n_generators).to(device)
3
4 # Binary cross entropy
5 criterion = nn.BCELoss()

2 frames
… last 1 frames repeated, from the frame below …

/usr/local/lib/python3.10/dist-packages/torch/nn/modules/module.py in _apply(self, fn)
795 def _apply(self, fn):
796 for module in self.children():
→ 797 module._apply(fn)
798
799 def compute_should_use_set_data(tensor, tensor_applied):

RecursionError: maximum recursion depth exceeded while calling a Python object

Hi @Eleonora_Panini ,

Thank you for your question.

I see you started from our demo on Quantum GANs and modified some things. I would recommend trying to run the demo as-is and letting us know if you also run into this issue.

From the error traceback I can see that the error arises from Torch. If the demo works but your code doesn’t then I would recommend making a smaller version of this problem and testing to see if you get the same error.

If after reducing your dataset and minimizing the changes in the code with respect to the demo, you still have the same error please share your code again with us specifying every line that you changed with respect to the demo and we’ll do our best to help you.

I hope this helps you debug your problem.

This is the full code, I tried to reduce the dataset from 200 images RGB (100 for each classes) to 20 and the output have the same problem when I launch the training cell (RecursionError: maximum recursion depth exceeded while calling a Python object). I provide you the part of code that I modified in order to adapt the demo to my code/dataset: look at the respective comments on the code below. Do you have any idea about how to solve the problem?
I appreciate your help, and thank you in advance.
import math
import random
import numpy as np
#import pandas as pd
import cv2
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
#import pennylane as qml
import sys
import os

Pytorch imports

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import Dataset, DataLoader
from PIL import Image
import sklearn
from sklearn.preprocessing import MinMaxScaler
#%%
import pennylane as qml
#%%

Set the random seed for reproducibility

seed = 42
torch.manual_seed(seed)
np.random.seed(seed)
random.seed(seed)

THIS PART OF TEXT READ THE DATASET, RESIZE IMAGES 64X64 AND STORE IN ARRAY
#‘class DigitsDataset(Dataset)’ OF THE DEMO IS REPLACED BY THE FOLLOWING CODE:
ImgLocation=“C:/Users/elyon/OneDrive/Desktop/Tesi/dataset/”

List image categories we are interested in

CATEGORIES = set([“Dilbert”,“Boss”])

Create a list to store image paths

ImagePaths=
for category in CATEGORIES:
for image in list(os.listdir(ImgLocation+category)):
ImagePaths=ImagePaths+[ImgLocation+category+“/”+image]

Load images and resize to 200x200

data_lowres=
for img in ImagePaths:
image = cv2.imread(img)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image_lowres = cv2.resize(image, (64, 64))
data_lowres.append(image_lowres)

Convert image data to numpy array and standardize values (divide by 255 since RGB values ranges from 0 to 255)

data_lowres = np.array(data_lowres, dtype=“float32”) / 255.0
data_mng=data_lowres.reshape(data_lowres.shape[0], 64, 64, 3)

Show data shape

print("Shape of data_lowres: ", data_lowres.shape)
print("Shape of the scaled array: ", data_mng.shape)
#%%
#data = data_lowres.astype(np.float32).reshape(64, 64)

**** LOADER DATASET
#%%
image_size = 8 # Height / width of the square images
batch_size = 1
dataloader = torch.utils.data.DataLoader(
data_mng, batch_size=batch_size, shuffle=True, drop_last=True
)
DISCRIMINATOR (the same of demo)
class Discriminator(nn.Module):
“”“Fully connected classical discriminator”“”

def __init__(self):
    super().__init__()

    self.model = nn.Sequential(
        self.double() ,
        # Inputs to first hidden layer (num_input_features -> 64)
        nn.Linear(image_size * image_size, 64),
        nn.ReLU(),
        # First hidden layer (64 -> 16)
        nn.Linear(64, 16),
        nn.ReLU(),
        # Second hidden layer (16 -> output)
        nn.Linear(16, 1),
        nn.Sigmoid(),
    )

def forward(self, x):
    return self.model(x)

#%%
quantum simulator is the same of demo

Quantum variables

n_qubits = 5 # Total number of qubits / N
n_a_qubits = 1 # Number of ancillary qubits / N_A
q_depth = 6 # Depth of the parameterised quantum circuit / D
n_generators = 4 # Number of subgenerators for the patch method / N_G

Quantum simulator

dev = qml.device(“lightning.qubit”, wires=n_qubits)

Enable CUDA device if available

device = torch.device(“cuda:0” if torch.cuda.is_available() else “cpu”)
#%%
@qml.qnode(dev, interface=“torch”, diff_method=“parameter-shift”)
def quantum_circuit(noise, weights):

weights = weights.reshape(q_depth, n_qubits)

# Initialise latent vectors
for i in range(n_qubits):
    qml.RY(noise[i], wires=i)

# Repeated layer
for i in range(q_depth):
    # Parameterised layer
    for y in range(n_qubits):
        qml.RY(weights[i][y], wires=y)

    # Control Z gates
    for y in range(n_qubits - 1):
        qml.CZ(wires=[y, y + 1])

return qml.probs(wires=list(range(n_qubits)))

def partial_measure(noise, weights):
# Non-linear Transform
probs = quantum_circuit(noise, weights)
probsgiven0 = probs[: (2 ** (n_qubits - n_a_qubits))]
probsgiven0 /= torch.sum(probs)

# Post-Processing
probsgiven = probsgiven0 / torch.max(probsgiven0)
return probsgiven

#%%
GENERATOR the same of demo
class PatchQuantumGenerator(nn.Module):
“”“Quantum generator class for the patch method”“”

def __init__(self, n_generators, q_delta=1):
    """
    Args:
        n_generators (int): Number of sub-generators to be used in the patch method.
        q_delta (float, optional): Spread of the random distribution for parameter initialisation.
    """

    super().__init__()

    self.q_params = nn.ParameterList(
        [
            nn.Parameter(q_delta * torch.rand(q_depth * n_qubits), requires_grad=True)
            for _ in range(n_generators)
        ]
    )
    self.n_generators = n_generators

def forward(self, x):
    # Size of each sub-generator output
    patch_size = 2 ** (n_qubits - n_a_qubits)

    # Create a Tensor to 'catch' a batch of images from the for loop. x.size(0) is the batch size.
    images = torch.Tensor(x.size(0), 0).to(device)

    # Iterate over all sub-generators
    for params in self.q_params:

        # Create a Tensor to 'catch' a batch of the patches from a single sub-generator
        patches = torch.Tensor(0, patch_size).to(device)
        for elem in x:
            q_out = partial_measure(elem, params).float().unsqueeze(0)
            patches = torch.cat((patches, q_out))

        # Each batch of patches is concatenated with each other to create a batch of images
        images = torch.cat((images, patches), 1)

    return images

#%%
lrG = 0.3 # Learning rate for the generator
lrD = 0.01 # Learning rate for the discriminator
num_iter = 500 # Number of training iterations

#sys.setrecursionlimit(10000)
print(sys.getrecursionlimit())
#%%
TRAINING->when I run this cell I get the error
#I replaced -1 with 3 because images are RGB colour
discriminator = Discriminator().to(device)
generator = PatchQuantumGenerator(n_generators).to(device)

Binary cross entropy

criterion = nn.BCELoss()

Optimisers

optD = optim.SGD(discriminator.parameters(), lr=lrD)
optG = optim.SGD(generator.parameters(), lr=lrG)

real_labels = torch.full((batch_size,), 1.0, dtype=torch.float, device=device)
fake_labels = torch.full((batch_size,), 0.0, dtype=torch.float, device=device)

Fixed noise allows us to visually track the generated images throughout training

fixed_noise = torch.rand(8, n_qubits, device=device) * math.pi / 2

Iteration counter

counter = 0

Collect images for plotting later

results =

while True:
#for i (data, _) in enumerate(dataloader):
for data in (dataloader):
# Data for training the discriminator
data = data.reshape(3, image_size * image_size)
real_data = data.to(device)

    # Noise follwing a uniform distribution in range [0,pi/2)
    noise = torch.rand(batch_size, n_qubits, device=device) * math.pi / 2
    fake_data = generator(noise)

    # Training the discriminator
    discriminator.zero_grad()
    outD_real = discriminator(real_data).view(3)
    outD_fake = discriminator(fake_data.detach()).view(3)

    errD_real = criterion(outD_real, real_labels)
    errD_fake = criterion(outD_fake, fake_labels)
    # Propagate gradients
    errD_real.backward()
    errD_fake.backward()

    errD = errD_real + errD_fake
    optD.step()

    # Training the generator
    generator.zero_grad()
    outD_fake = discriminator(fake_data).view(3)
    errG = criterion(outD_fake, real_labels)
    errG.backward()
    optG.step()

    counter += 1

    # Show loss values
    if counter % 10 == 0:
        print(f'Iteration: {counter}, Discriminator Loss: {errD:0.3f}, Generator Loss: {errG:0.3f}')
        test_images = generator(fixed_noise).view(8,1,image_size,image_size).cpu().detach()

        # Save images every 50 iterations
        if counter % 50 == 0:
            results.append(test_images)

    if counter == num_iter:
        break
if counter == num_iter:
    break

#%%
PLOT IMAGES
‘’’
fig = plt.figure(figsize=(10, 5))
outer = gridspec.GridSpec(5, 2, wspace=0.1)

for i, images in enumerate(results):
inner = gridspec.GridSpecFromSubplotSpec(1, images.size(0),
subplot_spec=outer[i])

images = torch.squeeze(images, dim=1)
for j, im in enumerate(images):

    ax = plt.Subplot(fig, inner[j])
    ax.imshow(im.numpy(), cmap="gray")
    ax.set_xticks([])
    ax.set_yticks([])
    if j==0:
        ax.set_title(f'Iteration {50+i*50}', loc='left')
    fig.add_subplot(ax)

plt.show()
‘’’

Hi @Eleonora_Panini, thank you for sharing your code. Unfortunately it’s very hard to read as it is. Can you please use the <> symbol here in the forum and then paste your code inside the code block? Thanks! Let me know if this isn’t clear for you and I can try to explain it differently.


import math
import random
import numpy as np
import pandas as pd
import cv2
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import sys
import os
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import Dataset, DataLoader
from PIL import Image
import sklearn
from sklearn.preprocessing import MinMaxScaler
import pennylane as qml

seed = 42
torch.manual_seed(seed)
np.random.seed(seed)
random.seed(seed)

#THIS PART OF TEXT READ THE DATASET, RESIZE IMAGES 64X64 AND STORE IN ARRAY
#‘class DigitsDataset(Dataset)’ OF THE DEMO IS REPLACED BY THE FOLLOWING CODE:
#I tried also with reduced dataset of 20 images instead of 200
ImgLocation=“C:/Users/elyon/OneDrive/Desktop/Tesi/dataset/”
CATEGORIES = set([“Dilbert”,“Boss”])
for category in CATEGORIES:
    for image in list(os.listdir(ImgLocation+category)):
       ImagePaths=ImagePaths+[ImgLocation+category+“/”+image]
data_lowres=[ ]
for img in ImagePaths:
     image = cv2.imread(img)
     image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
     image_lowres = cv2.resize(image, (64, 64))
     data_lowres.append(image_lowres)
data_lowres= np.array(data_lowres, dtype=“float32”) / 255.0
data_mng=data_lowres.reshape(data_lowres.shape[0], 64, 64, 3)
#the output shape is : 20 (n. of images),64,64,3 (RGB)
#LOADER DATASET
image_size = 8 # Height / width of the square images
batch_size = 1 #I tried also changing the batch size
dataloader = torch.utils.data.DataLoader(
data_mng, batch_size=batch_size, shuffle=True, drop_last=True
)
#DISCRIMINATOR (the same of demo)
class Discriminator(nn.Module):
“”“Fully connected classical discriminator”“”
def __init__(self):
    super().__init__()

    self.model = nn.Sequential(
        self.double() ,
        # Inputs to first hidden layer (num_input_features -> 64)
        nn.Linear(image_size * image_size, 64),
        nn.ReLU(),
        # First hidden layer (64 -> 16)
        nn.Linear(64, 16),
        nn.ReLU(),
        # Second hidden layer (16 -> output)
        nn.Linear(16, 1),
        nn.Sigmoid(),
    )

def forward(self, x):
    return self.model(x)
#quantum simulator is the same of demo
n_qubits = 5 # Total number of qubits / N
n_a_qubits = 1 # Number of ancillary qubits / N_A
q_depth = 6 # Depth of the parameterised quantum circuit / D
n_generators = 4 # Number of subgenerators for the patch method / N_G
dev = qml.device(“lightning.qubit”, wires=n_qubits)
device = torch.device(“cuda:0” if torch.cuda.is_available() else “cpu”)

@qml.qnode(dev, interface=“torch”, diff_method=“parameter-shift”)
def quantum_circuit(noise, weights):
    weights = weights.reshape(q_depth, n_qubits)

# Initialise latent vectors
for i in range(n_qubits):
    qml.RY(noise[i], wires=i)

# Repeated layer
for i in range(q_depth):
    # Parameterised layer
    for y in range(n_qubits):
        qml.RY(weights[i][y], wires=y)

    # Control Z gates
    for y in range(n_qubits - 1):
        qml.CZ(wires=[y, y + 1])

return qml.probs(wires=list(range(n_qubits)))
def partial_measure(noise, weights):
# Non-linear Transform
probs = quantum_circuit(noise, weights)
probsgiven0 = probs[: (2 ** (n_qubits - n_a_qubits))]
probsgiven0 /= torch.sum(probs)
# Post-Processing
probsgiven = probsgiven0 / torch.max(probsgiven0)
return probsgiven
#GENERATOR the same of demo
class PatchQuantumGenerator(nn.Module):
“”“Quantum generator class for the patch method”“”
def __init__(self, n_generators, q_delta=1):
    """
    Args:
        n_generators (int): Number of sub-generators to be used in the patch method.
        q_delta (float, optional): Spread of the random distribution for parameter initialisation.
    """

    super().__init__()

    self.q_params = nn.ParameterList(
        [
            nn.Parameter(q_delta * torch.rand(q_depth * n_qubits), requires_grad=True)
            for _ in range(n_generators)
        ]
    )
    self.n_generators = n_generators

def forward(self, x):
    # Size of each sub-generator output
    patch_size = 2 ** (n_qubits - n_a_qubits)

    # Create a Tensor to 'catch' a batch of images from the for loop. x.size(0) is the batch size.
    images = torch.Tensor(x.size(0), 0).to(device)

    # Iterate over all sub-generators
    for params in self.q_params:

        # Create a Tensor to 'catch' a batch of the patches from a single sub-generator
        patches = torch.Tensor(0, patch_size).to(device)
        for elem in x:
            q_out = partial_measure(elem, params).float().unsqueeze(0)
            patches = torch.cat((patches, q_out))

        # Each batch of patches is concatenated with each other to create a batch of images
        images = torch.cat((images, patches), 1)

    return images
lrG = 0.3 # Learning rate for the generator
lrD = 0.01 # Learning rate for the discriminator
num_iter = 500 # Number of training iterations

#TRAINING->when I run this cell I get the error
#I replaced -1 with 3 because images are RGB colour(I tried also with -1 but it get the same error)
discriminator = Discriminator().to(device)
generator = PatchQuantumGenerator(n_generators).to(device)
criterion = nn.BCELoss()
optD=optim.SGD(discriminator.parameters(), lr=lrD)
optG = optim.SGD(generator.parameters(), lr=lrG)

real_labels = torch.full((batch_size,), 1.0, dtype=torch.float, device=device)
fake_labels = torch.full((batch_size,), 0.0, dtype=torch.float, device=device)
fixed_noise = torch.rand(8, n_qubits, device=device) * math.pi / 2
counter = 0
results=[]
while True:
#for i (data, _) in enumerate(dataloader):
for data in (dataloader):
# Data for training the discriminator
data = data.reshape(3, image_size * image_size)
real_data = data.to(device)
# Noise follwing a uniform distribution in range [0,pi/2)
    noise = torch.rand(batch_size, n_qubits, device=device) * math.pi / 2
    fake_data = generator(noise)

    # Training the discriminator
    discriminator.zero_grad()
    outD_real = discriminator(real_data).view(3)
    outD_fake = discriminator(fake_data.detach()).view(3)

    errD_real = criterion(outD_real, real_labels)
    errD_fake = criterion(outD_fake, fake_labels)
    # Propagate gradients
    errD_real.backward()
    errD_fake.backward()

    errD = errD_real + errD_fake
    optD.step()

    # Training the generator
    generator.zero_grad()
    outD_fake = discriminator(fake_data).view(3)
    errG = criterion(outD_fake, real_labels)
    errG.backward()
    optG.step()

    counter += 1

    # Show loss values
    if counter % 10 == 0:
        print(f'Iteration: {counter}, Discriminator Loss: {errD:0.3f}, Generator Loss: {errG:0.3f}')
        test_images = generator(fixed_noise).view(8,1,image_size,image_size).cpu().detach()

        # Save images every 50 iterations
        if counter % 50 == 0:
            results.append(test_images)

    if counter == num_iter:
        break
if counter == num_iter:
    break

#PLOT IMAGES
fig = plt.figure(figsize=(10, 5))
outer = gridspec.GridSpec(5, 2, wspace=0.1)

for i, images in enumerate(results):
inner = gridspec.GridSpecFromSubplotSpec(1, images.size(0),
subplot_spec=outer[i])
images = torch.squeeze(images, dim=1)
for j, im in enumerate(images):

    ax = plt.Subplot(fig, inner[j])
    ax.imshow(im.numpy(), cmap="gray")
    ax.set_xticks([])
    ax.set_yticks([])
    if j==0:
        ax.set_title(f'Iteration {50+i*50}', loc='left')
    fig.add_subplot(ax)
plt.show()

Hi @CatalinaAlbornoz I pasted my code in the previous post using your suggestions. The pennylane demo of quantum GAN works normally, meanwhile the quantum demo modified for my dataset does not work: during the training cell I get the error: (RecursionError: maximum recursion depth exceeded while calling a Python object) or stack overflow. Can you help me? Thank you

The problem seems to be in the discriminator structure because the recursion error appears there. My dataset is composed of 20 images 64x64x3 so the array with images stored is 20x64x64x3. How do you think the structure of the discriminator needs to be modified?
https://colab.research.google.com/drive/12qIF5GN2wVynqE4-CZPvGk9MZQiK6X51?usp=sharing

Thank you
Eleonora

Hi @Eleonora_Panini,

I can’t run your code because I don’t have access to your data, however I think the issue lies in the size of your images. In the code you have image_size = 8, however I can see that the shape of your images is larger. I don’t know if your images make any sense if they get reduced to this size but at least you can try to see if this lets you run the code with no errors.

I hope this helps!

‘’’

HI @CatalinaAlbornoz, I have just also tried reducing the images size to 8x8 and not 64x64, but this is not the problem.I get you access to my notebook in order to try runnin my code. See the link in previuous answer.

Thank you in advance for your help

Eleonora
‘’’

The error magically disappeared, I don’t know why. The code runs and I trained the model for 1000 epochs but the output images are a mix of coloured points without sense. I think the problem is the dimension of the images because I resized to 8x8 in order to follow the demo. My images need to be at least 64x64.

  1. What are the parts of the code that I have to modified in order to adapt larger images (64x64)? Can you underline each variable?
    How I need to change the architecture of discriminator and generator?

2)Having a dataset of 20 images RGB, the dataset as input in dataloder function is an array (20,64,64,3), but in order to fit it in the model, is it correct to convert in 2D array (20,4096) ? I did this for the test with 8x8 images(from (20,8,8,3) to (20,64)) because if I would not do this conversion, the code got some errors about dimensions. I hope it does not influence the quality of images.

Thank you :blush:

Hi @Eleonora_Panini ,

You shared your code but the images are in your own Google Drive so I can’t run the code without the images.

I don’t think the error magically disappeared. I think it disappeared because you set the new size of the images to 8.

Indeed it’s normal that you cannot do good training on 8x8 images unless the images still look different after this reduction, as is the case with MNIST. If you need to resize the images to a different size, eg:16. I would recommend that you resize them and visualize them to see that they still resemble the original image and not just a bunch of coloured points.

You can look for the different parts of the code that have the value “8” and change them to “image_size”. You should set the variable image_size in the code to be the size of the images, eg. 16. At the moment your code says image_size = 8.

Something else that I noticed is that you have diff_method="parameter-shift". This will make your code slower. I would avoid specifying this unless you specifically want this differentiation method for some reason.

The code is made to work for the DigitsDataset. I would recommend that you keep all dimensions as specified in the demo and start by running the original demo with a modified image_size so that you can figure out all of the places where you need to modify this information. Then you can look into modifying other aspects one by one until you get a full understanding of how the code works and how it can be modified. I understand that this can take some trial and error and time but with some work and patience you should be able to modify some parameters. There’s no guarantee that this will work for your specific dataset though, especially if you need a very big image size you will need to increase the number of qubits you’re using and you may hit a limit from your current device.

Let me know if this helps and if you get stuck again.

I hope you can get this working! :smiley:

By the way, we have a very small survey for PennyLane v0.32, and it would be awesome if you’d give us some feedback and tell us about your needs. Thank you! :grin:

I found a solution for the quantum GAN with rgb images 64x64. I changed the parameters of the demo:
Discriminator input is 3x64x64
N.qubits=13
Ancillary=2
Layers=6
N.generators=6
Now all parameters match and the training does not give any errors, but the training cell is still running for over 20minutes with the pointer blocked in:
ErrG=criterion(fake, real-labels).
I think the problem is the lack of resources (RAM,cpu/gpu…). I am using google colab.
What do you suggest in order to complete the training?

Hi @CatalinaAlbornoz,
In order to avoid the crash of RAM I tried to use the GPU T4 on Colab instead of the CPU, but an error occurred: the program reveals 2 devices cpu and cuda, so it suggest me to select one. Although, I have already selected the device as only cuda in the code and discriminator.to(device) and generator.to(device).
Why the error is still appearing?

Hi @Eleonora_Panini , I’m glad you managed to solve your previous issue!

13 qubits is something that you should be able to run on a laptop. I’m not sure why Colab is giving you trouble. Have you tried running your code on a laptop?

To install PennyLane locally it’s strongly recommended that you create a new virtual environment to prevent you from having any installation issues. You can create a virtual environment with Conda and install PennyLane as follows:

  1. Install Anaconda following the instructions here.
  2. Open your terminal (mac) or command line (Windows).
  3. Create a new Conda environment with: conda create --name
    <name_of_your_environment> python=3.10
  4. Activate the environment with: conda activate <name_of_your_environment>
  5. Install PennyLane with: python -m pip install pennylane
  6. Install other useful packages with: python -m pip install pennylane
    jupyter

Note that you will be installing 2 packages here: PennyLane, and Jupyter. Also, note that where it says <name_of_your_environment> you can choose any name that you want. After this you can write jupyter notebook in your terminal/command line and you will be ready to create programs using PennyLane.

You can also watch the video here for detailed PennyLane installation instructions.

Regarding your GPU problem, could you please post the output of qml.about()? Did you make any changes to your code?

Google Colab offers free access to a GPU but only in limited amounts so if you want something that will run on a GPU for many hours you will need to look at buying a GPU or buying a Pro version of a service that gives you access to one.

Make sure that you install and test everything as recommended here to rule out any installation or dependency issues.

If this doesn’t fix your problem please post your full error message.

Hi @CaralinaAlbornoz,
I have just tried locally with my laptop and conda environment, also using jupyter, but there is the same problem. If I run the code using cpu, the running process after more than 20 minutes still running (it seems blocked) and if I try to use the GPU the same error appears. My laptop has 32 gb Ram and 8 Gb gpu and intel i9 processor, windows 11.
In this forum I can’t share images or files, can I try to send you the dataset and the code using a different mail (outside the forum)? Otherwise you can use everykind of dataset with coloured images stored in a folder. Can you try running my code in order to verify if you incurr in the same problem?
The code is this:

https://drive.google.com/file/d/1ilPQJmqquec0uTeWMHUduqG4saotLbsk/view?usp=drivesdk
Thank you
Eleonora

!pip install pennylane custatevec-cu11 pennylane-lightning-gpu
# Library imports
import math
import random
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import pennylane as qml

# Pytorch imports
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import Dataset, DataLoader

# Set the random seed for reproducibility
seed = 999
torch.manual_seed(seed)
np.random.seed(seed)
random.seed(seed)

# Number of workers for dataloader
workers = 2

# Batch size during training
batch_size = 32

# Spatial size of training images. All images will be resized to this
#   size using a transformer.
image_size = 64

# Number of channels in the training images. For color images this is 3
nc = 3

# Size of z latent vector (i.e. size of generator input)
nz = 13

# Size of feature maps in generator
ngf = 64

# Size of feature maps in discriminator
ndf = 64

# Number of training epochs
num_epochs = 100

# Learning rate for optimizers
lr = 0.0002

# Beta1 hyperparam for Adam optimizers
beta1 = 0.5

# Number of GPUs available. Use 0 for CPU mode.
ngpu = 1

from google.colab import drive
drive.mount('/content/drive')
device = torch.device("cuda:0" if (torch.cuda.is_available() and ngpu > 0) else "cpu")

import torchvision.datasets as dset
ImgLocation='/content/drive/MyDrive/Colab Notebooks/Subset_Dil_Bos/'

dataset = dset.ImageFolder(root=ImgLocation,
                           transform=transforms.Compose([
                               transforms.Resize(image_size),
                               transforms.CenterCrop(image_size),
                               transforms.ToTensor(),
                               transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
                           ]))

dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size,shuffle=True, num_workers=workers)
def weights_init(m):
    classname = m.__class__.__name__
    if classname.find('Conv') != -1:
        nn.init.normal_(m.weight.data, 0.0, 0.02)
    elif classname.find('BatchNorm') != -1:
        nn.init.normal_(m.weight.data, 1.0, 0.02)
        nn.init.constant_(m.bias.data, 0)

class Discriminator(nn.Module):
    """Fully connected classical discriminator"""

    def __init__(self,ngpu):
        super().__init__()
        self.ngpu = ngpu
        self.model = nn.Sequential(
            # Inputs to first hidden layer (num_input_features -> 64)
            nn.Linear(3*image_size * image_size, 64,device=device),
            nn.ReLU(),
            nn.Linear(64, 64,device=device),
            nn.ReLU(),
            # First hidden layer (64 -> 16)
            nn.Linear(64, 16,device=device),
            nn.ReLU(),
            # Second hidden layer (16 -> output)
            nn.Linear(16, 1,device=device),
            nn.Sigmoid(),
        )

    def forward(self, x):
        return self.model(x)

discriminator = Discriminator(ngpu).to(device)

# Handle multi-gpu if desired
if (device.type == 'cuda') and (ngpu > 1):
    discriminator = nn.DataParallel(discriminator, list(range(ngpu)))

# Apply the weights_init function to randomly initialize all weights
#  to mean=0, stdev=0.2.
discriminator.apply(weights_init)

# Print the model
print(discriminator)

# Quantum variables
n_qubits = 13  # Total number of qubits / N
n_a_qubits = 2  # Number of ancillary qubits / N_A
q_depth = 6  # Depth of the parameterised quantum circuit / D
n_generators = 6  # Number of subgenerators for the patch method / N_G

dev = qml.device("lightning.gpu", wires=n_qubits)
@qml.qnode(dev, interface="torch") #, diff_method="parameter-shift"
def quantum_circuit(noise, weights):

    weights = weights.reshape(q_depth, n_qubits)

    # Initialise latent vectors
    for i in range(n_qubits):
        qml.RY(noise[i], wires=i)

    # Repeated layer
    for i in range(q_depth):
        # Parameterised layer
        for y in range(n_qubits):
            qml.RY(weights[i][y], wires=y)

        # Control Z gates
        for y in range(n_qubits - 1):
            qml.CZ(wires=[y, y + 1])

    return qml.probs(wires=list(range(n_qubits)))


def partial_measure(noise, weights):
    # Non-linear Transform
    probs = quantum_circuit(noise, weights)
    probsgiven0 = probs[: (2 ** (n_qubits - n_a_qubits))]
    probsgiven0 /= torch.sum(probs)

    # Post-Processing
    probsgiven = probsgiven0 / torch.max(probsgiven0)
    return probsgiven


class PatchQuantumGenerator(nn.Module):
    """Quantum generator class for the patch method"""

    def __init__(self, n_generators, q_delta=1):
        """
        Args:
            n_generators (int): Number of sub-generators to be used in the patch method.
            q_delta (float, optional): Spread of the random distribution for parameter initialisation.
        """

        super().__init__()

        self.q_params = nn.ParameterList(
            [
                nn.Parameter(q_delta * torch.rand(q_depth * n_qubits), requires_grad=True)
                for _ in range(n_generators)
            ]
        )
        self.n_generators = n_generators

    def forward(self, x):
        # Size of each sub-generator output
        patch_size = 2 ** (n_qubits - n_a_qubits)

        # Create a Tensor to 'catch' a batch of images from the for loop. x.size(0) is the batch size.
        images = torch.Tensor(x.size(0), 0).to(device)

        # Iterate over all sub-generators
        for params in self.q_params:

            # Create a Tensor to 'catch' a batch of the patches from a single sub-generator
            patches = torch.Tensor(0, patch_size).to(device)
            for elem in x:
                q_out = partial_measure(elem, params).float().unsqueeze(0)
                patches = torch.cat((patches, q_out))

            # Each batch of patches is concatenated with each other to create a batch of images
            images = torch.cat((images, patches), 1)

        return images

lrG = 0.3  # Learning rate for the generator
lrD = 0.01  # Learning rate for the discriminator
num_iter = 100# Number of training iterations

discriminator = Discriminator(ngpu).to(device)

generator = PatchQuantumGenerator(n_generators).to(device)


# Binary cross entropy
criterion = nn.BCELoss()

# Optimisers
optD = optim.SGD(discriminator.parameters(), lr=lrD)
optG = optim.SGD(generator.parameters(), lr=lrG)

real_labels = torch.full((batch_size,), 1.0, dtype=torch.float, device=device)
fake_labels = torch.full((batch_size,), 0.0, dtype=torch.float, device=device)

# Fixed noise allows us to visually track the generated images throughout training
fixed_noise = torch.rand(image_size, n_qubits, device=device) * math.pi / 2



# Iteration counter
counter = 0

# Collect images for plotting later
results = []

while True:
    for i, (data, _) in enumerate(dataloader):


        # Data for training the discriminator
        data = data.reshape( -1,image_size * image_size*3) #data=(32,12288)

        real_data = data.to(device) #real_data=(32,12288)



        # Training the discriminator
        # Noise follwing a uniform distribution in range [0,pi/2)
        noise = torch.rand(batch_size, n_qubits, device=device) * math.pi / 2 #noise=(32,13)
        fake_data = generator(noise)  #fake_data=(32,12288)

        discriminator.zero_grad()
        outD_real = discriminator(real_data).view(-1) #(outD_real=32)

        errD_real = criterion(outD_real, real_labels)  #(criterion(32, 32))
        
        outD_fake = discriminator(fake_data.detach()).view(-1) #(outD_fake=32)

        errD_fake = criterion(outD_fake, fake_labels)  #(criterion(32,32))
        # Propagate gradients
        errD_real.backward()
        errD_fake.backward()

        errD = errD_real + errD_fake
        optD.step()

        # Training the generator
        generator.zero_grad()

        outD_fake = discriminator(fake_data).view(-1)  #outD_fake=32

        errG = criterion(outD_fake, real_labels) #criterion(32,32)
        errG.backward()
        optG.step()

        counter += 1

        # Show loss values
        if counter % 10 == 0:
            print(f'Iteration: {counter}, Discriminator Loss: {errD:0.3f}, Generator Loss: {errG:0.3f}')
            test_images = generator(fixed_noise).view(image_size,3,image_size,image_size).cpu().detach()

            # Save images every 50 iterations
            if counter % 50 == 0:
                results.append(test_images)

        if counter == num_iter:
            break
    if counter == num_iter:
        break

The output of qml.about():
Name: PennyLane
Version: 0.33.0
Summary: PennyLane is a Python quantum machine learning library by Xanadu Inc.
Home-page: GitHub - PennyLaneAI/pennylane: PennyLane is a cross-platform Python library for differentiable programming of quantum computers. Train a quantum computer the same way as a neural network.
Author:
Author-email:
License: Apache License 2.0
Location: /usr/local/lib/python3.10/dist-packages
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, pennylane-lightning, requests, rustworkx, scipy, semantic-version, toml, typing-extensions
Required-by: PennyLane-Lightning, PennyLane-Lightning-GPU

Platform info: Linux-5.15.120±x86_64-with-glibc2.35
Python version: 3.10.12
Numpy version: 1.23.5
Scipy version: 1.11.3
Installed devices:

I tried with lighting gpu as you suggested and he device used is: cuda:
dev = qml.device(“lightning.gpu”, wires=n_qubits)

Lightning GPU PennyLane plugin: [No binaries found - Fallback: default.qubit]
Short name: lightning.gpu
Package: pennylane_lightning
Plugin version: 0.33.0
Author: Xanadu Inc.
Wires: 13
Shots: None
When I select lightning_gpu I get this warning:
/usr/local/lib/python3.10/dist-packages/pennylane_lightning/lightning_gpu/lightning_gpu.py:958: UserWarning:
"Pre-compiled binaries for lightning.gpu are not available. Falling back to "
"using the Python-based default.qubit implementation. To manually compile from "
"source, follow the instructions at "
https://pennylane-lightning.readthedocs.io/en/latest/installation.html.”, warn(

The code still have the same problem, it is really slow and at a certain point crash the RAM or It appears an error of different size between outDreal and real_labels in this part of code:
errD_real = criterion(outD_real, real_labels)
but I check running only a part of the training code and the size is correct (32) for each variables, indeed the code print errD_real correctly each iteration of ‘for’ cycle. So I think this is a problem of RAM, at some point does not compute the operation in the right way.

What can I do in order to run the code and get the output?

Hi @Eleonora_Panini ,

Thanks for reporting the results on your laptop. If you could put your dataset on a GitHub repository and share it with me that would be very helpful because the problem seems intrinsically linked to the data you’re using.

Given your error there may indeed be a RAM problem.

Something you can try is circuit cutting. Here is a demo on this topic and these are the docs. I would recommend using the automatic cutter, which requires installing the KaHyPar library. You can pip install kahypar on Mac or Linux with Python=3.10.

Here’s an example code on how to use the automatic cutter. Note that we have a 3-qubit circuit running on a 2-qubit device. Depending on the specific architecture of your circuit this can help you with memory issues, although it may increase the time it takes to run. I tested this code on Google Colab and it works nicely. Remember to !pip install pennylane kahypar.

import pennylane as qml
from pennylane import numpy as pnp

dev = qml.device("lightning.qubit", wires=2)

@qml.qnode(dev)
def circuit(x):
    qml.RX(x, wires=0)
    qml.RY(0.9, wires=1)
    qml.RX(0.3, wires=2)

    qml.CZ(wires=[0, 1])
    qml.RY(-0.4, wires=0)

    qml.CZ(wires=[1, 2])

    return qml.expval(qml.pauli.string_to_pauli_word("ZZZ"))

cut_circuit = qml.cut_circuit(circuit, auto_cutter=True)

x = pnp.array(0.531, requires_grad=True)

cut_circuit(x)

You can try testing this on the original version of the GAN demo, which you know that it works. If everything works there, then you can try implementing it with your own version of the GAN and seeing if your memory use is reduced. I would recommend testing with a 7-qubit lightning.qubit device.

The GPU issue that you’re seeing might indicate that you used all of the GPU resources that Google Colab allocates for you so you should try running your problem on CPU instead.

I hope this helps!

HI @CatalinaAlbarnoz,
this is the code and dataset that I loaded on github, so if you can try to run it with a more performant hardware, tell me if it works:
https://github.com/Elyon7jkd/qGAN.git
Both on Colab and locally, I have already tried to run the code using both CPU and GPU but the results are the same.

I also tried to reduce the images of the dataset from 300 to 30 and using a batch size from 32 to 1, the code run really slow and after the iterations get a result, but the generated images are black with some coloured points without representing the subjects, maybe something went wrong during the training, or this is not the correct way to visualize the result images RGB. I attached to the mail the saved model (on github I can’t because it is too large)
mod.zip
Meanwhile, I will try with the automatic cutter, but I have only windows, so I need to use a virtualBox with Linux installed.