It’s a bit hard to distill the code because of how much is going on here, but this is a (relatively) minimal example for the issue I’ve been having. The goal is to load in a QuantumCircuit object from Qiskit, then to use this loaded object as the circuit in a Torch layer.
import time
import torch
import torch.nn.functional as F
import pennylane as qml
from torchquantum.datasets import MNIST
import pennylane as qml
from qiskit.circuit.library import EfficientSU2
torch_device_name = "cpu"
torch_device = torch.device(torch_device_name)
def example_accuracy(preds, labels):
_, indices = preds.topk(1, dim=1)
masks = indices.eq(labels.view(-1, 1).expand_as(indices))
corrects = masks.sum().item()
size = labels.shape[0]
accuracy = corrects / size
return accuracy
def train(model, train_dl, epochs, loss_fn, optimizer, device=None):
losses = []
for epoch in range(epochs):
running_loss = 0.0
batches = 0
total_batches = len(train_dl)
start_time = time.time()
for batch_dict in train_dl:
x = batch_dict['image']
y = batch_dict['digit']
y = y.to(torch.long)
x = x.to(torch_device)
y = y.to(torch_device)
optimizer.zero_grad()
if model.service == "TorchQuantum":
preds = model(device, x)
else:
preds = model(x)
loss = loss_fn(preds, y)
loss.backward()
optimizer.step()
running_loss += loss.item()
batches += 1
cur_time = time.time()
avg_batch_time = (cur_time-start_time)/batches
print(f"Epoch {epoch + 1} | Loss: {running_loss/batches} | Est. Time Remaining: {avg_batch_time*(total_batches-batches)}", end="\r")
print(f"Epoch {epoch + 1} | Loss: {running_loss/batches} | Time Elapsed: {cur_time-start_time}")
losses.append(running_loss/batches)
return losses
def qiskit_to_qnode_qnn(qc, device, n_wires, diff_method="best"):
q_func = qml.load(qc, format='qiskit')
qiskit_params = list(qc.parameters)
@qml.qnode(device, diff_method=diff_method)
def circuit(inputs, weights):
qml.AngleEmbedding(inputs, wires=range(n_wires))
q_func(params=dict(zip(qiskit_params, weights)))
# qml.BasicEntanglerLayers(weights=weights, wires=range(n_wires))
return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_wires)]
return circuit, qiskit_params
class PLNet(torch.nn.Module):
def __init__(self, qcirc, weight_shapes, use_softmax=False):
super().__init__()
self.use_softmax = use_softmax
self.circuit = qcirc
self.qlayer = qml.qnn.TorchLayer(self.circuit, weight_shapes)
self.service = "PennyLane"
def forward(self, x):
bsz = x.shape[0]
x = F.avg_pool2d(x, 6)
x = x.view(bsz, 16)
out = self.qlayer(x)
if self.use_softmax:
out = F.log_softmax(out, dim=1)
return out
if __name__ == "__main__":
reps = 1
n_qubits = 16
device = qml.device('qiskit.aer', wires=n_qubits)
qc = EfficientSU2(n_qubits, entanglement="linear", reps=reps)
qnode, qiskit_params = qiskit_to_qnode_qnn(qc, device, n_qubits)
weight_shapes = {"weights": (1, len(qiskit_params))}
# weight_shapes = {"weights": (1, n_qubits)}
net = PLNet(qnode, weight_shapes)
loss_fn = F.nll_loss
acc_fn = example_accuracy
optimizer = torch.optim.SGD(net.parameters(), lr=0.05)
dataset = MNIST(
root='./mnist_data',
train_valid_split_ratio=[.9, .1],
digits_of_interest=[0, 1],
n_test_samples=200,
)
train_dl = torch.utils.data.DataLoader(dataset['train'], batch_size=32, sampler=torch.utils.data.RandomSampler(dataset['train']))
val_dl = torch.utils.data.DataLoader(dataset['valid'], batch_size=32, sampler=torch.utils.data.RandomSampler(dataset['valid']))
test_dl = torch.utils.data.DataLoader(dataset['test'], batch_size=32, sampler=torch.utils.data.RandomSampler(dataset['test']))
print("--Training--")
train_losses = train(net, train_dl, 2, loss_fn, optimizer)
However, as I run the code, I get the following error, which suggests that there’s something going wrong with making the loaded circuit trainable. Notably, this error doesn’t happen if I replace
q_func(params=dict(zip(qiskit_params, weights)))
with
qml.BasicEntanglerLayers(weights=weights, wires=range(n_wires))
and accordingly change the weight_shapes to match.
As a seperate (but possibly related) issue, if I switch the device to use cuda instead of cpu, I also get an error relating to torch device incompatibility. It seems like running through the imported circuit switches the device of an input.
RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn