Skip to content

Preprocessing

hqnn_forge.preprocessing

hqnn_forge.preprocessing

Classical pre-processing pipeline — PCA dimensionality reduction and feature standardisation — implemented in pure NumPy (no scikit-learn runtime dependency).

Exported symbols

PCANormalizer Fits/applies PCA + per-feature standardisation; returns torch.Tensor. stratified_kfold Fold indices that keep class prevalence. smote SMOTE oversampling of a binary minority class. oversample_fold SMOTE one fold's training rows only; validation rows untouched. iter_folds Stratified folds with fold-safe oversampling. Fold, SmoteResult Result types of the above.

Fold

Bases: NamedTuple

One cross-validation fold with an oversampled training split.

X_train instance-attribute

X_train: FloatArray

y_train instance-attribute

y_train: NDArray[generic]

X_val instance-attribute

X_val: FloatArray

y_val instance-attribute

y_val: NDArray[generic]

train_idx instance-attribute

train_idx: IntArray

val_idx instance-attribute

val_idx: IntArray

sources instance-attribute

sources: IntArray

Original-row indices (into the full X) of each synthetic sample's pair.

SmoteResult

Bases: NamedTuple

Oversampled data.

Attributes:

Name Type Description
X, y

The input rows first, in their original order, followed by the synthetic minority rows.

sources IntArray

Shape (n_synthetic, 2): for each synthetic row, the input row indices of the sample it was interpolated from and of the neighbour it was interpolated towards.

X instance-attribute

X: FloatArray

y instance-attribute

y: NDArray[generic]

sources instance-attribute

sources: IntArray

PCANormalizer

Fit/apply PCA + per-feature standardisation without scikit-learn.

The fitted arrays (mean_, components_, explained_variance_ and std_) are set by fit and absent until then, as in scikit-learn; is_fitted_ exists from construction.

Parameters:

Name Type Description Default
n_components int | integer

Number of principal components to retain. Must be an integer ≥ 1 (int or a NumPy integer; bool and floats, even integral ones such as 3.0, are rejected rather than coerced); fit raises ValueError otherwise. Default: 8.

8
scale_to_pi bool

If True (default), rescale standardised components into [-π, π] via tanh(x) * π before returning. Ensures valid angle-embedding range without hard clipping.

True

Attributes:

Name Type Description
mean_ (ndarray, shape(n_features))

Per-feature mean computed during fit.

components_ (ndarray, shape(n_components, n_features))

Principal component matrix (rows = eigenvectors, sorted by descending explained variance). Each row's sign is canonicalised so its largest-magnitude entry is positive, making components_ -- and therefore transform -- a deterministic function of the input data rather than of the LAPACK build. This is part of the public contract, and holds whenever the eigenvalues are well separated. Entries tied for largest magnitude are resolved by column order, so mirrored feature pairs are covered; near-degenerate eigenvalues, however, leave the basis itself build-dependent, which no sign convention can repair. fit checks for this and emits a RuntimeWarning when two consecutive eigenvalues among the retained ones (or at the cutoff) differ by less than EIGENVALUE_GAP_WARN times the largest eigenvalue, which in practice means exactly degenerate up to rounding. The convention is the one scikit-learn's svd_flip applies with u_based_decision=False, reimplemented here rather than depended on.

explained_variance_ (ndarray, shape(n_components))

Eigenvalues corresponding to retained components.

std_ (ndarray, shape(n_components))

Per-component standard deviation (computed on training projections).

is_fitted_ bool

True after fit has been called.

Notes

Input is converted with np.asarray, so float64 input is used without copying and fit/transform may hold a view of the caller's buffer for the duration of the call. Neither method writes into it, and no view of it is kept in the fitted attributes, so the caller is free to modify or discard the array afterwards.

Examples:

>>> import numpy as np
>>> from hqnn_forge.preprocessing import PCANormalizer
>>> rng = np.random.default_rng(42)
>>> X_train = rng.standard_normal((1000, 30))  # 1000 samples, 30 raw features
>>> pca = PCANormalizer(n_components=8)
>>> pca.fit(X_train)  # returns the fitted normaliser
PCANormalizer(n_components=8, scale_to_pi=True, status=fitted)
>>> X_enc = pca.transform(X_train)  # torch.Tensor, shape (1000, 8)
>>> X_enc.shape
torch.Size([1000, 8])

mean_ instance-attribute

mean_: NDArray[float64]

components_ instance-attribute

components_: NDArray[float64]

explained_variance_ instance-attribute

explained_variance_: NDArray[float64]

std_ instance-attribute

std_: NDArray[float64]

n_components instance-attribute

n_components = n_components

scale_to_pi instance-attribute

scale_to_pi = scale_to_pi

is_fitted_ instance-attribute

is_fitted_: bool = False

explained_variance_ratio_ property

explained_variance_ratio_: NDArray[float64]

Fraction of total variance explained by each retained component.

Returns:

Type Description
(ndarray, shape(n_components))

fit

fit(X: ArrayLike) -> PCANormalizer

Compute PCA basis and per-component statistics on training data.

Parameters:

Name Type Description Default
X ArrayLike

Training data array-like of shape (n_samples, n_features). n_features must be ≥ 2 and ≥ n_components, and n_samples must be > n_components. Not copied when already float64, and never modified.

required

Returns:

Type Description
self

The fitted transformer (for method chaining).

Raises:

Type Description
ValueError

