Models¶
hqnn_forge.models ¶
hqnn_forge.models¶
Full hybrid quantum-classical architectures.
Exported symbols
ClassifierBase nn.Module base of every classifier: get_config, count_parameters. BinaryClassifierBase ClassifierBase + sigmoid predict_proba / thresholded predict. HybridBinaryClassifier Linear encoder → QuantumEncodingLayer → Linear head. ParallelHybridClassifier classical MLP branch ‖ QuantumEncodingLayer branch → Linear head. MulticlassHybridClassifier Linear encoder → QuantumEncodingLayer → n_classes heads (softmax / OvR). ClassicalBaseline Plain MLP with the same interface: the classical control of an ablation. LinearClassifier One affine map to a logit: a linear head, logistic regression under BCE.
BinaryClassifierBase ¶
Bases: ClassifierBase
Base class for hybrid binary classifiers.
Contract for subclasses
forward(x) takes (batch, n_input_features) and returns raw logits of
shape (batch, 1). predict_proba and predict are derived from it
and must not be overridden to keep the two models interchangeable.
Methods:
| Name | Description |
|---|---|
predict_proba |
Sigmoid of the logits, shape |
predict |
|
count_parameters |
From |
Attributes:
| Name | Type | Description |
|---|---|---|
head |
Linear
|
The output layer producing the single logit. Every subclass assigns it
in |
predict_proba ¶
predict_proba(x: Tensor) -> torch.Tensor
Compute positive-class probabilities (inference mode, no gradients).
Runs in eval mode whatever mode the model is in, so dropout is off and
repeated calls on the same input agree (except under finite shots,
whose readouts are sampled afresh on every call). Every submodule's training
flag is restored afterwards, so calling this mid-training leaves the
model exactly as it was.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Tensor
|
Input tensor, shape |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
Probability of class 1, shape |
predict ¶
predict(x: Tensor, threshold: float = 0.5) -> torch.Tensor
Predict binary labels. Runs in eval mode, like predict_proba.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Tensor
|
Input tensor, shape |
required |
threshold
|
float
|
Decision threshold. Default: 0.5. For imbalanced datasets consider tuning via ROC/PR curves. |
0.5
|
Returns:
| Type | Description |
|---|---|
Tensor
|
Binary label tensor of shape |
ClassifierBase ¶
Bases: Module
Head-agnostic base class for the classifiers.
Methods:
| Name | Description |
|---|---|
count_parameters |
Total number of (trainable) parameters, quantum and classical. |
get_config |
The constructor arguments, so |
count_parameters ¶
count_parameters(trainable_only: bool = True) -> int
Return total parameter count (quantum + classical).
get_config ¶
get_config() -> dict[str, Any]
Constructor arguments of this model, as a fresh dict.
type(model)(**model.get_config()) builds a model with the same
architecture (load a state_dict for the trained weights). Its
initial weights are fresh draws only if init_seed is None: a
model built with init_seed rebuilds the same initial weights, so
for restarts or ensemble members pass init_seed=None or a new seed.
The same holds for a sampling model's device seed, which would
replay the same shot noise: pass seed=None or a new one as well.
Used by hqnn_forge.utils.checkpoint.
A module argument (a custom classical_encoder) is deep-copied, so
a model built from the config does not share it with this one; the
copy carries the module's current weights, since the model never
re-initialises a custom encoder.
ClassicalBaseline ¶
Bases: BinaryClassifierBase
MLP binary classifier with the hybrid classifiers' interface.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_input_features
|
int
|
Input width. |
required |
hidden_dims
|
Sequence[int]
|
Width of each hidden layer, at least one. |
required |
activation
|
str
|
|
'relu'
|
dropout_p
|
float
|
Dropout before the output layer, in |
0.0
|
init_seed
|
int | None
|
Seed for weight initialisation, as on the hybrid classifiers. |
None
|
Examples:
>>> model = ClassicalBaseline(n_input_features=30, hidden_dims=[10])
>>> model.count_parameters()
321
HybridBinaryClassifier ¶
Bases: QuantumTrunk, BinaryClassifierBase
Hybrid quantum-classical binary classifier.
See module docstring for architecture overview.
The published SHNN (thesis / hqnn-fraud-detection-benchmark) is
embedding_rotation="Y", entangler="strongly_entangling",
readout="first", encoder_activation="sigmoid",
init_strategy="normal"; see published_shnn.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_input_features
|
int
|
Number of raw (or PCA-reduced) input features. Default: 8. |
8
|
n_qubits
|
int
|
Number of qubits in the quantum encoding layer. Default: 8. |
8
|
n_layers
|
int
|
VQC ansatz layers. Default: 2. At 1, with angle encoding and
|
2
|
use_classical_encoder
|
bool
|
Prepend |
True
|
dropout_p
|
float
|
Dropout probability applied after the quantum layer. Default: 0.0. |
0.0
|
device_name
|
DeviceName
|
PennyLane device name. Default |
'auto'
|
diff_method
|
DiffMethod
|
|
'auto'
|
init_strategy
|
str
|
|
'restricted'
|
encoding_type
|
str
|
The quantum embedding. Default:
|
'angle'
|
embedding_rotation
|
RotationAxis
|
Pauli axis of the angle embedding, |
'X'
|
entangler
|
Entangler
|
|
'ring'
|
readout
|
Readout
|
|
'all'
|
encoder_activation
|
str
|
|
DEFAULT_ENCODER_ACTIVATION
|
init_std
|
float
|
Standard deviation for |
DEFAULT_INIT_STD
|
noise_level
|
float
|
Training-time strength of |
0.0
|
noise_position
|
Position
|
|
'all'
|
noise_method
|
NoiseMethod
|
|
'density'
|
noise_trajectories
|
int
|
Draws averaged per sample with |
1
|
init_seed
|
int | None
|
Seed for weight initialisation. |
None
|
classical_encoder
|
Module | None
|
Your own module in place of the built-in |
None
|
trainable_input_scaling
|
bool
|
With |
False
|
shots
|
int | None
|
|
None
|
noise_channel
|
Channel
|
The channel |
'depolarizing'
|
seed
|
int | None
|
Seed of the device's random generator, which draws the shot samples,
so a shot-based model gives the same samples on every run. Default
|
None
|
readout_error
|
tuple[float, float] | None
|
|
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
classical_encoder |
Sequential or Identity
|
|
quantum_layer |
QuantumEncodingLayer
|
|
dropout |
Dropout
|
|
head |
Linear
|
|
Examples:
>>> import torch
>>> from hqnn_forge.models import HybridBinaryClassifier
>>> model = HybridBinaryClassifier(n_input_features=30, n_qubits=8, n_layers=2)
>>> x = torch.randn(4, 30)
>>> logits = model(x) # shape (4, 1)
>>> probs = model.predict_proba(x) # shape (4,), values in [0, 1]
published_shnn
classmethod
¶
published_shnn(**overrides: object) -> HybridBinaryClassifier
The SHNN configuration published in the thesis and in
hqnn-fraud-detection-benchmark (configs/default.yaml, shnn):
8 qubits, 2 layers, Linear(8→8) + π·sigmoid, RY angle embedding,
StronglyEntanglingLayers, ⟨Z_0⟩ readout, Linear(1→1) head,
N(0, 0.1²) quantum init. 122 trainable parameters, 48 quantum.
Live parameters: 102 of the 122. With the ⟨Z_0⟩ readout, 20 of the
48 quantum weights can never move the output, for any input or weight
values: the whole last-layer Rot on wires 0, 1, 3, 5 and 7, the
last-layer ω on wires 2, 4 and 6, and the first-layer ω on
wires 0 and 1 (measured with autograd and pinned in
tests/test_published_shnn_parity.py). circuit_summary reports
16 inert, a lower bound: backwards through the last layer's range-2
CNOTs, Z_0 becomes Z_2 Z_4 Z_6, a cancellation a per-wire
analysis cannot see.
The dead weights are kept (#234): the (2, 8, 3) weight shape is
the published one, so the model and its checkpoints stay
reproducible, and both counts are reported instead. Parameter
efficiency figures use the total, as the published results do (MCC
0.5758 over 0.122 kParam = 4.72); over the 102 live parameters the same
MCC is 5.65 per kParam. hqnn_forge.evaluation.parameter_efficiency
takes an integer count for that.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
**overrides
|
object
|
Constructor arguments to change, e.g. |
{}
|
forward ¶
forward(x: Tensor) -> torch.Tensor
Forward pass: classical encode → quantum encode → classification head.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Tensor
|
Input tensor, shape |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
Raw logits, shape |
LinearClassifier ¶
Bases: BinaryClassifierBase
Linear binary classifier, z = w · x + b, with the classifiers' interface.
One raw logit per sample, like every classifier in this package, so it
trains under hqnn_forge.training.train_model and scores through
predict_proba. The module docstring gives the model and says when it
is a logistic regression.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_input_features
|
int
|
Input width |
required |
init_seed
|
int | None
|
Seed for weight initialisation, as on the other classifiers. |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
head |
Linear
|
|
n_input_features |
int
|
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If |
Examples:
>>> import torch
>>> from hqnn_forge.models import LinearClassifier
>>> model = LinearClassifier(n_input_features=4)
>>> model.count_parameters() # 4 weights and a bias
5
>>> with torch.no_grad():
... _ = model.head.weight.copy_(torch.tensor([[1.0, 0.0, -1.0, 2.0]]))
... _ = model.head.bias.fill_(0.5)
>>> model(torch.tensor([[2.0, 7.0, 1.0, -1.0]])).tolist() # 2 − 1 − 2 + 0.5
[[-0.5]]
MulticlassHybridClassifier ¶
Bases: QuantumTrunk, ClassifierBase
Hybrid quantum-classical multiclass classifier.
See the module docstring for the architecture and the two strategies.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_input_features
|
int
|
Number of raw (or PCA-reduced) input features. Default: 8. |
8
|
n_qubits
|
int
|
Number of qubits in the quantum encoding layer. Default: 8. |
8
|
n_layers
|
int
|
VQC ansatz layers. Default: 2. |
2
|
n_classes
|
int
|
Number of classes. Default: 3. Must be |
3
|
strategy
|
MulticlassStrategy
|
|
'softmax'
|
use_classical_encoder
|
bool
|
|
True
|
dropout_p
|
bool
|
|
True
|
device_name
|
bool
|
|
True
|
diff_method
|
bool
|
|
True
|
init_strategy
|
str
|
|
'restricted'
|
encoding_type
|
str
|
|
'restricted'
|
embedding_rotation
|
str
|
|
'restricted'
|
entangler
|
str
|
|
'restricted'
|
readout
|
str
|
|
'restricted'
|
encoder_activation
|
str
|
|
DEFAULT_ENCODER_ACTIVATION
|
init_std
|
str
|
|
DEFAULT_ENCODER_ACTIVATION
|
noise_level
|
str
|
|
DEFAULT_ENCODER_ACTIVATION
|
noise_position
|
str
|
|
DEFAULT_ENCODER_ACTIVATION
|
noise_method
|
str
|
|
DEFAULT_ENCODER_ACTIVATION
|
noise_trajectories
|
int
|
|
1
|
classical_encoder
|
int
|
|
1
|
trainable_input_scaling
|
int
|
|
1
|
shots
|
int
|
|
1
|
noise_channel
|
int
|
|
1
|
seed
|
int
|
|
1
|
readout_error
|
tuple[float, float] | None
|
The trunk's options, exactly as for
|
None
|
init_seed
|
int | None
|
Seed for weight initialisation. |
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
classical_encoder |
Sequential or Identity
|
|
quantum_layer |
QuantumEncodingLayer or IQPEncodingLayer
|
|
dropout |
Dropout or Identity
|
|
head |
Linear
|
|
Examples:
>>> import torch
>>> from hqnn_forge.models import MulticlassHybridClassifier
>>> model = MulticlassHybridClassifier(n_input_features=6, n_qubits=4, n_layers=1, n_classes=3)
>>> x = torch.randn(5, 6)
>>> model(x).shape # logits
torch.Size([5, 3])
>>> model.predict_proba(x).shape # rows sum to 1
torch.Size([5, 3])
>>> model.predict(x).shape # argmax labels in {0, 1, 2}
torch.Size([5])
forward ¶
forward(x: Tensor) -> torch.Tensor
Forward pass: classical encode → quantum encode → class heads.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Tensor
|
Input tensor, shape |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
Raw logits, shape |
predict_proba ¶
predict_proba(x: Tensor) -> torch.Tensor
Per-class probabilities, shape (batch_size, n_classes), rows
summing to one. Softmax of the logits under "softmax"; sigmoid
of each logit, normalised across classes, under "one_vs_rest".
The latter is computed as softmax(logsigmoid(logits)), which is
the same quantity but stays finite when every sigmoid in a row
underflows (all logits below about -88 in float32), where the direct
ratio would be 0 / 0.
Runs in eval mode whatever mode the model is in (dropout off) and
restores every submodule's training flag afterwards.
predict ¶
predict(x: Tensor) -> torch.Tensor
Predicted class labels, shape (batch_size,), dtype torch.long:
the argmax of the logits, under both strategies.
This is the argmax of predict_proba in exact arithmetic, since
both normalisations are monotone per row, but not always in floating
point: under "one_vs_rest" large positive logits saturate the
sigmoid, so e.g. logits [17, 20, 30] give float32 probabilities in
which classes 1 and 2 tie, and predict_proba(x).argmax(-1) returns
class 1 while predict returns class 2. Use this method, not
predict_proba(x).argmax(-1), for labels.
one_hot ¶
one_hot(y: Tensor) -> torch.Tensor
Integer labels (batch_size,) → one-hot (batch_size, n_classes)
in the dtype of the class heads, the target format of
nn.BCEWithLogitsLoss for the one-vs-rest strategy. Matching the
heads' dtype keeps the loss of a model.double() in float64.
Float labels are accepted only if integer-valued; anything else (e.g. smoothed targets) raises instead of being truncated.
ParallelHybridClassifier ¶
Bases: QuantumTrunk, BinaryClassifierBase
Parallel-topology hybrid quantum-classical binary classifier.
See module docstring for architecture overview. API-consistent with
HybridBinaryClassifier — same encoding_type / init_strategy
options and predict_proba / predict / count_parameters
interface.
The published SHNN (thesis / hqnn-fraud-detection-benchmark) is
embedding_rotation="Y", entangler="strongly_entangling",
readout="first", encoder_activation="sigmoid",
init_strategy="normal"; see published_shnn.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_input_features
|
int
|
Number of raw (or PCA-reduced) input features. Default: 8. |
8
|
n_qubits
|
int
|
Number of qubits in the quantum branch. Default: 8. |
8
|
n_layers
|
int
|
VQC ansatz layers. Default: 2. At 1, with angle encoding and
|
2
|
classical_hidden_dim
|
int
|
Width of the classical MLP branch. Default: 16. |
16
|
use_classical_encoder
|
bool
|
Prepend |
True
|
dropout_p
|
float
|
Dropout probability applied to the fused branch outputs. Default: 0.0. |
0.0
|
device_name
|
DeviceName
|
PennyLane device name. Default |
'auto'
|
diff_method
|
DiffMethod
|
|
'auto'
|
init_strategy
|
str
|
|
'restricted'
|
encoding_type
|
str
|
The quantum embedding. Default:
|
'angle'
|
embedding_rotation
|
RotationAxis
|
Pauli axis of the angle embedding, |
'X'
|
entangler
|
Entangler
|
|
'ring'
|
readout
|
Readout
|
|
'all'
|
encoder_activation
|
str
|
|
DEFAULT_ENCODER_ACTIVATION
|
init_std
|
float
|
Standard deviation for |
DEFAULT_INIT_STD
|
noise_level
|
float
|
Training-time strength of |
0.0
|
noise_position
|
Position
|
|
'all'
|
noise_method
|
NoiseMethod
|
|
'density'
|
noise_trajectories
|
int
|
Draws averaged per sample with |
1
|
init_seed
|
int | None
|
Seed for weight initialisation. |
None
|
classical_encoder
|
Module | None
|
Your own module in place of the built-in |
None
|
trainable_input_scaling
|
bool
|
With |
False
|
shots
|
int | None
|
|
None
|
noise_channel
|
Channel
|
The channel |
'depolarizing'
|
seed
|
int | None
|
Seed of the device's random generator, which draws the shot samples,
so a shot-based model gives the same samples on every run. Default
|
None
|
readout_error
|
tuple[float, float] | None
|
|
None
|
Attributes:
| Name | Type | Description |
|---|---|---|
classical_branch |
Sequential
|
|
classical_encoder |
Sequential or Identity
|
|
quantum_layer |
QuantumEncodingLayer or IQPEncodingLayer
|
|
dropout |
Dropout
|
|
head |
Linear
|
|
Examples:
>>> import torch
>>> from hqnn_forge.models import ParallelHybridClassifier
>>> model = ParallelHybridClassifier(n_input_features=30, n_qubits=8, n_layers=2)
>>> x = torch.randn(4, 30)
>>> logits = model(x) # shape (4, 1)
>>> probs = model.predict_proba(x) # shape (4,), values in [0, 1]
classical_branch
instance-attribute
¶
classical_branch = nn.Sequential(nn.Linear(n_input_features, classical_hidden_dim), nn.ReLU(), nn.Linear(classical_hidden_dim, classical_hidden_dim), nn.ReLU())
published_shnn
classmethod
¶
published_shnn(**overrides: object) -> ParallelHybridClassifier
The published SHNN's quantum branch and encoder (see
HybridBinaryClassifier.published_shnn) alongside the classical
MLP branch. overrides are passed to the constructor.
forward ¶
forward(x: Tensor) -> torch.Tensor
Forward pass: classical branch and quantum branch process x
independently, are concatenated, and fed through the classification
head.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
Tensor
|
Input tensor, shape |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
Raw logits, shape |