Quantum GAN for RGB images

The values are really different, i agree with you for the normalization. We can try with ‘torchvision.transforms.Normalize().’ Or do you have other solutions for the normalization? We normalize test images during the training, for instance:
Torchvision.transform.Normalize(test-images).view(batch size,3,64,64).cpu.detach()

Try normalizing it either by this transform.Normalize() or by checking the internet.

I don’t understand which value as mean and standard deviation i have to insert into the function.
this is an example from
https://www.geeksforgeeks.org/how-to-normalize-images-in-pytorch/amp/
Which value I need to put replacing 1 and 2? The mean of all values of the matrix fake images?

mean, std ``= img_tr.mean([``1``,``2``]), img_tr.std([``1``,``2``])

I tried with
test_images_red = (test_images_red * 255/0.299).normal_()
test_images_green = (test_images_green * 255/0.587).normal_()
test_images_blue = (test_images_blue * 255/0.144).normal_()
This is test_images:

[[-1.1716092   2.6942506   0.54948026 ... -0.19643615 -0.12131502
   0.46801642]
 [ 0.00794261 -0.4683865  -1.4243741  ... -0.5513503  -2.0491612
   1.0240767 ]
 [-0.01530833  0.7667712  -2.8037696  ...  0.22845522  0.54489636
   0.89558566]
 ...
 [ 0.47998622 -0.8613797   0.37270096 ... -0.15087974 -0.26750377
   1.2982064 ]
 [-1.4559369  -0.02880584  0.48121873 ...  1.2115003  -0.5613345
  -0.31234595]
 [ 0.9679267   0.87197465  1.2925458  ...  1.2322869   0.91305363
  -0.02658005]]

But there is a problem about the loss (this is from 400 to 500 epochs):

image.png

Try increasing it to 1000 or more.

I trained the model for 1000 epochs, this is the output and loss of the last 100 epochs.

image.png

The result is the same although I trained the model for more than 1000 epochs. I tried to increase the depth from 2 to 3 and 4 but the result is always this and the training is slower. I also tried to change the learning rate, increasing it for the generator (that has a higher loss) for instance from lg=0.3 to lg=0.9 and ld=0.01. I tried to keep the same learning rate for both generator and discriminator lr=0.0002. Another test that I did is using an Adam optimizer instead of the SGD, but needlessly.
The normalization and the division of the generator in channels gave some improvement to the output, but it seems not to evolve anymore. I am thinking of other possible tests. Do you have any other suggestions? @mass_of_15 @isaacdevlugt @CatalinaAlbornoz
thank you for your help

@mass_of_15 @isaacdevlugt @CatalinaAlbornoz
I tried all the tests that I explained in the previous mail (changing learning rate and optimizer from SGD to Adam and using label smoothing) but the output is always the same after 2000 epochs, that seems to be the beginning of a training that never evolve.
image

Do you have other suggestions?

Then, another test is changing the discriminator from:


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),
            nn.ReLU(),
            nn.Linear(64, 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)

To:

nc=3
ndf=64
class Discriminator(nn.Module):
    def __init__(self, ngpu):
        super(Discriminator, self).__init__()
        self.ngpu = ngpu
        self.main = nn.Sequential(
            # input is (nc) x 64 x 64
            nn.Conv2d(nc, ndf, 4, 2, 1, bias=False),
            nn.LeakyReLU(0.2, inplace=True),
            # state size. (ndf) x 32 x 32
            nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 2),
            nn.LeakyReLU(0.2, inplace=True),
            # state size. (ndf*2) x 16 x 16
            nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 4),
            nn.LeakyReLU(0.2, inplace=True),
            # state size. (ndf*4) x 8 x 8
            nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 8),
            nn.LeakyReLU(0.2, inplace=True),
            # state size. (ndf*8) x 4 x 4
            nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False),
            nn.Sigmoid()
        )

    def forward(self, input):
        return self.main(input)

And something into the training loop:

