Hello Romain,
Thank you for your response. I was unable to find an example that specifically addresses my requirements of processing RGB images, encoding, and training the parameters of a PQC using a quantum circuit alone. Most existing examples tend to involve a classical feature extractor combined with a quantum FC layer, which is not what I am looking for.
I have written a self-contained example for binary classification using the bees/ants dataset, but facing an issue where the validation and training accuracy do not change. I believe this issue is not related to overfitting, but rather a problem in my code that I am unable to identify. I would appreciate it if you could take a look at my code and provide guidance on this and also how to address the related “float” tuple issue.
Here is the full code, all you have to do is extract the bees/ants dataset into ./datasets and run the code.
# Install necessary packages
# !pip install torch==1.12.1 torchvision==0.13.1 pennylane==0.30.0 efficientnet_pytorch
# Import required libraries
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
import pennylane as qml
from pennylane import numpy as np
import os
from tqdm import tqdm
import sys
from pennylane.operation import Tensor
import psutil
import matplotlib.pyplot as plt
# from qblocks.qgates import *
print("[Python version]:", sys.version)
print("[Deep Learning framework, Pytorch (Facebook) version]:", torch.__version__)
print("[Quantum Machine Learning framework (Pennylane) version]:", qml.__version__)
# Set device
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# Set parameters
patch_size = 3
img_size_single = 128
img_size_flat = img_size_single ** 2
RGB_C = 1 # Number of channels in the image (RGB)
batch_size = 32
num_epochs = 60
n_qubits = patch_size ** 2 * RGB_C
n_layers = 6
num_classes = None
# Note if you forget to wrap your circuit with dev: AttributeError: 'tuple' object has no attribute 'float'
dev_train = qml.device('default.qubit', wires=n_qubits)
# Define the quantum circuit
# @qml.qnode(dev_train, interface="torch")
def Q_Plot(cirq_0, q_b,q_d):
# print("Plot Q:", q_b)
fig, ax = qml.draw_mpl(cirq_0,expansion_strategy='device')(torch.zeros(q_b), torch.zeros(q_d))
# print (qml.draw(cirq_0,expansion_strategy='device')(torch.zeros(q_b), torch.zeros(q_d)))
# plt.figure(figsize=(5,3))
# from pylab import rcParams
# rcParams['figure.figsize'] = 3, 6
# fig.set_size_inches(12,6)
plt.show()
fig.show()
def Q_count_parameters(qnn):
print(dict(qnn.named_parameters()))
for name, param in qnn.named_parameters():
param.requires_grad=True
# print (name, param.data)
return sum(p.numel() for p in qnn.parameters() if p.requires_grad)
import random
def circuit(inputs, weights):
# print('inputs / weights {}/{}'.format(inputs.shape, weights.shape))
for qub in range(n_qubits):
qml.Hadamard(wires=qub)
qml.RY(inputs[qub], wires=qub)
for l in range(n_layers):
qubit_indices = list(range(n_qubits))
random.shuffle(qubit_indices) # Shuffle the qubit indices to create random pairs
for i in range(0, n_qubits, 2):
control_qubit = qubit_indices[i]
target_qubit = qubit_indices[(i + 1) % n_qubits]
# Apply CRZ gate with conditional RY gate
random_num = random.uniform(0, 1)
qml.CRZ(weights[control_qubit], wires=[control_qubit, target_qubit])
qml.RY(random_num * weights[control_qubit], wires=control_qubit)
qml.CNOT(wires=[control_qubit, target_qubit])
qml.CZ(wires=[control_qubit, (control_qubit + 2) % n_qubits]) # Additional CZ gate for entanglement
return qml.expval(Tensor(*[qml.PauliZ(i) for i in range(n_qubits)]))
# Define the Quanvolutional Neural Network
class QuanvolutionalNeuralNetwork(nn.Module):
def __init__(self, num_classes):
super().__init__()
self.fc1 = nn.Linear(n_qubits, num_classes)
self.q_params = nn.Parameter(torch.Tensor(n_qubits, n_qubits))
self.lr1 = nn.LeakyReLU(0.1)
nn.init.xavier_uniform_(self.q_params)
self.pqc = qml.QNode(circuit, dev_train, interface = 'torch')
Q_Plot(circuit,n_qubits,n_qubits)
def extract_patches(self, x):
patches = []
bs, c, w, h = x.size()
for i in range(w - patch_size + 1):
for j in range(h - patch_size + 1):
patch = x[:, :, i:i+patch_size, j:j+patch_size]
patches.append(patch)
patches = torch.stack(patches, dim=1).view(bs, -1, c * patch_size * patch_size)
return patches
def forward(self, x):
assert len(x.shape) == 4 # (bs, c, w, h)
bs = x.shape[0] # batch_size = x.size(0)
c = x.shape[1] # RGB
x = x.view(bs, c, img_size_single, img_size_single)
q_in = self.extract_patches(x)
q_in = q_in.to(device)
# print (q_in.shape)
# q_out = torch.Tensor(0, n_qubits)
q_out = torch.Tensor(0, n_qubits)
q_out = q_out.to(device)
for elem in q_in:
# print (elem.shape)
# print (self.q_params.shape)
q_out_elem = self.pqc(elem, self.q_params).float().unsqueeze(0)
q_out = torch.cat((q_out, q_out_elem))
x = self.lr1(q_out.view(-1, n_qubits))
x = self.fc1(x)
return x
# Set the data directory and transformations
data_dir = 'datasets/hymenoptera/'
# import splitfolders as sf
# sf.ratio('datasets/mri/train', 'output', ratio=(0.65, 0.05, 0.3), seed=42)
data_transforms = {
'train': transforms.Compose([
# transforms.Resize(256),
transforms.CenterCrop(img_size_single),
transforms.Grayscale() if RGB_C == 1 else transforms.Lambda(lambda x: x),
transforms.ToTensor(),
]),
'val': transforms.Compose([
# transforms.Resize(256),
transforms.CenterCrop(img_size_single),
transforms.Grayscale() if RGB_C == 1 else transforms.Lambda(lambda x: x),
transforms.ToTensor(),
]),
}
# Load the image datasets
image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x), data_transforms[x]) for x in ['train', 'val']}
dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']}
class_names = image_datasets['train'].classes
num_classes=len(class_names)
dataloaders = {x: DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) for x in ['train', 'val']}
# Initialize the Quanvolutional Neural Network
qnn = QuanvolutionalNeuralNetwork(num_classes=num_classes)
qnn = qnn.to(device)
# print ("Total trainable params:",Q_count_parameters(qnn))
# Set the loss function and optimizer
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(qnn.parameters(), lr=0.001)
# Train the model
for epoch in tqdm(range(num_epochs)):
# print("Epoch {}/{}, Qubits:{}".format(epoch, num_epochs, n_qubits))
cpu_percent = psutil.cpu_percent()
mem_usage= psutil.virtual_memory().total / (1024 ** 3)
used_ram_gb = psutil.virtual_memory().used / (1024 ** 3)
# print(f"Epoch{epoch+1}/{num_epochs}, Dataset:{data_dir,dataset_sizes}, Qubits:{n_qubits}, RGB:{RGB_C}, IMG:{img_size_single} Layers:{n_layers},QNN Params:{[sum(p.numel() for p in qnn.parameters())]},CPU:{cpu_percent},RAM(GB):{used_ram_gb}'/'{mem_usage}")
print(f"Epoch:[{epoch+1}/{num_epochs}], Dataset:{data_dir,dataset_sizes}, Qubits:{n_qubits}, RGB:{RGB_C},IMG:{img_size_single} Layers:{n_layers},QNN Params:{[sum(p.numel() for p in qnn.parameters())]} CPU:{cpu_percent}, RAM(GB):{used_ram_gb}/{mem_usage}")
qnn.train()
running_loss = 0
running_corrects = 0
total_samples = 0
for batch_idx, (data, target) in tqdm(enumerate(dataloaders['train'])):
data = data.to(device)
target = target.view(-1).to(device)
batch_size = data.size(0) # Get the actual batch size
optimizer.zero_grad()
output = qnn(data)
# Adjust the output tensor size if necessary
if output.size(0) > batch_size:
output = output[:batch_size]
loss = criterion(output, target)
loss.backward()
optimizer.step()
running_loss += loss.item()
_, predicted = torch.max(output, 1)
running_corrects += torch.sum(predicted == target.data)
total_samples += batch_size
batch_loss = running_loss / len(dataloaders['train'])
batch_acc = running_corrects / total_samples
print(f'[{epoch + 1}] Training Loss: {batch_loss:.3f}, Training Accuracy: {batch_acc:.3f}')
running_loss = 0.0
running_corrects = 0
total_samples = 0
val_correct = 0
val_total = 0
with torch.no_grad():
for val_data, val_target in dataloaders['val']:
val_data = val_data.to(device)
val_target = val_target.to(device)
batch_size = val_data.size(0) # Get the actual batch size
val_output = qnn(val_data)
_, val_predicted = torch.max(val_output.data, 1)
# Adjust the output and target tensors if necessary
if val_predicted.size(0) > batch_size:
val_predicted = val_predicted.narrow(0, 0, batch_size)
val_target = val_target.narrow(0, 0, batch_size)
val_total += batch_size
val_correct += (val_predicted == val_target).sum().item()
val_accuracy = 100 * val_correct / val_total
print(f"[{epoch + 1}] Validation Accuracy: {val_accuracy:.2f}%")
Thanks.