Hello, I would like to implement a classical-quantum hybrid neural network using Qiskit to construct a quantum circuit that includes a feature map (ZZFeatureMap
) and a parameterized strongly entangling layer (StronglyEntanglingLayers
). I aim to integrate this quantum circuit into PyTorch via PennyLane. However, when I start training the network, it seems there is an issue with batch processing. I have tried using batch_input
, but the error messages remain unchanged. I would greatly appreciate your help with this. Thank you very much!
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
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)
@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))
ansatz_pl(**ansatz_params, wires=range(4))
return [qml.expval(qml.PauliZ(i)) for i in range(4)]
weight_shapes = {"params": 24}
qlayer = qml.qnn.TorchLayer(qnode, weight_shapes)
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
class SimpleNet(nn.Module):
def __init__(self):
super(SimpleNet, self).__init__()
self.fc1 = nn.Linear(16, 4)
self.qdense = nn.Sequential(
nn.Linear(4, 4),
qlayer
)
self.fc3 = nn.Linear(4, 1)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
x = self.fc1(x)
x = torch.relu(x)
x = self.qdense(x)
x = torch.relu(x)
x = self.fc3(x)
x = self.sigmoid(x)
return x
def generate_data(num_samples=1000):
X = torch.randn(num_samples, 16)
y = torch.randint(0, 2, (num_samples, 1), dtype=torch.float32)
return X, y
X, y = generate_data(1000)
dataset = TensorDataset(X, y)
data_loader = DataLoader(dataset, batch_size=25, shuffle=True)
model = SimpleNet()
criterion = nn.BCELoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
def train(model, data_loader, criterion, optimizer, epochs=10):
model.train()
for epoch in range(epochs):
total_loss = 0
for batch_idx, (inputs, targets) in enumerate(data_loader):
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, targets)
loss.backward()
optimizer.step()
total_loss += loss.item()
print(f"Epoch {epoch + 1}/{epochs}, Loss: {total_loss / len(data_loader):.4f}")
train(model, data_loader, criterion, optimizer)
If you want help with diagnosing an error, please put the full error message below:
{
"name": "CircuitError",
"message": "\"Parameter vector 'x' has length 4, but was assigned to 25 values.\"",
"stack": "---------------------------------------------------------------------------
CircuitError Traceback (most recent call last)
Cell In[17], line 57
54 print(f\"Epoch {epoch + 1}/{epochs}, Loss: {total_loss / len(data_loader):.4f}\")
56 # 训练模型
---> 57 train(model, data_loader, criterion, optimizer)
Cell In[17], line 48, in train(model, data_loader, criterion, optimizer, epochs)
46 for batch_idx, (inputs, targets) in enumerate(data_loader):
47 optimizer.zero_grad()
---> 48 outputs = model(inputs)
49 loss = criterion(outputs, targets)
50 loss.backward()
File c:\\Users\\huminjun\\anaconda3\\envs\\PennyLane\\Lib\\site-packages\\torch\
n\\modules\\module.py:1736, in Module._wrapped_call_impl(self, *args, **kwargs)
1734 return self._compiled_call_impl(*args, **kwargs) # type: ignore[misc]
1735 else:
-> 1736 return self._call_impl(*args, **kwargs)
File c:\\Users\\huminjun\\anaconda3\\envs\\PennyLane\\Lib\\site-packages\\torch\
n\\modules\\module.py:1747, in Module._call_impl(self, *args, **kwargs)
1742 # If we don't have any hooks, we want to skip the rest of the logic in
1743 # this function, and just call forward.
1744 if not (self._backward_hooks or self._backward_pre_hooks or self._forward_hooks or self._forward_pre_hooks
1745 or _global_backward_pre_hooks or _global_backward_hooks
1746 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1747 return forward_call(*args, **kwargs)
1749 result = None
1750 called_always_called_hooks = set()
Cell In[17], line 20, in SimpleNet.forward(self, x)
18 x = self.fc1(x)
19 x = torch.relu(x)
---> 20 x = self.qdense(x)
21 x = torch.relu(x)
22 x = self.fc3(x)
File c:\\Users\\huminjun\\anaconda3\\envs\\PennyLane\\Lib\\site-packages\\torch\
n\\modules\\module.py:1736, in Module._wrapped_call_impl(self, *args, **kwargs)
1734 return self._compiled_call_impl(*args, **kwargs) # type: ignore[misc]
1735 else:
-> 1736 return self._call_impl(*args, **kwargs)
File c:\\Users\\huminjun\\anaconda3\\envs\\PennyLane\\Lib\\site-packages\\torch\
n\\modules\\module.py:1747, in Module._call_impl(self, *args, **kwargs)
1742 # If we don't have any hooks, we want to skip the rest of the logic in
1743 # this function, and just call forward.
1744 if not (self._backward_hooks or self._backward_pre_hooks or self._forward_hooks or self._forward_pre_hooks
1745 or _global_backward_pre_hooks or _global_backward_hooks
1746 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1747 return forward_call(*args, **kwargs)
1749 result = None
1750 called_always_called_hooks = set()
File c:\\Users\\huminjun\\anaconda3\\envs\\PennyLane\\Lib\\site-packages\\torch\
n\\modules\\container.py:250, in Sequential.forward(self, input)
248 def forward(self, input):
249 for module in self:
--> 250 input = module(input)
251 return input
File c:\\Users\\huminjun\\anaconda3\\envs\\PennyLane\\Lib\\site-packages\\torch\
n\\modules\\module.py:1736, in Module._wrapped_call_impl(self, *args, **kwargs)
1734 return self._compiled_call_impl(*args, **kwargs) # type: ignore[misc]
1735 else:
-> 1736 return self._call_impl(*args, **kwargs)
File c:\\Users\\huminjun\\anaconda3\\envs\\PennyLane\\Lib\\site-packages\\torch\
n\\modules\\module.py:1747, in Module._call_impl(self, *args, **kwargs)
1742 # If we don't have any hooks, we want to skip the rest of the logic in
1743 # this function, and just call forward.
1744 if not (self._backward_hooks or self._backward_pre_hooks or self._forward_hooks or self._forward_pre_hooks
1745 or _global_backward_pre_hooks or _global_backward_hooks
1746 or _global_forward_hooks or _global_forward_pre_hooks):
-> 1747 return forward_call(*args, **kwargs)
1749 result = None
1750 called_always_called_hooks = set()
File c:\\Users\\huminjun\\anaconda3\\envs\\PennyLane\\Lib\\site-packages\\pennylane\\qnn\\torch.py:404, in TorchLayer.forward(self, inputs)
401 inputs = torch.reshape(inputs, (-1, inputs.shape[-1]))
403 # calculate the forward pass as usual
--> 404 results = self._evaluate_qnode(inputs)
406 if isinstance(results, tuple):
407 if has_batch_dim:
File c:\\Users\\huminjun\\anaconda3\\envs\\PennyLane\\Lib\\site-packages\\pennylane\\qnn\\torch.py:430, in TorchLayer._evaluate_qnode(self, x)
418 \"\"\"Evaluates the QNode for a single input datapoint.
419
420 Args:
(...)
424 tensor: output datapoint
425 \"\"\"
426 kwargs = {
427 **{self.input_arg: x},
428 **{arg: weight.to(x) for arg, weight in self.qnode_weights.items()},
429 }
--> 430 res = self.qnode(**kwargs)
432 if isinstance(res, torch.Tensor):
433 return res.type(x.dtype)
File c:\\Users\\huminjun\\anaconda3\\envs\\PennyLane\\Lib\\site-packages\\pennylane\\workflow\\qnode.py:1020, in QNode.__call__(self, *args, **kwargs)
1018 if qml.capture.enabled():
1019 return qml.capture.qnode_call(self, *args, **kwargs)
-> 1020 return self._impl_call(*args, **kwargs)
File c:\\Users\\huminjun\\anaconda3\\envs\\PennyLane\\Lib\\site-packages\\pennylane\\workflow\\qnode.py:1002, in QNode._impl_call(self, *args, **kwargs)
999 override_shots = kwargs[\"shots\"]
1001 # construct the tape
-> 1002 self.construct(args, kwargs)
1004 original_grad_fn = [self.gradient_fn, self.gradient_kwargs, self.device]
1005 self._update_gradient_fn(shots=override_shots, tape=self._tape)
File c:\\Users\\huminjun\\anaconda3\\envs\\PennyLane\\Lib\\site-packages\\pennylane\\logging\\decorators.py:61, in log_string_debug_func.<locals>.wrapper_entry(*args, **kwargs)
54 s_caller = \"::L\".join(
55 [str(i) for i in inspect.getouterframes(inspect.currentframe(), 2)[1][1:3]]
56 )
57 lgr.debug(
58 f\"Calling {f_string} from {s_caller}\",
59 **_debug_log_kwargs,
60 )
---> 61 return func(*args, **kwargs)
File c:\\Users\\huminjun\\anaconda3\\envs\\PennyLane\\Lib\\site-packages\\pennylane\\workflow\\qnode.py:851, in QNode.construct(self, args, kwargs)
849 with pldb_device_manager(self.device):
850 with qml.queuing.AnnotatedQueue() as q:
--> 851 self._qfunc_output = self.func(*args, **kwargs)
853 self._tape = QuantumScript.from_queue(q, shots)
855 params = self.tape.get_parameters(trainable_only=False)
Cell In[15], line 18, in qnode(inputs, params)
12 ansatz_params.update({
13 f\"phi_{i//4}_{i%4}\": params[8 + i] for i in range(8)
14 })
15 ansatz_params.update({
16 f\"lambda_{i//4}_{i%4}\": params[16 + i] for i in range(8)
17 })
---> 18 feature_map_pl(x , wires=range(4))
19 ansatz_pl(**ansatz_params, wires=range(4))
20 return [qml.expval(qml.PauliZ(i)) for i in range(4)]
File c:\\Users\\huminjun\\anaconda3\\envs\\PennyLane\\Lib\\site-packages\\pennylane_qiskit\\converter.py:484, in load.<locals>._function(params, wires, *args, **kwargs)
482 params = _format_params_dict(quantum_circuit, params, *args, **kwargs)
483 unbound_params = _extract_variable_refs(params)
--> 484 qc = _check_circuit_and_assign_parameters(quantum_circuit, params, unbound_params)
486 # Wires from a qiskit circuit have unique IDs, so their hashes are unique too
487 qc_wires = [hash(q) for q in qc.qubits]
File c:\\Users\\huminjun\\anaconda3\\envs\\PennyLane\\Lib\\site-packages\\pennylane_qiskit\\converter.py:312, in _check_circuit_and_assign_parameters(quantum_circuit, params, diff_params)
309 del params[k]
311 # Disabling \"strict\" assignment allows extra parameters to be ignored.
--> 312 return quantum_circuit.assign_parameters(params, strict=False)
File c:\\Users\\huminjun\\anaconda3\\envs\\PennyLane\\Lib\\site-packages\\qiskit\\circuit\\quantumcircuit.py:4334, in QuantumCircuit.assign_parameters(self, parameters, inplace, flat_input, strict)
4331 target._name_update()
4333 if isinstance(parameters, collections.abc.Mapping):
-> 4334 raw_mapping = parameters if flat_input else self._unroll_param_dict(parameters)
4335 our_parameters = self._data.unsorted_parameters()
4336 if strict and (extras := raw_mapping.keys() - our_parameters):
File c:\\Users\\huminjun\\anaconda3\\envs\\PennyLane\\Lib\\site-packages\\qiskit\\circuit\\quantumcircuit.py:4391, in QuantumCircuit._unroll_param_dict(self, parameter_binds)
4389 if isinstance(parameter, ParameterVector):
4390 if len(parameter) != len(value):
-> 4391 raise CircuitError(
4392 f\"Parameter vector '{parameter.name}' has length {len(parameter)},\"
4393 f\" but was assigned to {len(value)} values.\"
4394 )
4395 out.update(zip(parameter, value))
4396 elif isinstance(parameter, str):
CircuitError: \"Parameter vector 'x' has length 4, but was assigned to 25 values.\""
}
qml.about()
.
Name: PennyLane
Version: 0.38.0
Summary: PennyLane is a cross-platform Python library for quantum computing, quantum machine learning, and quantum chemistry. Train a quantum computer the same way as a neural network.
Home-page: https://github.com/PennyLaneAI/pennylane
Author:
Author-email:
License: Apache License 2.0
Location: c:\Users\huminjun\anaconda3\envs\PennyLane\Lib\site-packages
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, packaging, pennylane-lightning, requests, rustworkx, scipy, toml, typing-extensions
Required-by: PennyLane-qiskit, PennyLane_Lightning
Platform info: Windows-10-10.0.19045-SP0
Python version: 3.12.4
Numpy version: 2.1.1
Scipy version: 1.14.0
Installed devices:
- default.clifford (PennyLane-0.38.0)
- default.gaussian (PennyLane-0.38.0)
- default.mixed (PennyLane-0.38.0)
- default.qubit (PennyLane-0.38.0)
- default.qubit.autograd (PennyLane-0.38.0)
- default.qubit.jax (PennyLane-0.38.0)
- default.qubit.legacy (PennyLane-0.38.0)
- default.qubit.tf (PennyLane-0.38.0)
- default.qubit.torch (PennyLane-0.38.0)
- default.qutrit (PennyLane-0.38.0)
- default.qutrit.mixed (PennyLane-0.38.0)
- default.tensor (PennyLane-0.38.0)
- null.qubit (PennyLane-0.38.0)
- lightning.qubit (PennyLane_Lightning-0.38.0)
- qiskit.aer (PennyLane-qiskit-0.39.0)
- qiskit.basicaer (PennyLane-qiskit-0.39.0)
- qiskit.basicsim (PennyLane-qiskit-0.39.0)
- qiskit.remote (PennyLane-qiskit-0.39.0)