nirs4all.operators.models.sklearn.oklmpls module

Online Koopman Latent-Mode PLS (OKLM-PLS) regressor for nirs4all.

A sklearn-compatible implementation of OKLM-PLS that combines Koopman operator theory with PLS for time-series regression. The model learns latent dynamics T_{t+1} ≈ F @ T_t while simultaneously fitting Y_t ≈ T_t @ B.

This is useful for spectral data collected over time where temporal coherence provides additional information for prediction.

Supports both NumPy (CPU) and JAX (GPU/TPU) backends.

References

Mathematical formulation

Let

  • X ∈ ℝ^{n×p} be the input matrix of n samples and p features (e.g. NIRS spectra), ordered in time t = 1,…,n.

  • Y ∈ ℝ^{n×q} be the corresponding response matrix.

An optional feature map ψ : ℝ^p → ℝ^d is applied to each row x_t, giving Z ∈ ℝ^{n×d} with rows

z_t = ψ(x_t), t = 1,…,n.

OKLM-PLS seeks:

  • a loading matrix W ∈ ℝ^{d×r} (r latent components),

  • a latent dynamic matrix F ∈ ℝ^{r×r},

  • a regression matrix B ∈ ℝ^{r×q},

such that the latent scores

T = Z W ∈ ℝ^{n×r}, t_t = row t of T,

(1) capture covariance between X (via Z) and Y in the PLS sense, (2) follow a simple linear dynamics in latent space, and (3) predict Y linearly.

The latent dynamics is modeled as a Koopman-like linear evolution:

t_{t+1} ≈ F t_t, for t = 1,…,n−1.

The regression in latent space is

y_t ≈ Bᵀ t_t, for t = 1,…,n,

or in matrix form

Y ≈ T B.

A simple joint objective is

L(W, F, B)
= λ_dyn ∑_{t=1}^{n−1} ‖ t_{t+1} − F t_t ‖₂²
  • λ_reg ∑_{t=1}^{n} ‖ y_t − Bᵀ t_t ‖₂²,

subject to PLS-style constraints on W and T (e.g. orthonormal scores and components ordered by decreasing covariance with Y):

(1/n) Tᵀ T = I_r, components sorted by cov(T_j, Y).

In practice, the objective is optimized by alternating updates:

  1. Latent scores Given W, we form Z and T = Z W.

  2. Dynamics update Given T, we estimate F as the least-squares solution of

    F = argmin_F ∑_{t=1}^{n−1} ‖ t_{t+1} − F t_t ‖₂²,

    which has the closed form

    F = (∑_{t} t_{t+1} t_tᵀ) (∑_{t} t_t t_tᵀ)^{-1}.

  3. Regression update Given T, we estimate B as the least-squares solution of

    B = argmin_B ‖ Y − T B ‖_F²,

    giving

    B = (Tᵀ T)^{-1} Tᵀ Y.

  4. PLS-like loading update W is initialized from a standard PLS on (Z, Y), and can optionally be refined by (approximate) gradient-based updates on L(W, F, B), while enforcing column normalization and PLS-style orthogonality constraints.

The fitted model predicts Y for new inputs X* by:

  1. applying the same preprocessing and feature map ψ to obtain Z*,

  2. computing scores T* = Z* W,

  3. returning Ŷ* = T* B (with inverse scaling if standardization is used).

The term “online” refers to the fact that, for streaming data, F and B can be updated recursively as new scores t_t arrive, while W is kept fixed or updated more infrequently.

class nirs4all.operators.models.sklearn.oklmpls.IdentityFeaturizer[source]

Bases: BaseEstimator, TransformerMixin

Identity featurizer: ψ(x) = x.

This is the default featurizer for OKLMPLS when no nonlinear transformation is needed.

fit(X, y=None)[source]

Fit the featurizer (no-op for identity).

Parameters:
Returns:

self

Return type:

IdentityFeaturizer

transform(X)[source]

Transform X (identity).

Parameters:

X (array-like of shape (n_samples, n_features)) – Data to transform.

Returns:

X – Same as input.

Return type:

ndarray of shape (n_samples, n_features)

