Source code for pyqit.models.layers.stages

import pennylane as qml

from pyqit.ansatzes.sel import SELAnsatz
from pyqit.core.embeddings import AngleEmbedding
from pyqit.core.measurements import measure_probs
from pyqit.models.base.base import BaseModel
from pyqit.models.classification.classifier_mixin import ClassifierMixin
from pyqit.models.layers.dense import ACTIVATIONS, to_probabilities
from pyqit.models.layers.vqc import BaseVQC, z_from_probs


[docs] class DenseLayer(BaseModel): """Classical dense stage: ``activation(X @ weight.T + bias)``. Emits features, not predictions, so it is a `QuantumPipeline` stage rather than something to fit alone. Stack several for a deeper network. Weights use ``torch.nn.Linear``'s default init on both backends, under ``dense.weight`` and ``dense.bias``. Parameters ---------- n_features : int n_out : int activation : {None, "tanh", "relu", "sigmoid"}, default None Examples -------- >>> from pyqit.models.layers import DenseLayer >>> layer = DenseLayer(n_features=8, n_out=4, activation="tanh") """ _tags = {"object_type": "layer", "is_quantum": False, "model_type": "classical"} def __init__(self, n_features, n_out, activation=None): if activation not in ACTIVATIONS: raise ValueError( f"activation must be one of {list(ACTIVATIONS)}, got {activation!r}." ) super().__init__() self.n_features = n_features self.n_out = n_out self.activation = activation self.register_dense("dense", n_features, n_out)
[docs] def forward(self, X, **custom_weights): """Return ``(n_samples, n_out)`` features.""" out = self.execute_qnode("dense", X, **custom_weights) return ACTIVATIONS[self.activation](out)
[docs] @classmethod def get_test_params(cls): """List constructor kwargs used to parametrize this class in the test suite.""" return [ {"n_features": 3, "n_out": 2}, {"n_features": 2, "n_out": 3, "activation": "tanh"}, ]
[docs] class DenseClassifier(BaseModel, ClassifierMixin): """Classical head stage: one dense layer, then sigmoid or softmax. The last stage of a hybrid `QuantumPipeline`, turning the features of the stage before it into class probabilities and hard labels. Like every pyqit classifier it emits probabilities, not logits. Weights sit under ``dense.weight`` and ``dense.bias``. Parameters ---------- n_features : int n_classes : int, default 2 Examples -------- >>> from pyqit.models.layers import DenseClassifier >>> head = DenseClassifier(n_features=4, n_classes=3) """ _tags = {"object_type": "layer", "is_quantum": False, "model_type": "classical"} def __init__(self, n_features, n_classes=2): super().__init__() self.n_features = n_features self.n_classes = n_classes self.register_dense("dense", n_features, 1 if n_classes == 2 else n_classes)
[docs] def forward(self, X, **custom_weights): """Return class probabilities. Probability of class 1 for binary; a ``(n_samples, n_classes)`` matrix otherwise. """ logits = self.execute_qnode("dense", X, **custom_weights) return to_probabilities(logits, self.n_classes)
[docs] @classmethod def get_test_params(cls): """List constructor kwargs used to parametrize this class in the test suite.""" return [{"n_features": 3}, {"n_features": 4, "n_classes": 3}]
[docs] class QuantumLayer(BaseVQC): """Quantum stage: an embedding, an ansatz, and ``<Z>`` read on every wire. Emits ``(n_samples, n_qubits)`` features in ``[-1, 1]``, so it is a `QuantumPipeline` stage rather than something to fit alone. The expectations are computed from the basis-state probabilities, which costs a ``2 ** n_qubits`` vector per sample. Parameters ---------- n_qubits : int, default 4 n_layers : int, default 3 ansatz : type, default SELAnsatz encoder : type, default AngleEmbedding Drives the prescaling the pipeline applies to this stage's input. 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. Examples -------- >>> from pyqit.models.layers import QuantumLayer >>> layer = QuantumLayer(n_qubits=4, n_layers=2) """ _tags = {"object_type": "layer"} def __init__( self, n_qubits=4, n_layers=3, ansatz=SELAnsatz, encoder=AngleEmbedding, device="default.qubit", shots=None, diff_method="best", ): super().__init__( n_qubits=n_qubits, n_layers=n_layers, ansatz=ansatz, encoder=encoder, device=device, shots=shots, diff_method=diff_method, ) self._z_from_probs = z_from_probs(n_qubits) def _resolve_readout(self, n_qubits, measure_fn, measure_wires): self._measure_fn = measure_probs self._measure_wires = list(range(n_qubits))
[docs] def forward(self, X, **custom_weights): """Return ``(n_samples, n_qubits)`` expectation values.""" probs = self.execute_qnode("main_circuit", X, **custom_weights) return qml.math.dot(probs, qml.math.cast_like(self._z_from_probs, probs))
[docs] @classmethod def get_test_params(cls): """List constructor kwargs used to parametrize this class in the test suite.""" return [{"n_qubits": 2, "n_layers": 1}]