Hi @gigajun , welcome to the Forum!
I’m not completely sure but I think the issue might be related to the issue shown here. The solution I proposed there would break the batch into each individual element before running it through the QNode.
I’ve created a very “draft” hybrid between the code I shared in that thread and your code. It doesn’t work perfect but it also doesn’t show any errors anymore so hopefully it can be a good starting point for you to keep iterating from there.
# imports
import torch
import numpy as np
import pennylane as qml
import pennylane_qiskit
from qiskit_aer import Aer
from qiskit import QuantumCircuit, transpile
from qiskit.circuit import Parameter
from qiskit.circuit.library import ZZFeatureMap
from qiskit_aer import AerSimulator
from collections import OrderedDict
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset
# Quantum circuit
# I didn't modify anything here
def StronglyEntanglingLayers(n_qubits, reps=2, entangler="cx"):
qc = QuantumCircuit(n_qubits)
parameters = []
for l in range(reps):
layer_params = []
for q in range(n_qubits):
theta = Parameter(f'theta_{l}_{q}')
phi = Parameter(f'phi_{l}_{q}')
lam = Parameter(f'lambda_{l}_{q}')
layer_params.append((theta, phi, lam))
parameters.append(layer_params)
for l in range(reps):
for q in range(n_qubits):
theta, phi, lam = parameters[l][q]
qc.rx(theta, q)
qc.ry(phi, q)
qc.rz(lam, q)
entangle_range = l + 1
for i in range(n_qubits):
target = (i + entangle_range) % n_qubits
if i != target:
if entangler == "cx":
qc.cx(i, target)
elif entangler == "cz":
qc.cz(i, target)
return qc
def create_feature_map(n_qubits):
feature_map = ZZFeatureMap(n_qubits)
qc = QuantumCircuit(n_qubits)
qc.compose(feature_map, inplace=True)
simulator = AerSimulator(method='statevector')
compiled_qc = transpile(qc, simulator)
return compiled_qc
def create_ansatz(n_qubits, num_layers):
ansatz = StronglyEntanglingLayers(n_qubits, reps=num_layers)
qc = QuantumCircuit(n_qubits)
qc.compose(ansatz, inplace=True)
simulator = AerSimulator(method='statevector')
compiled_qc = transpile(qc, simulator)
return compiled_qc
feature_map = create_feature_map(4)
ansatz = create_ansatz(4, 2)
# feature_map.draw('mpl')
dev = qml.device("lightning.qubit", wires=4)
feature_map_pl = pennylane_qiskit.load(feature_map, measurements=None)
ansatz_pl = pennylane_qiskit.load(ansatz, measurements=None)
# QNode (slightly modified)
@qml.qnode(dev,interface="torch",diff_method="best")
def qnode(inputs,params):
x = inputs.clone().detach().requires_grad_(False)
ansatz_params = OrderedDict({
f"theta_{i//4}_{i%4}": params[i] for i in range(8)
})
ansatz_params.update({
f"phi_{i//4}_{i%4}": params[8 + i] for i in range(8)
})
ansatz_params.update({
f"lambda_{i//4}_{i%4}": params[16 + i] for i in range(8)
})
#feature_map_pl(x , wires=range(4)) # Commented this
ansatz_pl(**ansatz_params, wires=range(4))
return [qml.expval(qml.PauliZ(i)) for i in range(4)]
# Data and batches (modified)
n_samples = 50 #1000
batch_size = 2 # 25
batches = n_samples // batch_size
def generate_data(num_samples=n_samples):
X = torch.randn(num_samples, 4)
y = torch.randint(0, 2, (num_samples, 1), dtype=torch.float32)
return X, y
X, y = generate_data(n_samples)
dataset = TensorDataset(X, y)
data_loader = DataLoader(dataset, batch_size=batch_size, shuffle=True)
# Model (completely changed)
n_qubits = 4
class HybridModel(nn.Module):
def __init__(self):
super().__init__()
self.clayer_1 = nn.Linear(4, n_qubits)
self.q_params = nn.Parameter(torch.randn(24))
self.clayer_2 = nn.Linear(n_qubits, 2)
self.softmax = nn.Softmax(dim=1)
def forward(self, inputs):
q_in = self.clayer_1(inputs)
# Apply the quantum circuit to each element of the batch and append to q_out
q_out = torch.Tensor(0, n_qubits)
for elem in q_in:
q_out_elem = torch.hstack(qnode(elem, self.q_params)).float().unsqueeze(0)
q_out = torch.cat((q_out, q_out_elem))
# return the two-dimensional prediction from the postprocessing layer
x = self.clayer_2(q_out)
return self.softmax(x)
model = HybridModel()
# Optimization (completely changed)
epochs = 2
loss = nn.L1Loss()
opt = torch.optim.SGD(model.parameters(), lr=0.2)
for epoch in range(epochs):
running_loss = 0
for xs, ys in data_loader:
opt.zero_grad()
loss_evaluated = loss(model(xs), ys)
loss_evaluated.backward()
opt.step()
running_loss += loss_evaluated
avg_loss = running_loss / batches
print("Average loss over epoch {}: {:.4f}".format(epoch + 1, avg_loss))
y_pred = model(X)
predictions = torch.argmax(y_pred, axis=1).detach().numpy()
correct = [1 if p == p_true else 0 for p, p_true in zip(predictions, y)]
accuracy = sum(correct) / len(correct)
print(f"Accuracy: {accuracy * 100}%")
I hope this helps! Let me know if you have any further questions.