If X is not 2-D, if n_features < 2 (a single column has no covariance to decompose), if n_components is not an integer or is < 1, if n_features < n_components, or if n_samples <= n_components (centred data then has rank below n_components, so some components have zero variance). Also if the centred data has rank below n_components for any other reason -- collinear features, duplicated samples, constant data -- which is detected from numpy.linalg.matrix_rank of the centred data, before any component is retained.

Notes

fit is all-or-nothing: every check above raises before the first fitted attribute is assigned, so a rejected fit leaves the instance exactly as it found it. An instance that was already fitted keeps that fit and stays usable; one that was not stays unfitted.

transform

transform(X: ArrayLike) -> torch.Tensor

Project and standardise (and optionally scale to [-π, π]).

Only statistics learned in fit (mean_, components_, std_) are used, so each row is encoded independently of the other rows in X. Standardised output has zero mean and unit variance on the training data; other data keeps its offset from the training distribution.

Parameters:

Name Type Description Default
X ArrayLike

Data array-like of shape (n_samples, n_features). Must have the same n_features as the training data. Not copied when already float64, and never modified.

required

Returns:

Type Description
Tensor

Encoded tensor of shape (n_samples, n_components), dtype float32, values in [-π, π] if scale_to_pi=True.

Raises:

Type Description
RuntimeError

If fit has not been called.

ValueError

If X is not 2-D, or if its feature dimension doesn't match training data.

fit_transform

fit_transform(X: ArrayLike) -> torch.Tensor

Fit and transform in a single call (convenience method).

Parameters:

Name Type Description Default
X ArrayLike

Training data, shape (n_samples, n_features).

required

Returns:

Type Description
Tensor

Transformed tensor, shape (n_samples, n_components).

iter_folds

iter_folds(X: ArrayLike, y: ArrayLike, n_splits: int = 5, *, shuffle: bool = True, random_state: int | Generator | None = None, oversample: bool = True, **smote_kwargs: object) -> Iterator[Fold]

Stratified folds, each with its training split oversampled in isolation.

Parameters:

Name Type Description Default
X ArrayLike

Full dataset.

required
y ArrayLike

Full dataset.

required
n_splits int

Passed to stratified_kfold. random_state also seeds SMOTE (a different stream per fold). A seed, a Generator or None.

5
shuffle int

Passed to stratified_kfold. random_state also seeds SMOTE (a different stream per fold). A seed, a Generator or None.

5
random_state int

Passed to stratified_kfold. random_state also seeds SMOTE (a different stream per fold). A seed, a Generator or None.

5
oversample bool

Set False to get the same folds without SMOTE, for a baseline.

True
**smote_kwargs object

Passed to smote (k_neighbors, sampling_ratio).

{}

Yields:

Type Description
Fold

oversample_fold

oversample_fold(X: ArrayLike, y: ArrayLike, train_idx: ArrayLike, val_idx: ArrayLike, **smote_kwargs: object) -> Fold

SMOTE the training rows of one fold; return the validation rows as-is.

Only X[train_idx] is ever passed to smote, so no synthetic sample can be derived from a validation row.

train_idx and val_idx are integer index arrays or boolean masks over the rows of X. Negative indices are rejected rather than interpreted the way NumPy would: -1 names the same row as n - 1 but does not collide with it under a set intersection, so a negative train_idx would pass the overlap check and then train on exactly the validation rows -- the leak this module exists to prevent, reported as safe by Fold.sources.

Raises:

Type Description
ValueError

If train_idx and val_idx overlap, or either contains an index outside [0, len(X)).

smote

smote(X: ArrayLike, y: ArrayLike, *, k_neighbors: int = 5, sampling_ratio: float = 1.0, random_state: int | Generator | None = None) -> SmoteResult

Oversample the minority class of a binary problem with SMOTE.

Each synthetic sample is x + u · (x_nn − x), where x is a minority sample drawn uniformly, x_nn one of its k_neighbors nearest minority neighbours (Euclidean, excluding itself), and u ~ U(0, 1).

Parameters:

Name Type Description Default
X ArrayLike

Features, shape (n_samples, n_features).

required
y ArrayLike

Binary labels, shape (n_samples,). The less frequent value is the minority class.

required
k_neighbors int

Neighbours considered per minority sample. Must be smaller than the minority count. Default: 5.

5
sampling_ratio float

Target n_minority / n_majority after oversampling, in (0, 1]. Default: 1.0 (balanced). If the data already meets it, nothing is generated.

1.0
random_state int | Generator | None

Seed or generator.

None

Returns:

Type Description
SmoteResult

Raises:

Type Description
ValueError

On shape mismatches, non-binary y, an out-of-range sampling_ratio, or too few minority samples for k_neighbors.

stratified_kfold

stratified_kfold(y: ArrayLike, n_splits: int = 5, *, shuffle: bool = True, random_state: int | Generator | None = None) -> list[tuple[IntArray, IntArray]]

Split sample indices into n_splits folds with equal class prevalence.

Each class's indices are (optionally) shuffled and dealt into n_splits chunks whose sizes differ by at most one; fold k validates on chunk k of every class and trains on the rest. Every sample is validated exactly once.

Parameters:

Name Type Description Default
y ArrayLike

Class labels, shape (n_samples,). Any hashable label values.

required
n_splits int

Number of folds, at least 2. Default: 5.

5
shuffle bool

Shuffle within each class before dealing. Default: True.

True
random_state int | Generator | None

Seed or generator for the shuffle.

None

Returns:

Type Description
list of (train_idx, val_idx)

Sorted index arrays, one pair per fold.

Raises:

Type Description
ValueError

If n_splits < 2, y is not 1-D, or a class has fewer members than n_splits (some fold would miss that class entirely).