DressedQuantumClassifier#
- class pyqit.models.DressedQuantumClassifier(n_features, n_qubits=4, n_layers=6, n_classes=2, q_delta=0.01, device='default.qubit', shots=None, diff_method='best')[source]#
Bases:
BaseQuantumModel,ClassifierMixinDressed quantum circuit of Mari et al. (2020): dense, circuit, dense.
A classical layer maps
n_featureston_qubitsangles throughtanh(.) * pi / 2; the circuit applies a Hadamard layer, encodes the angles with RY, thenn_layersblocks of a CNOT ladder followed by an RY layer, and reads<Z>on every wire; a second classical layer maps those to the classes. There is no separate embedding on the model, so the DataModule does not prescale.Inside, the network is a QuantumPipeline of three layers from
pyqit.models.layers: a DenseLayer, a QuantumLayer with HadamardAngleEmbedding and CNOTLadderAnsatz, and a DenseClassifier. Their weights are this model’s, underpre_net.*,quantum.*andpost_net.*. Compose those layers yourself for a different hybrid.The head differs from the paper in one way: pyqit models emit probabilities, so binary applies a sigmoid to one logit and multi-class a softmax, rather than handing logits to cross-entropy.
- Parameters:
n_features (int)
n_qubits (int, default 4)
n_layers (int, default 6) – Variational depth,
q_depthin the paper.n_classes (int, default 2)
q_delta (float, default 0.01) – Spread of the normal initial quantum weights, as in the paper. The classical layers use
torch.nn.Linear’s default init on both backends.device (str, default "default.qubit")
shots (int, optional)
diff_method (str, default "best") – Passed to the QNode.
"best"picks backprop on a simulator;"parameter-shift"rehearses a hardware run’s gradient cost.
References
Mari, Bromley, Izaac, Schuld, Killoran, “Transfer learning in hybrid classical-quantum neural networks”, Quantum 4, 340 (2020). PennyLane’s “Quantum transfer learning” demo is the reference implementation.
Examples
>>> import pyqit >>> from pyqit.models import DressedQuantumClassifier >>> model = DressedQuantumClassifier(n_features=8, n_qubits=4, n_layers=2) >>> history = pyqit.Trainer(max_epochs=5).fit(model, dm)
- diff_methods(X) dict#
Differentiation method per QNode.
- Parameters:
X (array-like) – One prescaled batch; only its shape matters.
- Returns:
QNode name to method name.
- Return type:
dict
- execute_qnode(name: str, X, **custom_weights)#
Run the QNode or dense layer registered under name on a batch.
- Parameters:
name (str) – Name passed to register_qnode.
X (array-like)
**custom_weights – Flat “<name>.<weight>” overrides; unprefixed keys are ignored. Falls back to the model’s own weights when empty.
- Return type:
array-like
- forward(X, **custom_weights)[source]#
Run dense, circuit, dense and return class probabilities.
- Parameters:
X (array-like) – Batch of
n_featurescolumns, normalized but not prescaled.**custom_weights – Override the model’s own weights, keyed as in weights.
- Returns:
Probability of class 1 for binary; a (n_samples, n_classes) probability matrix otherwise.
- Return type:
array-like
- get_interface()#
PennyLane QNode interface for the active backend.
- classmethod get_test_params()[source]#
List constructor kwargs used to parametrize this class in the test suite.
- init_weights(weight_shapes: dict) dict#
Draw uniform
[0, 1)starting weights from numpy’s global RNG.The range matches Qiskit ML’s default
initial_pointand PennyLane’s template examples; uniform[0, 2pi)is the Haar-like regime where gradients vanish (McClean et al. 2018).Call this before building the device: a PennyLane device seeded
"global"consumes numpy’s RNG at construction by a device-dependent amount, so weights drawn after it differ per device for the same seed.- Parameters:
weight_shapes (dict) – Weight name to shape, as returned by an ansatz’s get_weight_shapes.
- Return type:
dict
- is_fitted() bool#
Whether Trainer.fit has trained this model.
- predict_step(X)#
Predict hard class labels for
X.- Parameters:
X (array-like) – Input batch.
- Returns:
One label per row: 0/1 for binary, argmax index for multi-class.
- Return type:
array-like
- register_dense(name: str, n_in: int, n_out: int, weights=None)#
Register a classical dense layer
X @ weight.T + biasunder name.Lives in the same registry as the QNodes, so
weights,update_weights, checkpoints and the flat-kwargs routing cover it with no further plumbing. Run it withexecute_qnode.- Parameters:
name (str)
n_in (int)
n_out (int)
weights (dict, optional) –
{"weight", "bias"}from init_dense_weights; drawn when omitted.
- register_qnode(name: str, qnode: QNode, weight_shapes: dict, weights=None)#
Wrap qnode for the active backend and store it under name.
- Parameters:
name (str) – Key under which the node’s weights appear in weights.
qnode (qml.QNode)
weight_shapes (dict) – Weight name to shape, as returned by an ansatz’s get_weight_shapes.
weights (dict, optional) – Starting weights from init_weights; drawn here when omitted, so both backends start from the same point for the same seed.
- update_weights(flat_weights_dict)#
Write flat_weights_dict into the model’s own weights.
No-op under torch, where autograd owns the nn.Parameter objects directly.
- Parameters:
flat_weights_dict (dict) – Keyed like weights.
- weight_groups() dict#
weights keys by group,
"quantum"(QNodes) and"classical".Empty groups are omitted. The training loops build one optimizer per group, which is what lets
Trainer(learning_rate={...})set a rate per group.
- property weights#
Flat
{"<qnode_name>.<weight_name>": array}dict, both backends.