# Iteration counter
counter = 0
G_losses = []
D_losses = []
# Collect images for plotting later
results = []

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

        # 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)
        b_size = real_data.size(0)


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



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

        fake_red = fake_red * 255/0.299
        fake_green = fake_green * 255/0.587
        fake_blue = fake_blue * 255/0.144

        fake_conc = torch.cat((fake_red,fake_green,fake_blue),-1) #16,12288
        fake_data=fake_conc.view(batch_size,3,image_size,image_size) #16,3,64,64
        #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
        #transf = transforms.Compose([transforms.ToTensor()])
        # Show loss values
        if counter % 10 == 0:
            print(f'Iteration: {counter}, Discriminator Loss: {errD:0.3f}, Generator Loss: {errG:0.3f}')
            test_images_red = generator(fixed_noise)
            test_images_green = generator(fixed_noise)
            test_images_blue = generator(fixed_noise)
            test_images_red = (test_images_red * 255/0.299).normal_()
            test_images_green = (test_images_green * 255/0.587).normal_()
            test_images_blue = (test_images_blue * 255/0.144).normal_()
            test= torch.cat((test_images_red,test_images_green,test_images_blue),-1)
            #img_tr = transform(test)
            #mean, std = img_tr.mean([0.299,0.587,0.144]), img_tr.std([0.299,0.587,0.144])
            #transform_norm = transforms.Compose([ transforms.ToTensor(),transforms.Normalize(mean, std)])
            #test_norm=transform_norm(test)
            #test_images =test_norm.view(batch_size,3,image_size,image_size).cpu().detach().normal_()
            test_images =test.view(batch_size,3,image_size,image_size).cpu().detach()
            G_losses.append(errG.item())
            D_losses.append(errD.item())

            # Save images every 50 iterations
            if counter % 50 == 0:
                #results.append(vutils.make_grid(test_images, padding=2, normalize=True))
                results.append(test_images)

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

Using Adam optimizer and learning rate=0.0002 for both G and D.
But the training was interrupted after 10 epochs, maybe for RAM crash.

Hey @Eleonora_Panini! Seems like you’re making progress towards getting your code to give better results. If the results are the same after 2000 steps, it might be due to a combination of your parameter initialization strategy and hyperparameter choices, but no guarantees. You could also be stuck in a barren plateau :astonished: not out of the realm of possibility! Those are tricky to deal with, so might be worth your time to read some of the literature about barren plateaus to see if there are any tips and tricks that are applicable to your problem :thinking:

Finally, also the network with the discriminator composed by 2Dconvolution works, and the G and D losses are more balanced, but the output image is not changed.

image.png

I can try to train the net for more steps…but I don’t know if it is the solution.
Anyway, I am reading about barren plateau : https://arxiv.org/pdf/2203.02464.pdf
https://www.tensorflow.org/quantum/tutorials/barren_plateaus

https://pennylane.ai/qml/demos/tutorial_barren_plateaus/

Change it to

fake_data=fake_conc.view(int(b_size),3,image_size,image_size)

Ok, but i think there is another problem because the output images are not changed after the training…
So maybe the problem is not the kind of discriminator (i tried with both the linear layers and convolution layers), but the quantum generator. Also changing the optimizer and learning rates did not produce any improvements. Do you have other idea?

@Eleonora_Panini What if we change the quantum gates in the circuit? Is there any reason we should only use the ones in the demo? Try changing the gates and see if we can get any different output.

Yes I think the same…we can try to change the quantum circuit or the patch generator…I am not good enough dealing with quantum circuit… I understand the principles and the structure but it is difficult adapting a circuit for a GAN network or a generator of images. There are not other research about using Pennylane library… we can check on internet and think some methods in order to implement a new quantim circuit or changing it…if you find some proposal of circuit, tell me…and i will do the same… if we find a solution we can write a paper about

Check this paper: [2308.11096] MosaiQ: Quantum Generative Adversarial Networks for Image Generation on NISQ Computers

They have improved the image quality. However, the images used are still grayscale.