class nirs4all.operators.models.sklearn.oklmpls.OKLMPLS(n_components: int = 5, featurizer: TransformerMixin | None = None, lambda_dyn: float = 1.0, lambda_reg_y: float = 1.0, max_iter: int = 50, tol: float = 0.0001, warm_start_pls: bool = True, standardize: bool = True, backend: str = 'numpy', random_state: int | None = None)[source]

Bases: BaseEstimator, RegressorMixin

Online Koopman Latent-Mode Partial Least Squares (OKLM-PLS).

OKLM-PLS combines Koopman operator theory with PLS for time-series regression. It learns latent scores T = ψ(X) @ W and simultaneously: - Enforces dynamic coherence: T_{t+1} ≈ F @ T_t - Learns regression: Y_t ≈ T_t @ B

This is useful for spectral data collected over time where temporal coherence provides additional predictive information.

Parameters:
  • n_components (int, default=5) – Number of latent components.

  • featurizer (TransformerMixin, optional) – Feature map ψ: X -> Z. If None, identity is used. Options include PolynomialFeaturizer and RBFFeaturizer.

  • lambda_dyn (float, default=1.0) – Weight for dynamic consistency loss ||T_{t+1} - F @ T_t||². Higher values enforce stronger temporal coherence.

  • lambda_reg_y (float, default=1.0) – Weight for regression loss ||Y - T @ B||².

  • max_iter (int, default=50) – Maximum alternating optimization iterations.

  • tol (float, default=1e-4) – Convergence tolerance on the objective function.

  • warm_start_pls (bool, default=True) – If True, initialize W/B from a standard PLSRegression fit.

  • standardize (bool, default=True) – Whether to standardize X and Y before fitting.

  • backend (str, default='numpy') – Computational backend: - ‘numpy’: NumPy backend (CPU only). - ‘jax’: JAX backend (supports GPU/TPU).

  • random_state (int, optional) – Random seed for initialization.

n_features_in_

Number of features in input X.

Type:

int

n_components_

Actual number of components.

Type:

int

W_

Projection weights (in featurized space).

Type:

ndarray of shape (n_features_z, n_components_)

F_

Dynamics matrix for latent scores.

Type:

ndarray of shape (n_components_, n_components_)

B_

Regression coefficients.

Type:

ndarray of shape (n_components_, n_targets)

n_iter_

Number of iterations until convergence.

Type:

int

Examples

>>> from nirs4all.operators.models.sklearn.oklmpls import OKLMPLS
>>> import numpy as np
>>> # Generate time-series data
>>> np.random.seed(42)
>>> X = np.random.randn(100, 50)
>>> y = X[:, :5].sum(axis=1) + 0.1 * np.random.randn(100)
>>> # Fit OKLM-PLS
>>> model = OKLMPLS(n_components=10, lambda_dyn=1.0, lambda_reg_y=1.0)
>>> model.fit(X, y)
OKLMPLS(...)
>>> predictions = model.predict(X)
>>> # Use with polynomial featurizer for nonlinearity
>>> from nirs4all.operators.models.sklearn.oklmpls import PolynomialFeaturizer
>>> model_poly = OKLMPLS(n_components=10, featurizer=PolynomialFeaturizer(degree=2))
>>> model_poly.fit(X, y)

Notes

OKLM-PLS is designed for temporally-ordered data where samples are sequential in time. The dynamics constraint helps capture temporal patterns and can improve prediction when the underlying process has smooth temporal evolution.

For non-temporal data, set lambda_dyn=0 to disable the dynamics constraint (equivalent to standard PLS with optional featurization).

See also

SIMPLS

Standard PLS without dynamics.

RecursivePLS

Online PLS with forgetting factor.

__repr__() str[source]

Return string representation.

fit(X: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str], y: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str]) OKLMPLS[source]

Fit the OKLM-PLS model.

Parameters:
Returns:

self – Fitted estimator.

Return type:

OKLMPLS

Raises:
get_params(deep: bool = True) dict[source]

Get parameters for this estimator.

predict(X: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str]) ndarray[tuple[Any, ...], dtype[floating]][source]

