@CatalinaAlbornoz
@CatalinaAlbornoz , Hello greetings from Costa Rica
Yes, of course I already saw that result, I programmed the version for colors, in fact I already have it ready, I am running it Google COLAB with few iterations and using a GPU
# Define the quantum circuit and auxiliary functions
@qml.qnode(dev, interface='torch', diff_method='parameter-shift')
def quantum_circuit(noise, weights):
weights = weights.reshape(q_depth, n_qubits)
# Initialize the qubits with the latent vector
for i in range(n_qubits):
qml.RY(noise[i], wires=i)
# Apply parameterized layers
for i in range(q_depth):
for y in range(n_qubits):
qml.RY(weights[i][y], wires=y)
# CZ 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(x, weights):
"""
Applies the quantum circuit to each element in the batch.
Args:
x (torch.Tensor): Tensor of shape (batch_size, n_qubits)
weights (torch.Tensor): Tensor of parameters of shape (q_depth * n_qubits)
Returns:
torch.Tensor: Post-processed probabilities of shape (batch_size, 2**(n_qubits - n_a_qubits))
"""
batch_size = x.size(0)
probs_list = []
for i in range(batch_size):
noise = x[i].float().to(device) # Ensure noise is float32 and on the correct device
weights_tensor = weights.float().to(device) # Ensure weights is float32 and on the correct device
probs = quantum_circuit(noise, weights_tensor) # Shape: (2**n_qubits,)
probsgiven0 = probs[: (2 ** (n_qubits - n_a_qubits))]
probsgiven0 /= torch.sum(probs)
probsgiven = probsgiven0 / torch.max(probsgiven0)
probs_list.append(probsgiven)
probs_tensor = torch.stack(probs_list, dim=0) # (batch_size, 2**(n_qubits - n_a_qubits))
return probs_tensor
# Define the quantum generator class
class PatchQuantumGenerator(nn.Module):
"""Quantum generator for the patch method, adapted for 16x16 RGB images"""
def __init__(self, n_generators, q_delta=1):
"""
Args:
n_generators (int): Number of sub-generators per channel (should be 1 for 16x16 RGB images).
q_delta (float, optional): Range of the random distribution for parameter initialization.
"""
super().__init__()
# Generators for each color channel
self.red_params = nn.ParameterList([
nn.Parameter(q_delta * torch.rand(q_depth * n_qubits, device=device, dtype=torch.float32), requires_grad=True)
for _ in range(n_generators)
])
self.green_params = nn.ParameterList([
nn.Parameter(q_delta * torch.rand(q_depth * n_qubits, device=device, dtype=torch.float32), requires_grad=True)
for _ in range(n_generators)
])
self.blue_params = nn.ParameterList([
nn.Parameter(q_delta * torch.rand(q_depth * n_qubits, device=device, dtype=torch.float32), requires_grad=True)
for _ in range(n_generators)
])
self.n_generators = n_generators
def forward(self, x):
"""
Args:
x (torch.Tensor): Noise tensor of shape (batch_size, n_qubits)
Returns:
torch.Tensor: Generated images of shape (batch_size, 3, image_size, image_size)
"""
x = x.float() # Ensure x is float32
patch_size = 2 ** (n_qubits - n_a_qubits) # 256
batch_size = x.size(0)
# Lists for each channel
red_patches = []
green_patches = []
blue_patches = []
# Generate patches for each channel
for params in self.red_params:
patches = partial_measure(x, params) # Shape: (batch_size, patch_size)
red_patches.append(patches)
red_patches = torch.cat(red_patches, dim=1) # Shape: (batch_size, n_generators * patch_size)
for params in self.green_params:
patches = partial_measure(x, params)
green_patches.append(patches)
green_patches = torch.cat(green_patches, dim=1)
for params in self.blue_params:
patches = partial_measure(x, params)
blue_patches.append(patches)
blue_patches = torch.cat(blue_patches, dim=1)
# Concatenate the three channels
images = torch.cat([red_patches, green_patches, blue_patches], dim=1) # (batch_size, 3 * n_generators * patch_size)
# Reshape to (batch_size, 3, image_size, image_size)
images = images.view(batch_size, 3, image_size, image_size)
images = images.float() # Ensure images are float32
return images
# Define the classical Discriminator class
class Discriminator(nn.Module):
def __init__(self):
super(Discriminator, self).__init__()
self.model = nn.Sequential(
nn.Linear(image_size * image_size * 3, 2048), # Reduced from 4096 to 2048
nn.LeakyReLU(0.2, inplace=True),
nn.Linear(2048, 512), # Reduced from 1024 to 512
nn.LeakyReLU(0.2, inplace=True),
nn.Linear(512, 128), # Reduced from 256 to 128
nn.LeakyReLU(0.2, inplace=True),
nn.Linear(128, 1),
nn.Sigmoid()
)
def forward(self, x):
x = x.view(x.size(0), -1) # Flatten to (batch_size, 3*image_size*image_size)
x = x.float() # Ensure x is float32
return self.model(x)
This is the configuration I am using for the qubits, etc:
n_qubits = 9
n_a_qubits = 1
q_depth = 2
image_size = 16
batch_size = 4
n_generators = 1
nz = n_qubits # nz = 9
epochs = 10