Batch Input Errors in Circuits Transpiled from Qiskit

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.