Predict using the OKLM-PLS model.

Parameters:

X (array-like of shape (n_samples, n_features)) – Samples to predict.

Returns:

y_pred – Predicted values.

Return type:

ndarray of shape (n_samples,) or (n_samples, n_targets)

predict_dynamic(X: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str], n_steps: int = 1) ndarray[tuple[Any, ...], dtype[floating]][source]

Predict using dynamics model for future timesteps.

Given the last sample’s latent scores, predict future values using the learned dynamics T_{t+1} = F @ T_t.

Parameters:
  • X (array-like of shape (n_samples, n_features)) – Current data. Uses last sample for propagation.

  • n_steps (int, default=1) – Number of future timesteps to predict.

Returns:

y_future – Predicted future values.

Return type:

ndarray of shape (n_steps, n_targets)

set_params(**params) OKLMPLS[source]

Set the parameters of this estimator.

set_score_request(*, sample_weight: bool | None | str = '$UNCHANGED$') OKLMPLS

Configure whether metadata should be requested to be passed to the score method.

Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with enable_metadata_routing=True (see sklearn.set_config()). Please check the User Guide on how the routing mechanism works.

The options for each parameter are:

  • True: metadata is requested, and passed to score if provided. The request is ignored if metadata is not provided.

  • False: metadata is not requested and the meta-estimator will not pass it to score.

  • None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.

  • str: metadata should be passed to the meta-estimator with this given alias instead of the original name.

The default (sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.

Added in version 1.3.

Parameters:

sample_weight (str, True, False, or None, default=sklearn.utils.metadata_routing.UNCHANGED) – Metadata routing for sample_weight parameter in score.

Returns:

self – The updated object.

Return type:

object

transform(X: _Buffer | _SupportsArray[dtype[Any]] | _NestedSequence[_SupportsArray[dtype[Any]]] | complex | bytes | str | _NestedSequence[complex | bytes | str]) ndarray[tuple[Any, ...], dtype[floating]][source]

Transform X to latent score space.

Parameters:

X (array-like of shape (n_samples, n_features)) – Samples to transform.

Returns:

T – Latent scores.

Return type:

ndarray of shape (n_samples, n_components_)

class nirs4all.operators.models.sklearn.oklmpls.PolynomialFeaturizer(degree: int = 2, include_original: bool = True)[source]

Bases: BaseEstimator, TransformerMixin

Polynomial featurizer for OKLM-PLS.

Creates polynomial features up to specified degree without interaction terms (for efficiency with high-dimensional spectral data).

Parameters:
  • degree (int, default=2) – Maximum degree of polynomial features.

  • include_original (bool, default=True) – Whether to include the original features (degree 1).

fit(X, y=None)[source]

Fit the featurizer.

Parameters:
Returns:

self

Return type:

PolynomialFeaturizer

transform(X)[source]

Transform X to polynomial features.

Parameters:

X (array-like of shape (n_samples, n_features)) – Data to transform.

Returns:

X_poly – Polynomial features.

Return type:

ndarray of shape (n_samples, n_features * degree)

class nirs4all.operators.models.sklearn.oklmpls.RBFFeaturizer(n_components: int = 100, gamma: float | None = None, random_state: int | None = None)[source]

Bases: BaseEstimator, TransformerMixin

Random Fourier Features (RBF approximation) featurizer for OKLM-PLS.

Approximates the RBF kernel using random Fourier features, which is useful for adding nonlinearity to the Koopman embedding.

Parameters:
  • n_components (int, default=100) – Number of random Fourier features.

  • gamma (float, optional) – Kernel coefficient. If None, uses 1/n_features.

  • random_state (int, optional) – Random seed for reproducibility.

fit(X, y=None)[source]

Fit the featurizer by sampling random frequencies.

Parameters:
Returns:

self

Return type:

RBFFeaturizer

transform(X)[source]

Transform X to random Fourier features.

Parameters:

X (array-like of shape (n_samples, n_features)) – Data to transform.

Returns:

X_rff – Random Fourier features.

Return type:

ndarray of shape (n_samples, n_components)