I read the article and maybe we can try to apply their suggestions in order to improve the quality, although they used grayscale images, so I don’t know if it works.
I also found this article that analyzes the difference from standard and quantistics GAN.
QGAN: Quantized Generative Adversarial Networks (arxiv.org)

Meanwhile I am trying the last attempt to train the model changing the kind of loss function, the demo uses BCEloss(), but I can try also with others (i.e. CrossEntropyLoss,L1,MSE,CTC…)
https://pytorch.org/docs/stable/nn.html#loss-functions

@isaacdevlugt and @Eleonora_Panini, I have seen that in RGB images, the discriminator losses go into barren plateau mode while the generator losses keep increasing. I have tried it with many learning rates, but the results seem the same.

The above image shows that discrimination loss is almost constant after some iterations, while generator loss keeps increasing.

Any ideas about what seems to be the problem and how to tackle it?

I encountered the same problem whatever kind of learning rate or loss function I chose. Generator loss increases and discriminator loss decreases to zero.
I found these articles about barren plateau:
https://pennylane.ai/qml/demos/tutorial_local_cost_functions/

https://pennylane.ai/qml/demos/tutorial_barren_plateaus/

https://www.tensorflow.org/quantum/tutorials/barren_plateaus

But these are only examples of how to reproduce a barren plateau…I don’t know how we can optimize the circuit in order to avoid it…I am trying to do it.
The first article talks about a local cost function that is better than the global one. This tunable cost function increases the locality gradually during the training.

wires=n_qubits
rotations = np.array([[3.] * len(range(wires)), [0.] * len(range(wires))])
locality=2

@qml.qnode(dev, interface=“torch”)
def tunable_cost_simple(rotations):
for i in range(wires):
qml.RX(rotations[0][i], wires=i)
qml.RY(rotations[1][i], wires=i)
qml.broadcast(qml.CNOT, wires=range(wires), pattern=“chain”)
return qml.probs(range(locality))

I also found this code of quantum GAN: https://github.com/hannahaih/quantum-gan-image-generation
(Images seems to be a bit coloured in this example…but at the moment I don’t managed to adapt the generator of this code to our model)

I found this trick in order to deal with barren plateau:
https://pytorch.org/docs/stable/generated/torch.optim.lr_scheduler.ReduceLROnPlateau.html#torch.optim.lr_scheduler.ReduceLROnPlateau

I am trying if it works…
In mode, learning rate will be reduced when the quantity monitored has stopped decreasing;
in mode learning rate will be reduced when the quantity monitored has stopped increasing.

Would you set the variables like this?
schedulerD = ReduceLROnPlateau(optD, ‘max’)
schedulerG = ReduceLROnPlateau(optG, ‘min’)

@mass_of_15 @CatalinaAlbornoz @isaacdevlugt
Model training with the scheduler reduceLRonPlateau function that I explained in the previous mail produced an improvement on learning rate trend, But after 100 epochs there are not improvement in output image…I am trying with other 100 epochs…
hese are the result from training with the scheduler function…the loss is ok, but the output image is the same…can you help me?

Figure: loss trend during training with scheduler, convolutional layer discriminator and Adam optimizer after 100 epochs

Figure: loss trend during training with scheduler, convolutional layer discriminator and Adam optimizer after 200 epochs

Figure: loss trend during training with scheduler, linear layer discriminator and SGD optimizer after 100 epochs

Figure: loss trend during training with scheduler, linear layer discriminator and SGD optimizer after 200 epochs

Hey @Eleonora_Panini!

It’s great to see that you’ve been able to advance in this project! And @mass_of_15 it’s been great to see you collaborating here too! The issues you’re encountering at the moment seem more related to development and training of your application itself than to issues or bugs in PennyLane. Please feel free to tag me if you encounter what you think is a bug, but unfortunately we won’t be able to help you with normal development work. The process of solving a problem with quantum computing can sometimes take time but you’re doing great so far — keep up the good work!