nnetsauce

  1from .base.base import Base
  2from .base.baseRegressor import BaseRegressor
  3from .boosting.adaBoostClassifier import AdaBoostClassifier
  4from .custom.customClassifier import CustomClassifier
  5from .custom.customRegressor import CustomRegressor
  6from .custom.customBackpropRegressor import CustomBackPropRegressor
  7from .datasets import Downloader
  8from .deep.deepClassifier import DeepClassifier
  9from .deep.deepRegressor import DeepRegressor
 10from .deep.deepMTS import DeepMTS
 11from .elasticnet2.enet2 import ElasticNet2Regressor
 12from .glm.glmClassifier import GLMClassifier
 13from .glm.glmRegressor import GLMRegressor
 14from .kernel.kernel import KernelRidge
 15from .lazypredict.lazydeepClassifier import LazyDeepClassifier, LazyClassifier
 16from .lazypredict.lazydeepRegressor import LazyDeepRegressor, LazyRegressor
 17from .lazypredict.lazydeepClassifier import LazyDeepClassifier
 18from .lazypredict.lazydeepRegressor import LazyDeepRegressor
 19from .lazypredict.lazydeepMTS import LazyDeepMTS, LazyMTS
 20from .mts.mts import MTS
 21from .mts.mlarch import MLARCH
 22from .mts.classical import ClassicalMTS
 23from .mts.stackedmts import MTSStacker
 24from .mts.multioutputmts import MultiOutputMTS
 25from .mts.discretetokenmts import DiscreteTokenMTS
 26from .multitask.multitaskClassifier import MultitaskClassifier
 27from .multitask.simplemultitaskClassifier import SimpleMultitaskClassifier
 28from .neuralnet.neuralnetregression import NeuralNetRegressor
 29from .neuralnet.neuralnetclassification import NeuralNetClassifier
 30from .optimizers.optimizer import Optimizer
 31from .predictioninterval import PredictionInterval
 32from .predictionset import PredictionSet
 33from .quantile.quantileregression import QuantileRegressor
 34from .quantile.quantileclassification import QuantileClassifier
 35from .randombag.randomBagClassifier import RandomBagClassifier
 36from .randombag.randomBagRegressor import RandomBagRegressor
 37from .randomfourier.randomfourier import RandomFourierEstimator
 38from .rff.rffridge import (
 39    RandomFourierFeaturesRidge,
 40    RandomFourierFeaturesRidgeGCV,
 41)
 42from .ridge.ridge import RidgeRegressor
 43from .ridge2.ridge2Classifier import Ridge2Classifier
 44from .ridge2.ridge2Regressor import Ridge2Regressor
 45from .ridge2.ridge2MultitaskClassifier import Ridge2MultitaskClassifier
 46from .ridge2.ridge2MTSJAX import Ridge2Forecaster
 47from .ridge2.ridge2multioutputregressor import Ridge2MultiOutputRegressor
 48from .rvfl.bayesianrvflRegressor import BayesianRVFLRegressor
 49from .rvfl.bayesianrvfl2Regressor import BayesianRVFL2Regressor
 50from .sampling import SubSampler
 51from .updater import RegressorUpdater, ClassifierUpdater
 52from .votingregressor import MedianVotingRegressor
 53
 54__all__ = [
 55    "AdaBoostClassifier",
 56    "Base",
 57    "BaseRegressor",
 58    "BayesianRVFLRegressor",
 59    "BayesianRVFL2Regressor",
 60    "ClassicalMTS",
 61    "CustomClassifier",
 62    "CustomRegressor",
 63    "CustomBackPropRegressor",
 64    "DeepClassifier",
 65    "DeepRegressor",
 66    "DeepMTS",
 67    "DiscreteTokenMTS",
 68    "Downloader",
 69    "ElasticNet2Regressor",
 70    "GLMClassifier",
 71    "GLMRegressor",
 72    "KernelRidge",
 73    "LazyClassifier",
 74    "LazyRegressor",
 75    "LazyDeepClassifier",
 76    "LazyDeepRegressor",
 77    "LazyMTS",
 78    "LazyDeepMTS",
 79    "MLARCH",
 80    "MedianVotingRegressor",
 81    "MTS",
 82    "MTSStacker",
 83    "MultiOutputMTS",
 84    "MultitaskClassifier",
 85    "NeuralNetRegressor",
 86    "NeuralNetClassifier",
 87    "PredictionInterval",
 88    "PredictionSet",
 89    "SimpleMultitaskClassifier",
 90    "Optimizer",
 91    "QuantileRegressor",
 92    "QuantileClassifier",
 93    "RandomBagRegressor",
 94    "RandomBagClassifier",
 95    "RandomFourierEstimator",
 96    "RandomFourierFeaturesRidge",
 97    "RandomFourierFeaturesRidgeGCV",
 98    "RegressorUpdater",
 99    "ClassifierUpdater",
100    "RidgeRegressor",
101    "Ridge2Regressor",
102    "Ridge2MultiOutputRegressor",
103    "Ridge2Classifier",
104    "Ridge2MultitaskClassifier",
105    "Ridge2Forecaster",
106    "SubSampler",
107]
class AdaBoostClassifier(nnetsauce.boosting.bst.Boosting, sklearn.base.ClassifierMixin):
 21class AdaBoostClassifier(Boosting, ClassifierMixin):
 22    """AdaBoost Classification (SAMME) model class derived from class Boosting
 23
 24    Parameters:
 25
 26        obj: object
 27            any object containing a method fit (obj.fit()) and a method predict
 28            (obj.predict())
 29
 30        n_estimators: int
 31            number of boosting iterations
 32
 33        learning_rate: float
 34            learning rate of the boosting procedure
 35
 36        n_hidden_features: int
 37            number of nodes in the hidden layer
 38
 39        reg_lambda: float
 40            regularization parameter for weights
 41
 42        reg_alpha: float
 43            controls compromize between l1 and l2 norm of weights
 44
 45        activation_name: str
 46            activation function: 'relu', 'tanh', 'sigmoid', 'prelu' or 'elu'
 47
 48        a: float
 49            hyperparameter for 'prelu' or 'elu' activation function
 50
 51        nodes_sim: str
 52            type of simulation for the nodes: 'sobol', 'hammersley', 'halton',
 53            'uniform'
 54
 55        bias: boolean
 56            indicates if the hidden layer contains a bias term (True) or not
 57            (False)
 58
 59        dropout: float
 60            regularization parameter; (random) percentage of nodes dropped out
 61            of the training
 62
 63        direct_link: boolean
 64            indicates if the original predictors are included (True) in model's
 65            fitting or not (False)
 66
 67        n_clusters: int
 68            number of clusters for 'kmeans' or 'gmm' clustering (could be 0:
 69                no clustering)
 70
 71        cluster_encode: bool
 72            defines how the variable containing clusters is treated (default is one-hot)
 73            if `False`, then labels are used, without one-hot encoding
 74
 75        type_clust: str
 76            type of clustering method: currently k-means ('kmeans') or Gaussian
 77            Mixture Model ('gmm')
 78
 79        type_scaling: a tuple of 3 strings
 80            scaling methods for inputs, hidden layer, and clustering respectively
 81            (and when relevant).
 82            Currently available: standardization ('std') or MinMax scaling ('minmax')
 83
 84        col_sample: float
 85            percentage of covariates randomly chosen for training
 86
 87        row_sample: float
 88            percentage of rows chosen for training, by stratified bootstrapping
 89
 90        seed: int
 91            reproducibility seed for nodes_sim=='uniform'
 92
 93        verbose: int
 94            0 for no output, 1 for a progress bar (default is 1)
 95
 96        method: str
 97            type of Adaboost method, 'SAMME' (discrete) or 'SAMME.R' (real)
 98
 99        backend: str
100            "cpu" or "gpu" or "tpu"
101
102    Attributes:
103
104        alpha_: list
105            AdaBoost coefficients alpha_m
106
107        base_learners_: dict
108            a dictionary containing the base learners
109
110    Examples:
111
112    See also [https://github.com/Techtonique/nnetsauce/blob/master/examples/adaboost_classification.py](https://github.com/Techtonique/nnetsauce/blob/master/examples/adaboost_classification.py)
113
114    ```python
115    import nnetsauce as ns
116    import numpy as np
117    from sklearn.datasets import load_breast_cancer
118    from sklearn.linear_model import LogisticRegression
119    from sklearn.model_selection import train_test_split
120    from sklearn import metrics
121    from time import time
122
123    breast_cancer = load_breast_cancer()
124    Z = breast_cancer.data
125    t = breast_cancer.target
126    np.random.seed(123)
127    X_train, X_test, y_train, y_test = train_test_split(Z, t, test_size=0.2)
128
129    # SAMME.R
130    clf = LogisticRegression(solver='liblinear', multi_class = 'ovr',
131                            random_state=123)
132    fit_obj = ns.AdaBoostClassifier(clf,
133                                    n_hidden_features=int(11.22338867),
134                                    direct_link=True,
135                                    n_estimators=250, learning_rate=0.01126343,
136                                    col_sample=0.72684326, row_sample=0.86429443,
137                                    dropout=0.63078613, n_clusters=2,
138                                    type_clust="gmm",
139                                    verbose=1, seed = 123,
140                                    method="SAMME.R")
141
142    start = time()
143    fit_obj.fit(X_train, y_train)
144    print(f"Elapsed {time() - start}")
145
146    start = time()
147    print(fit_obj.score(X_test, y_test))
148    print(f"Elapsed {time() - start}")
149
150    preds = fit_obj.predict(X_test)
151
152    print(metrics.classification_report(preds, y_test))
153
154    ```
155
156    """
157
158    # construct the object -----
159    _estimator_type = "classifier"
160
161    def __init__(
162        self,
163        obj,
164        n_estimators=10,
165        learning_rate=0.1,
166        n_hidden_features=1,
167        reg_lambda=0,
168        reg_alpha=0.5,
169        activation_name="relu",
170        a=0.01,
171        nodes_sim="sobol",
172        bias=True,
173        dropout=0,
174        direct_link=False,
175        n_clusters=2,
176        cluster_encode=True,
177        type_clust="kmeans",
178        type_scaling=("std", "std", "std"),
179        col_sample=1,
180        row_sample=1,
181        seed=123,
182        verbose=1,
183        method="SAMME",
184        backend="cpu",
185    ):
186        self.type_fit = "classification"
187        self.verbose = verbose
188        self.method = method
189        self.reg_lambda = reg_lambda
190        self.reg_alpha = reg_alpha
191
192        super().__init__(
193            obj=obj,
194            n_estimators=n_estimators,
195            learning_rate=learning_rate,
196            n_hidden_features=n_hidden_features,
197            activation_name=activation_name,
198            a=a,
199            nodes_sim=nodes_sim,
200            bias=bias,
201            dropout=dropout,
202            direct_link=direct_link,
203            n_clusters=n_clusters,
204            cluster_encode=cluster_encode,
205            type_clust=type_clust,
206            type_scaling=type_scaling,
207            col_sample=col_sample,
208            row_sample=row_sample,
209            seed=seed,
210            backend=backend,
211        )
212
213        self.alpha_ = []
214        self.base_learners_ = dict.fromkeys(range(n_estimators))
215
216    def fit(self, X, y, sample_weight=None, **kwargs):
217        """Fit Boosting model to training data (X, y).
218
219        Parameters:
220
221            X: {array-like}, shape = [n_samples, n_features]
222                Training vectors, where n_samples is the number
223                of samples and n_features is the number of features.
224
225            y: array-like, shape = [n_samples]
226                Target values.
227
228            **kwargs: additional parameters to be passed to
229                    self.cook_training_set or self.obj.fit
230
231        Returns:
232
233             self: object
234        """
235
236        assert mx.is_factor(y), "y must contain only integers"
237
238        assert self.method in (
239            "SAMME",
240            "SAMME.R",
241        ), "`method` must be either 'SAMME' or 'SAMME.R'"
242
243        assert (self.reg_lambda <= 1) & (
244            self.reg_lambda >= 0
245        ), "must have self.reg_lambda <= 1 &  self.reg_lambda >= 0"
246
247        assert (self.reg_alpha <= 1) & (
248            self.reg_alpha >= 0
249        ), "must have self.reg_alpha <= 1 &  self.reg_alpha >= 0"
250
251        # training
252        n, p = X.shape
253        self.n_classes = len(np.unique(y))
254        self.classes_ = np.unique(y)  # for compatibility with sklearn
255        self.n_classes_ = len(self.classes_)  # for compatibility with sklearn
256
257        if sample_weight is None:
258            w_m = np.repeat(1.0 / n, n)
259        else:
260            w_m = np.asarray(sample_weight)
261
262        base_learner = CustomClassifier(
263            self.obj,
264            n_hidden_features=self.n_hidden_features,
265            activation_name=self.activation_name,
266            a=self.a,
267            nodes_sim=self.nodes_sim,
268            bias=self.bias,
269            dropout=self.dropout,
270            direct_link=self.direct_link,
271            n_clusters=self.n_clusters,
272            type_clust=self.type_clust,
273            type_scaling=self.type_scaling,
274            col_sample=self.col_sample,
275            row_sample=self.row_sample,
276            seed=self.seed,
277        )
278
279        if self.verbose == 1:
280            pbar = Progbar(self.n_estimators)
281
282        if self.method == "SAMME":
283            err_m = 1e6
284            err_bound = 1 - 1 / self.n_classes
285            self.alpha_.append(1.0)
286            x_range_n = range(n)
287
288            for m in range(self.n_estimators):
289                preds = base_learner.fit(
290                    X, y, sample_weight=w_m.ravel(), **kwargs
291                ).predict(X)
292
293                self.base_learners_.update({m: deepcopy(base_learner)})
294
295                cond = [y[i] != preds[i] for i in x_range_n]
296
297                err_m = max(
298                    sum([elt[0] * elt[1] for elt in zip(cond, w_m)]),
299                    2.220446049250313e-16,
300                )  # sum(w_m) == 1
301
302                if self.reg_lambda > 0:
303                    err_m += self.reg_lambda * (
304                        (1 - self.reg_alpha) * 0.5 * sum([x**2 for x in w_m])
305                        + self.reg_alpha * sum([abs(x) for x in w_m])
306                    )
307
308                err_m = min(err_m, err_bound)
309
310                alpha_m = self.learning_rate * log(
311                    (self.n_classes - 1) * (1 - err_m) / err_m
312                )
313
314                self.alpha_.append(alpha_m)
315
316                w_m_temp = [exp(alpha_m * cond[i]) for i in x_range_n]
317
318                sum_w_m = sum(w_m_temp)
319
320                w_m = np.asarray([w_m_temp[i] / sum_w_m for i in x_range_n])
321
322                base_learner.set_params(seed=self.seed + (m + 1) * 1000)
323
324                if self.verbose == 1:
325                    pbar.update(m)
326
327            if self.verbose == 1:
328                pbar.update(self.n_estimators)
329
330            self.n_estimators = len(self.base_learners_)
331            self.classes_ = np.unique(y)
332
333            return self
334
335        if self.method == "SAMME.R":
336            Y = mo.one_hot_encode2(y, self.n_classes)
337
338            if sample_weight is None:
339                w_m = np.repeat(1.0 / n, n)  # (N, 1)
340
341            else:
342                w_m = np.asarray(sample_weight)
343
344            for m in range(self.n_estimators):
345                probs = base_learner.fit(
346                    X, y, sample_weight=w_m.ravel(), **kwargs
347                ).predict_proba(X)
348
349                np.clip(
350                    a=probs, a_min=2.220446049250313e-16, a_max=1.0, out=probs
351                )
352
353                self.base_learners_.update({m: deepcopy(base_learner)})
354
355                w_m *= np.exp(
356                    -1.0
357                    * self.learning_rate
358                    * (1.0 - 1.0 / self.n_classes)
359                    * xlogy(Y, probs).sum(axis=1)
360                )
361
362                w_m /= np.sum(w_m)
363
364                base_learner.set_params(seed=self.seed + (m + 1) * 1000)
365
366                if self.verbose == 1:
367                    pbar.update(m)
368
369            if self.verbose == 1:
370                pbar.update(self.n_estimators)
371
372            self.n_estimators = len(self.base_learners_)
373            self.classes_ = np.unique(y)
374
375            return self
376
377    def predict(self, X, **kwargs):
378        """Predict test data X.
379
380        Parameters:
381
382            X: {array-like}, shape = [n_samples, n_features]
383                Training vectors, where n_samples is the number
384                of samples and n_features is the number of features.
385
386            **kwargs: additional parameters to be passed to
387                  self.cook_test_set
388
389        Returns:
390
391            model predictions: {array-like}
392        """
393        return self.predict_proba(X, **kwargs).argmax(axis=1)
394
395    def predict_proba(self, X, **kwargs):
396        """Predict probabilities for test data X.
397
398        Parameters:
399
400            X: {array-like}, shape = [n_samples, n_features]
401                Training vectors, where n_samples is the number
402                of samples and n_features is the number of features.
403
404            **kwargs: additional parameters to be passed to
405                  self.cook_test_set
406
407        Returns:
408
409            probability estimates for test data: {array-like}
410
411        """
412
413        n_iter = len(self.base_learners_)
414
415        if self.method == "SAMME":
416            ensemble_learner = np.zeros((X.shape[0], self.n_classes))
417
418            # if self.verbose == 1:
419            #    pbar = Progbar(n_iter)
420
421            for idx, base_learner in self.base_learners_.items():
422                preds = base_learner.predict(X, **kwargs)
423
424                ensemble_learner += self.alpha_[idx] * mo.one_hot_encode2(
425                    preds, self.n_classes
426                )
427
428                # if self.verbose == 1:
429                #    pbar.update(idx)
430
431            # if self.verbose == 1:
432            #    pbar.update(n_iter)
433
434            expit_ensemble_learner = expit(ensemble_learner)
435
436            sum_ensemble = expit_ensemble_learner.sum(axis=1)
437
438            return expit_ensemble_learner / sum_ensemble[:, None]
439
440        # if self.method == "SAMME.R":
441        ensemble_learner = 0
442
443        # if self.verbose == 1:
444        #    pbar = Progbar(n_iter)
445
446        for idx, base_learner in self.base_learners_.items():
447            probs = base_learner.predict_proba(X, **kwargs)
448
449            np.clip(a=probs, a_min=2.220446049250313e-16, a_max=1.0, out=probs)
450
451            log_preds_proba = np.log(probs)
452
453            ensemble_learner += (
454                log_preds_proba - log_preds_proba.mean(axis=1)[:, None]
455            )
456
457            # if self.verbose == 1:
458            #    pbar.update(idx)
459
460        ensemble_learner *= self.n_classes - 1
461
462        # if self.verbose == 1:
463        #    pbar.update(n_iter)
464
465        expit_ensemble_learner = expit(ensemble_learner)
466
467        sum_ensemble = expit_ensemble_learner.sum(axis=1)
468
469        return expit_ensemble_learner / sum_ensemble[:, None]
470
471    @property
472    def _estimator_type(self):
473        return "classifier"

AdaBoost Classification (SAMME) model class derived from class Boosting

Parameters:

obj: object
    any object containing a method fit (obj.fit()) and a method predict
    (obj.predict())

n_estimators: int
    number of boosting iterations

learning_rate: float
    learning rate of the boosting procedure

n_hidden_features: int
    number of nodes in the hidden layer

reg_lambda: float
    regularization parameter for weights

reg_alpha: float
    controls compromize between l1 and l2 norm of weights

activation_name: str
    activation function: 'relu', 'tanh', 'sigmoid', 'prelu' or 'elu'

a: float
    hyperparameter for 'prelu' or 'elu' activation function

nodes_sim: str
    type of simulation for the nodes: 'sobol', 'hammersley', 'halton',
    'uniform'

bias: boolean
    indicates if the hidden layer contains a bias term (True) or not
    (False)

dropout: float
    regularization parameter; (random) percentage of nodes dropped out
    of the training

direct_link: boolean
    indicates if the original predictors are included (True) in model's
    fitting or not (False)

n_clusters: int
    number of clusters for 'kmeans' or 'gmm' clustering (could be 0:
        no clustering)

cluster_encode: bool
    defines how the variable containing clusters is treated (default is one-hot)
    if `False`, then labels are used, without one-hot encoding

type_clust: str
    type of clustering method: currently k-means ('kmeans') or Gaussian
    Mixture Model ('gmm')

type_scaling: a tuple of 3 strings
    scaling methods for inputs, hidden layer, and clustering respectively
    (and when relevant).
    Currently available: standardization ('std') or MinMax scaling ('minmax')

col_sample: float
    percentage of covariates randomly chosen for training

row_sample: float
    percentage of rows chosen for training, by stratified bootstrapping

seed: int
    reproducibility seed for nodes_sim=='uniform'

verbose: int
    0 for no output, 1 for a progress bar (default is 1)

method: str
    type of Adaboost method, 'SAMME' (discrete) or 'SAMME.R' (real)

backend: str
    "cpu" or "gpu" or "tpu"

Attributes:

alpha_: list
    AdaBoost coefficients alpha_m

base_learners_: dict
    a dictionary containing the base learners

Examples:

See also https://github.com/Techtonique/nnetsauce/blob/master/examples/adaboost_classification.py

import nnetsauce as ns
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn import metrics
from time import time

breast_cancer = load_breast_cancer()
Z = breast_cancer.data
t = breast_cancer.target
np.random.seed(123)
X_train, X_test, y_train, y_test = train_test_split(Z, t, test_size=0.2)

# SAMME.R
clf = LogisticRegression(solver='liblinear', multi_class = 'ovr',
                        random_state=123)
fit_obj = ns.AdaBoostClassifier(clf,
                                n_hidden_features=int(11.22338867),
                                direct_link=True,
                                n_estimators=250, learning_rate=0.01126343,
                                col_sample=0.72684326, row_sample=0.86429443,
                                dropout=0.63078613, n_clusters=2,
                                type_clust="gmm",
                                verbose=1, seed = 123,
                                method="SAMME.R")

start = time()
fit_obj.fit(X_train, y_train)
print(f"Elapsed {time() - start}")

start = time()
print(fit_obj.score(X_test, y_test))
print(f"Elapsed {time() - start}")

preds = fit_obj.predict(X_test)

print(metrics.classification_report(preds, y_test))
def fit(self, X, y, sample_weight=None, **kwargs):
216    def fit(self, X, y, sample_weight=None, **kwargs):
217        """Fit Boosting model to training data (X, y).
218
219        Parameters:
220
221            X: {array-like}, shape = [n_samples, n_features]
222                Training vectors, where n_samples is the number
223                of samples and n_features is the number of features.
224
225            y: array-like, shape = [n_samples]
226                Target values.
227
228            **kwargs: additional parameters to be passed to
229                    self.cook_training_set or self.obj.fit
230
231        Returns:
232
233             self: object
234        """
235
236        assert mx.is_factor(y), "y must contain only integers"
237
238        assert self.method in (
239            "SAMME",
240            "SAMME.R",
241        ), "`method` must be either 'SAMME' or 'SAMME.R'"
242
243        assert (self.reg_lambda <= 1) & (
244            self.reg_lambda >= 0
245        ), "must have self.reg_lambda <= 1 &  self.reg_lambda >= 0"
246
247        assert (self.reg_alpha <= 1) & (
248            self.reg_alpha >= 0
249        ), "must have self.reg_alpha <= 1 &  self.reg_alpha >= 0"
250
251        # training
252        n, p = X.shape
253        self.n_classes = len(np.unique(y))
254        self.classes_ = np.unique(y)  # for compatibility with sklearn
255        self.n_classes_ = len(self.classes_)  # for compatibility with sklearn
256
257        if sample_weight is None:
258            w_m = np.repeat(1.0 / n, n)
259        else:
260            w_m = np.asarray(sample_weight)
261
262        base_learner = CustomClassifier(
263            self.obj,
264            n_hidden_features=self.n_hidden_features,
265            activation_name=self.activation_name,
266            a=self.a,
267            nodes_sim=self.nodes_sim,
268            bias=self.bias,
269            dropout=self.dropout,
270            direct_link=self.direct_link,
271            n_clusters=self.n_clusters,
272            type_clust=self.type_clust,
273            type_scaling=self.type_scaling,
274            col_sample=self.col_sample,
275            row_sample=self.row_sample,
276            seed=self.seed,
277        )
278
279        if self.verbose == 1:
280            pbar = Progbar(self.n_estimators)
281
282        if self.method == "SAMME":
283            err_m = 1e6
284            err_bound = 1 - 1 / self.n_classes
285            self.alpha_.append(1.0)
286            x_range_n = range(n)
287
288            for m in range(self.n_estimators):
289                preds = base_learner.fit(
290                    X, y, sample_weight=w_m.ravel(), **kwargs
291                ).predict(X)
292
293                self.base_learners_.update({m: deepcopy(base_learner)})
294
295                cond = [y[i] != preds[i] for i in x_range_n]
296
297                err_m = max(
298                    sum([elt[0] * elt[1] for elt in zip(cond, w_m)]),
299                    2.220446049250313e-16,
300                )  # sum(w_m) == 1
301
302                if self.reg_lambda > 0:
303                    err_m += self.reg_lambda * (
304                        (1 - self.reg_alpha) * 0.5 * sum([x**2 for x in w_m])
305                        + self.reg_alpha * sum([abs(x) for x in w_m])
306                    )
307
308                err_m = min(err_m, err_bound)
309
310                alpha_m = self.learning_rate * log(
311                    (self.n_classes - 1) * (1 - err_m) / err_m
312                )
313
314                self.alpha_.append(alpha_m)
315
316                w_m_temp = [exp(alpha_m * cond[i]) for i in x_range_n]
317
318                sum_w_m = sum(w_m_temp)
319
320                w_m = np.asarray([w_m_temp[i] / sum_w_m for i in x_range_n])
321
322                base_learner.set_params(seed=self.seed + (m + 1) * 1000)
323
324                if self.verbose == 1:
325                    pbar.update(m)
326
327            if self.verbose == 1:
328                pbar.update(self.n_estimators)
329
330            self.n_estimators = len(self.base_learners_)
331            self.classes_ = np.unique(y)
332
333            return self
334
335        if self.method == "SAMME.R":
336            Y = mo.one_hot_encode2(y, self.n_classes)
337
338            if sample_weight is None:
339                w_m = np.repeat(1.0 / n, n)  # (N, 1)
340
341            else:
342                w_m = np.asarray(sample_weight)
343
344            for m in range(self.n_estimators):
345                probs = base_learner.fit(
346                    X, y, sample_weight=w_m.ravel(), **kwargs
347                ).predict_proba(X)
348
349                np.clip(
350                    a=probs, a_min=2.220446049250313e-16, a_max=1.0, out=probs
351                )
352
353                self.base_learners_.update({m: deepcopy(base_learner)})
354
355                w_m *= np.exp(
356                    -1.0
357                    * self.learning_rate
358                    * (1.0 - 1.0 / self.n_classes)
359                    * xlogy(Y, probs).sum(axis=1)
360                )
361
362                w_m /= np.sum(w_m)
363
364                base_learner.set_params(seed=self.seed + (m + 1) * 1000)
365
366                if self.verbose == 1:
367                    pbar.update(m)
368
369            if self.verbose == 1:
370                pbar.update(self.n_estimators)
371
372            self.n_estimators = len(self.base_learners_)
373            self.classes_ = np.unique(y)
374
375            return self

Fit Boosting model to training data (X, y).

Parameters:

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features.

y: array-like, shape = [n_samples]
    Target values.

**kwargs: additional parameters to be passed to
        self.cook_training_set or self.obj.fit

Returns:

 self: object
def predict(self, X, **kwargs):
377    def predict(self, X, **kwargs):
378        """Predict test data X.
379
380        Parameters:
381
382            X: {array-like}, shape = [n_samples, n_features]
383                Training vectors, where n_samples is the number
384                of samples and n_features is the number of features.
385
386            **kwargs: additional parameters to be passed to
387                  self.cook_test_set
388
389        Returns:
390
391            model predictions: {array-like}
392        """
393        return self.predict_proba(X, **kwargs).argmax(axis=1)

Predict test data X.

Parameters:

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features.

**kwargs: additional parameters to be passed to
      self.cook_test_set

Returns:

model predictions: {array-like}
def predict_proba(self, X, **kwargs):
395    def predict_proba(self, X, **kwargs):
396        """Predict probabilities for test data X.
397
398        Parameters:
399
400            X: {array-like}, shape = [n_samples, n_features]
401                Training vectors, where n_samples is the number
402                of samples and n_features is the number of features.
403
404            **kwargs: additional parameters to be passed to
405                  self.cook_test_set
406
407        Returns:
408
409            probability estimates for test data: {array-like}
410
411        """
412
413        n_iter = len(self.base_learners_)
414
415        if self.method == "SAMME":
416            ensemble_learner = np.zeros((X.shape[0], self.n_classes))
417
418            # if self.verbose == 1:
419            #    pbar = Progbar(n_iter)
420
421            for idx, base_learner in self.base_learners_.items():
422                preds = base_learner.predict(X, **kwargs)
423
424                ensemble_learner += self.alpha_[idx] * mo.one_hot_encode2(
425                    preds, self.n_classes
426                )
427
428                # if self.verbose == 1:
429                #    pbar.update(idx)
430
431            # if self.verbose == 1:
432            #    pbar.update(n_iter)
433
434            expit_ensemble_learner = expit(ensemble_learner)
435
436            sum_ensemble = expit_ensemble_learner.sum(axis=1)
437
438            return expit_ensemble_learner / sum_ensemble[:, None]
439
440        # if self.method == "SAMME.R":
441        ensemble_learner = 0
442
443        # if self.verbose == 1:
444        #    pbar = Progbar(n_iter)
445
446        for idx, base_learner in self.base_learners_.items():
447            probs = base_learner.predict_proba(X, **kwargs)
448
449            np.clip(a=probs, a_min=2.220446049250313e-16, a_max=1.0, out=probs)
450
451            log_preds_proba = np.log(probs)
452
453            ensemble_learner += (
454                log_preds_proba - log_preds_proba.mean(axis=1)[:, None]
455            )
456
457            # if self.verbose == 1:
458            #    pbar.update(idx)
459
460        ensemble_learner *= self.n_classes - 1
461
462        # if self.verbose == 1:
463        #    pbar.update(n_iter)
464
465        expit_ensemble_learner = expit(ensemble_learner)
466
467        sum_ensemble = expit_ensemble_learner.sum(axis=1)
468
469        return expit_ensemble_learner / sum_ensemble[:, None]

Predict probabilities for test data X.

Parameters:

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features.

**kwargs: additional parameters to be passed to
      self.cook_test_set

Returns:

probability estimates for test data: {array-like}
class Base(sklearn.base.BaseEstimator):
  48class Base(BaseEstimator):
  49    """Base model from which all the other classes inherit.
  50
  51    This class contains the most important data preprocessing/feature engineering methods.
  52
  53    Parameters:
  54
  55        n_hidden_features: int
  56            number of nodes in the hidden layer
  57
  58        activation_name: str
  59            activation function: 'relu', 'tanh', 'sigmoid', 'prelu' or 'elu'
  60
  61        a: float
  62            hyperparameter for 'prelu' or 'elu' activation function
  63
  64        nodes_sim: str
  65            type of simulation for hidden layer nodes: 'sobol', 'hammersley', 'halton',
  66            'uniform'
  67
  68        bias: boolean
  69            indicates if the hidden layer contains a bias term (True) or
  70            not (False)
  71
  72        dropout: float
  73            regularization parameter; (random) percentage of nodes dropped out
  74            of the training
  75
  76        direct_link: boolean
  77            indicates if the original features are included (True) in model's
  78            fitting or not (False)
  79
  80        n_clusters: int
  81            number of clusters for type_clust='kmeans' or type_clust='gmm'
  82            clustering (could be 0: no clustering)
  83
  84        cluster_encode: bool
  85            defines how the variable containing clusters is treated (default is one-hot);
  86            if `False`, then labels are used, without one-hot encoding
  87
  88        type_clust: str
  89            type of clustering method: currently k-means ('kmeans') or Gaussian
  90            Mixture Model ('gmm')
  91
  92        type_scaling: a tuple of 3 strings
  93            scaling methods for inputs, hidden layer, and clustering respectively
  94            (and when relevant).
  95            Currently available: standardization ('std') or MinMax scaling ('minmax') or robust scaling ('robust') or  max absolute scaling ('maxabs')
  96
  97        col_sample: float
  98            percentage of features randomly chosen for training
  99
 100        row_sample: float
 101            percentage of rows chosen for training, by stratified bootstrapping
 102
 103        seed: int
 104            reproducibility seed for nodes_sim=='uniform', clustering and dropout
 105
 106        backend: str
 107            "cpu" or "gpu" or "tpu"
 108
 109    """
 110
 111    # construct the object -----
 112
 113    def __init__(
 114        self,
 115        n_hidden_features=5,
 116        activation_name="relu",
 117        a=0.01,
 118        nodes_sim="sobol",
 119        bias=True,
 120        dropout=0,
 121        direct_link=True,
 122        n_clusters=2,
 123        cluster_encode=True,
 124        type_clust="kmeans",
 125        type_scaling=("std", "std", "std"),
 126        col_sample=1,
 127        row_sample=1,
 128        seed=123,
 129        backend="cpu",
 130    ):
 131        if not JAX_AVAILABLE and backend != "cpu":
 132            raise RuntimeError(
 133                "JAX is required for this feature. Install with: pip install yourpackage[jax]"
 134            )
 135
 136        # input checks -----
 137
 138        sys_platform = platform.system()
 139
 140        if (sys_platform == "Windows") and (backend in ("gpu", "tpu")):
 141            warnings.warn(
 142                "No GPU/TPU computing on Windows yet, backend set to 'cpu'"
 143            )
 144            backend = "cpu"
 145
 146        assert activation_name in (
 147            "relu",
 148            "tanh",
 149            "sigmoid",
 150            "prelu",
 151            "elu",
 152        ), "'activation_name' must be in ('relu', 'tanh', 'sigmoid','prelu', 'elu')"
 153
 154        assert nodes_sim in (
 155            "sobol",
 156            "hammersley",
 157            "uniform",
 158            "halton",
 159        ), "'nodes_sim' must be in ('sobol', 'hammersley', 'uniform', 'halton')"
 160
 161        assert type_clust in (
 162            "kmeans",
 163            "gmm",
 164        ), "'type_clust' must be in ('kmeans', 'gmm')"
 165
 166        assert (len(type_scaling) == 3) & all(
 167            type_scaling[i] in ("minmax", "std", "robust", "maxabs")
 168            for i in range(len(type_scaling))
 169        ), "'type_scaling' must have length 3, and available scaling methods are 'minmax' scaling, standardization ('std'), robust scaling ('robust') and max absolute ('maxabs')"
 170
 171        assert (col_sample >= 0) & (
 172            col_sample <= 1
 173        ), "'col_sample' must be comprised between 0 and 1 (both included)"
 174
 175        assert backend in (
 176            "cpu",
 177            "gpu",
 178            "tpu",
 179        ), "must have 'backend' in ('cpu', 'gpu', 'tpu')"
 180
 181        self.n_hidden_features = n_hidden_features
 182        self.activation_name = activation_name
 183        self.a = a
 184        self.nodes_sim = nodes_sim
 185        self.bias = bias
 186        self.seed = seed
 187        self.backend = backend
 188        self.dropout = dropout
 189        self.direct_link = direct_link
 190        self.cluster_encode = cluster_encode
 191        self.type_clust = type_clust
 192        self.type_scaling = type_scaling
 193        self.col_sample = col_sample
 194        self.row_sample = row_sample
 195        self.n_clusters = n_clusters
 196        if isinstance(self, RegressorMixin):
 197            self.type_fit = "regression"
 198        elif isinstance(self, ClassifierMixin):
 199            self.type_fit = "classification"
 200        self.subsampler_ = None
 201        self.index_col_ = None
 202        self.index_row_ = True
 203        self.clustering_obj_ = None
 204        self.clustering_scaler_ = None
 205        self.nn_scaler_ = None
 206        self.scaler_ = None
 207        self.encoder_ = None
 208        self.W_ = None
 209        self.X_ = None
 210        self.y_ = None
 211        self.y_mean_ = None
 212        self.beta_ = None
 213
 214        # activation function -----
 215
 216        activation_options = {
 217            "relu": ac.relu if (self.backend == "cpu") else jnn.relu,
 218            "tanh": np.tanh if (self.backend == "cpu") else jnp.tanh,
 219            "sigmoid": (ac.sigmoid if (self.backend == "cpu") else jnn.sigmoid),
 220            "prelu": partial(ac.prelu, a=a),
 221            "elu": (
 222                partial(ac.elu, a=a)
 223                if (self.backend == "cpu")
 224                else partial(jnn.elu, a=a)
 225            ),
 226        }
 227
 228        self.activation_func = activation_options[activation_name]
 229
 230    # "preprocessing" methods to be inherited -----
 231
 232    def encode_clusters(self, X=None, predict=False, scaler=None, **kwargs):  #
 233        """Create new covariates with kmeans or GMM clustering
 234
 235        Parameters:
 236
 237            X: {array-like}, shape = [n_samples, n_features]
 238                Training vectors, where n_samples is the number
 239                of samples and n_features is the number of features.
 240
 241            predict: boolean
 242                is False on training set and True on test set
 243
 244            scaler: {object} of class StandardScaler, MinMaxScaler, RobustScaler or MaxAbsScaler
 245                if scaler has already been fitted on training data (online training), it can be passed here
 246
 247            **kwargs:
 248                additional parameters to be passed to the
 249                clustering method
 250
 251        Returns:
 252
 253            Clusters' matrix, one-hot encoded: {array-like}
 254
 255        """
 256
 257        np.random.seed(self.seed)
 258
 259        if X is None:
 260            X = self.X_
 261
 262        if isinstance(X, pd.DataFrame):
 263            X = copy.deepcopy(X.values.astype(float))
 264
 265        if len(X.shape) == 1:
 266            X = X.reshape(1, -1)
 267
 268        if predict is False:  # encode training set
 269            # scale input data before clustering
 270            self.clustering_scaler_, scaled_X = mo.scale_covariates(
 271                X, choice=self.type_scaling[2], scaler=self.clustering_scaler_
 272            )
 273
 274            self.clustering_obj_, X_clustered = mo.cluster_covariates(
 275                scaled_X,
 276                self.n_clusters,
 277                self.seed,
 278                type_clust=self.type_clust,
 279                **kwargs
 280            )
 281
 282            if self.cluster_encode:
 283                return mo.one_hot_encode(X_clustered, self.n_clusters).astype(
 284                    np.float16
 285                )
 286
 287            return X_clustered.astype(np.float16)
 288
 289        # if predict == True, encode test set
 290        X_clustered = self.clustering_obj_.predict(
 291            self.clustering_scaler_.transform(X)
 292        )
 293
 294        if self.cluster_encode == True:
 295            return mo.one_hot_encode(X_clustered, self.n_clusters).astype(
 296                np.float16
 297            )
 298
 299        return X_clustered.astype(np.float16)
 300
 301    def create_layer(self, scaled_X, W=None):
 302        """Create hidden layer.
 303
 304        Parameters:
 305
 306            scaled_X: {array-like}, shape = [n_samples, n_features]
 307                Training vectors, where n_samples is the number
 308                of samples and n_features is the number of features
 309
 310            W: {array-like}, shape = [n_features, hidden_features]
 311                if provided, constructs the hidden layer with W; otherwise computed internally
 312
 313        Returns:
 314
 315            Hidden layer matrix: {array-like}
 316
 317        """
 318
 319        n_features = scaled_X.shape[1]
 320
 321        # hash_sim = {
 322        #         "sobol": generate_sobol,
 323        #         "hammersley": generate_hammersley,
 324        #         "uniform": generate_uniform,
 325        #         "halton": generate_halton
 326        #     }
 327
 328        if self.bias is False:  # no bias term in the hidden layer
 329            if W is None:
 330                if self.nodes_sim == "sobol":
 331                    self.W_ = generate_sobol(
 332                        n_dims=n_features,
 333                        n_points=self.n_hidden_features,
 334                        seed=self.seed,
 335                    )
 336                elif self.nodes_sim == "hammersley":
 337                    self.W_ = generate_hammersley(
 338                        n_dims=n_features,
 339                        n_points=self.n_hidden_features,
 340                        seed=self.seed,
 341                    )
 342                elif self.nodes_sim == "uniform":
 343                    self.W_ = generate_uniform(
 344                        n_dims=n_features,
 345                        n_points=self.n_hidden_features,
 346                        seed=self.seed,
 347                    )
 348                else:
 349                    self.W_ = generate_halton(
 350                        n_dims=n_features,
 351                        n_points=self.n_hidden_features,
 352                        seed=self.seed,
 353                    )
 354
 355                assert (
 356                    scaled_X.shape[1] == self.W_.shape[0]
 357                ), "check dimensions of covariates X and matrix W"
 358
 359                return mo.dropout(
 360                    x=self.activation_func(
 361                        mo.safe_sparse_dot(
 362                            a=scaled_X, b=self.W_, backend=self.backend
 363                        )
 364                    ),
 365                    drop_prob=self.dropout,
 366                    seed=self.seed,
 367                )
 368
 369            # W is not none
 370            assert (
 371                scaled_X.shape[1] == W.shape[0]
 372            ), "check dimensions of covariates X and matrix W"
 373
 374            # self.W_ = W
 375            return mo.dropout(
 376                x=self.activation_func(
 377                    mo.safe_sparse_dot(a=scaled_X, b=W, backend=self.backend)
 378                ),
 379                drop_prob=self.dropout,
 380                seed=self.seed,
 381            )
 382
 383        # with bias term in the hidden layer
 384        if W is None:
 385            n_features_1 = n_features + 1
 386
 387            if self.nodes_sim == "sobol":
 388                self.W_ = generate_sobol(
 389                    n_dims=n_features_1,
 390                    n_points=self.n_hidden_features,
 391                    seed=self.seed,
 392                )
 393            elif self.nodes_sim == "hammersley":
 394                self.W_ = generate_hammersley(
 395                    n_dims=n_features_1,
 396                    n_points=self.n_hidden_features,
 397                    seed=self.seed,
 398                )
 399            elif self.nodes_sim == "uniform":
 400                self.W_ = generate_uniform(
 401                    n_dims=n_features_1,
 402                    n_points=self.n_hidden_features,
 403                    seed=self.seed,
 404                )
 405            else:
 406                self.W_ = generate_halton(
 407                    n_dims=n_features_1,
 408                    n_points=self.n_hidden_features,
 409                    seed=self.seed,
 410                )
 411
 412            # self.W_ = hash_sim[self.nodes_sim](
 413            #         n_dims=n_features_1,
 414            #         n_points=self.n_hidden_features,
 415            #         seed=self.seed,
 416            #     )
 417
 418            return mo.dropout(
 419                x=self.activation_func(
 420                    mo.safe_sparse_dot(
 421                        a=mo.cbind(
 422                            np.ones(scaled_X.shape[0]),
 423                            scaled_X,
 424                            backend=self.backend,
 425                        ),
 426                        b=self.W_,
 427                        backend=self.backend,
 428                    )
 429                ),
 430                drop_prob=self.dropout,
 431                seed=self.seed,
 432            )
 433
 434        # W is not None
 435        # self.W_ = W
 436        return mo.dropout(
 437            x=self.activation_func(
 438                mo.safe_sparse_dot(
 439                    a=mo.cbind(
 440                        np.ones(scaled_X.shape[0]),
 441                        scaled_X,
 442                        backend=self.backend,
 443                    ),
 444                    b=W,
 445                    backend=self.backend,
 446                )
 447            ),
 448            drop_prob=self.dropout,
 449            seed=self.seed,
 450        )
 451
 452    def _jax_create_layer(self, scaled_X, W=None):
 453        """JAX-compatible version of create_layer that exactly matches the original functionality."""
 454        key = jax.random.PRNGKey(self.seed)
 455        n_features = scaled_X.shape[1]
 456
 457        # Generate weights if not provided
 458        if W is None:
 459            if self.bias:
 460                n_features_1 = n_features + 1
 461                shape = (n_features_1, self.n_hidden_features)
 462            else:
 463                shape = (n_features, self.n_hidden_features)
 464
 465            # JAX-compatible weight generation matching original behavior
 466            if self.nodes_sim == "sobol":
 467                W_np = generate_sobol(
 468                    n_dims=n_features_1,
 469                    n_points=self.n_hidden_features,
 470                    seed=self.seed,
 471                )
 472                W = jnp.asarray(W_np)
 473            elif self.nodes_sim == "hammersley":
 474                W_np = generate_hammersley(
 475                    n_dims=n_features_1,
 476                    n_points=self.n_hidden_features,
 477                    seed=self.seed,
 478                )
 479                W = jnp.asarray(W_np)
 480            elif self.nodes_sim == "uniform":
 481                key, subkey = jax.random.split(key)
 482                W = jax.random.uniform(
 483                    subkey, shape=shape, minval=-1.0, maxval=1.0
 484                )
 485            else:  # halton
 486                W_np = generate_halton(
 487                    n_dims=n_features_1,
 488                    n_points=self.n_hidden_features,
 489                    seed=self.seed,
 490                )
 491                W = jnp.asarray(W_np)
 492
 493            self.W_ = np.array(W)  # Store as numpy for original methods
 494
 495        # Prepare input with bias if needed
 496        if self.bias:
 497            X_with_bias = jnp.hstack(
 498                [jnp.ones((scaled_X.shape[0], 1)), scaled_X]
 499            )
 500            print("X_with_bias shape:", X_with_bias.shape)
 501            print("W shape:", W.shape)
 502            linear_output = jnp.dot(X_with_bias, W)
 503        else:
 504            linear_output = jnp.dot(scaled_X, W)
 505
 506        # Apply activation function
 507        if self.activation_name == "relu":
 508            activated = jax.nn.relu(linear_output)
 509        elif self.activation_name == "tanh":
 510            activated = jnp.tanh(linear_output)
 511        elif self.activation_name == "sigmoid":
 512            activated = jax.nn.sigmoid(linear_output)
 513        else:  # leaky relu
 514            activated = jax.nn.leaky_relu(linear_output, negative_slope=self.a)
 515
 516        # Apply dropout
 517        if self.dropout > 0:
 518            key, subkey = jax.random.split(key)
 519            mask = jax.random.bernoulli(
 520                subkey, p=1 - self.dropout, shape=activated.shape
 521            )
 522            activated = jnp.where(mask, activated / (1 - self.dropout), 0)
 523
 524        return activated
 525
 526    def cook_training_set(self, y=None, X=None, W=None, **kwargs):
 527        """Create new hidden features for training set, with hidden layer, center the response.
 528
 529        Parameters:
 530
 531            y: array-like, shape = [n_samples]
 532                Target values
 533
 534            X: {array-like}, shape = [n_samples, n_features]
 535                Training vectors, where n_samples is the number
 536                of samples and n_features is the number of features
 537
 538            W: {array-like}, shape = [n_features, hidden_features]
 539                if provided, constructs the hidden layer via W
 540
 541        Returns:
 542
 543            (centered response, direct link + hidden layer matrix): {tuple}
 544
 545        """
 546
 547        # either X and y are stored or not
 548        # assert ((y is None) & (X is None)) | ((y is not None) & (X is not None))
 549        if self.n_hidden_features > 0:  # has a hidden layer
 550            assert (
 551                len(self.type_scaling) >= 2
 552            ), "must have len(self.type_scaling) >= 2 when self.n_hidden_features > 0"
 553
 554        if X is None:
 555            if self.col_sample == 1:
 556                input_X = self.X_
 557            else:
 558                n_features = self.X_.shape[1]
 559                new_n_features = int(np.ceil(n_features * self.col_sample))
 560                assert (
 561                    new_n_features >= 1
 562                ), "check class attribute 'col_sample' and the number of covariates provided for X"
 563                np.random.seed(self.seed)
 564                index_col = np.random.choice(
 565                    range(n_features), size=new_n_features, replace=False
 566                )
 567                self.index_col_ = index_col
 568                input_X = self.X_[:, self.index_col_]
 569
 570        else:  # X is not None # keep X vs self.X_
 571            if isinstance(X, pd.DataFrame):
 572                X = copy.deepcopy(X.values.astype(float))
 573
 574            if self.col_sample == 1:
 575                input_X = X
 576            else:
 577                n_features = X.shape[1]
 578                new_n_features = int(np.ceil(n_features * self.col_sample))
 579                assert (
 580                    new_n_features >= 1
 581                ), "check class attribute 'col_sample' and the number of covariates provided for X"
 582                np.random.seed(self.seed)
 583                index_col = np.random.choice(
 584                    range(n_features), size=new_n_features, replace=False
 585                )
 586                self.index_col_ = index_col
 587                input_X = X[:, self.index_col_]
 588
 589        if self.n_clusters <= 0:
 590            # data without any clustering: self.n_clusters is None -----
 591
 592            if self.n_hidden_features > 0:  # with hidden layer
 593                self.nn_scaler_, scaled_X = mo.scale_covariates(
 594                    input_X, choice=self.type_scaling[1], scaler=self.nn_scaler_
 595                )
 596                Phi_X = (
 597                    self.create_layer(scaled_X)
 598                    if W is None
 599                    else self.create_layer(scaled_X, W=W)
 600                )
 601                Z = (
 602                    mo.cbind(input_X, Phi_X, backend=self.backend)
 603                    if self.direct_link is True
 604                    else Phi_X
 605                )
 606                self.scaler_, scaled_Z = mo.scale_covariates(
 607                    Z, choice=self.type_scaling[0], scaler=self.scaler_
 608                )
 609            else:  # no hidden layer
 610                Z = input_X
 611                self.scaler_, scaled_Z = mo.scale_covariates(
 612                    Z, choice=self.type_scaling[0], scaler=self.scaler_
 613                )
 614
 615        else:
 616            # data with clustering: self.n_clusters is not None ----- # keep
 617
 618            augmented_X = mo.cbind(
 619                input_X,
 620                self.encode_clusters(input_X, **kwargs),
 621                backend=self.backend,
 622            )
 623
 624            if self.n_hidden_features > 0:  # with hidden layer
 625                self.nn_scaler_, scaled_X = mo.scale_covariates(
 626                    augmented_X,
 627                    choice=self.type_scaling[1],
 628                    scaler=self.nn_scaler_,
 629                )
 630                Phi_X = (
 631                    self.create_layer(scaled_X)
 632                    if W is None
 633                    else self.create_layer(scaled_X, W=W)
 634                )
 635                Z = (
 636                    mo.cbind(augmented_X, Phi_X, backend=self.backend)
 637                    if self.direct_link is True
 638                    else Phi_X
 639                )
 640                self.scaler_, scaled_Z = mo.scale_covariates(
 641                    Z, choice=self.type_scaling[0], scaler=self.scaler_
 642                )
 643            else:  # no hidden layer
 644                Z = augmented_X
 645                self.scaler_, scaled_Z = mo.scale_covariates(
 646                    Z, choice=self.type_scaling[0], scaler=self.scaler_
 647                )
 648
 649        # Returning model inputs -----
 650        if mx.is_factor(y) is False:  # regression
 651            # center y
 652            if y is None:
 653                self.y_mean_, centered_y = mo.center_response(self.y_)
 654            else:
 655                self.y_mean_, centered_y = mo.center_response(y)
 656
 657            # y is subsampled
 658            if self.row_sample < 1:
 659                n, p = Z.shape
 660
 661                self.subsampler_ = (
 662                    SubSampler(
 663                        y=self.y_, row_sample=self.row_sample, seed=self.seed
 664                    )
 665                    if y is None
 666                    else SubSampler(
 667                        y=y, row_sample=self.row_sample, seed=self.seed
 668                    )
 669                )
 670
 671                self.index_row_ = self.subsampler_.subsample()
 672
 673                n_row_sample = len(self.index_row_)
 674                # regression
 675                return (
 676                    centered_y[self.index_row_].reshape(n_row_sample),
 677                    self.scaler_.transform(
 678                        Z[self.index_row_, :].reshape(n_row_sample, p)
 679                    ),
 680                )
 681            # y is not subsampled
 682            # regression
 683            return (centered_y, self.scaler_.transform(Z))
 684
 685        # classification
 686        # y is subsampled
 687        if self.row_sample < 1:
 688            n, p = Z.shape
 689
 690            self.subsampler_ = (
 691                SubSampler(
 692                    y=self.y_, row_sample=self.row_sample, seed=self.seed
 693                )
 694                if y is None
 695                else SubSampler(y=y, row_sample=self.row_sample, seed=self.seed)
 696            )
 697
 698            self.index_row_ = self.subsampler_.subsample()
 699
 700            n_row_sample = len(self.index_row_)
 701            # classification
 702            return (
 703                y[self.index_row_].reshape(n_row_sample),
 704                self.scaler_.transform(
 705                    Z[self.index_row_, :].reshape(n_row_sample, p)
 706                ),
 707            )
 708        # y is not subsampled
 709        # classification
 710        return (y, self.scaler_.transform(Z))
 711
 712    def cook_test_set(self, X, **kwargs):
 713        """Transform data from test set, with hidden layer.
 714
 715        Parameters:
 716
 717            X: {array-like}, shape = [n_samples, n_features]
 718                Training vectors, where n_samples is the number
 719                of samples and n_features is the number of features
 720
 721            **kwargs: additional parameters to be passed to self.encode_cluster
 722
 723        Returns:
 724
 725            Transformed test set : {array-like}
 726        """
 727
 728        if isinstance(X, pd.DataFrame):
 729            X = copy.deepcopy(X.values.astype(float))
 730
 731        if len(X.shape) == 1:
 732            X = X.reshape(1, -1)
 733
 734        if (
 735            self.n_clusters == 0
 736        ):  # data without clustering: self.n_clusters is None -----
 737            if self.n_hidden_features > 0:
 738                # if hidden layer
 739                scaled_X = (
 740                    self.nn_scaler_.transform(X)
 741                    if (self.col_sample == 1)
 742                    else self.nn_scaler_.transform(X[:, self.index_col_])
 743                )
 744                Phi_X = self.create_layer(scaled_X, self.W_)
 745                if self.direct_link:
 746                    return self.scaler_.transform(
 747                        mo.cbind(scaled_X, Phi_X, backend=self.backend)
 748                    )
 749                # when self.direct_link == False
 750                return self.scaler_.transform(Phi_X)
 751            # if no hidden layer # self.n_hidden_features == 0
 752            return self.scaler_.transform(X)
 753
 754        # data with clustering: self.n_clusters > 0 -----
 755        if self.col_sample == 1:
 756            predicted_clusters = self.encode_clusters(
 757                X=X, predict=True, **kwargs
 758            )
 759            augmented_X = mo.cbind(X, predicted_clusters, backend=self.backend)
 760        else:
 761            predicted_clusters = self.encode_clusters(
 762                X=X[:, self.index_col_], predict=True, **kwargs
 763            )
 764            augmented_X = mo.cbind(
 765                X[:, self.index_col_], predicted_clusters, backend=self.backend
 766            )
 767
 768        if self.n_hidden_features > 0:  # if hidden layer
 769            scaled_X = self.nn_scaler_.transform(augmented_X)
 770            Phi_X = self.create_layer(scaled_X, self.W_)
 771            if self.direct_link:
 772                return self.scaler_.transform(
 773                    mo.cbind(augmented_X, Phi_X, backend=self.backend)
 774                )
 775            return self.scaler_.transform(Phi_X)
 776
 777        # if no hidden layer
 778        return self.scaler_.transform(augmented_X)
 779
 780    def cook_training_set_jax(self, y=None, X=None, W=None, **kwargs):
 781        """JAX-compatible version of cook_training_set that maintains side effects."""
 782        # Initialize random key
 783        key = jax.random.PRNGKey(self.seed)
 784
 785        # Convert inputs to JAX arrays
 786        X = jnp.asarray(X) if X is not None else jnp.asarray(self.X_)
 787        y = jnp.asarray(y) if y is not None else jnp.asarray(self.y_)
 788
 789        # Handle column sampling
 790        if self.col_sample < 1:
 791            n_features = X.shape[1]
 792            new_n_features = int(jnp.ceil(n_features * self.col_sample))
 793            assert new_n_features >= 1, "Invalid col_sample"
 794
 795            key, subkey = jax.random.split(key)
 796            index_col = jax.random.choice(
 797                subkey, n_features, shape=(new_n_features,), replace=False
 798            )
 799            self.index_col_ = np.array(
 800                index_col
 801            )  # Store as numpy for original methods
 802            input_X = X[:, index_col]
 803            n_features = (
 804                new_n_features  # Update n_features after column sampling
 805            )
 806        else:
 807            input_X = X
 808            n_features = X.shape[1]
 809
 810        augmented_X = input_X
 811
 812        # JAX-compatible scaling
 813        def jax_scale(data, mean=None, std=None):
 814            if mean is None:
 815                mean = jnp.mean(data, axis=0)
 816            if std is None:
 817                std = jnp.std(data, axis=0)
 818            return (data - mean) / (std + 1e-10), mean, std
 819
 820        # Hidden layer processing
 821        if self.n_hidden_features > 0:
 822            # Initialize weights if not provided
 823            if W is None:
 824                shape = (n_features, self.n_hidden_features)
 825
 826                # JAX-compatible weight generation
 827                if self.nodes_sim == "uniform":
 828                    key, subkey = jax.random.split(key)
 829                    W = jax.random.uniform(
 830                        subkey, shape=shape, minval=-1.0, maxval=1.0
 831                    ) * (1 / jnp.sqrt(n_features))
 832                else:
 833                    # For other sequences, use numpy generation then convert to JAX
 834                    if self.nodes_sim == "sobol":
 835                        W_np = generate_sobol(
 836                            n_dims=shape[0],
 837                            n_points=shape[1],
 838                            seed=self.seed,
 839                        )
 840                    elif self.nodes_sim == "hammersley":
 841                        W_np = generate_hammersley(
 842                            n_dims=shape[0],
 843                            n_points=shape[1],
 844                            seed=self.seed,
 845                        )
 846                    elif self.nodes_sim == "halton":
 847                        W_np = generate_halton(
 848                            n_dims=shape[0],
 849                            n_points=shape[1],
 850                            seed=self.seed,
 851                        )
 852                    else:  # default to uniform
 853                        key, subkey = jax.random.split(key)
 854                        W = jax.random.uniform(
 855                            subkey, shape=shape, minval=-1.0, maxval=1.0
 856                        ) * (1 / jnp.sqrt(n_features))
 857
 858                    if self.nodes_sim in ["sobol", "hammersley", "halton"]:
 859                        W = jnp.asarray(W_np) * (1 / jnp.sqrt(n_features))
 860
 861                self.W_ = np.array(W)  # Store as numpy for original methods
 862
 863            # Scale features
 864            scaled_X, self.nn_mean_, self.nn_std_ = jax_scale(
 865                augmented_X,
 866                getattr(self, "nn_mean_", None),
 867                getattr(self, "nn_std_", None),
 868            )
 869
 870            # Create hidden layer with proper bias handling
 871            linear_output = jnp.dot(scaled_X, W)
 872
 873            # Apply activation
 874            if self.activation_name == "relu":
 875                Phi_X = jax.nn.relu(linear_output)
 876            elif self.activation_name == "tanh":
 877                Phi_X = jnp.tanh(linear_output)
 878            elif self.activation_name == "sigmoid":
 879                Phi_X = jax.nn.sigmoid(linear_output)
 880            else:  # leaky relu
 881                Phi_X = jax.nn.leaky_relu(linear_output, negative_slope=self.a)
 882
 883            # Apply dropout
 884            if self.dropout > 0:
 885                key, subkey = jax.random.split(key)
 886                mask = jax.random.bernoulli(
 887                    subkey, p=1 - self.dropout, shape=Phi_X.shape
 888                )
 889                Phi_X = jnp.where(mask, Phi_X / (1 - self.dropout), 0)
 890
 891            Z = jnp.hstack([scaled_X, Phi_X]) if self.direct_link else Phi_X
 892        else:
 893            Z = augmented_X
 894
 895        # Final scaling
 896        scaled_Z, self.scale_mean_, self.scale_std_ = jax_scale(
 897            Z,
 898            getattr(self, "scale_mean_", None),
 899            getattr(self, "scale_std_", None),
 900        )
 901
 902        # Center response for regression
 903        if not hasattr(mx, "is_factor") or not mx.is_factor(
 904            y
 905        ):  # regression case
 906            self.y_mean_ = float(
 907                jnp.mean(y)
 908            )  # Convert to Python float for compatibility
 909            centered_y = y - self.y_mean_
 910        else:
 911            centered_y = y
 912
 913        # Handle row sampling
 914        if self.row_sample < 1:
 915            key, subkey = jax.random.split(key)
 916            n_samples = Z.shape[0]
 917            n_row_sample = int(jnp.ceil(n_samples * self.row_sample))
 918            index_row = jax.random.choice(
 919                subkey, n_samples, shape=(n_row_sample,), replace=False
 920            )
 921            self.index_row_ = np.array(
 922                index_row
 923            )  # Store as numpy for original methods
 924            return (centered_y[index_row], scaled_Z[index_row])
 925
 926        return (centered_y, scaled_Z)
 927
 928    def cook_test_set_jax(self, X, **kwargs):
 929        """JAX-compatible test set processing with matching dimension handling."""
 930        X = jnp.asarray(X)
 931
 932        if len(X.shape) == 1:
 933            X = X.reshape(1, -1)
 934
 935        # Handle column sampling
 936        input_X = (
 937            X if self.col_sample == 1 else X[:, jnp.asarray(self.index_col_)]
 938        )
 939
 940        augmented_X = input_X
 941
 942        # JAX-compatible scaling
 943        scaled_X = (augmented_X - self.nn_mean_) / (self.nn_std_ + 1e-10)
 944
 945        # Process hidden layer if needed
 946        if self.n_hidden_features > 0:
 947            Phi_X = self._jax_create_layer(scaled_X, jnp.asarray(self.W_))
 948            Z = jnp.hstack([scaled_X, Phi_X]) if self.direct_link else Phi_X
 949        else:
 950            Z = augmented_X
 951
 952        # Final scaling
 953        scaled_Z = (Z - self.scale_mean_) / (self.scale_std_ + 1e-10)
 954
 955        return scaled_Z
 956
 957    def _jax_create_layer(self, X, W):
 958        """JAX-compatible hidden layer creation."""
 959        # print("X", X.shape)
 960        # print("W", W.shape)
 961        # print("self.W_", self.W_.shape)
 962        linear_output = jnp.dot(X, W)
 963
 964        if self.activation_name == "relu":
 965            return jax.nn.relu(linear_output)
 966        elif self.activation_name == "tanh":
 967            return jnp.tanh(linear_output)
 968        elif self.activation_name == "sigmoid":
 969            return jax.nn.sigmoid(linear_output)
 970        else:  # leaky relu
 971            return jax.nn.leaky_relu(linear_output, negative_slope=self.a)
 972
 973    def cross_val_score(
 974        self,
 975        X,
 976        y,
 977        cv=5,
 978        scoring="accuracy",
 979        random_state=42,
 980        n_jobs=-1,
 981        epsilon=0.5,
 982        penalized=True,
 983        objective="abs",
 984        **kwargs
 985    ):
 986        """
 987        Penalized Cross-validation score for a model.
 988
 989        Parameters:
 990
 991            X: {array-like}, shape = [n_samples, n_features]
 992                Training vectors, where n_samples is the number
 993                of samples and n_features is the number of features
 994
 995            y: array-like, shape = [n_samples]
 996                Target values
 997
 998            X_test: {array-like}, shape = [n_samples, n_features]
 999                Test vectors, where n_samples is the number
1000                of samples and n_features is the number of features
1001
1002            y_test: array-like, shape = [n_samples]
1003                Target values
1004
1005            cv: int
1006                Number of folds
1007
1008            scoring: str
1009                Scoring metric
1010
1011            random_state: int
1012                Random state
1013
1014            n_jobs: int
1015                Number of jobs to run in parallel
1016
1017            epsilon: float
1018                Penalty parameter
1019
1020            penalized: bool
1021                Whether to obtain penalized cross-validation score or not
1022
1023            objective: str
1024                'abs': Minimize the absolute difference between cross-validation score and validation score
1025                'relative': Minimize the relative difference between cross-validation score and validation score
1026        Returns:
1027
1028            A namedtuple with the following fields:
1029                - cv_score: float
1030                    cross-validation score
1031                - val_score: float
1032                    validation score
1033                - penalized_score: float
1034                    penalized cross-validation score: cv_score / val_score + epsilon*(1/val_score + 1/cv_score)
1035                    If higher scoring metric is better, minimize the function result.
1036                    If lower scoring metric is better, maximize the function result.
1037        """
1038        if scoring == "accuracy":
1039            scoring_func = accuracy_score
1040        elif scoring == "balanced_accuracy":
1041            scoring_func = balanced_accuracy_score
1042        elif scoring == "f1":
1043            scoring_func = f1_score
1044        elif scoring == "roc_auc":
1045            scoring_func = roc_auc_score
1046        elif scoring == "r2":
1047            scoring_func = r2_score
1048        elif scoring == "mse":
1049            scoring_func = mean_squared_error
1050        elif scoring == "mae":
1051            scoring_func = mean_absolute_error
1052        elif scoring == "mape":
1053            scoring_func = mean_absolute_percentage_error
1054        elif scoring == "rmse":
1055
1056            def scoring_func(y_true, y_pred):
1057                return np.sqrt(mean_squared_error(y_true, y_pred))
1058
1059        X_train, X_val, y_train, y_val = train_test_split(
1060            X, y, test_size=0.2, random_state=random_state
1061        )
1062
1063        res = cross_val_score(
1064            self, X_train, y_train, cv=cv, scoring=scoring, n_jobs=n_jobs
1065        )  # cross-validation error
1066
1067        if penalized == False:
1068            return res
1069
1070        DescribeResult = namedtuple(
1071            "DescribeResult", ["cv_score", "val_score", "penalized_score"]
1072        )
1073
1074        numerator = res.mean()
1075
1076        # Evaluate on the (cv+1)-th fold
1077        preds_val = self.fit(X_train, y_train).predict(X_val)
1078        try:
1079            denominator = scoring(y_val, preds_val)  # validation error
1080        except Exception as e:
1081            denominator = scoring_func(y_val, preds_val)
1082
1083        # if higher is better
1084        if objective == "abs":
1085            penalized_score = np.abs(numerator - denominator) + epsilon * (
1086                1 / denominator + 1 / numerator
1087            )
1088        elif objective == "relative":
1089            ratio = numerator / denominator
1090            penalized_score = np.abs(ratio - 1) + epsilon * (
1091                1 / denominator + 1 / numerator
1092            )
1093
1094        return DescribeResult(
1095            cv_score=numerator,
1096            val_score=denominator,
1097            penalized_score=penalized_score,
1098        )

Base model from which all the other classes inherit.

This class contains the most important data preprocessing/feature engineering methods.

Parameters:

n_hidden_features: int
    number of nodes in the hidden layer

activation_name: str
    activation function: 'relu', 'tanh', 'sigmoid', 'prelu' or 'elu'

a: float
    hyperparameter for 'prelu' or 'elu' activation function

nodes_sim: str
    type of simulation for hidden layer nodes: 'sobol', 'hammersley', 'halton',
    'uniform'

bias: boolean
    indicates if the hidden layer contains a bias term (True) or
    not (False)

dropout: float
    regularization parameter; (random) percentage of nodes dropped out
    of the training

direct_link: boolean
    indicates if the original features are included (True) in model's
    fitting or not (False)

n_clusters: int
    number of clusters for type_clust='kmeans' or type_clust='gmm'
    clustering (could be 0: no clustering)

cluster_encode: bool
    defines how the variable containing clusters is treated (default is one-hot);
    if `False`, then labels are used, without one-hot encoding

type_clust: str
    type of clustering method: currently k-means ('kmeans') or Gaussian
    Mixture Model ('gmm')

type_scaling: a tuple of 3 strings
    scaling methods for inputs, hidden layer, and clustering respectively
    (and when relevant).
    Currently available: standardization ('std') or MinMax scaling ('minmax') or robust scaling ('robust') or  max absolute scaling ('maxabs')

col_sample: float
    percentage of features randomly chosen for training

row_sample: float
    percentage of rows chosen for training, by stratified bootstrapping

seed: int
    reproducibility seed for nodes_sim=='uniform', clustering and dropout

backend: str
    "cpu" or "gpu" or "tpu"
def encode_clusters(self, X=None, predict=False, scaler=None, **kwargs):
232    def encode_clusters(self, X=None, predict=False, scaler=None, **kwargs):  #
233        """Create new covariates with kmeans or GMM clustering
234
235        Parameters:
236
237            X: {array-like}, shape = [n_samples, n_features]
238                Training vectors, where n_samples is the number
239                of samples and n_features is the number of features.
240
241            predict: boolean
242                is False on training set and True on test set
243
244            scaler: {object} of class StandardScaler, MinMaxScaler, RobustScaler or MaxAbsScaler
245                if scaler has already been fitted on training data (online training), it can be passed here
246
247            **kwargs:
248                additional parameters to be passed to the
249                clustering method
250
251        Returns:
252
253            Clusters' matrix, one-hot encoded: {array-like}
254
255        """
256
257        np.random.seed(self.seed)
258
259        if X is None:
260            X = self.X_
261
262        if isinstance(X, pd.DataFrame):
263            X = copy.deepcopy(X.values.astype(float))
264
265        if len(X.shape) == 1:
266            X = X.reshape(1, -1)
267
268        if predict is False:  # encode training set
269            # scale input data before clustering
270            self.clustering_scaler_, scaled_X = mo.scale_covariates(
271                X, choice=self.type_scaling[2], scaler=self.clustering_scaler_
272            )
273
274            self.clustering_obj_, X_clustered = mo.cluster_covariates(
275                scaled_X,
276                self.n_clusters,
277                self.seed,
278                type_clust=self.type_clust,
279                **kwargs
280            )
281
282            if self.cluster_encode:
283                return mo.one_hot_encode(X_clustered, self.n_clusters).astype(
284                    np.float16
285                )
286
287            return X_clustered.astype(np.float16)
288
289        # if predict == True, encode test set
290        X_clustered = self.clustering_obj_.predict(
291            self.clustering_scaler_.transform(X)
292        )
293
294        if self.cluster_encode == True:
295            return mo.one_hot_encode(X_clustered, self.n_clusters).astype(
296                np.float16
297            )
298
299        return X_clustered.astype(np.float16)

Create new covariates with kmeans or GMM clustering

Parameters:

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features.

predict: boolean
    is False on training set and True on test set

scaler: {object} of class StandardScaler, MinMaxScaler, RobustScaler or MaxAbsScaler
    if scaler has already been fitted on training data (online training), it can be passed here

**kwargs:
    additional parameters to be passed to the
    clustering method

Returns:

Clusters' matrix, one-hot encoded: {array-like}
def create_layer(self, scaled_X, W=None):
301    def create_layer(self, scaled_X, W=None):
302        """Create hidden layer.
303
304        Parameters:
305
306            scaled_X: {array-like}, shape = [n_samples, n_features]
307                Training vectors, where n_samples is the number
308                of samples and n_features is the number of features
309
310            W: {array-like}, shape = [n_features, hidden_features]
311                if provided, constructs the hidden layer with W; otherwise computed internally
312
313        Returns:
314
315            Hidden layer matrix: {array-like}
316
317        """
318
319        n_features = scaled_X.shape[1]
320
321        # hash_sim = {
322        #         "sobol": generate_sobol,
323        #         "hammersley": generate_hammersley,
324        #         "uniform": generate_uniform,
325        #         "halton": generate_halton
326        #     }
327
328        if self.bias is False:  # no bias term in the hidden layer
329            if W is None:
330                if self.nodes_sim == "sobol":
331                    self.W_ = generate_sobol(
332                        n_dims=n_features,
333                        n_points=self.n_hidden_features,
334                        seed=self.seed,
335                    )
336                elif self.nodes_sim == "hammersley":
337                    self.W_ = generate_hammersley(
338                        n_dims=n_features,
339                        n_points=self.n_hidden_features,
340                        seed=self.seed,
341                    )
342                elif self.nodes_sim == "uniform":
343                    self.W_ = generate_uniform(
344                        n_dims=n_features,
345                        n_points=self.n_hidden_features,
346                        seed=self.seed,
347                    )
348                else:
349                    self.W_ = generate_halton(
350                        n_dims=n_features,
351                        n_points=self.n_hidden_features,
352                        seed=self.seed,
353                    )
354
355                assert (
356                    scaled_X.shape[1] == self.W_.shape[0]
357                ), "check dimensions of covariates X and matrix W"
358
359                return mo.dropout(
360                    x=self.activation_func(
361                        mo.safe_sparse_dot(
362                            a=scaled_X, b=self.W_, backend=self.backend
363                        )
364                    ),
365                    drop_prob=self.dropout,
366                    seed=self.seed,
367                )
368
369            # W is not none
370            assert (
371                scaled_X.shape[1] == W.shape[0]
372            ), "check dimensions of covariates X and matrix W"
373
374            # self.W_ = W
375            return mo.dropout(
376                x=self.activation_func(
377                    mo.safe_sparse_dot(a=scaled_X, b=W, backend=self.backend)
378                ),
379                drop_prob=self.dropout,
380                seed=self.seed,
381            )
382
383        # with bias term in the hidden layer
384        if W is None:
385            n_features_1 = n_features + 1
386
387            if self.nodes_sim == "sobol":
388                self.W_ = generate_sobol(
389                    n_dims=n_features_1,
390                    n_points=self.n_hidden_features,
391                    seed=self.seed,
392                )
393            elif self.nodes_sim == "hammersley":
394                self.W_ = generate_hammersley(
395                    n_dims=n_features_1,
396                    n_points=self.n_hidden_features,
397                    seed=self.seed,
398                )
399            elif self.nodes_sim == "uniform":
400                self.W_ = generate_uniform(
401                    n_dims=n_features_1,
402                    n_points=self.n_hidden_features,
403                    seed=self.seed,
404                )
405            else:
406                self.W_ = generate_halton(
407                    n_dims=n_features_1,
408                    n_points=self.n_hidden_features,
409                    seed=self.seed,
410                )
411
412            # self.W_ = hash_sim[self.nodes_sim](
413            #         n_dims=n_features_1,
414            #         n_points=self.n_hidden_features,
415            #         seed=self.seed,
416            #     )
417
418            return mo.dropout(
419                x=self.activation_func(
420                    mo.safe_sparse_dot(
421                        a=mo.cbind(
422                            np.ones(scaled_X.shape[0]),
423                            scaled_X,
424                            backend=self.backend,
425                        ),
426                        b=self.W_,
427                        backend=self.backend,
428                    )
429                ),
430                drop_prob=self.dropout,
431                seed=self.seed,
432            )
433
434        # W is not None
435        # self.W_ = W
436        return mo.dropout(
437            x=self.activation_func(
438                mo.safe_sparse_dot(
439                    a=mo.cbind(
440                        np.ones(scaled_X.shape[0]),
441                        scaled_X,
442                        backend=self.backend,
443                    ),
444                    b=W,
445                    backend=self.backend,
446                )
447            ),
448            drop_prob=self.dropout,
449            seed=self.seed,
450        )

Create hidden layer.

Parameters:

scaled_X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features

W: {array-like}, shape = [n_features, hidden_features]
    if provided, constructs the hidden layer with W; otherwise computed internally

Returns:

Hidden layer matrix: {array-like}
def cook_training_set(self, y=None, X=None, W=None, **kwargs):
526    def cook_training_set(self, y=None, X=None, W=None, **kwargs):
527        """Create new hidden features for training set, with hidden layer, center the response.
528
529        Parameters:
530
531            y: array-like, shape = [n_samples]
532                Target values
533
534            X: {array-like}, shape = [n_samples, n_features]
535                Training vectors, where n_samples is the number
536                of samples and n_features is the number of features
537
538            W: {array-like}, shape = [n_features, hidden_features]
539                if provided, constructs the hidden layer via W
540
541        Returns:
542
543            (centered response, direct link + hidden layer matrix): {tuple}
544
545        """
546
547        # either X and y are stored or not
548        # assert ((y is None) & (X is None)) | ((y is not None) & (X is not None))
549        if self.n_hidden_features > 0:  # has a hidden layer
550            assert (
551                len(self.type_scaling) >= 2
552            ), "must have len(self.type_scaling) >= 2 when self.n_hidden_features > 0"
553
554        if X is None:
555            if self.col_sample == 1:
556                input_X = self.X_
557            else:
558                n_features = self.X_.shape[1]
559                new_n_features = int(np.ceil(n_features * self.col_sample))
560                assert (
561                    new_n_features >= 1
562                ), "check class attribute 'col_sample' and the number of covariates provided for X"
563                np.random.seed(self.seed)
564                index_col = np.random.choice(
565                    range(n_features), size=new_n_features, replace=False
566                )
567                self.index_col_ = index_col
568                input_X = self.X_[:, self.index_col_]
569
570        else:  # X is not None # keep X vs self.X_
571            if isinstance(X, pd.DataFrame):
572                X = copy.deepcopy(X.values.astype(float))
573
574            if self.col_sample == 1:
575                input_X = X
576            else:
577                n_features = X.shape[1]
578                new_n_features = int(np.ceil(n_features * self.col_sample))
579                assert (
580                    new_n_features >= 1
581                ), "check class attribute 'col_sample' and the number of covariates provided for X"
582                np.random.seed(self.seed)
583                index_col = np.random.choice(
584                    range(n_features), size=new_n_features, replace=False
585                )
586                self.index_col_ = index_col
587                input_X = X[:, self.index_col_]
588
589        if self.n_clusters <= 0:
590            # data without any clustering: self.n_clusters is None -----
591
592            if self.n_hidden_features > 0:  # with hidden layer
593                self.nn_scaler_, scaled_X = mo.scale_covariates(
594                    input_X, choice=self.type_scaling[1], scaler=self.nn_scaler_
595                )
596                Phi_X = (
597                    self.create_layer(scaled_X)
598                    if W is None
599                    else self.create_layer(scaled_X, W=W)
600                )
601                Z = (
602                    mo.cbind(input_X, Phi_X, backend=self.backend)
603                    if self.direct_link is True
604                    else Phi_X
605                )
606                self.scaler_, scaled_Z = mo.scale_covariates(
607                    Z, choice=self.type_scaling[0], scaler=self.scaler_
608                )
609            else:  # no hidden layer
610                Z = input_X
611                self.scaler_, scaled_Z = mo.scale_covariates(
612                    Z, choice=self.type_scaling[0], scaler=self.scaler_
613                )
614
615        else:
616            # data with clustering: self.n_clusters is not None ----- # keep
617
618            augmented_X = mo.cbind(
619                input_X,
620                self.encode_clusters(input_X, **kwargs),
621                backend=self.backend,
622            )
623
624            if self.n_hidden_features > 0:  # with hidden layer
625                self.nn_scaler_, scaled_X = mo.scale_covariates(
626                    augmented_X,
627                    choice=self.type_scaling[1],
628                    scaler=self.nn_scaler_,
629                )
630                Phi_X = (
631                    self.create_layer(scaled_X)
632                    if W is None
633                    else self.create_layer(scaled_X, W=W)
634                )
635                Z = (
636                    mo.cbind(augmented_X, Phi_X, backend=self.backend)
637                    if self.direct_link is True
638                    else Phi_X
639                )
640                self.scaler_, scaled_Z = mo.scale_covariates(
641                    Z, choice=self.type_scaling[0], scaler=self.scaler_
642                )
643            else:  # no hidden layer
644                Z = augmented_X
645                self.scaler_, scaled_Z = mo.scale_covariates(
646                    Z, choice=self.type_scaling[0], scaler=self.scaler_
647                )
648
649        # Returning model inputs -----
650        if mx.is_factor(y) is False:  # regression
651            # center y
652            if y is None:
653                self.y_mean_, centered_y = mo.center_response(self.y_)
654            else:
655                self.y_mean_, centered_y = mo.center_response(y)
656
657            # y is subsampled
658            if self.row_sample < 1:
659                n, p = Z.shape
660
661                self.subsampler_ = (
662                    SubSampler(
663                        y=self.y_, row_sample=self.row_sample, seed=self.seed
664                    )
665                    if y is None
666                    else SubSampler(
667                        y=y, row_sample=self.row_sample, seed=self.seed
668                    )
669                )
670
671                self.index_row_ = self.subsampler_.subsample()
672
673                n_row_sample = len(self.index_row_)
674                # regression
675                return (
676                    centered_y[self.index_row_].reshape(n_row_sample),
677                    self.scaler_.transform(
678                        Z[self.index_row_, :].reshape(n_row_sample, p)
679                    ),
680                )
681            # y is not subsampled
682            # regression
683            return (centered_y, self.scaler_.transform(Z))
684
685        # classification
686        # y is subsampled
687        if self.row_sample < 1:
688            n, p = Z.shape
689
690            self.subsampler_ = (
691                SubSampler(
692                    y=self.y_, row_sample=self.row_sample, seed=self.seed
693                )
694                if y is None
695                else SubSampler(y=y, row_sample=self.row_sample, seed=self.seed)
696            )
697
698            self.index_row_ = self.subsampler_.subsample()
699
700            n_row_sample = len(self.index_row_)
701            # classification
702            return (
703                y[self.index_row_].reshape(n_row_sample),
704                self.scaler_.transform(
705                    Z[self.index_row_, :].reshape(n_row_sample, p)
706                ),
707            )
708        # y is not subsampled
709        # classification
710        return (y, self.scaler_.transform(Z))

Create new hidden features for training set, with hidden layer, center the response.

Parameters:

y: array-like, shape = [n_samples]
    Target values

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features

W: {array-like}, shape = [n_features, hidden_features]
    if provided, constructs the hidden layer via W

Returns:

(centered response, direct link + hidden layer matrix): {tuple}
def cook_test_set(self, X, **kwargs):
712    def cook_test_set(self, X, **kwargs):
713        """Transform data from test set, with hidden layer.
714
715        Parameters:
716
717            X: {array-like}, shape = [n_samples, n_features]
718                Training vectors, where n_samples is the number
719                of samples and n_features is the number of features
720
721            **kwargs: additional parameters to be passed to self.encode_cluster
722
723        Returns:
724
725            Transformed test set : {array-like}
726        """
727
728        if isinstance(X, pd.DataFrame):
729            X = copy.deepcopy(X.values.astype(float))
730
731        if len(X.shape) == 1:
732            X = X.reshape(1, -1)
733
734        if (
735            self.n_clusters == 0
736        ):  # data without clustering: self.n_clusters is None -----
737            if self.n_hidden_features > 0:
738                # if hidden layer
739                scaled_X = (
740                    self.nn_scaler_.transform(X)
741                    if (self.col_sample == 1)
742                    else self.nn_scaler_.transform(X[:, self.index_col_])
743                )
744                Phi_X = self.create_layer(scaled_X, self.W_)
745                if self.direct_link:
746                    return self.scaler_.transform(
747                        mo.cbind(scaled_X, Phi_X, backend=self.backend)
748                    )
749                # when self.direct_link == False
750                return self.scaler_.transform(Phi_X)
751            # if no hidden layer # self.n_hidden_features == 0
752            return self.scaler_.transform(X)
753
754        # data with clustering: self.n_clusters > 0 -----
755        if self.col_sample == 1:
756            predicted_clusters = self.encode_clusters(
757                X=X, predict=True, **kwargs
758            )
759            augmented_X = mo.cbind(X, predicted_clusters, backend=self.backend)
760        else:
761            predicted_clusters = self.encode_clusters(
762                X=X[:, self.index_col_], predict=True, **kwargs
763            )
764            augmented_X = mo.cbind(
765                X[:, self.index_col_], predicted_clusters, backend=self.backend
766            )
767
768        if self.n_hidden_features > 0:  # if hidden layer
769            scaled_X = self.nn_scaler_.transform(augmented_X)
770            Phi_X = self.create_layer(scaled_X, self.W_)
771            if self.direct_link:
772                return self.scaler_.transform(
773                    mo.cbind(augmented_X, Phi_X, backend=self.backend)
774                )
775            return self.scaler_.transform(Phi_X)
776
777        # if no hidden layer
778        return self.scaler_.transform(augmented_X)

Transform data from test set, with hidden layer.

Parameters:

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features

**kwargs: additional parameters to be passed to self.encode_cluster

Returns:

Transformed test set : {array-like}
class BaseRegressor(nnetsauce.Base, sklearn.base.RegressorMixin):
 15class BaseRegressor(Base, RegressorMixin):
 16    """Random Vector Functional Link Network regression without shrinkage
 17
 18    Parameters:
 19
 20        n_hidden_features: int
 21            number of nodes in the hidden layer
 22
 23        activation_name: str
 24            activation function: 'relu', 'tanh', 'sigmoid', 'prelu' or 'elu'
 25
 26        a: float
 27            hyperparameter for 'prelu' or 'elu' activation function
 28
 29        nodes_sim: str
 30            type of simulation for hidden layer nodes: 'sobol', 'hammersley', 'halton',
 31            'uniform'
 32
 33        bias: boolean
 34            indicates if the hidden layer contains a bias term (True) or
 35            not (False)
 36
 37        dropout: float
 38            regularization parameter; (random) percentage of nodes dropped out
 39            of the training
 40
 41        direct_link: boolean
 42            indicates if the original features are included (True) in model's
 43            fitting or not (False)
 44
 45        n_clusters: int
 46            number of clusters for type_clust='kmeans' or type_clust='gmm'
 47            clustering (could be 0: no clustering)
 48
 49        cluster_encode: bool
 50            defines how the variable containing clusters is treated (default is one-hot);
 51            if `False`, then labels are used, without one-hot encoding
 52
 53        type_clust: str
 54            type of clustering method: currently k-means ('kmeans') or Gaussian
 55            Mixture Model ('gmm')
 56
 57        type_scaling: a tuple of 3 strings
 58            scaling methods for inputs, hidden layer, and clustering respectively
 59            (and when relevant).
 60            Currently available: standardization ('std') or MinMax scaling ('minmax')
 61
 62        col_sample: float
 63            percentage of features randomly chosen for training
 64
 65        row_sample: float
 66            percentage of rows chosen for training, by stratified bootstrapping
 67
 68        seed: int
 69            reproducibility seed for nodes_sim=='uniform', clustering and dropout
 70
 71        backend: str
 72            "cpu" or "gpu" or "tpu"
 73
 74    Attributes:
 75
 76        beta_: vector
 77            regression coefficients
 78
 79        GCV_: float
 80            Generalized Cross-Validation error
 81
 82    """
 83
 84    # construct the object -----
 85
 86    def __init__(
 87        self,
 88        n_hidden_features=5,
 89        activation_name="relu",
 90        a=0.01,
 91        nodes_sim="sobol",
 92        bias=True,
 93        dropout=0,
 94        direct_link=True,
 95        n_clusters=2,
 96        cluster_encode=True,
 97        type_clust="kmeans",
 98        type_scaling=("std", "std", "std"),
 99        col_sample=1,
100        row_sample=1,
101        seed=123,
102        backend="cpu",
103    ):
104        super().__init__(
105            n_hidden_features=n_hidden_features,
106            activation_name=activation_name,
107            a=a,
108            nodes_sim=nodes_sim,
109            bias=bias,
110            dropout=dropout,
111            direct_link=direct_link,
112            n_clusters=n_clusters,
113            cluster_encode=cluster_encode,
114            type_clust=type_clust,
115            type_scaling=type_scaling,
116            col_sample=col_sample,
117            row_sample=row_sample,
118            seed=seed,
119            backend=backend,
120        )
121
122    def fit(self, X, y, **kwargs):
123        """Fit BaseRegressor to training data (X, y)
124
125        Parameters:
126
127            X: {array-like}, shape = [n_samples, n_features]
128                Training vectors, where n_samples is the number
129                of samples and n_features is the number of features
130
131            y: array-like, shape = [n_samples]
132                Target values
133
134            **kwargs: additional parameters to be passed to self.cook_training_set
135
136        Returns:
137
138            self: object
139        """
140
141        centered_y, scaled_Z = self.cook_training_set(y=y, X=X, **kwargs)
142
143        fit_obj = lmf.beta_Sigma_hat(
144            X=scaled_Z, y=centered_y, backend=self.backend
145        )
146
147        self.beta_ = fit_obj["beta_hat"]
148
149        self.GCV_ = fit_obj["GCV"]
150
151        return self
152
153    def predict(self, X, **kwargs):
154        """Predict test data X.
155
156        Parameters:
157
158            X: {array-like}, shape = [n_samples, n_features]
159                Training vectors, where n_samples is the number
160                of samples and n_features is the number of features
161
162            **kwargs: additional parameters to be passed to self.cook_test_set
163
164        Returns:
165
166            model predictions: {array-like}
167        """
168
169        if len(X.shape) == 1:
170            n_features = X.shape[0]
171            new_X = mo.rbind(
172                X.reshape(1, n_features),
173                np.ones(n_features).reshape(1, n_features),
174            )
175
176            return (
177                self.y_mean_
178                + mo.safe_sparse_dot(
179                    a=self.cook_test_set(new_X, **kwargs),
180                    b=self.beta_,
181                    backend=self.backend,
182                )
183            )[0]
184
185        return self.y_mean_ + mo.safe_sparse_dot(
186            a=self.cook_test_set(X, **kwargs),
187            b=self.beta_,
188            backend=self.backend,
189        )

Random Vector Functional Link Network regression without shrinkage

Parameters:

n_hidden_features: int
    number of nodes in the hidden layer

activation_name: str
    activation function: 'relu', 'tanh', 'sigmoid', 'prelu' or 'elu'

a: float
    hyperparameter for 'prelu' or 'elu' activation function

nodes_sim: str
    type of simulation for hidden layer nodes: 'sobol', 'hammersley', 'halton',
    'uniform'

bias: boolean
    indicates if the hidden layer contains a bias term (True) or
    not (False)

dropout: float
    regularization parameter; (random) percentage of nodes dropped out
    of the training

direct_link: boolean
    indicates if the original features are included (True) in model's
    fitting or not (False)

n_clusters: int
    number of clusters for type_clust='kmeans' or type_clust='gmm'
    clustering (could be 0: no clustering)

cluster_encode: bool
    defines how the variable containing clusters is treated (default is one-hot);
    if `False`, then labels are used, without one-hot encoding

type_clust: str
    type of clustering method: currently k-means ('kmeans') or Gaussian
    Mixture Model ('gmm')

type_scaling: a tuple of 3 strings
    scaling methods for inputs, hidden layer, and clustering respectively
    (and when relevant).
    Currently available: standardization ('std') or MinMax scaling ('minmax')

col_sample: float
    percentage of features randomly chosen for training

row_sample: float
    percentage of rows chosen for training, by stratified bootstrapping

seed: int
    reproducibility seed for nodes_sim=='uniform', clustering and dropout

backend: str
    "cpu" or "gpu" or "tpu"

Attributes:

beta_: vector
    regression coefficients

GCV_: float
    Generalized Cross-Validation error
def fit(self, X, y, **kwargs):
122    def fit(self, X, y, **kwargs):
123        """Fit BaseRegressor to training data (X, y)
124
125        Parameters:
126
127            X: {array-like}, shape = [n_samples, n_features]
128                Training vectors, where n_samples is the number
129                of samples and n_features is the number of features
130
131            y: array-like, shape = [n_samples]
132                Target values
133
134            **kwargs: additional parameters to be passed to self.cook_training_set
135
136        Returns:
137
138            self: object
139        """
140
141        centered_y, scaled_Z = self.cook_training_set(y=y, X=X, **kwargs)
142
143        fit_obj = lmf.beta_Sigma_hat(
144            X=scaled_Z, y=centered_y, backend=self.backend
145        )
146
147        self.beta_ = fit_obj["beta_hat"]
148
149        self.GCV_ = fit_obj["GCV"]
150
151        return self

Fit BaseRegressor to training data (X, y)

Parameters:

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features

y: array-like, shape = [n_samples]
    Target values

**kwargs: additional parameters to be passed to self.cook_training_set

Returns:

self: object
def predict(self, X, **kwargs):
153    def predict(self, X, **kwargs):
154        """Predict test data X.
155
156        Parameters:
157
158            X: {array-like}, shape = [n_samples, n_features]
159                Training vectors, where n_samples is the number
160                of samples and n_features is the number of features
161
162            **kwargs: additional parameters to be passed to self.cook_test_set
163
164        Returns:
165
166            model predictions: {array-like}
167        """
168
169        if len(X.shape) == 1:
170            n_features = X.shape[0]
171            new_X = mo.rbind(
172                X.reshape(1, n_features),
173                np.ones(n_features).reshape(1, n_features),
174            )
175
176            return (
177                self.y_mean_
178                + mo.safe_sparse_dot(
179                    a=self.cook_test_set(new_X, **kwargs),
180                    b=self.beta_,
181                    backend=self.backend,
182                )
183            )[0]
184
185        return self.y_mean_ + mo.safe_sparse_dot(
186            a=self.cook_test_set(X, **kwargs),
187            b=self.beta_,
188            backend=self.backend,
189        )

Predict test data X.

Parameters:

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features

**kwargs: additional parameters to be passed to self.cook_test_set

Returns:

model predictions: {array-like}
class BayesianRVFLRegressor(nnetsauce.Base, sklearn.base.RegressorMixin):
 15class BayesianRVFLRegressor(Base, RegressorMixin):
 16    """Bayesian Random Vector Functional Link Network regression with one prior
 17
 18    Parameters:
 19
 20        n_hidden_features: int
 21            number of nodes in the hidden layer
 22
 23        activation_name: str
 24            activation function: 'relu', 'tanh', 'sigmoid', 'prelu' or 'elu'
 25
 26        a: float
 27            hyperparameter for 'prelu' or 'elu' activation function
 28
 29        nodes_sim: str
 30            type of simulation for the nodes: 'sobol', 'hammersley', 'halton', 'uniform'
 31
 32        bias: boolean
 33            indicates if the hidden layer contains a bias term (True) or not (False)
 34
 35        dropout: float
 36            regularization parameter; (random) percentage of nodes dropped out
 37            of the training
 38
 39        direct_link: boolean
 40            indicates if the original features are included (True) in model''s fitting or not (False)
 41
 42        n_clusters: int
 43            number of clusters for 'kmeans' or 'gmm' clustering (could be 0: no clustering)
 44
 45        cluster_encode: bool
 46            defines how the variable containing clusters is treated (default is one-hot)
 47            if `False`, then labels are used, without one-hot encoding
 48
 49        type_clust: str
 50            type of clustering method: currently k-means ('kmeans') or Gaussian Mixture Model ('gmm')
 51
 52        type_scaling: a tuple of 3 strings
 53            scaling methods for inputs, hidden layer, and clustering respectively
 54            (and when relevant).
 55            Currently available: standardization ('std') or MinMax scaling ('minmax')
 56
 57        seed: int
 58            reproducibility seed for nodes_sim=='uniform'
 59
 60        s: float
 61            std. dev. of regression parameters in Bayesian Ridge Regression
 62
 63        sigma: float
 64            std. dev. of residuals in Bayesian Ridge Regression
 65
 66        return_std: boolean
 67            if True, uncertainty around predictions is evaluated
 68
 69        backend: str
 70            "cpu" or "gpu" or "tpu"
 71
 72    Attributes:
 73
 74        beta_: array-like
 75            regression''s coefficients
 76
 77        Sigma_: array-like
 78            covariance of the distribution of fitted parameters
 79
 80        GCV_: float
 81            Generalized cross-validation error
 82
 83        y_mean_: float
 84            average response
 85
 86    Examples:
 87
 88    ```python
 89    TBD
 90    ```
 91
 92    """
 93
 94    # construct the object -----
 95
 96    def __init__(
 97        self,
 98        n_hidden_features=5,
 99        activation_name="relu",
100        a=0.01,
101        nodes_sim="sobol",
102        bias=True,
103        dropout=0,
104        direct_link=True,
105        n_clusters=2,
106        cluster_encode=True,
107        type_clust="kmeans",
108        type_scaling=("std", "std", "std"),
109        seed=123,
110        s=0.1,
111        sigma=0.05,
112        return_std=True,
113        backend="cpu",
114    ):
115        super().__init__(
116            n_hidden_features=n_hidden_features,
117            activation_name=activation_name,
118            a=a,
119            nodes_sim=nodes_sim,
120            bias=bias,
121            dropout=dropout,
122            direct_link=direct_link,
123            n_clusters=n_clusters,
124            cluster_encode=cluster_encode,
125            type_clust=type_clust,
126            type_scaling=type_scaling,
127            seed=seed,
128            backend=backend,
129        )
130        self.s = s
131        self.sigma = sigma
132        self.beta_ = None
133        self.Sigma_ = None
134        self.GCV_ = None
135        self.return_std = return_std
136
137    def fit(self, X, y, **kwargs):
138        """Fit BayesianRVFLRegressor to training data (X, y).
139
140        Parameters:
141
142            X: {array-like}, shape = [n_samples, n_features]
143                Training vectors, where n_samples is the number
144                of samples and n_features is the number of features.
145
146            y: array-like, shape = [n_samples]
147                Target values.
148
149            **kwargs: additional parameters to be passed to
150                    self.cook_training_set
151
152        Returns:
153
154            self: object
155
156        """
157
158        centered_y, scaled_Z = self.cook_training_set(y=y, X=X, **kwargs)
159
160        fit_obj = lmf.beta_Sigma_hat_rvfl(
161            X=scaled_Z,
162            y=centered_y,
163            s=self.s,
164            sigma=self.sigma,
165            fit_intercept=False,
166            return_cov=self.return_std,
167            backend=self.backend,
168        )
169
170        self.beta_ = fit_obj["beta_hat"]
171
172        if self.return_std == True:
173            self.Sigma_ = fit_obj["Sigma_hat"]
174
175        self.GCV_ = fit_obj["GCV"]
176
177        return self
178
179    def predict(self, X, return_std=False, **kwargs):
180        """Predict test data X.
181
182        Parameters:
183
184            X: {array-like}, shape = [n_samples, n_features]
185                Training vectors, where n_samples is the number
186                of samples and n_features is the number of features.
187
188            return_std: {boolean}, standard dev. is returned or not
189
190            **kwargs: additional parameters to be passed to
191                    self.cook_test_set
192
193        Returns:
194
195            model predictions: {array-like}
196
197        """
198
199        if len(X.shape) == 1:  # one observation in the test set only
200            n_features = X.shape[0]
201            new_X = mo.rbind(
202                x=X.reshape(1, n_features),
203                y=np.ones(n_features).reshape(1, n_features),
204                backend=self.backend,
205            )
206
207        self.return_std = return_std
208
209        if self.return_std == False:
210            if len(X.shape) == 1:
211                return (
212                    self.y_mean_
213                    + mo.safe_sparse_dot(
214                        a=self.cook_test_set(new_X, **kwargs),
215                        b=self.beta_,
216                        backend=self.backend,
217                    )
218                )[0]
219
220            return self.y_mean_ + mo.safe_sparse_dot(
221                a=self.cook_test_set(X, **kwargs),
222                b=self.beta_,
223                backend=self.backend,
224            )
225
226        else:  # confidence interval required for preds?
227            if len(X.shape) == 1:
228                Z = self.cook_test_set(new_X, **kwargs)
229
230                pred_obj = lmf.beta_Sigma_hat_rvfl(
231                    s=self.s,
232                    sigma=self.sigma,
233                    X_star=Z,
234                    return_cov=True,
235                    beta_hat_=self.beta_,
236                    Sigma_hat_=self.Sigma_,
237                    backend=self.backend,
238                )
239
240                return (
241                    self.y_mean_ + pred_obj["preds"][0],
242                    pred_obj["preds_std"][0],
243                )
244
245            Z = self.cook_test_set(X, **kwargs)
246
247            pred_obj = lmf.beta_Sigma_hat_rvfl(
248                s=self.s,
249                sigma=self.sigma,
250                X_star=Z,
251                return_cov=True,
252                beta_hat_=self.beta_,
253                Sigma_hat_=self.Sigma_,
254                backend=self.backend,
255            )
256
257            return (self.y_mean_ + pred_obj["preds"], pred_obj["preds_std"])

Bayesian Random Vector Functional Link Network regression with one prior

Parameters:

n_hidden_features: int
    number of nodes in the hidden layer

activation_name: str
    activation function: 'relu', 'tanh', 'sigmoid', 'prelu' or 'elu'

a: float
    hyperparameter for 'prelu' or 'elu' activation function

nodes_sim: str
    type of simulation for the nodes: 'sobol', 'hammersley', 'halton', 'uniform'

bias: boolean
    indicates if the hidden layer contains a bias term (True) or not (False)

dropout: float
    regularization parameter; (random) percentage of nodes dropped out
    of the training

direct_link: boolean
    indicates if the original features are included (True) in model''s fitting or not (False)

n_clusters: int
    number of clusters for 'kmeans' or 'gmm' clustering (could be 0: no clustering)

cluster_encode: bool
    defines how the variable containing clusters is treated (default is one-hot)
    if `False`, then labels are used, without one-hot encoding

type_clust: str
    type of clustering method: currently k-means ('kmeans') or Gaussian Mixture Model ('gmm')

type_scaling: a tuple of 3 strings
    scaling methods for inputs, hidden layer, and clustering respectively
    (and when relevant).
    Currently available: standardization ('std') or MinMax scaling ('minmax')

seed: int
    reproducibility seed for nodes_sim=='uniform'

s: float
    std. dev. of regression parameters in Bayesian Ridge Regression

sigma: float
    std. dev. of residuals in Bayesian Ridge Regression

return_std: boolean
    if True, uncertainty around predictions is evaluated

backend: str
    "cpu" or "gpu" or "tpu"

Attributes:

beta_: array-like
    regression''s coefficients

Sigma_: array-like
    covariance of the distribution of fitted parameters

GCV_: float
    Generalized cross-validation error

y_mean_: float
    average response

Examples:

TBD
def fit(self, X, y, **kwargs):
137    def fit(self, X, y, **kwargs):
138        """Fit BayesianRVFLRegressor to training data (X, y).
139
140        Parameters:
141
142            X: {array-like}, shape = [n_samples, n_features]
143                Training vectors, where n_samples is the number
144                of samples and n_features is the number of features.
145
146            y: array-like, shape = [n_samples]
147                Target values.
148
149            **kwargs: additional parameters to be passed to
150                    self.cook_training_set
151
152        Returns:
153
154            self: object
155
156        """
157
158        centered_y, scaled_Z = self.cook_training_set(y=y, X=X, **kwargs)
159
160        fit_obj = lmf.beta_Sigma_hat_rvfl(
161            X=scaled_Z,
162            y=centered_y,
163            s=self.s,
164            sigma=self.sigma,
165            fit_intercept=False,
166            return_cov=self.return_std,
167            backend=self.backend,
168        )
169
170        self.beta_ = fit_obj["beta_hat"]
171
172        if self.return_std == True:
173            self.Sigma_ = fit_obj["Sigma_hat"]
174
175        self.GCV_ = fit_obj["GCV"]
176
177        return self

Fit BayesianRVFLRegressor to training data (X, y).

Parameters:

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features.

y: array-like, shape = [n_samples]
    Target values.

**kwargs: additional parameters to be passed to
        self.cook_training_set

Returns:

self: object
def predict(self, X, return_std=False, **kwargs):
179    def predict(self, X, return_std=False, **kwargs):
180        """Predict test data X.
181
182        Parameters:
183
184            X: {array-like}, shape = [n_samples, n_features]
185                Training vectors, where n_samples is the number
186                of samples and n_features is the number of features.
187
188            return_std: {boolean}, standard dev. is returned or not
189
190            **kwargs: additional parameters to be passed to
191                    self.cook_test_set
192
193        Returns:
194
195            model predictions: {array-like}
196
197        """
198
199        if len(X.shape) == 1:  # one observation in the test set only
200            n_features = X.shape[0]
201            new_X = mo.rbind(
202                x=X.reshape(1, n_features),
203                y=np.ones(n_features).reshape(1, n_features),
204                backend=self.backend,
205            )
206
207        self.return_std = return_std
208
209        if self.return_std == False:
210            if len(X.shape) == 1:
211                return (
212                    self.y_mean_
213                    + mo.safe_sparse_dot(
214                        a=self.cook_test_set(new_X, **kwargs),
215                        b=self.beta_,
216                        backend=self.backend,
217                    )
218                )[0]
219
220            return self.y_mean_ + mo.safe_sparse_dot(
221                a=self.cook_test_set(X, **kwargs),
222                b=self.beta_,
223                backend=self.backend,
224            )
225
226        else:  # confidence interval required for preds?
227            if len(X.shape) == 1:
228                Z = self.cook_test_set(new_X, **kwargs)
229
230                pred_obj = lmf.beta_Sigma_hat_rvfl(
231                    s=self.s,
232                    sigma=self.sigma,
233                    X_star=Z,
234                    return_cov=True,
235                    beta_hat_=self.beta_,
236                    Sigma_hat_=self.Sigma_,
237                    backend=self.backend,
238                )
239
240                return (
241                    self.y_mean_ + pred_obj["preds"][0],
242                    pred_obj["preds_std"][0],
243                )
244
245            Z = self.cook_test_set(X, **kwargs)
246
247            pred_obj = lmf.beta_Sigma_hat_rvfl(
248                s=self.s,
249                sigma=self.sigma,
250                X_star=Z,
251                return_cov=True,
252                beta_hat_=self.beta_,
253                Sigma_hat_=self.Sigma_,
254                backend=self.backend,
255            )
256
257            return (self.y_mean_ + pred_obj["preds"], pred_obj["preds_std"])

Predict test data X.

Parameters:

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features.

return_std: {boolean}, standard dev. is returned or not

**kwargs: additional parameters to be passed to
        self.cook_test_set

Returns:

model predictions: {array-like}
class BayesianRVFL2Regressor(nnetsauce.Base, sklearn.base.RegressorMixin):
 15class BayesianRVFL2Regressor(Base, RegressorMixin):
 16    """Bayesian Random Vector Functional Link Network regression with two priors
 17
 18    Parameters:
 19
 20        n_hidden_features: int
 21            number of nodes in the hidden layer
 22
 23        activation_name: str
 24            activation function: 'relu', 'tanh', 'sigmoid', 'prelu' or 'elu'
 25
 26        a: float
 27            hyperparameter for 'prelu' or 'elu' activation function
 28
 29        nodes_sim: str
 30            type of simulation for the nodes: 'sobol', 'hammersley', 'halton', 'uniform'
 31
 32        bias: boolean
 33            indicates if the hidden layer contains a bias term (True) or not (False)
 34
 35        dropout: float
 36            regularization parameter; (random) percentage of nodes dropped out
 37            of the training
 38
 39        direct_link: boolean
 40            indicates if the original features are included (True) in model''s fitting or not (False)
 41
 42        n_clusters: int
 43            number of clusters for 'kmeans' or 'gmm' clustering (could be 0: no clustering)
 44
 45        cluster_encode: bool
 46            defines how the variable containing clusters is treated (default is one-hot)
 47            if `False`, then labels are used, without one-hot encoding
 48
 49        type_clust: str
 50            type of clustering method: currently k-means ('kmeans') or Gaussian Mixture Model ('gmm')
 51
 52        type_scaling: a tuple of 3 strings
 53            scaling methods for inputs, hidden layer, and clustering respectively
 54            (and when relevant).
 55            Currently available: standardization ('std') or MinMax scaling ('minmax')
 56
 57        seed: int
 58            reproducibility seed for nodes_sim=='uniform'
 59
 60        s1: float
 61            std. dev. of init. regression parameters in Bayesian Ridge Regression
 62
 63        s2: float
 64            std. dev. of augmented regression parameters in Bayesian Ridge Regression
 65
 66        sigma: float
 67            std. dev. of residuals in Bayesian Ridge Regression
 68
 69        return_std: boolean
 70            if True, uncertainty around predictions is evaluated
 71
 72        backend: str
 73            "cpu" or "gpu" or "tpu"
 74
 75    Attributes:
 76
 77        beta_: array-like
 78            regression''s coefficients
 79
 80        Sigma_: array-like
 81            covariance of the distribution of fitted parameters
 82
 83        GCV_: float
 84            Generalized cross-validation error
 85
 86        y_mean_: float
 87            average response
 88
 89    Examples:
 90
 91    ```python
 92    TBD
 93    ```
 94
 95    """
 96
 97    # construct the object -----
 98
 99    def __init__(
100        self,
101        n_hidden_features=5,
102        activation_name="relu",
103        a=0.01,
104        nodes_sim="sobol",
105        bias=True,
106        dropout=0,
107        direct_link=True,
108        n_clusters=0,
109        cluster_encode=True,
110        type_clust="kmeans",
111        type_scaling=("std", "std", "std"),
112        seed=123,
113        s1=0.1,
114        s2=0.1,
115        sigma=0.05,
116        return_std=True,
117        backend="cpu",
118    ):
119        super().__init__(
120            n_hidden_features=n_hidden_features,
121            activation_name=activation_name,
122            a=a,
123            nodes_sim=nodes_sim,
124            bias=bias,
125            dropout=dropout,
126            direct_link=direct_link,
127            n_clusters=n_clusters,
128            cluster_encode=cluster_encode,
129            type_clust=type_clust,
130            type_scaling=type_scaling,
131            seed=seed,
132            backend=backend,
133        )
134
135        self.s1 = s1
136        self.s2 = s2
137        self.sigma = sigma
138        self.beta_ = None
139        self.Sigma_ = None
140        self.GCV_ = None
141        self.return_std = return_std
142        self.coef_ = None
143
144    def fit(self, X, y, **kwargs):
145        """Fit BayesianRVFL2Regressor to training data (X, y)
146
147        Parameters:
148
149            X: {array-like}, shape = [n_samples, n_features]
150                Training vectors, where n_samples is the number
151                of samples and n_features is the number of features
152
153            y: array-like, shape = [n_samples]
154                Target values
155
156            **kwargs: additional parameters to be passed to
157                    self.cook_training_set
158
159        Returns:
160
161            self: object
162
163        """
164
165        centered_y, scaled_Z = self.cook_training_set(y=y, X=X, **kwargs)
166
167        n, p = X.shape
168        q = self.n_hidden_features
169
170        if self.direct_link == True:
171            r = p + self.n_clusters
172
173            block11 = (self.s1**2) * np.eye(r)
174            block12 = np.zeros((r, q))
175            block21 = np.zeros((q, r))
176            block22 = (self.s2**2) * np.eye(q)
177
178            Sigma_prior = mo.rbind(
179                x=mo.cbind(x=block11, y=block12, backend=self.backend),
180                y=mo.cbind(x=block21, y=block22, backend=self.backend),
181                backend=self.backend,
182            )
183
184        else:
185            Sigma_prior = (self.s2**2) * np.eye(q)
186
187        fit_obj = lmf.beta_Sigma_hat_rvfl2(
188            X=scaled_Z,
189            y=centered_y,
190            Sigma=Sigma_prior,
191            sigma=self.sigma,
192            fit_intercept=False,
193            return_cov=self.return_std,
194            backend=self.backend,
195        )
196
197        self.beta_ = fit_obj["beta_hat"]
198
199        self.coef_ = self.beta_
200
201        if self.return_std == True:
202            self.Sigma_ = fit_obj["Sigma_hat"]
203
204        self.GCV_ = fit_obj["GCV"]
205
206        return self
207
208    def predict(self, X, return_std=False, **kwargs):
209        """Predict test data X.
210
211        Parameters:
212
213            X: {array-like}, shape = [n_samples, n_features]
214                Training vectors, where n_samples is the number
215                of samples and n_features is the number of features.
216
217            return_std: {boolean}, standard dev. is returned or not
218
219            **kwargs: additional parameters to be passed to
220                    self.cook_test_set
221
222        Returns:
223
224            model predictions: {array-like}
225
226        """
227
228        if len(X.shape) == 1:  # one observation in the test set only
229            n_features = X.shape[0]
230            new_X = mo.rbind(
231                x=X.reshape(1, n_features),
232                y=np.ones(n_features).reshape(1, n_features),
233                backend=self.backend,
234            )
235
236        self.return_std = return_std
237
238        if self.return_std == False:
239            if len(X.shape) == 1:
240                return (
241                    self.y_mean_
242                    + mo.safe_sparse_dot(
243                        self.cook_test_set(new_X, **kwargs),
244                        self.beta_,
245                        backend=self.backend,
246                    )
247                )[0]
248
249            return self.y_mean_ + mo.safe_sparse_dot(
250                self.cook_test_set(X, **kwargs),
251                self.beta_,
252                backend=self.backend,
253            )
254
255        else:  # confidence interval required for preds?
256            if len(X.shape) == 1:
257                Z = self.cook_test_set(new_X, **kwargs)
258
259                pred_obj = lmf.beta_Sigma_hat_rvfl2(
260                    X_star=Z,
261                    return_cov=self.return_std,
262                    beta_hat_=self.beta_,
263                    Sigma_hat_=self.Sigma_,
264                    backend=self.backend,
265                )
266
267                return (
268                    self.y_mean_ + pred_obj["preds"][0],
269                    pred_obj["preds_std"][0],
270                )
271
272            Z = self.cook_test_set(X, **kwargs)
273
274            pred_obj = lmf.beta_Sigma_hat_rvfl2(
275                X_star=Z,
276                return_cov=self.return_std,
277                beta_hat_=self.beta_,
278                Sigma_hat_=self.Sigma_,
279                backend=self.backend,
280            )
281
282            return (self.y_mean_ + pred_obj["preds"], pred_obj["preds_std"])

Bayesian Random Vector Functional Link Network regression with two priors

Parameters:

n_hidden_features: int
    number of nodes in the hidden layer

activation_name: str
    activation function: 'relu', 'tanh', 'sigmoid', 'prelu' or 'elu'

a: float
    hyperparameter for 'prelu' or 'elu' activation function

nodes_sim: str
    type of simulation for the nodes: 'sobol', 'hammersley', 'halton', 'uniform'

bias: boolean
    indicates if the hidden layer contains a bias term (True) or not (False)

dropout: float
    regularization parameter; (random) percentage of nodes dropped out
    of the training

direct_link: boolean
    indicates if the original features are included (True) in model''s fitting or not (False)

n_clusters: int
    number of clusters for 'kmeans' or 'gmm' clustering (could be 0: no clustering)

cluster_encode: bool
    defines how the variable containing clusters is treated (default is one-hot)
    if `False`, then labels are used, without one-hot encoding

type_clust: str
    type of clustering method: currently k-means ('kmeans') or Gaussian Mixture Model ('gmm')

type_scaling: a tuple of 3 strings
    scaling methods for inputs, hidden layer, and clustering respectively
    (and when relevant).
    Currently available: standardization ('std') or MinMax scaling ('minmax')

seed: int
    reproducibility seed for nodes_sim=='uniform'

s1: float
    std. dev. of init. regression parameters in Bayesian Ridge Regression

s2: float
    std. dev. of augmented regression parameters in Bayesian Ridge Regression

sigma: float
    std. dev. of residuals in Bayesian Ridge Regression

return_std: boolean
    if True, uncertainty around predictions is evaluated

backend: str
    "cpu" or "gpu" or "tpu"

Attributes:

beta_: array-like
    regression''s coefficients

Sigma_: array-like
    covariance of the distribution of fitted parameters

GCV_: float
    Generalized cross-validation error

y_mean_: float
    average response

Examples:

TBD
def fit(self, X, y, **kwargs):
144    def fit(self, X, y, **kwargs):
145        """Fit BayesianRVFL2Regressor to training data (X, y)
146
147        Parameters:
148
149            X: {array-like}, shape = [n_samples, n_features]
150                Training vectors, where n_samples is the number
151                of samples and n_features is the number of features
152
153            y: array-like, shape = [n_samples]
154                Target values
155
156            **kwargs: additional parameters to be passed to
157                    self.cook_training_set
158
159        Returns:
160
161            self: object
162
163        """
164
165        centered_y, scaled_Z = self.cook_training_set(y=y, X=X, **kwargs)
166
167        n, p = X.shape
168        q = self.n_hidden_features
169
170        if self.direct_link == True:
171            r = p + self.n_clusters
172
173            block11 = (self.s1**2) * np.eye(r)
174            block12 = np.zeros((r, q))
175            block21 = np.zeros((q, r))
176            block22 = (self.s2**2) * np.eye(q)
177
178            Sigma_prior = mo.rbind(
179                x=mo.cbind(x=block11, y=block12, backend=self.backend),
180                y=mo.cbind(x=block21, y=block22, backend=self.backend),
181                backend=self.backend,
182            )
183
184        else:
185            Sigma_prior = (self.s2**2) * np.eye(q)
186
187        fit_obj = lmf.beta_Sigma_hat_rvfl2(
188            X=scaled_Z,
189            y=centered_y,
190            Sigma=Sigma_prior,
191            sigma=self.sigma,
192            fit_intercept=False,
193            return_cov=self.return_std,
194            backend=self.backend,
195        )
196
197        self.beta_ = fit_obj["beta_hat"]
198
199        self.coef_ = self.beta_
200
201        if self.return_std == True:
202            self.Sigma_ = fit_obj["Sigma_hat"]
203
204        self.GCV_ = fit_obj["GCV"]
205
206        return self

Fit BayesianRVFL2Regressor to training data (X, y)

Parameters:

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features

y: array-like, shape = [n_samples]
    Target values

**kwargs: additional parameters to be passed to
        self.cook_training_set

Returns:

self: object
def predict(self, X, return_std=False, **kwargs):
208    def predict(self, X, return_std=False, **kwargs):
209        """Predict test data X.
210
211        Parameters:
212
213            X: {array-like}, shape = [n_samples, n_features]
214                Training vectors, where n_samples is the number
215                of samples and n_features is the number of features.
216
217            return_std: {boolean}, standard dev. is returned or not
218
219            **kwargs: additional parameters to be passed to
220                    self.cook_test_set
221
222        Returns:
223
224            model predictions: {array-like}
225
226        """
227
228        if len(X.shape) == 1:  # one observation in the test set only
229            n_features = X.shape[0]
230            new_X = mo.rbind(
231                x=X.reshape(1, n_features),
232                y=np.ones(n_features).reshape(1, n_features),
233                backend=self.backend,
234            )
235
236        self.return_std = return_std
237
238        if self.return_std == False:
239            if len(X.shape) == 1:
240                return (
241                    self.y_mean_
242                    + mo.safe_sparse_dot(
243                        self.cook_test_set(new_X, **kwargs),
244                        self.beta_,
245                        backend=self.backend,
246                    )
247                )[0]
248
249            return self.y_mean_ + mo.safe_sparse_dot(
250                self.cook_test_set(X, **kwargs),
251                self.beta_,
252                backend=self.backend,
253            )
254
255        else:  # confidence interval required for preds?
256            if len(X.shape) == 1:
257                Z = self.cook_test_set(new_X, **kwargs)
258
259                pred_obj = lmf.beta_Sigma_hat_rvfl2(
260                    X_star=Z,
261                    return_cov=self.return_std,
262                    beta_hat_=self.beta_,
263                    Sigma_hat_=self.Sigma_,
264                    backend=self.backend,
265                )
266
267                return (
268                    self.y_mean_ + pred_obj["preds"][0],
269                    pred_obj["preds_std"][0],
270                )
271
272            Z = self.cook_test_set(X, **kwargs)
273
274            pred_obj = lmf.beta_Sigma_hat_rvfl2(
275                X_star=Z,
276                return_cov=self.return_std,
277                beta_hat_=self.beta_,
278                Sigma_hat_=self.Sigma_,
279                backend=self.backend,
280            )
281
282            return (self.y_mean_ + pred_obj["preds"], pred_obj["preds_std"])

Predict test data X.

Parameters:

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features.

return_std: {boolean}, standard dev. is returned or not

**kwargs: additional parameters to be passed to
        self.cook_test_set

Returns:

model predictions: {array-like}
class ClassicalMTS(nnetsauce.MTS):
 42class ClassicalMTS(MTS):
 43    """Time series with statistical models (statsmodels), mostly for benchmarks
 44
 45    Parameters:
 46
 47        model: type of model: str.
 48            currently, 'VAR', 'VECM', 'ARIMA', 'ETS', 'Theta'
 49            Default is None
 50
 51        obj: object
 52            A time series model from statsmodels
 53
 54    Attributes:
 55
 56        df_: data frame
 57            the input data frame, in case a data.frame is provided to `fit`
 58
 59        level_: int
 60            level of confidence for prediction intervals (default is 95)
 61
 62    Examples:
 63    See examples/classical_mts_timeseries.py
 64    """
 65
 66    # construct the object -----
 67
 68    def __init__(self, model="VAR", obj=None):
 69        if obj is not None:
 70            self.model = None
 71            self.obj = obj
 72        else:
 73            self.model = model
 74            if self.model == "VAR":
 75                self.obj = VAR
 76            elif self.model == "VECM":
 77                self.obj = VECM
 78            elif self.model == "ARIMA":
 79                self.obj = ARIMA
 80            elif self.model == "ETS":
 81                self.obj = ExponentialSmoothing
 82            elif self.model == "Theta":
 83                self.obj = ThetaModel
 84            else:
 85                raise ValueError("model not recognized")
 86        self.n_series = None
 87        self.replications = None
 88        self.mean_ = None
 89        self.upper_ = None
 90        self.lower_ = None
 91        self.output_dates_ = None
 92        self.alpha_ = None
 93        self.df_ = None
 94        self.residuals_ = []
 95        self.sims_ = None
 96        self.level_ = None
 97
 98    def fit(self, X, **kwargs):
 99        """Fit ClassicalMTS model to training data X, with optional regressors xreg
100
101        Parameters:
102
103        X: {array-like}, shape = [n_samples, n_features]
104            Training time series, where n_samples is the number
105            of samples and n_features is the number of features;
106            X must be in increasing order (most recent observations last)
107
108        **kwargs: for now, additional parameters to be passed to for kernel density estimation, when needed (see sklearn.neighbors.KernelDensity)
109
110        Returns:
111
112        self: object
113        """
114
115        try:
116            self.n_series = X.shape[1]
117        except Exception:
118            self.n_series = 1
119
120        if (isinstance(X, pd.DataFrame) is False) and isinstance(
121            X, pd.Series
122        ) is False:  # input data set is a numpy array
123            X = pd.DataFrame(X)
124            if self.n_series > 1:
125                self.series_names = [
126                    "series" + str(i) for i in range(X.shape[1])
127                ]
128            else:
129                self.series_names = "series0"
130
131        else:  # input data set is a DataFrame or Series with column names
132            X_index = None
133            if X.index is not None and len(X.shape) > 1:
134                X_index = X.index
135                X = copy.deepcopy(mo.convert_df_to_numeric(X))
136            if X_index is not None:
137                try:
138                    X.index = X_index
139                except Exception:
140                    pass
141            if isinstance(X, pd.DataFrame):
142                self.series_names = X.columns.tolist()
143            else:
144                self.series_names = X.name
145
146        if isinstance(X, pd.DataFrame) or isinstance(X, pd.Series):
147            self.df_ = X
148            X = X.values
149            self.df_.columns = self.series_names
150            self.input_dates = ts.compute_input_dates(self.df_)
151        else:
152            self.df_ = pd.DataFrame(X, columns=self.series_names)
153
154        if self.model == "Theta":
155            try:
156                self.obj = self.obj(self.df_, **kwargs).fit()
157            except Exception as e:
158                self.obj = self.obj(self.df_.values, **kwargs).fit()
159            self.residuals_ = None
160        else:
161            self.obj = self.obj(X, **kwargs).fit()
162            try:
163                self.residuals_ = self.obj.resid
164            except Exception as e:  # Theta
165                self.residuals_ = None
166
167        return self
168
169    def predict(self, h=5, level=95, **kwargs):
170        """Forecast all the time series, h steps ahead
171
172        Parameters:
173
174        h: {integer}
175            Forecasting horizon
176
177        **kwargs: additional parameters to be passed to
178                self.cook_test_set
179
180        Returns:
181
182        model predictions for horizon = h: {array-like}
183
184        """
185
186        self.output_dates_, frequency = ts.compute_output_dates(self.df_, h)
187        self.level_ = level
188        self.lower_ = None  # do not remove (/!\)
189        self.upper_ = None  # do not remove (/!\)
190        self.sims_ = None  # do not remove (/!\)
191        self.level_ = level
192        self.alpha_ = 100 - level
193
194        pi_multiplier = norm.ppf(1 - self.alpha_ / 200)
195
196        # Named tuple for forecast results
197        DescribeResult = namedtuple(
198            "DescribeResult", ("mean", "lower", "upper")
199        )
200
201        if (
202            self.obj is not None
203        ):  # try all the special cases of the else section (there's probably a better way)
204            try:
205                (
206                    mean_forecast,
207                    lower_bound,
208                    upper_bound,
209                ) = self.obj.forecast_interval(
210                    self.obj.endog, steps=h, alpha=self.alpha_ / 100, **kwargs
211                )
212
213            except Exception as e:
214                try:
215                    forecast_result = self.obj.predict(steps=h)
216                    mean_forecast = forecast_result
217                    (
218                        lower_bound,
219                        upper_bound,
220                    ) = self._compute_confidence_intervals(
221                        forecast_result, alpha=self.alpha_ / 100, **kwargs
222                    )
223
224                except Exception as e:
225                    try:
226                        forecast_result = self.obj.get_forecast(steps=h)
227                        mean_forecast = forecast_result.predicted_mean
228                        lower_bound = forecast_result.conf_int()[:, 0]
229                        upper_bound = forecast_result.conf_int()[:, 1]
230
231                    except Exception as e:
232                        try:
233                            forecast_result = self.obj.forecast(steps=h)
234                            residuals = self.obj.resid
235                            std_errors = np.std(residuals)
236                            mean_forecast = forecast_result
237                            lower_bound = (
238                                forecast_result - pi_multiplier * std_errors
239                            )
240                            upper_bound = (
241                                forecast_result + pi_multiplier * std_errors
242                            )
243
244                        except Exception as e:
245                            try:
246                                mean_forecast = self.obj.forecast(
247                                    steps=h
248                                ).values
249                                forecast_result = self.obj.prediction_intervals(
250                                    steps=h, alpha=self.alpha_ / 100, **kwargs
251                                )
252                                lower_bound = forecast_result["lower"].values
253                                upper_bound = forecast_result["upper"].values
254                            except Exception:
255                                mean_forecast = self.obj.forecast(steps=h)
256                                forecast_result = self.obj.prediction_intervals(
257                                    steps=h, alpha=self.alpha_ / 100, **kwargs
258                                )
259                                lower_bound = forecast_result["lower"]
260                                upper_bound = forecast_result["upper"]
261
262        else:
263            if self.model == "VAR":
264                (
265                    mean_forecast,
266                    lower_bound,
267                    upper_bound,
268                ) = self.obj.forecast_interval(
269                    self.obj.endog, steps=h, alpha=self.alpha_ / 100, **kwargs
270                )
271
272            elif self.model == "VECM":
273                forecast_result = self.obj.predict(steps=h)
274                mean_forecast = forecast_result
275                lower_bound, upper_bound = self._compute_confidence_intervals(
276                    forecast_result, alpha=self.alpha_ / 100, **kwargs
277                )
278
279            elif self.model == "ARIMA":
280                forecast_result = self.obj.get_forecast(steps=h)
281                mean_forecast = forecast_result.predicted_mean
282                lower_bound = forecast_result.conf_int()[:, 0]
283                upper_bound = forecast_result.conf_int()[:, 1]
284
285            elif self.model == "ETS":
286                forecast_result = self.obj.forecast(steps=h)
287                residuals = self.obj.resid
288                std_errors = np.std(residuals)
289                mean_forecast = forecast_result
290                lower_bound = forecast_result - pi_multiplier * std_errors
291                upper_bound = forecast_result + pi_multiplier * std_errors
292
293            elif self.model == "Theta":
294                try:
295                    mean_forecast = self.obj.forecast(steps=h).values
296                    forecast_result = self.obj.prediction_intervals(
297                        steps=h, alpha=self.alpha_ / 100, **kwargs
298                    )
299                    lower_bound = forecast_result["lower"].values
300                    upper_bound = forecast_result["upper"].values
301                except Exception:
302                    mean_forecast = self.obj.forecast(steps=h)
303                    forecast_result = self.obj.prediction_intervals(
304                        steps=h, alpha=self.alpha_ / 100, **kwargs
305                    )
306                    lower_bound = forecast_result["lower"]
307                    upper_bound = forecast_result["upper"]
308
309            else:
310                raise ValueError("model not recognized")
311
312        try:
313            self.mean_ = pd.DataFrame(
314                mean_forecast,
315                columns=self.series_names,
316                index=self.output_dates_,
317            )
318            self.lower_ = pd.DataFrame(
319                lower_bound, columns=self.series_names, index=self.output_dates_
320            )
321            self.upper_ = pd.DataFrame(
322                upper_bound, columns=self.series_names, index=self.output_dates_
323            )
324        except Exception:
325            self.mean_ = pd.Series(
326                mean_forecast, name=self.series_names, index=self.output_dates_
327            )
328            self.lower_ = pd.Series(
329                lower_bound, name=self.series_names, index=self.output_dates_
330            )
331            self.upper_ = pd.Series(
332                upper_bound, name=self.series_names, index=self.output_dates_
333            )
334
335        return DescribeResult(
336            mean=self.mean_, lower=self.lower_, upper=self.upper_
337        )
338
339    def _compute_confidence_intervals(self, forecast_result, alpha):
340        """
341        Compute confidence intervals for VECM forecasts.
342        Uses the covariance of residuals to approximate the confidence intervals.
343        """
344        residuals = self.obj.resid
345        cov_matrix = np.cov(residuals.T)  # Covariance matrix of residuals
346        std_errors = np.sqrt(np.diag(cov_matrix))  # Standard errors
347
348        z_value = norm.ppf(1 - alpha / 2)  # Z-score for the given alpha level
349        lower_bound = forecast_result - z_value * std_errors
350        upper_bound = forecast_result + z_value * std_errors
351
352        return lower_bound, upper_bound
353
354    def score(self, X, training_index, testing_index, scoring=None, **kwargs):
355        """Train on training_index, score on testing_index."""
356
357        assert (
358            bool(set(training_index).intersection(set(testing_index))) == False
359        ), "Non-overlapping 'training_index' and 'testing_index' required"
360
361        # Dimensions
362        try:
363            # multivariate time series
364            n, p = X.shape
365        except:
366            # univariate time series
367            n = X.shape[0]
368            p = 1
369
370        # Training and testing sets
371        if p > 1:
372            X_train = X[training_index, :]
373            X_test = X[testing_index, :]
374        else:
375            X_train = X[training_index]
376            X_test = X[testing_index]
377
378        # Horizon
379        h = len(testing_index)
380        assert (
381            len(training_index) + h
382        ) <= n, "Please check lengths of training and testing windows"
383
384        # Fit and predict
385        self.fit(X_train, **kwargs)
386        preds = self.predict(h=h, **kwargs)
387
388        if scoring is None:
389            scoring = "neg_root_mean_squared_error"
390
391        # check inputs
392        assert scoring in (
393            "explained_variance",
394            "neg_mean_absolute_error",
395            "neg_mean_squared_error",
396            "neg_root_mean_squared_error",
397            "neg_mean_squared_log_error",
398            "neg_median_absolute_error",
399            "r2",
400        ), "'scoring' should be in ('explained_variance', 'neg_mean_absolute_error', \
401                               'neg_mean_squared_error', 'neg_root_mean_squared_error', 'neg_mean_squared_log_error', \
402                               'neg_median_absolute_error', 'r2')"
403
404        scoring_options = {
405            "explained_variance": skm2.explained_variance_score,
406            "neg_mean_absolute_error": skm2.mean_absolute_error,
407            "neg_mean_squared_error": lambda x, y: np.mean((x - y) ** 2),
408            "neg_root_mean_squared_error": lambda x, y: np.sqrt(
409                np.mean((x - y) ** 2)
410            ),
411            "neg_mean_squared_log_error": skm2.mean_squared_log_error,
412            "neg_median_absolute_error": skm2.median_absolute_error,
413            "r2": skm2.r2_score,
414        }
415
416        # if p > 1:
417        #     return tuple(
418        #         [
419        #             scoring_options[scoring](
420        #                 X_test[:, i], preds[:, i]#, **kwargs
421        #             )
422        #             for i in range(p)
423        #         ]
424        #     )
425        # else:
426        return scoring_options[scoring](X_test, preds)
427
428    def plot(self, series=None, type_axis="dates", type_plot="pi"):
429        """Plot time series forecast
430
431        Parameters:
432
433        series: {integer} or {string}
434            series index or name
435
436        """
437
438        assert all(
439            [
440                self.mean_ is not None,
441                self.lower_ is not None,
442                self.upper_ is not None,
443                self.output_dates_ is not None,
444            ]
445        ), "model forecasting must be obtained first (with predict)"
446
447        if series is None:
448            assert (
449                self.n_series == 1
450            ), "please specify series index or name (n_series > 1)"
451            series = 0
452
453        if isinstance(series, str):
454            assert (
455                series in self.series_names
456            ), f"series {series} doesn't exist in the input dataset"
457            series_idx = self.df_.columns.get_loc(series)
458        else:
459            assert isinstance(series, int) and (
460                0 <= series < self.n_series
461            ), f"check series index (< {self.n_series})"
462            series_idx = series
463
464        if isinstance(self.df_, pd.DataFrame):
465            y_all = list(self.df_.iloc[:, series_idx]) + list(
466                self.mean_.iloc[:, series_idx]
467            )
468            y_test = list(self.mean_.iloc[:, series_idx])
469        else:
470            y_all = list(self.df_.values) + list(self.mean_.values)
471            y_test = list(self.mean_.values)
472        n_points_all = len(y_all)
473        n_points_train = self.df_.shape[0]
474
475        if type_axis == "numeric":
476            x_all = [i for i in range(n_points_all)]
477            x_test = [i for i in range(n_points_train, n_points_all)]
478
479        if type_axis == "dates":  # use dates
480            x_all = np.concatenate(
481                (self.input_dates.values, self.output_dates_.values), axis=None
482            )
483            x_test = self.output_dates_.values
484
485        if type_plot == "pi":
486            fig, ax = plt.subplots()
487            ax.plot(x_all, y_all, "-")
488            ax.plot(x_test, y_test, "-", color="orange")
489            try:
490                ax.fill_between(
491                    x_test,
492                    self.lower_.iloc[:, series_idx],
493                    self.upper_.iloc[:, series_idx],
494                    alpha=0.2,
495                    color="orange",
496                )
497            except Exception:
498                ax.fill_between(
499                    x_test,
500                    self.lower_.values,
501                    self.upper_.values,
502                    alpha=0.2,
503                    color="orange",
504                )
505            if self.replications is None:
506                if self.n_series > 1:
507                    plt.title(
508                        f"prediction intervals for {series}",
509                        loc="left",
510                        fontsize=12,
511                        fontweight=0,
512                        color="black",
513                    )
514                else:
515                    plt.title(
516                        f"prediction intervals for input time series",
517                        loc="left",
518                        fontsize=12,
519                        fontweight=0,
520                        color="black",
521                    )
522                plt.show()
523            else:  # self.replications is not None
524                if self.n_series > 1:
525                    plt.title(
526                        f"prediction intervals for {self.replications} simulations of {series}",
527                        loc="left",
528                        fontsize=12,
529                        fontweight=0,
530                        color="black",
531                    )
532                else:
533                    plt.title(
534                        f"prediction intervals for {self.replications} simulations of input time series",
535                        loc="left",
536                        fontsize=12,
537                        fontweight=0,
538                        color="black",
539                    )
540                plt.show()
541
542        if type_plot == "spaghetti":
543            palette = plt.get_cmap("Set1")
544            sims_ix = getsims(self.sims_, series_idx)
545            plt.plot(x_all, y_all, "-")
546            for col_ix in range(
547                sims_ix.shape[1]
548            ):  # avoid this when there are thousands of simulations
549                plt.plot(
550                    x_test,
551                    sims_ix[:, col_ix],
552                    "-",
553                    color=palette(col_ix),
554                    linewidth=1,
555                    alpha=0.9,
556                )
557            plt.plot(x_all, y_all, "-", color="black")
558            plt.plot(x_test, y_test, "-", color="blue")
559            # Add titles
560            if self.n_series > 1:
561                plt.title(
562                    f"{self.replications} simulations of {series}",
563                    loc="left",
564                    fontsize=12,
565                    fontweight=0,
566                    color="black",
567                )
568            else:
569                plt.title(
570                    f"{self.replications} simulations of input time series",
571                    loc="left",
572                    fontsize=12,
573                    fontweight=0,
574                    color="black",
575                )
576            plt.xlabel("Time")
577            plt.ylabel("Values")
578            # Show the graph
579            plt.show()
580
581    def cross_val_score(
582        self,
583        X,
584        scoring="root_mean_squared_error",
585        n_jobs=None,
586        verbose=0,
587        xreg=None,
588        initial_window=5,
589        horizon=3,
590        fixed_window=False,
591        show_progress=True,
592        level=95,
593        **kwargs,
594    ):
595        """Evaluate a score by time series cross-validation.
596
597        Parameters:
598
599            X: {array-like, sparse matrix} of shape (n_samples, n_features)
600                The data to fit.
601
602            scoring: str or a function
603                A str in ('root_mean_squared_error', 'mean_squared_error', 'mean_error',
604                'mean_absolute_error', 'mean_error', 'mean_percentage_error',
605                'mean_absolute_percentage_error',  'winkler_score', 'coverage')
606                Or a function defined as 'coverage' and 'winkler_score' in `utils.timeseries`
607
608            n_jobs: int, default=None
609                Number of jobs to run in parallel.
610
611            verbose: int, default=0
612                The verbosity level.
613
614            xreg: array-like, optional (default=None)
615                Additional (external) regressors to be passed to `fit`
616                xreg must be in 'increasing' order (most recent observations last)
617
618            initial_window: int
619                initial number of consecutive values in each training set sample
620
621            horizon: int
622                number of consecutive values in test set sample
623
624            fixed_window: boolean
625                if False, all training samples start at index 0, and the training
626                window's size is increasing.
627                if True, the training window's size is fixed, and the window is
628                rolling forward
629
630            show_progress: boolean
631                if True, a progress bar is printed
632
633            **kwargs: dict
634                additional parameters to be passed to `fit` and `predict`
635
636        Returns:
637
638            A tuple: descriptive statistics or errors and raw errors
639
640        """
641        tscv = TimeSeriesSplit()
642
643        tscv_obj = tscv.split(
644            X,
645            initial_window=initial_window,
646            horizon=horizon,
647            fixed_window=fixed_window,
648        )
649
650        if isinstance(scoring, str):
651            assert scoring in (
652                "root_mean_squared_error",
653                "mean_squared_error",
654                "mean_error",
655                "mean_absolute_error",
656                "mean_percentage_error",
657                "mean_absolute_percentage_error",
658                "winkler_score",
659                "coverage",
660            ), "must have scoring in ('root_mean_squared_error', 'mean_squared_error', 'mean_error', 'mean_absolute_error', 'mean_error', 'mean_percentage_error', 'mean_absolute_percentage_error',  'winkler_score', 'coverage')"
661
662            def err_func(X_test, X_pred, scoring):
663                if (self.replications is not None) or (
664                    self.type_pi == "gaussian"
665                ):  # probabilistic
666                    if scoring == "winkler_score":
667                        return winkler_score(X_pred, X_test, level=level)
668                    elif scoring == "coverage":
669                        return coverage(X_pred, X_test, level=level)
670                    else:
671                        return mean_errors(
672                            pred=X_pred.mean, actual=X_test, scoring=scoring
673                        )
674                else:  # not probabilistic
675                    return mean_errors(
676                        pred=X_pred, actual=X_test, scoring=scoring
677                    )
678
679        else:  # isinstance(scoring, str) = False
680            err_func = scoring
681
682        errors = []
683
684        train_indices = []
685
686        test_indices = []
687
688        for train_index, test_index in tscv_obj:
689            train_indices.append(train_index)
690            test_indices.append(test_index)
691
692        if show_progress is True:
693            iterator = tqdm(
694                zip(train_indices, test_indices), total=len(train_indices)
695            )
696        else:
697            iterator = zip(train_indices, test_indices)
698
699        for train_index, test_index in iterator:
700            if verbose == 1:
701                print(f"TRAIN: {train_index}")
702                print(f"TEST: {test_index}")
703
704            if isinstance(X, pd.DataFrame):
705                self.fit(X.iloc[train_index, :], xreg=xreg, **kwargs)
706                X_test = X.iloc[test_index, :]
707            else:
708                self.fit(X[train_index, :], xreg=xreg, **kwargs)
709                X_test = X[test_index, :]
710            X_pred = self.predict(h=int(len(test_index)), level=level, **kwargs)
711
712            errors.append(err_func(X_test, X_pred, scoring))
713
714        res = np.asarray(errors)
715
716        return res, describe(res)

Time series with statistical models (statsmodels), mostly for benchmarks

Parameters:

model: type of model: str.
    currently, 'VAR', 'VECM', 'ARIMA', 'ETS', 'Theta'
    Default is None

obj: object
    A time series model from statsmodels

Attributes:

df_: data frame
    the input data frame, in case a data.frame is provided to `fit`

level_: int
    level of confidence for prediction intervals (default is 95)

Examples: See examples/classical_mts_timeseries.py

def fit(self, X, **kwargs):
 98    def fit(self, X, **kwargs):
 99        """Fit ClassicalMTS model to training data X, with optional regressors xreg
100
101        Parameters:
102
103        X: {array-like}, shape = [n_samples, n_features]
104            Training time series, where n_samples is the number
105            of samples and n_features is the number of features;
106            X must be in increasing order (most recent observations last)
107
108        **kwargs: for now, additional parameters to be passed to for kernel density estimation, when needed (see sklearn.neighbors.KernelDensity)
109
110        Returns:
111
112        self: object
113        """
114
115        try:
116            self.n_series = X.shape[1]
117        except Exception:
118            self.n_series = 1
119
120        if (isinstance(X, pd.DataFrame) is False) and isinstance(
121            X, pd.Series
122        ) is False:  # input data set is a numpy array
123            X = pd.DataFrame(X)
124            if self.n_series > 1:
125                self.series_names = [
126                    "series" + str(i) for i in range(X.shape[1])
127                ]
128            else:
129                self.series_names = "series0"
130
131        else:  # input data set is a DataFrame or Series with column names
132            X_index = None
133            if X.index is not None and len(X.shape) > 1:
134                X_index = X.index
135                X = copy.deepcopy(mo.convert_df_to_numeric(X))
136            if X_index is not None:
137                try:
138                    X.index = X_index
139                except Exception:
140                    pass
141            if isinstance(X, pd.DataFrame):
142                self.series_names = X.columns.tolist()
143            else:
144                self.series_names = X.name
145
146        if isinstance(X, pd.DataFrame) or isinstance(X, pd.Series):
147            self.df_ = X
148            X = X.values
149            self.df_.columns = self.series_names
150            self.input_dates = ts.compute_input_dates(self.df_)
151        else:
152            self.df_ = pd.DataFrame(X, columns=self.series_names)
153
154        if self.model == "Theta":
155            try:
156                self.obj = self.obj(self.df_, **kwargs).fit()
157            except Exception as e:
158                self.obj = self.obj(self.df_.values, **kwargs).fit()
159            self.residuals_ = None
160        else:
161            self.obj = self.obj(X, **kwargs).fit()
162            try:
163                self.residuals_ = self.obj.resid
164            except Exception as e:  # Theta
165                self.residuals_ = None
166
167        return self

Fit ClassicalMTS model to training data X, with optional regressors xreg

Parameters:

X: {array-like}, shape = [n_samples, n_features] Training time series, where n_samples is the number of samples and n_features is the number of features; X must be in increasing order (most recent observations last)

**kwargs: for now, additional parameters to be passed to for kernel density estimation, when needed (see sklearn.neighbors.KernelDensity)

Returns:

self: object

def predict(self, h=5, level=95, **kwargs):
169    def predict(self, h=5, level=95, **kwargs):
170        """Forecast all the time series, h steps ahead
171
172        Parameters:
173
174        h: {integer}
175            Forecasting horizon
176
177        **kwargs: additional parameters to be passed to
178                self.cook_test_set
179
180        Returns:
181
182        model predictions for horizon = h: {array-like}
183
184        """
185
186        self.output_dates_, frequency = ts.compute_output_dates(self.df_, h)
187        self.level_ = level
188        self.lower_ = None  # do not remove (/!\)
189        self.upper_ = None  # do not remove (/!\)
190        self.sims_ = None  # do not remove (/!\)
191        self.level_ = level
192        self.alpha_ = 100 - level
193
194        pi_multiplier = norm.ppf(1 - self.alpha_ / 200)
195
196        # Named tuple for forecast results
197        DescribeResult = namedtuple(
198            "DescribeResult", ("mean", "lower", "upper")
199        )
200
201        if (
202            self.obj is not None
203        ):  # try all the special cases of the else section (there's probably a better way)
204            try:
205                (
206                    mean_forecast,
207                    lower_bound,
208                    upper_bound,
209                ) = self.obj.forecast_interval(
210                    self.obj.endog, steps=h, alpha=self.alpha_ / 100, **kwargs
211                )
212
213            except Exception as e:
214                try:
215                    forecast_result = self.obj.predict(steps=h)
216                    mean_forecast = forecast_result
217                    (
218                        lower_bound,
219                        upper_bound,
220                    ) = self._compute_confidence_intervals(
221                        forecast_result, alpha=self.alpha_ / 100, **kwargs
222                    )
223
224                except Exception as e:
225                    try:
226                        forecast_result = self.obj.get_forecast(steps=h)
227                        mean_forecast = forecast_result.predicted_mean
228                        lower_bound = forecast_result.conf_int()[:, 0]
229                        upper_bound = forecast_result.conf_int()[:, 1]
230
231                    except Exception as e:
232                        try:
233                            forecast_result = self.obj.forecast(steps=h)
234                            residuals = self.obj.resid
235                            std_errors = np.std(residuals)
236                            mean_forecast = forecast_result
237                            lower_bound = (
238                                forecast_result - pi_multiplier * std_errors
239                            )
240                            upper_bound = (
241                                forecast_result + pi_multiplier * std_errors
242                            )
243
244                        except Exception as e:
245                            try:
246                                mean_forecast = self.obj.forecast(
247                                    steps=h
248                                ).values
249                                forecast_result = self.obj.prediction_intervals(
250                                    steps=h, alpha=self.alpha_ / 100, **kwargs
251                                )
252                                lower_bound = forecast_result["lower"].values
253                                upper_bound = forecast_result["upper"].values
254                            except Exception:
255                                mean_forecast = self.obj.forecast(steps=h)
256                                forecast_result = self.obj.prediction_intervals(
257                                    steps=h, alpha=self.alpha_ / 100, **kwargs
258                                )
259                                lower_bound = forecast_result["lower"]
260                                upper_bound = forecast_result["upper"]
261
262        else:
263            if self.model == "VAR":
264                (
265                    mean_forecast,
266                    lower_bound,
267                    upper_bound,
268                ) = self.obj.forecast_interval(
269                    self.obj.endog, steps=h, alpha=self.alpha_ / 100, **kwargs
270                )
271
272            elif self.model == "VECM":
273                forecast_result = self.obj.predict(steps=h)
274                mean_forecast = forecast_result
275                lower_bound, upper_bound = self._compute_confidence_intervals(
276                    forecast_result, alpha=self.alpha_ / 100, **kwargs
277                )
278
279            elif self.model == "ARIMA":
280                forecast_result = self.obj.get_forecast(steps=h)
281                mean_forecast = forecast_result.predicted_mean
282                lower_bound = forecast_result.conf_int()[:, 0]
283                upper_bound = forecast_result.conf_int()[:, 1]
284
285            elif self.model == "ETS":
286                forecast_result = self.obj.forecast(steps=h)
287                residuals = self.obj.resid
288                std_errors = np.std(residuals)
289                mean_forecast = forecast_result
290                lower_bound = forecast_result - pi_multiplier * std_errors
291                upper_bound = forecast_result + pi_multiplier * std_errors
292
293            elif self.model == "Theta":
294                try:
295                    mean_forecast = self.obj.forecast(steps=h).values
296                    forecast_result = self.obj.prediction_intervals(
297                        steps=h, alpha=self.alpha_ / 100, **kwargs
298                    )
299                    lower_bound = forecast_result["lower"].values
300                    upper_bound = forecast_result["upper"].values
301                except Exception:
302                    mean_forecast = self.obj.forecast(steps=h)
303                    forecast_result = self.obj.prediction_intervals(
304                        steps=h, alpha=self.alpha_ / 100, **kwargs
305                    )
306                    lower_bound = forecast_result["lower"]
307                    upper_bound = forecast_result["upper"]
308
309            else:
310                raise ValueError("model not recognized")
311
312        try:
313            self.mean_ = pd.DataFrame(
314                mean_forecast,
315                columns=self.series_names,
316                index=self.output_dates_,
317            )
318            self.lower_ = pd.DataFrame(
319                lower_bound, columns=self.series_names, index=self.output_dates_
320            )
321            self.upper_ = pd.DataFrame(
322                upper_bound, columns=self.series_names, index=self.output_dates_
323            )
324        except Exception:
325            self.mean_ = pd.Series(
326                mean_forecast, name=self.series_names, index=self.output_dates_
327            )
328            self.lower_ = pd.Series(
329                lower_bound, name=self.series_names, index=self.output_dates_
330            )
331            self.upper_ = pd.Series(
332                upper_bound, name=self.series_names, index=self.output_dates_
333            )
334
335        return DescribeResult(
336            mean=self.mean_, lower=self.lower_, upper=self.upper_
337        )

Forecast all the time series, h steps ahead

Parameters:

h: {integer} Forecasting horizon

**kwargs: additional parameters to be passed to self.cook_test_set

Returns:

model predictions for horizon = h: {array-like}

def score(self, X, training_index, testing_index, scoring=None, **kwargs):
354    def score(self, X, training_index, testing_index, scoring=None, **kwargs):
355        """Train on training_index, score on testing_index."""
356
357        assert (
358            bool(set(training_index).intersection(set(testing_index))) == False
359        ), "Non-overlapping 'training_index' and 'testing_index' required"
360
361        # Dimensions
362        try:
363            # multivariate time series
364            n, p = X.shape
365        except:
366            # univariate time series
367            n = X.shape[0]
368            p = 1
369
370        # Training and testing sets
371        if p > 1:
372            X_train = X[training_index, :]
373            X_test = X[testing_index, :]
374        else:
375            X_train = X[training_index]
376            X_test = X[testing_index]
377
378        # Horizon
379        h = len(testing_index)
380        assert (
381            len(training_index) + h
382        ) <= n, "Please check lengths of training and testing windows"
383
384        # Fit and predict
385        self.fit(X_train, **kwargs)
386        preds = self.predict(h=h, **kwargs)
387
388        if scoring is None:
389            scoring = "neg_root_mean_squared_error"
390
391        # check inputs
392        assert scoring in (
393            "explained_variance",
394            "neg_mean_absolute_error",
395            "neg_mean_squared_error",
396            "neg_root_mean_squared_error",
397            "neg_mean_squared_log_error",
398            "neg_median_absolute_error",
399            "r2",
400        ), "'scoring' should be in ('explained_variance', 'neg_mean_absolute_error', \
401                               'neg_mean_squared_error', 'neg_root_mean_squared_error', 'neg_mean_squared_log_error', \
402                               'neg_median_absolute_error', 'r2')"
403
404        scoring_options = {
405            "explained_variance": skm2.explained_variance_score,
406            "neg_mean_absolute_error": skm2.mean_absolute_error,
407            "neg_mean_squared_error": lambda x, y: np.mean((x - y) ** 2),
408            "neg_root_mean_squared_error": lambda x, y: np.sqrt(
409                np.mean((x - y) ** 2)
410            ),
411            "neg_mean_squared_log_error": skm2.mean_squared_log_error,
412            "neg_median_absolute_error": skm2.median_absolute_error,
413            "r2": skm2.r2_score,
414        }
415
416        # if p > 1:
417        #     return tuple(
418        #         [
419        #             scoring_options[scoring](
420        #                 X_test[:, i], preds[:, i]#, **kwargs
421        #             )
422        #             for i in range(p)
423        #         ]
424        #     )
425        # else:
426        return scoring_options[scoring](X_test, preds)

Train on training_index, score on testing_index.

class CustomClassifier(nnetsauce.custom.custom.Custom, sklearn.base.ClassifierMixin):
 16class CustomClassifier(Custom, ClassifierMixin):
 17    """Custom Classification model
 18
 19    Attributes:
 20
 21        obj: object
 22            any object containing a method fit (obj.fit()) and a method predict
 23            (obj.predict())
 24
 25        n_hidden_features: int
 26            number of nodes in the hidden layer
 27
 28        activation_name: str
 29            activation function: 'relu', 'tanh', 'sigmoid', 'prelu' or 'elu'
 30
 31        a: float
 32            hyperparameter for 'prelu' or 'elu' activation function
 33
 34        nodes_sim: str
 35            type of simulation for the nodes: 'sobol', 'hammersley', 'halton',
 36            'uniform'
 37
 38        bias: boolean
 39            indicates if the hidden layer contains a bias term (True) or not
 40            (False)
 41
 42        dropout: float
 43            regularization parameter; (random) percentage of nodes dropped out
 44            of the training
 45
 46        direct_link: boolean
 47            indicates if the original predictors are included (True) in model''s
 48            fitting or not (False)
 49
 50        n_clusters: int
 51            number of clusters for 'kmeans' or 'gmm' clustering (could be 0:
 52                no clustering)
 53
 54        cluster_encode: bool
 55            defines how the variable containing clusters is treated (default is one-hot)
 56            if `False`, then labels are used, without one-hot encoding
 57
 58        type_clust: str
 59            type of clustering method: currently k-means ('kmeans') or Gaussian
 60            Mixture Model ('gmm')
 61
 62        type_scaling: a tuple of 3 strings
 63            scaling methods for inputs, hidden layer, and clustering respectively
 64            (and when relevant).
 65            Currently available: standardization ('std') or MinMax scaling ('minmax')
 66
 67        col_sample: float
 68            percentage of covariates randomly chosen for training
 69
 70        row_sample: float
 71            percentage of rows chosen for training, by stratified bootstrapping
 72
 73        cv_calibration: int, cross-validation generator, or iterable, default=2
 74            Determines the cross-validation splitting strategy. Same as
 75            `sklearn.calibration.CalibratedClassifierCV`
 76
 77        calibration_method: str
 78            {‘sigmoid’, ‘isotonic’}, default=’sigmoid’
 79            The method to use for calibration. Same as
 80            `sklearn.calibration.CalibratedClassifierCV`
 81
 82        seed: int
 83            reproducibility seed for nodes_sim=='uniform'
 84
 85        backend: str
 86            "cpu" or "gpu" or "tpu"
 87
 88    Examples:
 89
 90    Note: it's better to use the `DeepClassifier` or `LazyDeepClassifier` classes directly
 91
 92    ```python
 93    import nnetsauce as ns
 94    from sklearn.ensemble import RandomForestClassifier
 95    from sklearn.model_selection import train_test_split
 96    from sklearn.datasets import load_digits
 97    from time import time
 98
 99    digits = load_digits()
100    X = digits.data
101    y = digits.target
102    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,
103                                                        random_state=123)
104
105    # layer 1 (base layer) ----
106    layer1_regr = RandomForestClassifier(n_estimators=10, random_state=123)
107
108    start = time()
109
110    layer1_regr.fit(X_train, y_train)
111
112    # Accuracy in layer 1
113    print(layer1_regr.score(X_test, y_test))
114
115    # layer 2 using layer 1 ----
116    layer2_regr = ns.CustomClassifier(obj = layer1_regr, n_hidden_features=5,
117                            direct_link=True, bias=True,
118                            nodes_sim='uniform', activation_name='relu',
119                            n_clusters=2, seed=123)
120    layer2_regr.fit(X_train, y_train)
121
122    # Accuracy in layer 2
123    print(layer2_regr.score(X_test, y_test))
124
125    # layer 3 using layer 2 ----
126    layer3_regr = ns.CustomClassifier(obj = layer2_regr, n_hidden_features=10,
127                            direct_link=True, bias=True, dropout=0.7,
128                            nodes_sim='uniform', activation_name='relu',
129                            n_clusters=2, seed=123)
130    layer3_regr.fit(X_train, y_train)
131
132    # Accuracy in layer 3
133    print(layer3_regr.score(X_test, y_test))
134
135    print(f"Elapsed {time() - start}")
136    ```
137
138    """
139
140    # construct the object -----
141    _estimator_type = "classifier"
142
143    def __init__(
144        self,
145        obj,
146        n_hidden_features=5,
147        activation_name="relu",
148        a=0.01,
149        nodes_sim="sobol",
150        bias=True,
151        dropout=0,
152        direct_link=True,
153        n_clusters=2,
154        cluster_encode=True,
155        type_clust="kmeans",
156        type_scaling=("std", "std", "std"),
157        col_sample=1,
158        row_sample=1,
159        cv_calibration=2,
160        calibration_method="sigmoid",
161        seed=123,
162        backend="cpu",
163    ):
164        super().__init__(
165            obj=obj,
166            n_hidden_features=n_hidden_features,
167            activation_name=activation_name,
168            a=a,
169            nodes_sim=nodes_sim,
170            bias=bias,
171            dropout=dropout,
172            direct_link=direct_link,
173            n_clusters=n_clusters,
174            cluster_encode=cluster_encode,
175            type_clust=type_clust,
176            type_scaling=type_scaling,
177            col_sample=col_sample,
178            row_sample=row_sample,
179            seed=seed,
180            backend=backend,
181        )
182        self.coef_ = None
183        self.intercept_ = None
184        self.type_fit = "classification"
185        self.cv_calibration = cv_calibration
186        self.calibration_method = calibration_method
187
188    def __sklearn_clone__(self):
189        """Create a clone of the estimator.
190
191        This is required for scikit-learn's calibration system to work properly.
192        """
193        # Create a new instance with the same parameters
194        clone = CustomClassifier(
195            obj=self.obj,
196            n_hidden_features=self.n_hidden_features,
197            activation_name=self.activation_name,
198            a=self.a,
199            nodes_sim=self.nodes_sim,
200            bias=self.bias,
201            dropout=self.dropout,
202            direct_link=self.direct_link,
203            n_clusters=self.n_clusters,
204            cluster_encode=self.cluster_encode,
205            type_clust=self.type_clust,
206            type_scaling=self.type_scaling,
207            col_sample=self.col_sample,
208            row_sample=self.row_sample,
209            cv_calibration=self.cv_calibration,
210            calibration_method=self.calibration_method,
211            seed=self.seed,
212            backend=self.backend,
213        )
214        return clone
215
216    def fit(self, X, y, sample_weight=None, **kwargs):
217        """Fit custom model to training data (X, y).
218
219        Parameters:
220
221            X: {array-like}, shape = [n_samples, n_features]
222                Training vectors, where n_samples is the number
223                of samples and n_features is the number of features.
224
225            y: array-like, shape = [n_samples]
226                Target values.
227
228            sample_weight: array-like, shape = [n_samples]
229                Sample weights.
230
231            **kwargs: additional parameters to be passed to
232                        self.cook_training_set or self.obj.fit
233
234        Returns:
235
236            self: object
237        """
238
239        if len(X.shape) == 1:
240            if isinstance(X, pd.DataFrame):
241                X = pd.DataFrame(X.values.reshape(1, -1), columns=X.columns)
242            else:
243                X = X.reshape(1, -1)
244
245        output_y, scaled_Z = self.cook_training_set(y=y, X=X, **kwargs)
246        self.classes_ = np.unique(y)
247        self.n_classes_ = len(self.classes_)  # for compatibility with sklearn
248
249        # Wrap in CalibratedClassifierCV if needed
250        if self.cv_calibration is not None:
251            self.obj = CalibratedClassifierCV(
252                self.obj, cv=self.cv_calibration, method=self.calibration_method
253            )
254
255        # if sample_weights, else: (must use self.row_index)
256        if sample_weight is not None:
257            self.obj.fit(
258                scaled_Z,
259                output_y,
260                sample_weight=sample_weight[self.index_row_].ravel(),
261                **kwargs
262            )
263            return self
264
265        # if sample_weight is None:
266        self.obj.fit(scaled_Z, output_y, **kwargs)
267        self.classes_ = np.unique(y)  # for compatibility with sklearn
268        self.n_classes_ = len(self.classes_)  # for compatibility with sklearn
269
270        if hasattr(self.obj, "coef_"):
271            self.coef_ = self.obj.coef_
272
273        if hasattr(self.obj, "intercept_"):
274            self.intercept_ = self.obj.intercept_
275
276        return self
277
278    def partial_fit(self, X, y, sample_weight=None, **kwargs):
279        """Partial fit custom model to training data (X, y).
280
281        Parameters:
282
283            X: {array-like}, shape = [n_samples, n_features]
284                Subset of training vectors, where n_samples is the number
285                of samples and n_features is the number of features.
286
287            y: array-like, shape = [n_samples]
288                Subset of target values.
289
290            sample_weight: array-like, shape = [n_samples]
291                Sample weights.
292
293            **kwargs: additional parameters to be passed to
294                        self.cook_training_set or self.obj.fit
295
296        Returns:
297
298            self: object
299        """
300
301        if len(X.shape) == 1:
302            if isinstance(X, pd.DataFrame):
303                X = pd.DataFrame(X.values.reshape(1, -1), columns=X.columns)
304            else:
305                X = X.reshape(1, -1)
306            y = np.array([y], dtype=int)
307
308        output_y, scaled_Z = self.cook_training_set(y=y, X=X, **kwargs)
309        self.n_classes_ = len(np.unique(y))  # for compatibility with sklearn
310
311        # if sample_weights, else: (must use self.row_index)
312        if sample_weight is not None:
313            try:
314                self.obj.partial_fit(
315                    scaled_Z,
316                    output_y,
317                    sample_weight=sample_weight[self.index_row_].ravel(),
318                    # **kwargs
319                )
320            except:
321                NotImplementedError
322
323            return self
324
325        # if sample_weight is None:
326        # try:
327        self.obj.partial_fit(scaled_Z, output_y)
328        # except:
329        #    raise NotImplementedError
330
331        self.classes_ = np.unique(y)  # for compatibility with sklearn
332        self.n_classes_ = len(self.classes_)  # for compatibility with sklearn
333
334        return self
335
336    def predict(self, X, **kwargs):
337        """Predict test data X.
338
339        Parameters:
340
341            X: {array-like}, shape = [n_samples, n_features]
342                Training vectors, where n_samples is the number
343                of samples and n_features is the number of features.
344
345            **kwargs: additional parameters to be passed to
346                    self.cook_test_set
347
348        Returns:
349
350            model predictions: {array-like}
351        """
352
353        if len(X.shape) == 1:
354            n_features = X.shape[0]
355            new_X = mo.rbind(
356                X.reshape(1, n_features),
357                np.ones(n_features).reshape(1, n_features),
358            )
359
360            return (
361                self.obj.predict(self.cook_test_set(new_X, **kwargs), **kwargs)
362            )[0]
363
364        return self.obj.predict(self.cook_test_set(X, **kwargs), **kwargs)
365
366    def predict_proba(self, X, **kwargs):
367        """Predict probabilities for test data X.
368
369        Args:
370
371            X: {array-like}, shape = [n_samples, n_features]
372                Training vectors, where n_samples is the number
373                of samples and n_features is the number of features.
374
375            **kwargs: additional parameters to be passed to
376                    self.cook_test_set
377
378        Returns:
379
380            probability estimates for test data: {array-like}
381        """
382
383        if len(X.shape) == 1:
384            n_features = X.shape[0]
385            new_X = mo.rbind(
386                X.reshape(1, n_features),
387                np.ones(n_features).reshape(1, n_features),
388            )
389            return (
390                self.obj.predict_proba(
391                    self.cook_test_set(new_X, **kwargs), **kwargs
392                )
393            )[0]
394        return self.obj.predict_proba(self.cook_test_set(X, **kwargs), **kwargs)
395
396    def decision_function(self, X, **kwargs):
397        """Compute the decision function of X.
398
399        Parameters:
400            X: {array-like}, shape = [n_samples, n_features]
401                Samples to compute decision function for.
402
403            **kwargs: additional parameters to be passed to
404                    self.cook_test_set
405
406        Returns:
407            array-like of shape (n_samples,) or (n_samples, n_classes)
408            Decision function of the input samples. The order of outputs is the same
409            as that of the classes passed to fit.
410        """
411        if not hasattr(self.obj, "decision_function"):
412            # If base classifier doesn't have decision_function, use predict_proba
413            proba = self.predict_proba(X, **kwargs)
414            if proba.shape[1] == 2:
415                return proba[:, 1]  # For binary classification
416            return proba  # For multiclass
417
418        if len(X.shape) == 1:
419            n_features = X.shape[0]
420            new_X = mo.rbind(
421                X.reshape(1, n_features),
422                np.ones(n_features).reshape(1, n_features),
423            )
424
425            return (
426                self.obj.decision_function(
427                    self.cook_test_set(new_X, **kwargs), **kwargs
428                )
429            )[0]
430
431        return self.obj.decision_function(
432            self.cook_test_set(X, **kwargs), **kwargs
433        )
434
435    def score(self, X, y, scoring=None):
436        """Scoring function for classification.
437
438        Args:
439
440            X: {array-like}, shape = [n_samples, n_features]
441                Training vectors, where n_samples is the number
442                of samples and n_features is the number of features.
443
444            y: array-like, shape = [n_samples]
445                Target values.
446
447            scoring: str
448                scoring method (default is accuracy)
449
450        Returns:
451
452            score: float
453        """
454
455        if scoring is None:
456            scoring = "accuracy"
457
458        if scoring == "accuracy":
459            return skm2.accuracy_score(y, self.predict(X))
460
461        if scoring == "f1":
462            return skm2.f1_score(y, self.predict(X))
463
464        if scoring == "precision":
465            return skm2.precision_score(y, self.predict(X))
466
467        if scoring == "recall":
468            return skm2.recall_score(y, self.predict(X))
469
470        if scoring == "roc_auc":
471            return skm2.roc_auc_score(y, self.predict(X))
472
473        if scoring == "log_loss":
474            return skm2.log_loss(y, self.predict_proba(X))
475
476        if scoring == "balanced_accuracy":
477            return skm2.balanced_accuracy_score(y, self.predict(X))
478
479        if scoring == "average_precision":
480            return skm2.average_precision_score(y, self.predict(X))
481
482        if scoring == "neg_brier_score":
483            return -skm2.brier_score_loss(y, self.predict_proba(X))
484
485        if scoring == "neg_log_loss":
486            return -skm2.log_loss(y, self.predict_proba(X))
487
488    @property
489    def _estimator_type(self):
490        return "classifier"

Custom Classification model

Attributes:

obj: object
    any object containing a method fit (obj.fit()) and a method predict
    (obj.predict())

n_hidden_features: int
    number of nodes in the hidden layer

activation_name: str
    activation function: 'relu', 'tanh', 'sigmoid', 'prelu' or 'elu'

a: float
    hyperparameter for 'prelu' or 'elu' activation function

nodes_sim: str
    type of simulation for the nodes: 'sobol', 'hammersley', 'halton',
    'uniform'

bias: boolean
    indicates if the hidden layer contains a bias term (True) or not
    (False)

dropout: float
    regularization parameter; (random) percentage of nodes dropped out
    of the training

direct_link: boolean
    indicates if the original predictors are included (True) in model''s
    fitting or not (False)

n_clusters: int
    number of clusters for 'kmeans' or 'gmm' clustering (could be 0:
        no clustering)

cluster_encode: bool
    defines how the variable containing clusters is treated (default is one-hot)
    if `False`, then labels are used, without one-hot encoding

type_clust: str
    type of clustering method: currently k-means ('kmeans') or Gaussian
    Mixture Model ('gmm')

type_scaling: a tuple of 3 strings
    scaling methods for inputs, hidden layer, and clustering respectively
    (and when relevant).
    Currently available: standardization ('std') or MinMax scaling ('minmax')

col_sample: float
    percentage of covariates randomly chosen for training

row_sample: float
    percentage of rows chosen for training, by stratified bootstrapping

cv_calibration: int, cross-validation generator, or iterable, default=2
    Determines the cross-validation splitting strategy. Same as
    `sklearn.calibration.CalibratedClassifierCV`

calibration_method: str
    {‘sigmoid’, ‘isotonic’}, default=’sigmoid’
    The method to use for calibration. Same as
    `sklearn.calibration.CalibratedClassifierCV`

seed: int
    reproducibility seed for nodes_sim=='uniform'

backend: str
    "cpu" or "gpu" or "tpu"

Examples:

Note: it's better to use the DeepClassifier or LazyDeepClassifier classes directly

import nnetsauce as ns
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.datasets import load_digits
from time import time

digits = load_digits()
X = digits.data
y = digits.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2,
                                                    random_state=123)

# layer 1 (base layer) ----
layer1_regr = RandomForestClassifier(n_estimators=10, random_state=123)

start = time()

layer1_regr.fit(X_train, y_train)

# Accuracy in layer 1
print(layer1_regr.score(X_test, y_test))

# layer 2 using layer 1 ----
layer2_regr = ns.CustomClassifier(obj = layer1_regr, n_hidden_features=5,
                        direct_link=True, bias=True,
                        nodes_sim='uniform', activation_name='relu',
                        n_clusters=2, seed=123)
layer2_regr.fit(X_train, y_train)

# Accuracy in layer 2
print(layer2_regr.score(X_test, y_test))

# layer 3 using layer 2 ----
layer3_regr = ns.CustomClassifier(obj = layer2_regr, n_hidden_features=10,
                        direct_link=True, bias=True, dropout=0.7,
                        nodes_sim='uniform', activation_name='relu',
                        n_clusters=2, seed=123)
layer3_regr.fit(X_train, y_train)

# Accuracy in layer 3
print(layer3_regr.score(X_test, y_test))

print(f"Elapsed {time() - start}")
def fit(self, X, y, sample_weight=None, **kwargs):
216    def fit(self, X, y, sample_weight=None, **kwargs):
217        """Fit custom model to training data (X, y).
218
219        Parameters:
220
221            X: {array-like}, shape = [n_samples, n_features]
222                Training vectors, where n_samples is the number
223                of samples and n_features is the number of features.
224
225            y: array-like, shape = [n_samples]
226                Target values.
227
228            sample_weight: array-like, shape = [n_samples]
229                Sample weights.
230
231            **kwargs: additional parameters to be passed to
232                        self.cook_training_set or self.obj.fit
233
234        Returns:
235
236            self: object
237        """
238
239        if len(X.shape) == 1:
240            if isinstance(X, pd.DataFrame):
241                X = pd.DataFrame(X.values.reshape(1, -1), columns=X.columns)
242            else:
243                X = X.reshape(1, -1)
244
245        output_y, scaled_Z = self.cook_training_set(y=y, X=X, **kwargs)
246        self.classes_ = np.unique(y)
247        self.n_classes_ = len(self.classes_)  # for compatibility with sklearn
248
249        # Wrap in CalibratedClassifierCV if needed
250        if self.cv_calibration is not None:
251            self.obj = CalibratedClassifierCV(
252                self.obj, cv=self.cv_calibration, method=self.calibration_method
253            )
254
255        # if sample_weights, else: (must use self.row_index)
256        if sample_weight is not None:
257            self.obj.fit(
258                scaled_Z,
259                output_y,
260                sample_weight=sample_weight[self.index_row_].ravel(),
261                **kwargs
262            )
263            return self
264
265        # if sample_weight is None:
266        self.obj.fit(scaled_Z, output_y, **kwargs)
267        self.classes_ = np.unique(y)  # for compatibility with sklearn
268        self.n_classes_ = len(self.classes_)  # for compatibility with sklearn
269
270        if hasattr(self.obj, "coef_"):
271            self.coef_ = self.obj.coef_
272
273        if hasattr(self.obj, "intercept_"):
274            self.intercept_ = self.obj.intercept_
275
276        return self

Fit custom model to training data (X, y).

Parameters:

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features.

y: array-like, shape = [n_samples]
    Target values.

sample_weight: array-like, shape = [n_samples]
    Sample weights.

**kwargs: additional parameters to be passed to
            self.cook_training_set or self.obj.fit

Returns:

self: object
def predict(self, X, **kwargs):
336    def predict(self, X, **kwargs):
337        """Predict test data X.
338
339        Parameters:
340
341            X: {array-like}, shape = [n_samples, n_features]
342                Training vectors, where n_samples is the number
343                of samples and n_features is the number of features.
344
345            **kwargs: additional parameters to be passed to
346                    self.cook_test_set
347
348        Returns:
349
350            model predictions: {array-like}
351        """
352
353        if len(X.shape) == 1:
354            n_features = X.shape[0]
355            new_X = mo.rbind(
356                X.reshape(1, n_features),
357                np.ones(n_features).reshape(1, n_features),
358            )
359
360            return (
361                self.obj.predict(self.cook_test_set(new_X, **kwargs), **kwargs)
362            )[0]
363
364        return self.obj.predict(self.cook_test_set(X, **kwargs), **kwargs)

Predict test data X.

Parameters:

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features.

**kwargs: additional parameters to be passed to
        self.cook_test_set

Returns:

model predictions: {array-like}
def predict_proba(self, X, **kwargs):
366    def predict_proba(self, X, **kwargs):
367        """Predict probabilities for test data X.
368
369        Args:
370
371            X: {array-like}, shape = [n_samples, n_features]
372                Training vectors, where n_samples is the number
373                of samples and n_features is the number of features.
374
375            **kwargs: additional parameters to be passed to
376                    self.cook_test_set
377
378        Returns:
379
380            probability estimates for test data: {array-like}
381        """
382
383        if len(X.shape) == 1:
384            n_features = X.shape[0]
385            new_X = mo.rbind(
386                X.reshape(1, n_features),
387                np.ones(n_features).reshape(1, n_features),
388            )
389            return (
390                self.obj.predict_proba(
391                    self.cook_test_set(new_X, **kwargs), **kwargs
392                )
393            )[0]
394        return self.obj.predict_proba(self.cook_test_set(X, **kwargs), **kwargs)

Predict probabilities for test data X.

Args:

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features.

**kwargs: additional parameters to be passed to
        self.cook_test_set

Returns:

probability estimates for test data: {array-like}
def score(self, X, y, scoring=None):
435    def score(self, X, y, scoring=None):
436        """Scoring function for classification.
437
438        Args:
439
440            X: {array-like}, shape = [n_samples, n_features]
441                Training vectors, where n_samples is the number
442                of samples and n_features is the number of features.
443
444            y: array-like, shape = [n_samples]
445                Target values.
446
447            scoring: str
448                scoring method (default is accuracy)
449
450        Returns:
451
452            score: float
453        """
454
455        if scoring is None:
456            scoring = "accuracy"
457
458        if scoring == "accuracy":
459            return skm2.accuracy_score(y, self.predict(X))
460
461        if scoring == "f1":
462            return skm2.f1_score(y, self.predict(X))
463
464        if scoring == "precision":
465            return skm2.precision_score(y, self.predict(X))
466
467        if scoring == "recall":
468            return skm2.recall_score(y, self.predict(X))
469
470        if scoring == "roc_auc":
471            return skm2.roc_auc_score(y, self.predict(X))
472
473        if scoring == "log_loss":
474            return skm2.log_loss(y, self.predict_proba(X))
475
476        if scoring == "balanced_accuracy":
477            return skm2.balanced_accuracy_score(y, self.predict(X))
478
479        if scoring == "average_precision":
480            return skm2.average_precision_score(y, self.predict(X))
481
482        if scoring == "neg_brier_score":
483            return -skm2.brier_score_loss(y, self.predict_proba(X))
484
485        if scoring == "neg_log_loss":
486            return -skm2.log_loss(y, self.predict_proba(X))

Scoring function for classification.

Args:

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features.

y: array-like, shape = [n_samples]
    Target values.

scoring: str
    scoring method (default is accuracy)

Returns:

score: float
class CustomRegressor(nnetsauce.custom.custom.Custom, sklearn.base.RegressorMixin):
 18class CustomRegressor(Custom, RegressorMixin):
 19    """Custom Regression model
 20
 21    This class is used to 'augment' any regression model with transformed features.
 22
 23    Parameters:
 24
 25        obj: object
 26            any object containing a method fit (obj.fit()) and a method predict
 27            (obj.predict())
 28
 29        n_hidden_features: int
 30            number of nodes in the hidden layer
 31
 32        activation_name: str
 33            activation function: 'relu', 'tanh', 'sigmoid', 'prelu' or 'elu'
 34
 35        a: float
 36            hyperparameter for 'prelu' or 'elu' activation function
 37
 38        nodes_sim: str
 39            type of simulation for the nodes: 'sobol', 'hammersley', 'halton',
 40            'uniform'
 41
 42        bias: boolean
 43            indicates if the hidden layer contains a bias term (True) or not
 44            (False)
 45
 46        dropout: float
 47            regularization parameter; (random) percentage of nodes dropped out
 48            of the training
 49
 50        direct_link: boolean
 51            indicates if the original predictors are included (True) in model's
 52            fitting or not (False)
 53
 54        n_clusters: int
 55            number of clusters for 'kmeans' or 'gmm' clustering (could be 0:
 56                no clustering)
 57
 58        cluster_encode: bool
 59            defines how the variable containing clusters is treated (default is one-hot)
 60            if `False`, then labels are used, without one-hot encoding
 61
 62        type_clust: str
 63            type of clustering method: currently k-means ('kmeans') or Gaussian
 64            Mixture Model ('gmm')
 65
 66        type_scaling: a tuple of 3 strings
 67            scaling methods for inputs, hidden layer, and clustering respectively
 68            (and when relevant).
 69            Currently available: standardization ('std') or MinMax scaling ('minmax')
 70
 71        type_pi: str.
 72            type of prediction interval; currently `None` (split or local
 73            conformal without simulation), "kde" or "bootstrap" (simulated split
 74            conformal).
 75
 76        replications: int.
 77            number of replications (if needed) for predictive simulation.
 78            Used only in `self.predict`, for `self.kernel` in ('gaussian',
 79            'tophat') and `self.type_pi = 'kde'`. Default is `None`.
 80
 81        kernel: str.
 82            the kernel to use for kernel density estimation (used for predictive
 83            simulation in `self.predict`, with `method='splitconformal'` and
 84            `type_pi = 'kde'`). Currently, either 'gaussian' or 'tophat'.
 85
 86        type_split: str.
 87            Type of splitting for conformal prediction. None (default), or
 88            "random" (random split of data) or "sequential" (sequential split of data)
 89
 90        col_sample: float
 91            percentage of covariates randomly chosen for training
 92
 93        row_sample: float
 94            percentage of rows chosen for training, by stratified bootstrapping
 95
 96        level: float
 97            confidence level for prediction intervals
 98
 99        pi_method: str
100            method for prediction intervals: 'splitconformal' or 'localconformal'
101
102        seed: int
103            reproducibility seed for nodes_sim=='uniform'
104
105        type_fit: str
106            'regression'
107
108        backend: str
109            "cpu" or "gpu" or "tpu"
110
111    Examples:
112
113    See [https://thierrymoudiki.github.io/blog/2024/03/18/python/conformal-and-bayesian-regression](https://thierrymoudiki.github.io/blog/2024/03/18/python/conformal-and-bayesian-regression)
114
115    """
116
117    # construct the object -----
118
119    def __init__(
120        self,
121        obj,
122        n_hidden_features=5,
123        activation_name="relu",
124        a=0.01,
125        nodes_sim="sobol",
126        bias=True,
127        dropout=0,
128        direct_link=True,
129        n_clusters=2,
130        cluster_encode=True,
131        type_clust="kmeans",
132        type_scaling=("std", "std", "std"),
133        type_pi=None,
134        replications=None,
135        kernel=None,
136        type_split=None,
137        col_sample=1,
138        row_sample=1,
139        level=None,
140        pi_method=None,
141        seed=123,
142        backend="cpu",
143    ):
144        super().__init__(
145            obj=obj,
146            n_hidden_features=n_hidden_features,
147            activation_name=activation_name,
148            a=a,
149            nodes_sim=nodes_sim,
150            bias=bias,
151            dropout=dropout,
152            direct_link=direct_link,
153            n_clusters=n_clusters,
154            cluster_encode=cluster_encode,
155            type_clust=type_clust,
156            type_scaling=type_scaling,
157            col_sample=col_sample,
158            row_sample=row_sample,
159            seed=seed,
160            backend=backend,
161        )
162
163        self.type_fit = "regression"
164        self.type_pi = type_pi
165        self.replications = replications
166        self.kernel = kernel
167        self.type_split = type_split
168        self.level = level
169        self.pi_method = pi_method
170        self.coef_ = None
171        self.intercept_ = None
172        self.X_ = None
173        self.y_ = None
174        self.aic_ = None
175        self.aicc_ = None
176        self.bic_ = None
177
178    def fit(self, X, y, sample_weight=None, **kwargs):
179        """Fit custom model to training data (X, y).
180
181        Parameters:
182
183            X: {array-like}, shape = [n_samples, n_features]
184                Training vectors, where n_samples is the number
185                of samples and n_features is the number of features.
186
187            y: array-like, shape = [n_samples]
188                Target values.
189
190            sample_weight: array-like, shape = [n_samples]
191                Sample weights.
192
193            **kwargs: additional parameters to be passed to
194                self.cook_training_set or self.obj.fit
195
196        Returns:
197
198            self: object
199
200        """
201
202        centered_y, scaled_Z = self.cook_training_set(y=y, X=X, **kwargs)
203
204        if self.level is not None:
205            self.obj = PredictionInterval(
206                obj=self.obj, method=self.pi_method, level=self.level
207            )
208
209        # if sample_weights, else: (must use self.row_index)
210        if sample_weight is not None:
211            self.obj.fit(
212                scaled_Z,
213                centered_y,
214                sample_weight=sample_weight[self.index_row_].ravel(),
215                **kwargs
216            )
217
218            return self
219
220        self.obj.fit(scaled_Z, centered_y, **kwargs)
221
222        self.X_ = X
223
224        self.y_ = y
225
226        # Compute SSE
227        centered_y_pred = self.obj.predict(scaled_Z)
228        self.sse_ = np.sum((centered_y - centered_y_pred) ** 2)
229
230        # Get number of parameters
231        n_params = (
232            self.n_hidden_features + X.shape[1]
233        )  # hidden features + original features
234        if self.n_clusters > 0:
235            n_params += self.n_clusters  # add clusters if used
236
237        # Compute information criteria
238        n_samples = X.shape[0]
239        temp = n_samples * np.log(self.sse_ / n_samples)
240        self.aic_ = temp + 2 * n_params
241        self.bic_ = temp + np.log(n_samples) * n_params
242
243        if hasattr(self.obj, "coef_"):
244            self.coef_ = self.obj.coef_
245
246        if hasattr(self.obj, "intercept_"):
247            self.intercept_ = self.obj.intercept_
248
249        return self
250
251    def partial_fit(self, X, y, **kwargs):
252        """Partial fit custom model to training data (X, y).
253
254        Parameters:
255
256            X: {array-like}, shape = [n_samples, n_features]
257                Subset of training vectors, where n_samples is the number
258                of samples and n_features is the number of features.
259
260            y: array-like, shape = [n_samples]
261                Subset of target values.
262
263            **kwargs: additional parameters to be passed to
264                self.cook_training_set or self.obj.fit
265
266        Returns:
267
268            self: object
269
270        """
271
272        if len(X.shape) == 1:
273            if isinstance(X, pd.DataFrame):
274                X = pd.DataFrame(X.values.reshape(1, -1), columns=X.columns)
275            else:
276                X = X.reshape(1, -1)
277            y = np.array([y])
278
279        centered_y, scaled_Z = self.cook_training_set(y=y, X=X, **kwargs)
280
281        self.obj.partial_fit(scaled_Z, centered_y, **kwargs)
282
283        self.X_ = X
284
285        self.y_ = y
286
287        return self
288
289    def predict(self, X, level=95, method="splitconformal", **kwargs):
290        """Predict test data X.
291
292        Parameters:
293
294            X: {array-like}, shape = [n_samples, n_features]
295                Training vectors, where n_samples is the number
296                of samples and n_features is the number of features.
297
298            level: int
299                Level of confidence (default = 95)
300
301            method: str
302                'splitconformal', 'localconformal'
303                prediction (if you specify `return_pi = True`)
304
305            **kwargs: additional parameters
306                    `return_pi = True` for conformal prediction,
307                    with `method` in ('splitconformal', 'localconformal')
308                    or `return_std = True` for `self.obj` in
309                    (`sklearn.linear_model.BayesianRidge`,
310                    `sklearn.linear_model.ARDRegressor`,
311                    `sklearn.gaussian_process.GaussianProcessRegressor`)`
312
313        Returns:
314
315            model predictions:
316                an array if uncertainty quantification is not requested,
317                  or a tuple if with prediction intervals and simulations
318                  if `return_std = True` (mean, standard deviation,
319                  lower and upper prediction interval) or `return_pi = True`
320                  ()
321
322        """
323
324        if "return_std" in kwargs:
325            alpha = 100 - level
326            pi_multiplier = norm.ppf(1 - alpha / 200)
327
328            if len(X.shape) == 1:
329                n_features = X.shape[0]
330                new_X = mo.rbind(
331                    X.reshape(1, n_features),
332                    np.ones(n_features).reshape(1, n_features),
333                )
334
335                mean_, std_ = self.obj.predict(
336                    self.cook_test_set(new_X, **kwargs), return_std=True
337                )[0]
338
339                preds = self.y_mean_ + mean_
340                lower = self.y_mean_ + (mean_ - pi_multiplier * std_)
341                upper = self.y_mean_ + (mean_ + pi_multiplier * std_)
342
343                DescribeResults = namedtuple(
344                    "DescribeResults", ["mean", "std", "lower", "upper"]
345                )
346
347                return DescribeResults(preds, std_, lower, upper)
348
349            # len(X.shape) > 1
350            mean_, std_ = self.obj.predict(
351                self.cook_test_set(X, **kwargs), return_std=True
352            )
353
354            preds = self.y_mean_ + mean_
355            lower = self.y_mean_ + (mean_ - pi_multiplier * std_)
356            upper = self.y_mean_ + (mean_ + pi_multiplier * std_)
357
358            DescribeResults = namedtuple(
359                "DescribeResults", ["mean", "std", "lower", "upper"]
360            )
361
362            return DescribeResults(preds, std_, lower, upper)
363
364        if "return_pi" in kwargs:
365            assert method in (
366                "splitconformal",
367                "localconformal",
368            ), "method must be in ('splitconformal', 'localconformal')"
369            self.pi = PredictionInterval(
370                obj=self,
371                method=method,
372                level=level,
373                type_pi=self.type_pi,
374                replications=self.replications,
375                kernel=self.kernel,
376            )
377
378            if len(self.X_.shape) == 1:
379                if isinstance(X, pd.DataFrame):
380                    self.X_ = pd.DataFrame(
381                        self.X_.values.reshape(1, -1), columns=self.X_.columns
382                    )
383                else:
384                    self.X_ = self.X_.reshape(1, -1)
385                self.y_ = np.array([self.y_])
386
387            self.pi.fit(self.X_, self.y_)
388            # self.X_ = None # consumes memory to keep, dangerous to delete (side effect)
389            # self.y_ = None # consumes memory to keep, dangerous to delete (side effect)
390            preds = self.pi.predict(X, return_pi=True)
391            return preds
392
393        # "return_std" not in kwargs
394        if len(X.shape) == 1:
395            n_features = X.shape[0]
396            new_X = mo.rbind(
397                X.reshape(1, n_features),
398                np.ones(n_features).reshape(1, n_features),
399            )
400
401            return (
402                self.y_mean_
403                + self.obj.predict(
404                    self.cook_test_set(new_X, **kwargs), **kwargs
405                )
406            )[0]
407
408        # len(X.shape) > 1
409        return self.y_mean_ + self.obj.predict(
410            self.cook_test_set(X, **kwargs), **kwargs
411        )
412
413    def score(self, X, y, scoring=None):
414        """Compute the score of the model.
415
416        Parameters:
417
418            X: {array-like}, shape = [n_samples, n_features]
419                Training vectors, where n_samples is the number
420                of samples and n_features is the number of features.
421
422            y: array-like, shape = [n_samples]
423                Target values.
424
425            scoring: str
426                scoring method
427
428        Returns:
429
430            score: float
431
432        """
433
434        if scoring is None:
435            return np.sqrt(np.mean((self.predict(X) - y) ** 2))
436
437        return skm2.get_scorer(scoring)(self, X, y)

Custom Regression model

This class is used to 'augment' any regression model with transformed features.

Parameters:

obj: object
    any object containing a method fit (obj.fit()) and a method predict
    (obj.predict())

n_hidden_features: int
    number of nodes in the hidden layer

activation_name: str
    activation function: 'relu', 'tanh', 'sigmoid', 'prelu' or 'elu'

a: float
    hyperparameter for 'prelu' or 'elu' activation function

nodes_sim: str
    type of simulation for the nodes: 'sobol', 'hammersley', 'halton',
    'uniform'

bias: boolean
    indicates if the hidden layer contains a bias term (True) or not
    (False)

dropout: float
    regularization parameter; (random) percentage of nodes dropped out
    of the training

direct_link: boolean
    indicates if the original predictors are included (True) in model's
    fitting or not (False)

n_clusters: int
    number of clusters for 'kmeans' or 'gmm' clustering (could be 0:
        no clustering)

cluster_encode: bool
    defines how the variable containing clusters is treated (default is one-hot)
    if `False`, then labels are used, without one-hot encoding

type_clust: str
    type of clustering method: currently k-means ('kmeans') or Gaussian
    Mixture Model ('gmm')

type_scaling: a tuple of 3 strings
    scaling methods for inputs, hidden layer, and clustering respectively
    (and when relevant).
    Currently available: standardization ('std') or MinMax scaling ('minmax')

type_pi: str.
    type of prediction interval; currently `None` (split or local
    conformal without simulation), "kde" or "bootstrap" (simulated split
    conformal).

replications: int.
    number of replications (if needed) for predictive simulation.
    Used only in `self.predict`, for `self.kernel` in ('gaussian',
    'tophat') and `self.type_pi = 'kde'`. Default is `None`.

kernel: str.
    the kernel to use for kernel density estimation (used for predictive
    simulation in `self.predict`, with `method='splitconformal'` and
    `type_pi = 'kde'`). Currently, either 'gaussian' or 'tophat'.

type_split: str.
    Type of splitting for conformal prediction. None (default), or
    "random" (random split of data) or "sequential" (sequential split of data)

col_sample: float
    percentage of covariates randomly chosen for training

row_sample: float
    percentage of rows chosen for training, by stratified bootstrapping

level: float
    confidence level for prediction intervals

pi_method: str
    method for prediction intervals: 'splitconformal' or 'localconformal'

seed: int
    reproducibility seed for nodes_sim=='uniform'

type_fit: str
    'regression'

backend: str
    "cpu" or "gpu" or "tpu"

Examples:

See https://thierrymoudiki.github.io/blog/2024/03/18/python/conformal-and-bayesian-regression

def fit(self, X, y, sample_weight=None, **kwargs):
178    def fit(self, X, y, sample_weight=None, **kwargs):
179        """Fit custom model to training data (X, y).
180
181        Parameters:
182
183            X: {array-like}, shape = [n_samples, n_features]
184                Training vectors, where n_samples is the number
185                of samples and n_features is the number of features.
186
187            y: array-like, shape = [n_samples]
188                Target values.
189
190            sample_weight: array-like, shape = [n_samples]
191                Sample weights.
192
193            **kwargs: additional parameters to be passed to
194                self.cook_training_set or self.obj.fit
195
196        Returns:
197
198            self: object
199
200        """
201
202        centered_y, scaled_Z = self.cook_training_set(y=y, X=X, **kwargs)
203
204        if self.level is not None:
205            self.obj = PredictionInterval(
206                obj=self.obj, method=self.pi_method, level=self.level
207            )
208
209        # if sample_weights, else: (must use self.row_index)
210        if sample_weight is not None:
211            self.obj.fit(
212                scaled_Z,
213                centered_y,
214                sample_weight=sample_weight[self.index_row_].ravel(),
215                **kwargs
216            )
217
218            return self
219
220        self.obj.fit(scaled_Z, centered_y, **kwargs)
221
222        self.X_ = X
223
224        self.y_ = y
225
226        # Compute SSE
227        centered_y_pred = self.obj.predict(scaled_Z)
228        self.sse_ = np.sum((centered_y - centered_y_pred) ** 2)
229
230        # Get number of parameters
231        n_params = (
232            self.n_hidden_features + X.shape[1]
233        )  # hidden features + original features
234        if self.n_clusters > 0:
235            n_params += self.n_clusters  # add clusters if used
236
237        # Compute information criteria
238        n_samples = X.shape[0]
239        temp = n_samples * np.log(self.sse_ / n_samples)
240        self.aic_ = temp + 2 * n_params
241        self.bic_ = temp + np.log(n_samples) * n_params
242
243        if hasattr(self.obj, "coef_"):
244            self.coef_ = self.obj.coef_
245
246        if hasattr(self.obj, "intercept_"):
247            self.intercept_ = self.obj.intercept_
248
249        return self

Fit custom model to training data (X, y).

Parameters:

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features.

y: array-like, shape = [n_samples]
    Target values.

sample_weight: array-like, shape = [n_samples]
    Sample weights.

**kwargs: additional parameters to be passed to
    self.cook_training_set or self.obj.fit

Returns:

self: object
def predict(self, X, level=95, method='splitconformal', **kwargs):
289    def predict(self, X, level=95, method="splitconformal", **kwargs):
290        """Predict test data X.
291
292        Parameters:
293
294            X: {array-like}, shape = [n_samples, n_features]
295                Training vectors, where n_samples is the number
296                of samples and n_features is the number of features.
297
298            level: int
299                Level of confidence (default = 95)
300
301            method: str
302                'splitconformal', 'localconformal'
303                prediction (if you specify `return_pi = True`)
304
305            **kwargs: additional parameters
306                    `return_pi = True` for conformal prediction,
307                    with `method` in ('splitconformal', 'localconformal')
308                    or `return_std = True` for `self.obj` in
309                    (`sklearn.linear_model.BayesianRidge`,
310                    `sklearn.linear_model.ARDRegressor`,
311                    `sklearn.gaussian_process.GaussianProcessRegressor`)`
312
313        Returns:
314
315            model predictions:
316                an array if uncertainty quantification is not requested,
317                  or a tuple if with prediction intervals and simulations
318                  if `return_std = True` (mean, standard deviation,
319                  lower and upper prediction interval) or `return_pi = True`
320                  ()
321
322        """
323
324        if "return_std" in kwargs:
325            alpha = 100 - level
326            pi_multiplier = norm.ppf(1 - alpha / 200)
327
328            if len(X.shape) == 1:
329                n_features = X.shape[0]
330                new_X = mo.rbind(
331                    X.reshape(1, n_features),
332                    np.ones(n_features).reshape(1, n_features),
333                )
334
335                mean_, std_ = self.obj.predict(
336                    self.cook_test_set(new_X, **kwargs), return_std=True
337                )[0]
338
339                preds = self.y_mean_ + mean_
340                lower = self.y_mean_ + (mean_ - pi_multiplier * std_)
341                upper = self.y_mean_ + (mean_ + pi_multiplier * std_)
342
343                DescribeResults = namedtuple(
344                    "DescribeResults", ["mean", "std", "lower", "upper"]
345                )
346
347                return DescribeResults(preds, std_, lower, upper)
348
349            # len(X.shape) > 1
350            mean_, std_ = self.obj.predict(
351                self.cook_test_set(X, **kwargs), return_std=True
352            )
353
354            preds = self.y_mean_ + mean_
355            lower = self.y_mean_ + (mean_ - pi_multiplier * std_)
356            upper = self.y_mean_ + (mean_ + pi_multiplier * std_)
357
358            DescribeResults = namedtuple(
359                "DescribeResults", ["mean", "std", "lower", "upper"]
360            )
361
362            return DescribeResults(preds, std_, lower, upper)
363
364        if "return_pi" in kwargs:
365            assert method in (
366                "splitconformal",
367                "localconformal",
368            ), "method must be in ('splitconformal', 'localconformal')"
369            self.pi = PredictionInterval(
370                obj=self,
371                method=method,
372                level=level,
373                type_pi=self.type_pi,
374                replications=self.replications,
375                kernel=self.kernel,
376            )
377
378            if len(self.X_.shape) == 1:
379                if isinstance(X, pd.DataFrame):
380                    self.X_ = pd.DataFrame(
381                        self.X_.values.reshape(1, -1), columns=self.X_.columns
382                    )
383                else:
384                    self.X_ = self.X_.reshape(1, -1)
385                self.y_ = np.array([self.y_])
386
387            self.pi.fit(self.X_, self.y_)
388            # self.X_ = None # consumes memory to keep, dangerous to delete (side effect)
389            # self.y_ = None # consumes memory to keep, dangerous to delete (side effect)
390            preds = self.pi.predict(X, return_pi=True)
391            return preds
392
393        # "return_std" not in kwargs
394        if len(X.shape) == 1:
395            n_features = X.shape[0]
396            new_X = mo.rbind(
397                X.reshape(1, n_features),
398                np.ones(n_features).reshape(1, n_features),
399            )
400
401            return (
402                self.y_mean_
403                + self.obj.predict(
404                    self.cook_test_set(new_X, **kwargs), **kwargs
405                )
406            )[0]
407
408        # len(X.shape) > 1
409        return self.y_mean_ + self.obj.predict(
410            self.cook_test_set(X, **kwargs), **kwargs
411        )

Predict test data X.

Parameters:

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features.

level: int
    Level of confidence (default = 95)

method: str
    'splitconformal', 'localconformal'
    prediction (if you specify `return_pi = True`)

**kwargs: additional parameters
        `return_pi = True` for conformal prediction,
        with `method` in ('splitconformal', 'localconformal')
        or `return_std = True` for `self.obj` in
        (`sklearn.linear_model.BayesianRidge`,
        `sklearn.linear_model.ARDRegressor`,
        `sklearn.gaussian_process.GaussianProcessRegressor`)`

Returns:

model predictions:
    an array if uncertainty quantification is not requested,
      or a tuple if with prediction intervals and simulations
      if `return_std = True` (mean, standard deviation,
      lower and upper prediction interval) or `return_pi = True`
      ()
def score(self, X, y, scoring=None):
413    def score(self, X, y, scoring=None):
414        """Compute the score of the model.
415
416        Parameters:
417
418            X: {array-like}, shape = [n_samples, n_features]
419                Training vectors, where n_samples is the number
420                of samples and n_features is the number of features.
421
422            y: array-like, shape = [n_samples]
423                Target values.
424
425            scoring: str
426                scoring method
427
428        Returns:
429
430            score: float
431
432        """
433
434        if scoring is None:
435            return np.sqrt(np.mean((self.predict(X) - y) ** 2))
436
437        return skm2.get_scorer(scoring)(self, X, y)

Compute the score of the model.

Parameters:

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features.

y: array-like, shape = [n_samples]
    Target values.

scoring: str
    scoring method

Returns:

score: float
class CustomBackPropRegressor(nnetsauce.custom.custom.Custom, sklearn.base.RegressorMixin):
 18class CustomBackPropRegressor(Custom, RegressorMixin):
 19    """
 20    Finite difference trainer for nnetsauce models.
 21
 22    Parameters
 23    ----------
 24
 25    base_model : str
 26        The name of the base model (e.g., 'RidgeCV').
 27
 28    type_grad : {'finitediff', 'autodiff'}, optional
 29        Type of gradient computation to use (default='finitediff').
 30
 31    lr : float, optional
 32        Learning rate for optimization (default=1e-4).
 33
 34    optimizer : {'gd', 'sgd', 'adam', 'cd'}, optional
 35        Optimization algorithm: gradient descent ('gd'), stochastic gradient descent ('sgd'),
 36        Adam ('adam'), or coordinate descent ('cd'). Default is 'gd'.
 37
 38    eps : float, optional
 39        Scaling factor for adaptive finite difference step size (default=1e-3).
 40
 41    batch_size : int, optional
 42        Batch size for 'sgd' optimizer (default=32).
 43
 44    alpha : float, optional
 45        Elastic net penalty strength (default=0.0).
 46
 47    l1_ratio : float, optional
 48        Elastic net mixing parameter (0 = Ridge, 1 = Lasso, default=0.0).
 49
 50    type_loss : {'mse', 'quantile'}, optional
 51        Type of loss function to use (default='mse').
 52
 53    q : float, optional
 54        Quantile for quantile loss (default=0.5).
 55
 56    **kwargs
 57        Additional parameters to pass to the scikit-learn model.
 58
 59    """
 60
 61    def __init__(
 62        self,
 63        base_model,
 64        type_grad="finitediff",
 65        lr=1e-4,
 66        optimizer="gd",
 67        eps=1e-3,
 68        batch_size=32,
 69        alpha=0.0,
 70        l1_ratio=0.0,
 71        type_loss="mse",
 72        q=0.5,
 73        backend="cpu",
 74        **kwargs,
 75    ):
 76        super().__init__(base_model, True, **kwargs)
 77        self.base_model = base_model
 78        self.custom_kwargs = kwargs
 79        self.backend = backend
 80        self.model = ns.CustomRegressor(
 81            self.base_model, backend=self.backend, **self.custom_kwargs
 82        )
 83        assert isinstance(
 84            self.model, ns.CustomRegressor
 85        ), "'model' must be of class ns.CustomRegressor"
 86        self.type_grad = type_grad
 87        self.lr = lr
 88        self.optimizer = optimizer
 89        self.eps = eps
 90        self.loss_history_ = []
 91        self.opt_state = None
 92        self.batch_size = batch_size  # for SGD
 93        self.loss_history_ = []
 94        self._cd_index = 0  # For coordinate descent
 95        self.alpha = alpha
 96        self.l1_ratio = l1_ratio
 97        self.type_loss = type_loss
 98        self.q = q
 99
100    def _loss(self, X, y, **kwargs):
101        """
102        Compute the loss (with elastic net penalty) for the current model.
103
104        Parameters
105        ----------
106
107        X : array-like of shape (n_samples, n_features)
108            Input data.
109
110        y : array-like of shape (n_samples,)
111            Target values.
112
113        **kwargs
114            Additional keyword arguments for loss calculation.
115
116        Returns
117        -------
118        float
119            The computed loss value.
120        """
121        y_pred = self.model.predict(X)
122        if self.type_loss == "mse":
123            loss = np.mean((y - y_pred) ** 2)
124        elif self.type_loss == "quantile":
125            loss = mean_pinball_loss(y, y_pred, alpha=self.q, **kwargs)
126        W = self.model.W_
127        l1 = np.sum(np.abs(W))
128        l2 = np.sum(W**2)
129        return loss + self.alpha * (
130            self.l1_ratio * l1 + 0.5 * (1 - self.l1_ratio) * l2
131        )
132
133    def _compute_grad(self, X, y):
134        """
135        Compute the gradient of the loss with respect to W_ using finite differences.
136
137        Parameters
138        ----------
139
140        X : array-like of shape (n_samples, n_features)
141            Input data.
142
143        y : array-like of shape (n_samples,)
144            Target values.
145
146        Returns
147        -------
148
149        ndarray
150            Gradient array with the same shape as W_.
151        """
152
153        # Finite difference gradient computation
154        W = deepcopy(self.model.W_)
155        shape = W.shape
156        W_flat = W.flatten()
157        n_params = W_flat.size
158
159        # Adaptive finite difference step
160        h_vec = self.eps * np.maximum(1.0, np.abs(W_flat))
161        eye = np.eye(n_params)
162
163        loss_plus = np.zeros(n_params)
164        loss_minus = np.zeros(n_params)
165
166        for i in range(n_params):
167            h_i = h_vec[i]
168            Wp = W_flat.copy()
169            Wp[i] += h_i
170            Wm = W_flat.copy()
171            Wm[i] -= h_i
172
173            self.model.W_ = Wp.reshape(shape)
174            loss_plus[i] = self._loss(X, y)
175
176            self.model.W_ = Wm.reshape(shape)
177            loss_minus[i] = self._loss(X, y)
178
179        grad = ((loss_plus - loss_minus) / (2 * h_vec)).reshape(shape)
180
181        # Add elastic net gradient
182        l1_grad = self.alpha * self.l1_ratio * np.sign(W)
183        l2_grad = self.alpha * (1 - self.l1_ratio) * W
184        grad += l1_grad + l2_grad
185
186        self.model.W_ = W  # restore original
187        return grad
188
189    def fit(
190        self,
191        X,
192        y,
193        epochs=10,
194        verbose=True,
195        show_progress=True,
196        sample_weight=None,
197        **kwargs,
198    ):
199        """
200        Fit the model using finite difference optimization.
201
202        Parameters
203        ----------
204
205        X : array-like of shape (n_samples, n_features)
206            Training data.
207
208        y : array-like of shape (n_samples,)
209            Target values.
210
211        epochs : int, optional
212            Number of optimization steps (default=10).
213
214        verbose : bool, optional
215            Whether to print progress messages (default=True).
216
217        show_progress : bool, optional
218            Whether to show tqdm progress bar (default=True).
219
220        sample_weight : array-like, optional
221            Sample weights.
222
223        **kwargs
224            Additional keyword arguments.
225
226        Returns
227        -------
228
229        self : object
230            Returns self.
231        """
232
233        self.model.fit(X, y)
234
235        iterator = tqdm(range(epochs)) if show_progress else range(epochs)
236
237        for epoch in iterator:
238            grad = self._compute_grad(X, y)
239
240            if self.optimizer == "gd":
241                self.model.W_ -= self.lr * grad
242                self.model.W_ = np.clip(self.model.W_, 0, 1)
243                # print("self.model.W_", self.model.W_)
244
245            elif self.optimizer == "sgd":
246                # Sample a mini-batch for stochastic gradient
247                n_samples = X.shape[0]
248                idxs = np.random.choice(
249                    n_samples, self.batch_size, replace=False
250                )
251                if isinstance(X, pd.DataFrame):
252                    X_batch = X.iloc[idxs, :]
253                else:
254                    X_batch = X[idxs, :]
255                y_batch = y[idxs]
256                grad = self._compute_grad(X_batch, y_batch)
257
258                self.model.W_ -= self.lr * grad
259                self.model.W_ = np.clip(self.model.W_, 0, 1)
260
261            elif self.optimizer == "adam":
262                if self.opt_state is None:
263                    self.opt_state = {
264                        "m": np.zeros_like(grad),
265                        "v": np.zeros_like(grad),
266                        "t": 0,
267                    }
268                beta1, beta2, eps = 0.9, 0.999, 1e-8
269                self.opt_state["t"] += 1
270                self.opt_state["m"] = (
271                    beta1 * self.opt_state["m"] + (1 - beta1) * grad
272                )
273                self.opt_state["v"] = beta2 * self.opt_state["v"] + (
274                    1 - beta2
275                ) * (grad**2)
276                m_hat = self.opt_state["m"] / (1 - beta1 ** self.opt_state["t"])
277                v_hat = self.opt_state["v"] / (1 - beta2 ** self.opt_state["t"])
278
279                self.model.W_ -= self.lr * m_hat / (np.sqrt(v_hat) + eps)
280                self.model.W_ = np.clip(self.model.W_, 0, 1)
281                # print("self.model.W_", self.model.W_)
282
283            elif self.optimizer == "cd":  # coordinate descent
284                W_shape = self.model.W_.shape
285                W_flat_size = self.model.W_.size
286                W_flat = self.model.W_.flatten()
287                grad_flat = grad.flatten()
288
289                # Update only one coordinate per epoch (cyclic)
290                idx = self._cd_index % W_flat_size
291                W_flat[idx] -= self.lr * grad_flat[idx]
292                # Clip the updated value
293                W_flat[idx] = np.clip(W_flat[idx], 0, 1)
294
295                # Restore W_
296                self.model.W_ = W_flat.reshape(W_shape)
297
298                self._cd_index += 1
299
300            else:
301                raise ValueError(f"Unsupported optimizer: {self.optimizer}")
302
303            loss = self._loss(X, y)
304            self.loss_history_.append(loss)
305
306            if verbose:
307                print(f"Epoch {epoch+1}: Loss = {loss:.6f}")
308
309        # if sample_weights, else: (must use self.row_index)
310        if sample_weight in kwargs:
311            self.model.fit(
312                X,
313                y,
314                sample_weight=sample_weight[self.index_row_].ravel(),
315                **kwargs,
316            )
317
318            return self
319
320        return self
321
322    def predict(self, X, level=95, method="splitconformal", **kwargs):
323        """
324        Predict using the trained model.
325
326        Parameters
327        ----------
328
329        X : array-like of shape (n_samples, n_features)
330            Input data.
331
332        level : int, optional
333            Level of confidence for prediction intervals (default=95).
334
335        method : {'splitconformal', 'localconformal'}, optional
336            Method for conformal prediction (default='splitconformal').
337
338        **kwargs
339            Additional keyword arguments. Use `return_pi=True` for prediction intervals,
340            or `return_std=True` for standard deviation estimates.
341
342        Returns
343        -------
344
345        array or tuple
346            Model predictions, or a tuple with prediction intervals or standard deviations if requested.
347        """
348        if "return_std" in kwargs:
349            alpha = 100 - level
350            pi_multiplier = norm.ppf(1 - alpha / 200)
351
352            if len(X.shape) == 1:
353                n_features = X.shape[0]
354                new_X = mo.rbind(
355                    X.reshape(1, n_features),
356                    np.ones(n_features).reshape(1, n_features),
357                )
358
359                mean_, std_ = self.model.predict(new_X, return_std=True)[0]
360
361                preds = mean_
362                lower = mean_ - pi_multiplier * std_
363                upper = mean_ + pi_multiplier * std_
364
365                DescribeResults = namedtuple(
366                    "DescribeResults", ["mean", "std", "lower", "upper"]
367                )
368
369                return DescribeResults(preds, std_, lower, upper)
370
371            # len(X.shape) > 1
372            mean_, std_ = self.model.predict(X, return_std=True)
373
374            preds = mean_
375            lower = mean_ - pi_multiplier * std_
376            upper = mean_ + pi_multiplier * std_
377
378            DescribeResults = namedtuple(
379                "DescribeResults", ["mean", "std", "lower", "upper"]
380            )
381
382            return DescribeResults(preds, std_, lower, upper)
383
384        if "return_pi" in kwargs:
385            assert method in (
386                "splitconformal",
387                "localconformal",
388            ), "method must be in ('splitconformal', 'localconformal')"
389            self.pi = ns.PredictionInterval(
390                obj=self,
391                method=method,
392                level=level,
393                type_pi=self.type_pi,
394                replications=self.replications,
395                kernel=self.kernel,
396            )
397
398            if len(self.X_.shape) == 1:
399                if isinstance(X, pd.DataFrame):
400                    self.X_ = pd.DataFrame(
401                        self.X_.values.reshape(1, -1), columns=self.X_.columns
402                    )
403                else:
404                    self.X_ = self.X_.reshape(1, -1)
405                self.y_ = np.array([self.y_])
406
407            self.pi.fit(self.X_, self.y_)
408            # self.X_ = None # consumes memory to keep, dangerous to delete (side effect)
409            # self.y_ = None # consumes memory to keep, dangerous to delete (side effect)
410            preds = self.pi.predict(X, return_pi=True)
411            return preds
412
413        # "return_std" not in kwargs
414        if len(X.shape) == 1:
415            n_features = X.shape[0]
416            new_X = mo.rbind(
417                X.reshape(1, n_features),
418                np.ones(n_features).reshape(1, n_features),
419            )
420
421            return (0 + self.model.predict(new_X, **kwargs))[0]
422
423        # len(X.shape) > 1
424        return self.model.predict(X, **kwargs)

Finite difference trainer for nnetsauce models.

Parameters

base_model : str The name of the base model (e.g., 'RidgeCV').

type_grad : {'finitediff', 'autodiff'}, optional Type of gradient computation to use (default='finitediff').

lr : float, optional Learning rate for optimization (default=1e-4).

optimizer : {'gd', 'sgd', 'adam', 'cd'}, optional Optimization algorithm: gradient descent ('gd'), stochastic gradient descent ('sgd'), Adam ('adam'), or coordinate descent ('cd'). Default is 'gd'.

eps : float, optional Scaling factor for adaptive finite difference step size (default=1e-3).

batch_size : int, optional Batch size for 'sgd' optimizer (default=32).

alpha : float, optional Elastic net penalty strength (default=0.0).

l1_ratio : float, optional Elastic net mixing parameter (0 = Ridge, 1 = Lasso, default=0.0).

type_loss : {'mse', 'quantile'}, optional Type of loss function to use (default='mse').

q : float, optional Quantile for quantile loss (default=0.5).

**kwargs Additional parameters to pass to the scikit-learn model.

def fit( self, X, y, epochs=10, verbose=True, show_progress=True, sample_weight=None, **kwargs):
189    def fit(
190        self,
191        X,
192        y,
193        epochs=10,
194        verbose=True,
195        show_progress=True,
196        sample_weight=None,
197        **kwargs,
198    ):
199        """
200        Fit the model using finite difference optimization.
201
202        Parameters
203        ----------
204
205        X : array-like of shape (n_samples, n_features)
206            Training data.
207
208        y : array-like of shape (n_samples,)
209            Target values.
210
211        epochs : int, optional
212            Number of optimization steps (default=10).
213
214        verbose : bool, optional
215            Whether to print progress messages (default=True).
216
217        show_progress : bool, optional
218            Whether to show tqdm progress bar (default=True).
219
220        sample_weight : array-like, optional
221            Sample weights.
222
223        **kwargs
224            Additional keyword arguments.
225
226        Returns
227        -------
228
229        self : object
230            Returns self.
231        """
232
233        self.model.fit(X, y)
234
235        iterator = tqdm(range(epochs)) if show_progress else range(epochs)
236
237        for epoch in iterator:
238            grad = self._compute_grad(X, y)
239
240            if self.optimizer == "gd":
241                self.model.W_ -= self.lr * grad
242                self.model.W_ = np.clip(self.model.W_, 0, 1)
243                # print("self.model.W_", self.model.W_)
244
245            elif self.optimizer == "sgd":
246                # Sample a mini-batch for stochastic gradient
247                n_samples = X.shape[0]
248                idxs = np.random.choice(
249                    n_samples, self.batch_size, replace=False
250                )
251                if isinstance(X, pd.DataFrame):
252                    X_batch = X.iloc[idxs, :]
253                else:
254                    X_batch = X[idxs, :]
255                y_batch = y[idxs]
256                grad = self._compute_grad(X_batch, y_batch)
257
258                self.model.W_ -= self.lr * grad
259                self.model.W_ = np.clip(self.model.W_, 0, 1)
260
261            elif self.optimizer == "adam":
262                if self.opt_state is None:
263                    self.opt_state = {
264                        "m": np.zeros_like(grad),
265                        "v": np.zeros_like(grad),
266                        "t": 0,
267                    }
268                beta1, beta2, eps = 0.9, 0.999, 1e-8
269                self.opt_state["t"] += 1
270                self.opt_state["m"] = (
271                    beta1 * self.opt_state["m"] + (1 - beta1) * grad
272                )
273                self.opt_state["v"] = beta2 * self.opt_state["v"] + (
274                    1 - beta2
275                ) * (grad**2)
276                m_hat = self.opt_state["m"] / (1 - beta1 ** self.opt_state["t"])
277                v_hat = self.opt_state["v"] / (1 - beta2 ** self.opt_state["t"])
278
279                self.model.W_ -= self.lr * m_hat / (np.sqrt(v_hat) + eps)
280                self.model.W_ = np.clip(self.model.W_, 0, 1)
281                # print("self.model.W_", self.model.W_)
282
283            elif self.optimizer == "cd":  # coordinate descent
284                W_shape = self.model.W_.shape
285                W_flat_size = self.model.W_.size
286                W_flat = self.model.W_.flatten()
287                grad_flat = grad.flatten()
288
289                # Update only one coordinate per epoch (cyclic)
290                idx = self._cd_index % W_flat_size
291                W_flat[idx] -= self.lr * grad_flat[idx]
292                # Clip the updated value
293                W_flat[idx] = np.clip(W_flat[idx], 0, 1)
294
295                # Restore W_
296                self.model.W_ = W_flat.reshape(W_shape)
297
298                self._cd_index += 1
299
300            else:
301                raise ValueError(f"Unsupported optimizer: {self.optimizer}")
302
303            loss = self._loss(X, y)
304            self.loss_history_.append(loss)
305
306            if verbose:
307                print(f"Epoch {epoch+1}: Loss = {loss:.6f}")
308
309        # if sample_weights, else: (must use self.row_index)
310        if sample_weight in kwargs:
311            self.model.fit(
312                X,
313                y,
314                sample_weight=sample_weight[self.index_row_].ravel(),
315                **kwargs,
316            )
317
318            return self
319
320        return self

Fit the model using finite difference optimization.

Parameters

X : array-like of shape (n_samples, n_features) Training data.

y : array-like of shape (n_samples,) Target values.

epochs : int, optional Number of optimization steps (default=10).

verbose : bool, optional Whether to print progress messages (default=True).

show_progress : bool, optional Whether to show tqdm progress bar (default=True).

sample_weight : array-like, optional Sample weights.

**kwargs Additional keyword arguments.

Returns

self : object Returns self.

def predict(self, X, level=95, method='splitconformal', **kwargs):
322    def predict(self, X, level=95, method="splitconformal", **kwargs):
323        """
324        Predict using the trained model.
325
326        Parameters
327        ----------
328
329        X : array-like of shape (n_samples, n_features)
330            Input data.
331
332        level : int, optional
333            Level of confidence for prediction intervals (default=95).
334
335        method : {'splitconformal', 'localconformal'}, optional
336            Method for conformal prediction (default='splitconformal').
337
338        **kwargs
339            Additional keyword arguments. Use `return_pi=True` for prediction intervals,
340            or `return_std=True` for standard deviation estimates.
341
342        Returns
343        -------
344
345        array or tuple
346            Model predictions, or a tuple with prediction intervals or standard deviations if requested.
347        """
348        if "return_std" in kwargs:
349            alpha = 100 - level
350            pi_multiplier = norm.ppf(1 - alpha / 200)
351
352            if len(X.shape) == 1:
353                n_features = X.shape[0]
354                new_X = mo.rbind(
355                    X.reshape(1, n_features),
356                    np.ones(n_features).reshape(1, n_features),
357                )
358
359                mean_, std_ = self.model.predict(new_X, return_std=True)[0]
360
361                preds = mean_
362                lower = mean_ - pi_multiplier * std_
363                upper = mean_ + pi_multiplier * std_
364
365                DescribeResults = namedtuple(
366                    "DescribeResults", ["mean", "std", "lower", "upper"]
367                )
368
369                return DescribeResults(preds, std_, lower, upper)
370
371            # len(X.shape) > 1
372            mean_, std_ = self.model.predict(X, return_std=True)
373
374            preds = mean_
375            lower = mean_ - pi_multiplier * std_
376            upper = mean_ + pi_multiplier * std_
377
378            DescribeResults = namedtuple(
379                "DescribeResults", ["mean", "std", "lower", "upper"]
380            )
381
382            return DescribeResults(preds, std_, lower, upper)
383
384        if "return_pi" in kwargs:
385            assert method in (
386                "splitconformal",
387                "localconformal",
388            ), "method must be in ('splitconformal', 'localconformal')"
389            self.pi = ns.PredictionInterval(
390                obj=self,
391                method=method,
392                level=level,
393                type_pi=self.type_pi,
394                replications=self.replications,
395                kernel=self.kernel,
396            )
397
398            if len(self.X_.shape) == 1:
399                if isinstance(X, pd.DataFrame):
400                    self.X_ = pd.DataFrame(
401                        self.X_.values.reshape(1, -1), columns=self.X_.columns
402                    )
403                else:
404                    self.X_ = self.X_.reshape(1, -1)
405                self.y_ = np.array([self.y_])
406
407            self.pi.fit(self.X_, self.y_)
408            # self.X_ = None # consumes memory to keep, dangerous to delete (side effect)
409            # self.y_ = None # consumes memory to keep, dangerous to delete (side effect)
410            preds = self.pi.predict(X, return_pi=True)
411            return preds
412
413        # "return_std" not in kwargs
414        if len(X.shape) == 1:
415            n_features = X.shape[0]
416            new_X = mo.rbind(
417                X.reshape(1, n_features),
418                np.ones(n_features).reshape(1, n_features),
419            )
420
421            return (0 + self.model.predict(new_X, **kwargs))[0]
422
423        # len(X.shape) > 1
424        return self.model.predict(X, **kwargs)

Predict using the trained model.

Parameters

X : array-like of shape (n_samples, n_features) Input data.

level : int, optional Level of confidence for prediction intervals (default=95).

method : {'splitconformal', 'localconformal'}, optional Method for conformal prediction (default='splitconformal').

**kwargs Additional keyword arguments. Use return_pi=True for prediction intervals, or return_std=True for standard deviation estimates.

Returns

array or tuple Model predictions, or a tuple with prediction intervals or standard deviations if requested.

class DeepClassifier(nnetsauce.CustomClassifier, sklearn.base.ClassifierMixin):
 36class DeepClassifier(CustomClassifier, ClassifierMixin):
 37    """
 38    Deep Classifier
 39
 40    Parameters:
 41
 42        obj: an object
 43            A base learner, see also https://www.researchgate.net/publication/380701207_Deep_Quasi-Randomized_neural_Networks_for_classification
 44
 45        n_layers: int (default=3)
 46            Number of layers. `n_layers = 1` is a simple `CustomClassifier`
 47
 48        verbose : int, optional (default=0)
 49            Monitor progress when fitting.
 50
 51        All the other parameters are nnetsauce `CustomClassifier`'s
 52
 53    Examples:
 54
 55        ```python
 56        import nnetsauce as ns
 57        from sklearn.datasets import load_breast_cancer
 58        from sklearn.model_selection import train_test_split
 59        from sklearn.linear_model import LogisticRegressionCV
 60        data = load_breast_cancer()
 61        X = data.data
 62        y= data.target
 63        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.2, random_state=123)
 64        obj = LogisticRegressionCV()
 65        clf = ns.DeepClassifier(obj)
 66        clf.fit(X_train, y_train)
 67        print(clf.score(clf.predict(X_test), y_test))
 68        ```
 69    """
 70
 71    _estimator_type = "classifier"
 72
 73    def __init__(
 74        self,
 75        obj,
 76        # Defining depth
 77        n_layers=3,
 78        verbose=0,
 79        # CustomClassifier attributes
 80        n_hidden_features=5,
 81        activation_name="relu",
 82        a=0.01,
 83        nodes_sim="sobol",
 84        bias=True,
 85        dropout=0,
 86        direct_link=True,
 87        n_clusters=2,
 88        cluster_encode=True,
 89        type_clust="kmeans",
 90        type_scaling=("std", "std", "std"),
 91        col_sample=1,
 92        row_sample=1,
 93        cv_calibration=2,
 94        calibration_method="sigmoid",
 95        seed=123,
 96        backend="cpu",
 97    ):
 98        super().__init__(
 99            obj=obj,
100            n_hidden_features=n_hidden_features,
101            activation_name=activation_name,
102            a=a,
103            nodes_sim=nodes_sim,
104            bias=bias,
105            dropout=dropout,
106            direct_link=direct_link,
107            n_clusters=n_clusters,
108            cluster_encode=cluster_encode,
109            type_clust=type_clust,
110            type_scaling=type_scaling,
111            col_sample=col_sample,
112            row_sample=row_sample,
113            seed=seed,
114            backend=backend,
115        )
116        self.coef_ = None
117        self.intercept_ = None
118        self.type_fit = "classification"
119        self.cv_calibration = cv_calibration
120        self.calibration_method = calibration_method
121
122        # Only wrap in CalibratedClassifierCV if not already wrapped
123        # if not isinstance(obj, CalibratedClassifierCV):
124        #     self.obj = CalibratedClassifierCV(
125        #         self.obj,
126        #         cv=self.cv_calibration,
127        #         method=self.calibration_method
128        #     )
129        # else:
130        self.coef_ = None
131        self.intercept_ = None
132        self.type_fit = "classification"
133        self.cv_calibration = cv_calibration
134        self.calibration_method = calibration_method
135        self.obj = obj
136
137        assert n_layers >= 1, "must have n_layers >= 1"
138        self.stacked_obj = obj
139        self.verbose = verbose
140        self.n_layers = n_layers
141        self.classes_ = None
142        self.n_classes_ = None
143
144    def fit(self, X, y, **kwargs):
145        """Fit Classification algorithms to X and y.
146        Parameters
147        ----------
148        X : array-like,
149            Training vectors, where rows is the number of samples
150            and columns is the number of features.
151        y : array-like,
152            Training vectors, where rows is the number of samples
153            and columns is the number of features.
154        **kwargs: dict
155            Additional parameters to be passed to the fit method
156            of the base learner. For example, `sample_weight`.
157
158        Returns
159        -------
160        A fitted object
161        """
162
163        self.classes_ = np.unique(y)
164        self.n_classes_ = len(
165            self.classes_
166        )  # for compatibility with         scikit-learn
167
168        if isinstance(X, np.ndarray):
169            X = pd.DataFrame(X)
170
171        # init layer
172        self.stacked_obj = CustomClassifier(
173            obj=self.stacked_obj,
174            n_hidden_features=self.n_hidden_features,
175            activation_name=self.activation_name,
176            a=self.a,
177            nodes_sim=self.nodes_sim,
178            bias=self.bias,
179            dropout=self.dropout,
180            direct_link=self.direct_link,
181            n_clusters=self.n_clusters,
182            cluster_encode=self.cluster_encode,
183            type_clust=self.type_clust,
184            type_scaling=self.type_scaling,
185            col_sample=self.col_sample,
186            row_sample=self.row_sample,
187            cv_calibration=None,
188            calibration_method=None,
189            seed=self.seed,
190            backend=self.backend,
191        )
192
193        if self.verbose > 0:
194            iterator = tqdm(range(self.n_layers - 1))
195        else:
196            iterator = range(self.n_layers - 1)
197
198        for _ in iterator:
199            self.stacked_obj = deepcopy(
200                CustomClassifier(
201                    obj=self.stacked_obj,
202                    n_hidden_features=self.n_hidden_features,
203                    activation_name=self.activation_name,
204                    a=self.a,
205                    nodes_sim=self.nodes_sim,
206                    bias=self.bias,
207                    dropout=self.dropout,
208                    direct_link=self.direct_link,
209                    n_clusters=self.n_clusters,
210                    cluster_encode=self.cluster_encode,
211                    type_clust=self.type_clust,
212                    type_scaling=self.type_scaling,
213                    col_sample=self.col_sample,
214                    row_sample=self.row_sample,
215                    cv_calibration=None,
216                    calibration_method=None,
217                    seed=self.seed,
218                    backend=self.backend,
219                )
220            )
221            self.stacked_obj.fit(X, y, **kwargs)
222
223        return self
224
225    def partial_fit(self, X, y, **kwargs):
226        """Fit Regression algorithms to X and y.
227        Parameters
228        ----------
229        X : array-like,
230            Training vectors, where rows is the number of samples
231            and columns is the number of features.
232        y : array-like,
233            Training vectors, where rows is the number of samples
234            and columns is the number of features.
235        **kwargs: dict
236            Additional parameters to be passed to the fit method
237            of the base learner. For example, `sample_weight`.
238        Returns
239        -------
240        A fitted object
241        """
242        assert hasattr(self, "stacked_obj"), "model must be fitted first"
243        current_obj = self.stacked_obj
244        for _ in range(self.n_layers):
245            try:
246                input_X = current_obj.obj.cook_test_set(X)
247                current_obj.obj.partial_fit(input_X, y, **kwargs)
248                try:
249                    current_obj = current_obj.obj
250                except AttributeError:
251                    pass
252            except ValueError:
253                pass
254        return self
255
256    def predict(self, X):
257        return self.stacked_obj.predict(X)
258
259    def predict_proba(self, X):
260        return self.stacked_obj.predict_proba(X)
261
262    def score(self, X, y, scoring=None):
263        return self.stacked_obj.score(X, y, scoring)
264
265    def cross_val_optim(
266        self,
267        X_train,
268        y_train,
269        X_test=None,
270        y_test=None,
271        scoring="accuracy",
272        surrogate_obj=None,
273        cv=5,
274        n_jobs=None,
275        n_init=10,
276        n_iter=190,
277        abs_tol=1e-3,
278        verbose=2,
279        seed=123,
280        **kwargs,
281    ):
282        """Cross-validation function and hyperparameters' search
283
284        Parameters:
285
286            X_train: array-like,
287                Training vectors, where rows is the number of samples
288                and columns is the number of features.
289
290            y_train: array-like,
291                Training vectors, where rows is the number of samples
292                and columns is the number of features.
293
294            X_test: array-like,
295                Testing vectors, where rows is the number of samples
296                and columns is the number of features.
297
298            y_test: array-like,
299                Testing vectors, where rows is the number of samples
300                and columns is the number of features.
301
302            scoring: str
303                scoring metric; see https://scikit-learn.org/stable/modules/model_evaluation.html#the-scoring-parameter-defining-model-evaluation-rules
304
305            surrogate_obj: an object;
306                An ML model for estimating the uncertainty around the objective function
307
308            cv: int;
309                number of cross-validation folds
310
311            n_jobs: int;
312                number of jobs for parallel execution
313
314            n_init: an integer;
315                number of points in the initial setting, when `x_init` and `y_init` are not provided
316
317            n_iter: an integer;
318                number of iterations of the minimization algorithm
319
320            abs_tol: a float;
321                tolerance for convergence of the optimizer (early stopping based on acquisition function)
322
323            verbose: int
324                controls verbosity
325
326            seed: int
327                reproducibility seed
328
329            **kwargs: dict
330                additional parameters to be passed to the estimator
331
332        Examples:
333
334            ```python
335            ```
336        """
337
338        num_to_activation_name = {1: "relu", 2: "sigmoid", 3: "tanh"}
339        num_to_nodes_sim = {1: "sobol", 2: "uniform", 3: "hammersley"}
340        num_to_type_clust = {1: "kmeans", 2: "gmm"}
341
342        def deepclassifier_cv(
343            X_train,
344            y_train,
345            # Defining depth
346            n_layers=3,
347            # CustomClassifier attributes
348            n_hidden_features=5,
349            activation_name="relu",
350            nodes_sim="sobol",
351            dropout=0,
352            n_clusters=2,
353            type_clust="kmeans",
354            cv=5,
355            n_jobs=None,
356            scoring="accuracy",
357            seed=123,
358        ):
359            self.set_params(
360                **{
361                    "n_layers": n_layers,
362                    # CustomClassifier attributes
363                    "n_hidden_features": n_hidden_features,
364                    "activation_name": activation_name,
365                    "nodes_sim": nodes_sim,
366                    "dropout": dropout,
367                    "n_clusters": n_clusters,
368                    "type_clust": type_clust,
369                    **kwargs,
370                }
371            )
372            return -cross_val_score(
373                estimator=self,
374                X=X_train,
375                y=y_train,
376                scoring=scoring,
377                cv=cv,
378                n_jobs=n_jobs,
379                verbose=0,
380            ).mean()
381
382        # objective function for hyperparams tuning
383        def crossval_objective(xx):
384            return deepclassifier_cv(
385                X_train=X_train,
386                y_train=y_train,
387                # Defining depth
388                n_layers=int(np.ceil(xx[0])),
389                # CustomClassifier attributes
390                n_hidden_features=int(np.ceil(xx[1])),
391                activation_name=num_to_activation_name[np.ceil(xx[2])],
392                nodes_sim=num_to_nodes_sim[int(np.ceil(xx[3]))],
393                dropout=xx[4],
394                n_clusters=int(np.ceil(xx[5])),
395                type_clust=num_to_type_clust[int(np.ceil(xx[6]))],
396                cv=cv,
397                n_jobs=n_jobs,
398                scoring=scoring,
399                seed=seed,
400            )
401
402        if surrogate_obj is None:
403            gp_opt = gp.GPOpt(
404                objective_func=crossval_objective,
405                lower_bound=np.array([0, 3, 0, 0, 0.0, 0, 0]),
406                upper_bound=np.array([5, 100, 3, 3, 0.4, 5, 2]),
407                params_names=[
408                    "n_layers",
409                    # CustomClassifier attributes
410                    "n_hidden_features",
411                    "activation_name",
412                    "nodes_sim",
413                    "dropout",
414                    "n_clusters",
415                    "type_clust",
416                ],
417                method="bayesian",
418                n_init=n_init,
419                n_iter=n_iter,
420                seed=seed,
421            )
422        else:
423            gp_opt = gp.GPOpt(
424                objective_func=crossval_objective,
425                lower_bound=np.array([0, 3, 0, 0, 0.0, 0, 0]),
426                upper_bound=np.array([5, 100, 3, 3, 0.4, 5, 2]),
427                params_names=[
428                    "n_layers",
429                    # CustomClassifier attributes
430                    "n_hidden_features",
431                    "activation_name",
432                    "nodes_sim",
433                    "dropout",
434                    "n_clusters",
435                    "type_clust",
436                ],
437                acquisition="ucb",
438                method="splitconformal",
439                surrogate_obj=ns.PredictionInterval(
440                    obj=surrogate_obj, method="splitconformal"
441                ),
442                n_init=n_init,
443                n_iter=n_iter,
444                seed=seed,
445            )
446
447        res = gp_opt.optimize(verbose=verbose, abs_tol=abs_tol)
448        res.best_params["n_layers"] = int(np.ceil(res.best_params["n_layers"]))
449        res.best_params["n_hidden_features"] = int(
450            np.ceil(res.best_params["n_hidden_features"])
451        )
452        res.best_params["activation_name"] = num_to_activation_name[
453            np.ceil(res.best_params["activation_name"])
454        ]
455        res.best_params["nodes_sim"] = num_to_nodes_sim[
456            int(np.ceil(res.best_params["nodes_sim"]))
457        ]
458        res.best_params["dropout"] = res.best_params["dropout"]
459        res.best_params["n_clusters"] = int(
460            np.ceil(res.best_params["n_clusters"])
461        )
462        res.best_params["type_clust"] = num_to_type_clust[
463            int(np.ceil(res.best_params["type_clust"]))
464        ]
465
466        # out-of-sample error
467        if X_test is not None and y_test is not None:
468            self.set_params(**res.best_params, verbose=0, seed=seed)
469            preds = self.fit(X_train, y_train).predict(X_test)
470            # check error on y_test
471            oos_err = getattr(metrics, scoring + "_score")(
472                y_true=y_test, y_pred=preds
473            )
474            result = namedtuple("result", res._fields + ("test_" + scoring,))
475            return result(*res, oos_err)
476        else:
477            return res
478
479    def lazy_cross_val_optim(
480        self,
481        X_train,
482        y_train,
483        X_test=None,
484        y_test=None,
485        scoring="accuracy",
486        surrogate_objs=None,
487        customize=False,
488        cv=5,
489        n_jobs=None,
490        n_init=10,
491        n_iter=190,
492        abs_tol=1e-3,
493        verbose=1,
494        seed=123,
495    ):
496        """Automated Cross-validation function and hyperparameters' search using multiple surrogates
497
498        Parameters:
499
500            X_train: array-like,
501                Training vectors, where rows is the number of samples
502                and columns is the number of features.
503
504            y_train: array-like,
505                Training vectors, where rows is the number of samples
506                and columns is the number of features.
507
508            X_test: array-like,
509                Testing vectors, where rows is the number of samples
510                and columns is the number of features.
511
512            y_test: array-like,
513                Testing vectors, where rows is the number of samples
514                and columns is the number of features.
515
516            scoring: str
517                scoring metric; see https://scikit-learn.org/stable/modules/model_evaluation.html#the-scoring-parameter-defining-model-evaluation-rules
518
519            surrogate_objs: object names as a list of strings;
520                ML models for estimating the uncertainty around the objective function
521
522            customize: boolean
523                if True, the surrogate is transformed into a quasi-randomized network (default is False)
524
525            cv: int;
526                number of cross-validation folds
527
528            n_jobs: int;
529                number of jobs for parallel execution
530
531            n_init: an integer;
532                number of points in the initial setting, when `x_init` and `y_init` are not provided
533
534            n_iter: an integer;
535                number of iterations of the minimization algorithm
536
537            abs_tol: a float;
538                tolerance for convergence of the optimizer (early stopping based on acquisition function)
539
540            verbose: int
541                controls verbosity
542
543            seed: int
544                reproducibility seed
545
546        Examples:
547
548            ```python
549            ```
550        """
551
552        removed_regressors = [
553            "TheilSenRegressor",
554            "ARDRegression",
555            "CCA",
556            "GaussianProcessRegressor",
557            "GradientBoostingRegressor",
558            "HistGradientBoostingRegressor",
559            "IsotonicRegression",
560            "MultiOutputRegressor",
561            "MultiTaskElasticNet",
562            "MultiTaskElasticNetCV",
563            "MultiTaskLasso",
564            "MultiTaskLassoCV",
565            "OrthogonalMatchingPursuit",
566            "OrthogonalMatchingPursuitCV",
567            "PLSCanonical",
568            "PLSRegression",
569            "RadiusNeighborsRegressor",
570            "RegressorChain",
571            "StackingRegressor",
572            "VotingRegressor",
573        ]
574
575        results = []
576
577        for est in all_estimators():
578            if surrogate_objs is None:
579                if issubclass(est[1], RegressorMixin) and (
580                    est[0] not in removed_regressors
581                ):
582                    try:
583                        if customize == True:
584                            surr_obj = ns.CustomClassifier(obj=est[1]())
585                        else:
586                            surr_obj = est[1]()
587                        res = self.cross_val_optim(
588                            X_train=X_train,
589                            y_train=y_train,
590                            X_test=X_test,
591                            y_test=y_test,
592                            surrogate_obj=surr_obj,
593                            cv=cv,
594                            n_jobs=n_jobs,
595                            scoring=scoring,
596                            n_init=n_init,
597                            n_iter=n_iter,
598                            abs_tol=abs_tol,
599                            verbose=verbose,
600                            seed=seed,
601                        )
602                        if customize == True:
603                            results.append((f"CustomClassifier({est[0]})", res))
604                        else:
605                            results.append((est[0], res))
606                    except:
607                        pass
608
609            else:
610                if (
611                    issubclass(est[1], RegressorMixin)
612                    and (est[0] not in removed_regressors)
613                    and est[0] in surrogate_objs
614                ):
615                    try:
616                        if customize == True:
617                            surr_obj = ns.CustomClassifier(obj=est[1]())
618                        else:
619                            surr_obj = est[1]()
620                        res = self.cross_val_optim(
621                            X_train=X_train,
622                            y_train=y_train,
623                            X_test=X_test,
624                            y_test=y_test,
625                            surrogate_obj=surr_obj,
626                            cv=cv,
627                            n_jobs=n_jobs,
628                            scoring=scoring,
629                            n_init=n_init,
630                            n_iter=n_iter,
631                            abs_tol=abs_tol,
632                            verbose=verbose,
633                            seed=seed,
634                        )
635                        if customize == True:
636                            results.append((f"CustomClassifier({est[0]})", res))
637                        else:
638                            results.append((est[0], res))
639                    except:
640                        pass
641
642        return results
643
644    @property
645    def _estimator_type(self):
646        return "classifier"

Deep Classifier

Parameters:

obj: an object
    A base learner, see also https://www.researchgate.net/publication/380701207_Deep_Quasi-Randomized_neural_Networks_for_classification

n_layers: int (default=3)
    Number of layers. `n_layers = 1` is a simple `CustomClassifier`

verbose : int, optional (default=0)
    Monitor progress when fitting.

All the other parameters are nnetsauce `CustomClassifier`'s

Examples:

import nnetsauce as ns
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegressionCV
data = load_breast_cancer()
X = data.data
y= data.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.2, random_state=123)
obj = LogisticRegressionCV()
clf = ns.DeepClassifier(obj)
clf.fit(X_train, y_train)
print(clf.score(clf.predict(X_test), y_test))
def fit(self, X, y, **kwargs):
144    def fit(self, X, y, **kwargs):
145        """Fit Classification algorithms to X and y.
146        Parameters
147        ----------
148        X : array-like,
149            Training vectors, where rows is the number of samples
150            and columns is the number of features.
151        y : array-like,
152            Training vectors, where rows is the number of samples
153            and columns is the number of features.
154        **kwargs: dict
155            Additional parameters to be passed to the fit method
156            of the base learner. For example, `sample_weight`.
157
158        Returns
159        -------
160        A fitted object
161        """
162
163        self.classes_ = np.unique(y)
164        self.n_classes_ = len(
165            self.classes_
166        )  # for compatibility with         scikit-learn
167
168        if isinstance(X, np.ndarray):
169            X = pd.DataFrame(X)
170
171        # init layer
172        self.stacked_obj = CustomClassifier(
173            obj=self.stacked_obj,
174            n_hidden_features=self.n_hidden_features,
175            activation_name=self.activation_name,
176            a=self.a,
177            nodes_sim=self.nodes_sim,
178            bias=self.bias,
179            dropout=self.dropout,
180            direct_link=self.direct_link,
181            n_clusters=self.n_clusters,
182            cluster_encode=self.cluster_encode,
183            type_clust=self.type_clust,
184            type_scaling=self.type_scaling,
185            col_sample=self.col_sample,
186            row_sample=self.row_sample,
187            cv_calibration=None,
188            calibration_method=None,
189            seed=self.seed,
190            backend=self.backend,
191        )
192
193        if self.verbose > 0:
194            iterator = tqdm(range(self.n_layers - 1))
195        else:
196            iterator = range(self.n_layers - 1)
197
198        for _ in iterator:
199            self.stacked_obj = deepcopy(
200                CustomClassifier(
201                    obj=self.stacked_obj,
202                    n_hidden_features=self.n_hidden_features,
203                    activation_name=self.activation_name,
204                    a=self.a,
205                    nodes_sim=self.nodes_sim,
206                    bias=self.bias,
207                    dropout=self.dropout,
208                    direct_link=self.direct_link,
209                    n_clusters=self.n_clusters,
210                    cluster_encode=self.cluster_encode,
211                    type_clust=self.type_clust,
212                    type_scaling=self.type_scaling,
213                    col_sample=self.col_sample,
214                    row_sample=self.row_sample,
215                    cv_calibration=None,
216                    calibration_method=None,
217                    seed=self.seed,
218                    backend=self.backend,
219                )
220            )
221            self.stacked_obj.fit(X, y, **kwargs)
222
223        return self

Fit Classification algorithms to X and y.

Parameters

X : array-like, Training vectors, where rows is the number of samples and columns is the number of features. y : array-like, Training vectors, where rows is the number of samples and columns is the number of features. **kwargs: dict Additional parameters to be passed to the fit method of the base learner. For example, sample_weight.

Returns

A fitted object

def predict(self, X):
256    def predict(self, X):
257        return self.stacked_obj.predict(X)

Predict test data X.

Parameters:

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features.

**kwargs: additional parameters to be passed to
        self.cook_test_set

Returns:

model predictions: {array-like}
def predict_proba(self, X):
259    def predict_proba(self, X):
260        return self.stacked_obj.predict_proba(X)

Predict probabilities for test data X.

Args:

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features.

**kwargs: additional parameters to be passed to
        self.cook_test_set

Returns:

probability estimates for test data: {array-like}
def score(self, X, y, scoring=None):
262    def score(self, X, y, scoring=None):
263        return self.stacked_obj.score(X, y, scoring)

Scoring function for classification.

Args:

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features.

y: array-like, shape = [n_samples]
    Target values.

scoring: str
    scoring method (default is accuracy)

Returns:

score: float
class DeepRegressor(nnetsauce.CustomRegressor, sklearn.base.RegressorMixin):
 13class DeepRegressor(CustomRegressor, RegressorMixin):
 14    """
 15    Deep Regressor
 16
 17    Parameters:
 18
 19        obj: an object
 20            A base learner, see also https://www.researchgate.net/publication/380701207_Deep_Quasi-Randomized_neural_Networks_for_classification
 21
 22        verbose : int, optional (default=0)
 23            Monitor progress when fitting.
 24
 25        n_layers: int (default=2)
 26            Number of layers. `n_layers = 1` is a simple `CustomRegressor`
 27
 28        All the other parameters are nnetsauce `CustomRegressor`'s
 29
 30    Examples:
 31
 32        ```python
 33        import nnetsauce as ns
 34        from sklearn.datasets import load_diabetes
 35        from sklearn.model_selection import train_test_split
 36        from sklearn.linear_model import RidgeCV
 37        data = load_diabetes()
 38        X = data.data
 39        y= data.target
 40        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.2, random_state=123)
 41        obj = RidgeCV()
 42        clf = ns.DeepRegressor(obj)
 43        clf.fit(X_train, y_train)
 44        print(clf.score(clf.predict(X_test), y_test))
 45        ```
 46
 47    """
 48
 49    def __init__(
 50        self,
 51        obj,
 52        # Defining depth
 53        n_layers=2,
 54        verbose=0,
 55        # CustomRegressor attributes
 56        n_hidden_features=5,
 57        activation_name="relu",
 58        a=0.01,
 59        nodes_sim="sobol",
 60        bias=True,
 61        dropout=0,
 62        direct_link=True,
 63        n_clusters=2,
 64        cluster_encode=True,
 65        type_clust="kmeans",
 66        type_scaling=("std", "std", "std"),
 67        col_sample=1,
 68        row_sample=1,
 69        level=None,
 70        pi_method="splitconformal",
 71        seed=123,
 72        backend="cpu",
 73    ):
 74        super().__init__(
 75            obj=obj,
 76            n_hidden_features=n_hidden_features,
 77            activation_name=activation_name,
 78            a=a,
 79            nodes_sim=nodes_sim,
 80            bias=bias,
 81            dropout=dropout,
 82            direct_link=direct_link,
 83            n_clusters=n_clusters,
 84            cluster_encode=cluster_encode,
 85            type_clust=type_clust,
 86            type_scaling=type_scaling,
 87            col_sample=col_sample,
 88            row_sample=row_sample,
 89            level=level,
 90            pi_method=pi_method,
 91            seed=seed,
 92            backend=backend,
 93        )
 94
 95        assert n_layers >= 1, "must have n_layers >= 1"
 96
 97        self.stacked_obj = deepcopy(obj)
 98        self.verbose = verbose
 99        self.n_layers = n_layers
100        self.level = level
101        self.pi_method = pi_method
102        self.coef_ = None
103
104    def fit(self, X, y, **kwargs):
105        """Fit Regression algorithms to X and y.
106        Parameters
107        ----------
108        X : array-like,
109            Training vectors, where rows is the number of samples
110            and columns is the number of features.
111        y : array-like,
112            Training vectors, where rows is the number of samples
113            and columns is the number of features.
114        **kwargs: dict
115            Additional parameters to be passed to the fit method
116            of the base learner. For example, `sample_weight`.
117        Returns
118        -------
119        A fitted object
120        """
121
122        if isinstance(X, np.ndarray):
123            X = pd.DataFrame(X)
124
125        # init layer
126        self.stacked_obj = CustomRegressor(
127            obj=self.stacked_obj,
128            n_hidden_features=self.n_hidden_features,
129            activation_name=self.activation_name,
130            a=self.a,
131            nodes_sim=self.nodes_sim,
132            bias=self.bias,
133            dropout=self.dropout,
134            direct_link=self.direct_link,
135            n_clusters=self.n_clusters,
136            cluster_encode=self.cluster_encode,
137            type_clust=self.type_clust,
138            type_scaling=self.type_scaling,
139            col_sample=self.col_sample,
140            row_sample=self.row_sample,
141            seed=self.seed,
142            backend=self.backend,
143        )
144
145        if self.verbose > 0:
146            iterator = tqdm(range(self.n_layers - 1))
147        else:
148            iterator = range(self.n_layers - 1)
149
150        for _ in iterator:
151            self.stacked_obj = deepcopy(
152                CustomRegressor(
153                    obj=self.stacked_obj,
154                    n_hidden_features=self.n_hidden_features,
155                    activation_name=self.activation_name,
156                    a=self.a,
157                    nodes_sim=self.nodes_sim,
158                    bias=self.bias,
159                    dropout=self.dropout,
160                    direct_link=self.direct_link,
161                    n_clusters=self.n_clusters,
162                    cluster_encode=self.cluster_encode,
163                    type_clust=self.type_clust,
164                    type_scaling=self.type_scaling,
165                    col_sample=self.col_sample,
166                    row_sample=self.row_sample,
167                    seed=self.seed,
168                    backend=self.backend,
169                )
170            )
171
172        self.stacked_obj.fit(X, y, **kwargs)
173
174        if self.level is not None:
175            self.stacked_obj = PredictionInterval(
176                obj=self.stacked_obj, method=self.pi_method, level=self.level
177            )
178
179        if hasattr(self.stacked_obj, "clustering_obj_"):
180            self.clustering_obj_ = self.stacked_obj.clustering_obj_
181
182        if hasattr(self.stacked_obj, "coef_"):
183            self.coef_ = self.stacked_obj.coef_
184
185        if hasattr(self.stacked_obj, "scaler_"):
186            self.scaler_ = self.stacked_obj.scaler_
187
188        if hasattr(self.stacked_obj, "nn_scaler_"):
189            self.nn_scaler_ = self.stacked_obj.nn_scaler_
190
191        if hasattr(self.stacked_obj, "clustering_scaler_"):
192            self.clustering_scaler_ = self.stacked_obj.clustering_scaler_
193
194        return self
195
196    def partial_fit(self, X, y, **kwargs):
197        """Fit Regression algorithms to X and y.
198        Parameters
199        ----------
200        X : array-like,
201            Training vectors, where rows is the number of samples
202            and columns is the number of features.
203        y : array-like,
204            Training vectors, where rows is the number of samples
205            and columns is the number of features.
206        **kwargs: dict
207            Additional parameters to be passed to the fit method
208            of the base learner. For example, `sample_weight`.
209        Returns
210        -------
211        A fitted object
212        """
213        assert hasattr(self, "stacked_obj"), "model must be fitted first"
214        current_obj = self.stacked_obj
215        for _ in range(self.n_layers):
216            try:
217                input_X = current_obj.obj.cook_test_set(X)
218                current_obj.obj.partial_fit(input_X, y, **kwargs)
219                try:
220                    current_obj = current_obj.obj
221                except AttributeError:
222                    pass
223            except ValueError as e:
224                print(e)
225                pass
226        return self
227
228    def predict(self, X, **kwargs):
229        if self.level is not None:
230            return self.stacked_obj.predict(X, return_pi=True)
231        return self.stacked_obj.predict(X, **kwargs)
232
233    def score(self, X, y, scoring=None):
234        return self.stacked_obj.score(X, y, scoring)

Deep Regressor

Parameters:

obj: an object
    A base learner, see also https://www.researchgate.net/publication/380701207_Deep_Quasi-Randomized_neural_Networks_for_classification

verbose : int, optional (default=0)
    Monitor progress when fitting.

n_layers: int (default=2)
    Number of layers. `n_layers = 1` is a simple `CustomRegressor`

All the other parameters are nnetsauce `CustomRegressor`'s

Examples:

import nnetsauce as ns
from sklearn.datasets import load_diabetes
from sklearn.model_selection import train_test_split
from sklearn.linear_model import RidgeCV
data = load_diabetes()
X = data.data
y= data.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.2, random_state=123)
obj = RidgeCV()
clf = ns.DeepRegressor(obj)
clf.fit(X_train, y_train)
print(clf.score(clf.predict(X_test), y_test))
def fit(self, X, y, **kwargs):
104    def fit(self, X, y, **kwargs):
105        """Fit Regression algorithms to X and y.
106        Parameters
107        ----------
108        X : array-like,
109            Training vectors, where rows is the number of samples
110            and columns is the number of features.
111        y : array-like,
112            Training vectors, where rows is the number of samples
113            and columns is the number of features.
114        **kwargs: dict
115            Additional parameters to be passed to the fit method
116            of the base learner. For example, `sample_weight`.
117        Returns
118        -------
119        A fitted object
120        """
121
122        if isinstance(X, np.ndarray):
123            X = pd.DataFrame(X)
124
125        # init layer
126        self.stacked_obj = CustomRegressor(
127            obj=self.stacked_obj,
128            n_hidden_features=self.n_hidden_features,
129            activation_name=self.activation_name,
130            a=self.a,
131            nodes_sim=self.nodes_sim,
132            bias=self.bias,
133            dropout=self.dropout,
134            direct_link=self.direct_link,
135            n_clusters=self.n_clusters,
136            cluster_encode=self.cluster_encode,
137            type_clust=self.type_clust,
138            type_scaling=self.type_scaling,
139            col_sample=self.col_sample,
140            row_sample=self.row_sample,
141            seed=self.seed,
142            backend=self.backend,
143        )
144
145        if self.verbose > 0:
146            iterator = tqdm(range(self.n_layers - 1))
147        else:
148            iterator = range(self.n_layers - 1)
149
150        for _ in iterator:
151            self.stacked_obj = deepcopy(
152                CustomRegressor(
153                    obj=self.stacked_obj,
154                    n_hidden_features=self.n_hidden_features,
155                    activation_name=self.activation_name,
156                    a=self.a,
157                    nodes_sim=self.nodes_sim,
158                    bias=self.bias,
159                    dropout=self.dropout,
160                    direct_link=self.direct_link,
161                    n_clusters=self.n_clusters,
162                    cluster_encode=self.cluster_encode,
163                    type_clust=self.type_clust,
164                    type_scaling=self.type_scaling,
165                    col_sample=self.col_sample,
166                    row_sample=self.row_sample,
167                    seed=self.seed,
168                    backend=self.backend,
169                )
170            )
171
172        self.stacked_obj.fit(X, y, **kwargs)
173
174        if self.level is not None:
175            self.stacked_obj = PredictionInterval(
176                obj=self.stacked_obj, method=self.pi_method, level=self.level
177            )
178
179        if hasattr(self.stacked_obj, "clustering_obj_"):
180            self.clustering_obj_ = self.stacked_obj.clustering_obj_
181
182        if hasattr(self.stacked_obj, "coef_"):
183            self.coef_ = self.stacked_obj.coef_
184
185        if hasattr(self.stacked_obj, "scaler_"):
186            self.scaler_ = self.stacked_obj.scaler_
187
188        if hasattr(self.stacked_obj, "nn_scaler_"):
189            self.nn_scaler_ = self.stacked_obj.nn_scaler_
190
191        if hasattr(self.stacked_obj, "clustering_scaler_"):
192            self.clustering_scaler_ = self.stacked_obj.clustering_scaler_
193
194        return self

Fit Regression algorithms to X and y.

Parameters

X : array-like, Training vectors, where rows is the number of samples and columns is the number of features. y : array-like, Training vectors, where rows is the number of samples and columns is the number of features. **kwargs: dict Additional parameters to be passed to the fit method of the base learner. For example, sample_weight.

Returns

A fitted object

def predict(self, X, **kwargs):
228    def predict(self, X, **kwargs):
229        if self.level is not None:
230            return self.stacked_obj.predict(X, return_pi=True)
231        return self.stacked_obj.predict(X, **kwargs)

Predict test data X.

Parameters:

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features.

level: int
    Level of confidence (default = 95)

method: str
    'splitconformal', 'localconformal'
    prediction (if you specify `return_pi = True`)

**kwargs: additional parameters
        `return_pi = True` for conformal prediction,
        with `method` in ('splitconformal', 'localconformal')
        or `return_std = True` for `self.obj` in
        (`sklearn.linear_model.BayesianRidge`,
        `sklearn.linear_model.ARDRegressor`,
        `sklearn.gaussian_process.GaussianProcessRegressor`)`

Returns:

model predictions:
    an array if uncertainty quantification is not requested,
      or a tuple if with prediction intervals and simulations
      if `return_std = True` (mean, standard deviation,
      lower and upper prediction interval) or `return_pi = True`
      ()
def score(self, X, y, scoring=None):
233    def score(self, X, y, scoring=None):
234        return self.stacked_obj.score(X, y, scoring)

Compute the score of the model.

Parameters:

X: {array-like}, shape = [n_samples, n_features]
    Training vectors, where n_samples is the number
    of samples and n_features is the number of features.

y: array-like, shape = [n_samples]
    Target values.

scoring: str
    scoring method

Returns:

score: float
class DeepMTS(nnetsauce.MTS):
 11class DeepMTS(MTS):
 12    """Univariate and multivariate time series (DeepMTS) forecasting with Quasi-Randomized networks (Work in progress)
 13
 14    Parameters:
 15
 16        obj: object.
 17            any object containing a method fit (obj.fit()) and a method predict
 18            (obj.predict()).
 19
 20        n_layers: int.
 21            number of layers in the neural network.
 22
 23        n_hidden_features: int.
 24            number of nodes in the hidden layer.
 25
 26        activation_name: str.
 27            activation function: 'relu', 'tanh', 'sigmoid', 'prelu' or 'elu'.
 28
 29        a: float.
 30            hyperparameter for 'prelu' or 'elu' activation function.
 31
 32        nodes_sim: str.
 33            type of simulation for the nodes: 'sobol', 'hammersley', 'halton',
 34            'uniform'.
 35
 36        bias: boolean.
 37            indicates if the hidden layer contains a bias term (True) or not
 38            (False).
 39
 40        dropout: float.
 41            regularization parameter; (random) percentage of nodes dropped out
 42            of the training.
 43
 44        direct_link: boolean.
 45            indicates if the original predictors are included (True) in model's fitting or not (False).
 46
 47        n_clusters: int.
 48            number of clusters for 'kmeans' or 'gmm' clustering (could be 0: no clustering).
 49
 50        cluster_encode: bool.
 51            defines how the variable containing clusters is treated (default is one-hot)
 52            if `False`, then labels are used, without one-hot encoding.
 53
 54        type_clust: str.
 55            type of clustering method: currently k-means ('kmeans') or Gaussian
 56            Mixture Model ('gmm').
 57
 58        type_scaling: a tuple of 3 strings.
 59            scaling methods for inputs, hidden layer, and clustering respectively
 60            (and when relevant).
 61            Currently available: standardization ('std') or MinMax scaling ('minmax').
 62
 63        lags: int.
 64            number of lags used for each time series.
 65
 66        type_pi: str.
 67            type of prediction interval; currently:
 68            - "gaussian": simple, fast, but: assumes stationarity of Gaussian in-sample residuals and independence in the multivariate case
 69            - "kde": based on Kernel Density Estimation of in-sample residuals
 70            - "bootstrap": based on independent bootstrap of in-sample residuals
 71            - "block-bootstrap": based on basic block bootstrap of in-sample residuals
 72            - "scp-kde": Sequential split conformal prediction with Kernel Density Estimation of calibrated residuals
 73            - "scp-bootstrap": Sequential split conformal prediction with independent bootstrap of calibrated residuals
 74            - "scp-block-bootstrap": Sequential split conformal prediction with basic block bootstrap of calibrated residuals
 75            - "scp2-kde": Sequential split conformal prediction with Kernel Density Estimation of standardized calibrated residuals
 76            - "scp2-bootstrap": Sequential split conformal prediction with independent bootstrap of standardized calibrated residuals
 77            - "scp2-block-bootstrap": Sequential split conformal prediction with basic block bootstrap of standardized calibrated residuals
 78
 79        block_size: int.
 80            size of block for 'type_pi' in ("block-bootstrap", "scp-block-bootstrap", "scp2-block-bootstrap").
 81            Default is round(3.15*(n_residuals^1/3))
 82
 83        replications: int.
 84            number of replications (if needed, for predictive simulation). Default is 'None'.
 85
 86        kernel: str.
 87            the kernel to use for residuals density estimation (used for predictive simulation). Currently, either 'gaussian' or 'tophat'.
 88
 89        agg: str.
 90            either "mean" or "median" for simulation of bootstrap aggregating
 91
 92        seed: int.
 93            reproducibility seed for nodes_sim=='uniform' or predictive simulation.
 94
 95        backend: str.
 96            "cpu" or "gpu" or "tpu".
 97
 98        verbose: int.
 99            0: not printing; 1: printing
100
101        show_progress: bool.
102            True: progress bar when fitting each series; False: no progress bar when fitting each series
103
104    Attributes:
105
106        fit_objs_: dict
107            objects adjusted to each individual time series
108
109        y_: {array-like}
110            DeepMTS responses (most recent observations first)
111
112        X_: {array-like}
113            DeepMTS lags
114
115        xreg_: {array-like}
116            external regressors
117
118        y_means_: dict
119            a dictionary of each series mean values
120
121        preds_: {array-like}
122            successive model predictions
123
124        preds_std_: {array-like}
125            standard deviation around the predictions
126
127        return_std_: boolean
128            return uncertainty or not (set in predict)
129
130        df_: data frame
131            the input data frame, in case a data.frame is provided to `fit`
132
133    Examples:
134
135    Example 1:
136
137        ```python
138        import nnetsauce as ns
139        import numpy as np
140        from sklearn import linear_model
141        np.random.seed(123)
142
143        M = np.random.rand(10, 3)
144        M[:,0] = 10*M[:,0]
145        M[:,2] = 25*M[:,2]
146        print(M)
147
148        # Adjust Bayesian Ridge
149        regr4 = linear_model.BayesianRidge()
150        obj_DeepMTS = ns.DeepMTS(regr4, lags = 1, n_hidden_features=5)
151        obj_DeepMTS.fit(M)
152        print(obj_DeepMTS.predict())
153
154        # with credible intervals
155        print(obj_DeepMTS.predict(return_std=True, level=80))
156
157        print(obj_DeepMTS.predict(return_std=True, level=95))
158        ```
159
160    Example 2:
161
162        ```python
163        import nnetsauce as ns
164        import numpy as np
165        from sklearn import linear_model
166
167        dataset = {
168        'date' : ['2001-01-01', '2002-01-01', '2003-01-01', '2004-01-01', '2005-01-01'],
169        'series1' : [34, 30, 35.6, 33.3, 38.1],
170        'series2' : [4, 5.5, 5.6, 6.3, 5.1],
171        'series3' : [100, 100.5, 100.6, 100.2, 100.1]}
172        df = pd.DataFrame(dataset).set_index('date')
173        print(df)
174
175        # Adjust Bayesian Ridge
176        regr5 = linear_model.BayesianRidge()
177        obj_DeepMTS = ns.DeepMTS(regr5, lags = 1, n_hidden_features=5)
178        obj_DeepMTS.fit(df)
179        print(obj_DeepMTS.predict())
180
181        # with credible intervals
182        print(obj_DeepMTS.predict(return_std=True, level=80))
183
184        print(obj_DeepMTS.predict(return_std=True, level=95))
185        ```
186
187    """
188
189    # construct the object -----
190
191    def __init__(
192        self,
193        obj,
194        n_layers=3,
195        n_hidden_features=5,
196        activation_name="relu",
197        a=0.01,
198        nodes_sim="sobol",
199        bias=True,
200        dropout=0,
201        direct_link=True,
202        n_clusters=2,
203        cluster_encode=True,
204        type_clust="kmeans",
205        type_scaling=("std", "std", "std"),
206        lags=1,
207        type_pi="kde",
208        block_size=None,
209        replications=None,
210        kernel=None,
211        agg="mean",
212        seed=123,
213        backend="cpu",
214        verbose=0,
215        show_progress=True,
216    ):
217        assert int(lags) == lags, "parameter 'lags' should be an integer"
218        assert n_layers >= 1, "must have n_layers >= 1"
219        self.n_layers = int(n_layers)
220
221        if self.n_layers > 1:
222            for _ in range(self.n_layers - 1):
223                obj = CustomRegressor(
224                    obj=deepcopy(obj),
225                    n_hidden_features=n_hidden_features,
226                    activation_name=activation_name,
227                    a=a,
228                    nodes_sim=nodes_sim,
229                    bias=bias,
230                    dropout=dropout,
231                    direct_link=direct_link,
232                    n_clusters=n_clusters,
233                    cluster_encode=cluster_encode,
234                    type_clust=type_clust,
235                    type_scaling=type_scaling,
236                    seed=seed,
237                    backend=backend,
238                )
239
240        self.obj = deepcopy(obj)
241        super().__init__(
242            obj=self.obj,
243            n_hidden_features=n_hidden_features,
244            activation_name=activation_name,
245            a=a,
246            nodes_sim=nodes_sim,
247            bias=bias,
248            dropout=dropout,
249            direct_link=direct_link,
250            n_clusters=n_clusters,
251            cluster_encode=cluster_encode,
252            type_clust=type_clust,
253            type_scaling=type_scaling,
254            lags=lags,
255            type_pi=type_pi,
256            block_size=block_size,
257            replications=replications,
258            kernel=kernel,
259            agg=agg,
260            seed=seed,
261            backend=backend,
262            verbose=verbose,
263            show_progress=show_progress,
264        )

Univariate and multivariate time series (DeepMTS) forecasting with Quasi-Randomized networks (Work in progress)

Parameters:

obj: object.
    any object containing a method fit (obj.fit()) and a method predict
    (obj.predict()).

n_layers: int.
    number of layers in the neural network.

n_hidden_features: int.
    number of nodes in the hidden layer.

activation_name: str.
    activation function: 'relu', 'tanh', 'sigmoid', 'prelu' or 'elu'.

a: float.
    hyperparameter for 'prelu' or 'elu' activation function.

nodes_sim: str.
    type of simulation for the nodes: 'sobol', 'hammersley', 'halton',
    'uniform'.

bias: boolean.
    indicates if the hidden layer contains a bias term (True) or not
    (False).

dropout: float.
    regularization parameter; (random) percentage of nodes dropped out
    of the training.

direct_link: boolean.
    indicates if the original predictors are included (True) in model's fitting or not (False).

n_clusters: int.
    number of clusters for 'kmeans' or 'gmm' clustering (could be 0: no clustering).

cluster_encode: bool.
    defines how the variable containing clusters is treated (default is one-hot)
    if `False`, then labels are used, without one-hot encoding.

type_clust: str.
    type of clustering method: currently k-means ('kmeans') or Gaussian
    Mixture Model ('gmm').

type_scaling: a tuple of 3 strings.
    scaling methods for inputs, hidden layer, and clustering respectively
    (and when relevant).
    Currently available: standardization ('std') or MinMax scaling ('minmax').

lags: int.
    number of lags used for each time series.

type_pi: str.
    type of prediction interval; currently:
    - "gaussian": simple, fast, but: assumes stationarity of Gaussian in-sample residuals and independence in the multivariate case
    - "kde": based on Kernel Density Estimation of in-sample residuals
    - "bootstrap": based on independent bootstrap of in-sample residuals
    - "block-bootstrap": based on basic block bootstrap of in-sample residuals
    - "scp-kde": Sequential split conformal prediction with Kernel Density Estimation of calibrated residuals
    - "scp-bootstrap": Sequential split conformal prediction with independent bootstrap of calibrated residuals
    - "scp-block-bootstrap": Sequential split conformal prediction with basic block bootstrap of calibrated residuals
    - "scp2-kde": Sequential split conformal prediction with Kernel Density Estimation of standardized calibrated residuals
    - "scp2-bootstrap": Sequential split conformal prediction with independent bootstrap of standardized calibrated residuals
    - "scp2-block-bootstrap": Sequential split conformal prediction with basic block bootstrap of standardized calibrated residuals

block_size: int.
    size of block for 'type_pi' in ("block-bootstrap", "scp-block-bootstrap", "scp2-block-bootstrap").
    Default is round(3.15*(n_residuals^1/3))

replications: int.
    number of replications (if needed, for predictive simulation). Default is 'None'.

kernel: str.
    the kernel to use for residuals density estimation (used for predictive simulation). Currently, either 'gaussian' or 'tophat'.

agg: str.
    either "mean" or "median" for simulation of bootstrap aggregating

seed: int.
    reproducibility seed for nodes_sim=='uniform' or predictive simulation.

backend: str.
    "cpu" or "gpu" or "tpu".

verbose: int.
    0: not printing; 1: printing

show_progress: bool.
    True: progress bar when fitting each series; False: no progress bar when fitting each series

Attributes:

fit_objs_: dict
    objects adjusted to each individual time series

y_: {array-like}
    DeepMTS responses (most recent observations first)

X_: {array-like}
    DeepMTS lags

xreg_: {array-like}
    external regressors

y_means_: dict
    a dictionary of each series mean values

preds_: {array-like}
    successive model predictions

preds_std_: {array-like}
    standard deviation around the predictions

return_std_: boolean
    return uncertainty or not (set in predict)

df_: data frame
    the input data frame, in case a data.frame is provided to `fit`

Examples:

Example 1:

import nnetsauce as ns
import numpy as np
from sklearn import linear_model
np.random.seed(123)

M = np.random.rand(10, 3)
M[:,0] = 10*M[:,0]
M[:,2] = 25*M[:,2]
print(M)

# Adjust Bayesian Ridge
regr4 = linear_model.BayesianRidge()
obj_DeepMTS = ns.DeepMTS(regr4, lags = 1, n_hidden_features=5)
obj_DeepMTS.fit(M)
print(obj_DeepMTS.predict())

# with credible intervals
print(obj_DeepMTS.predict(return_std=True, level=80))

print(obj_DeepMTS.predict(return_std=True, level=95))

Example 2:

import nnetsauce as ns
import numpy as np
from sklearn import linear_model

dataset = {
'date' : ['2001-01-01', '2002-01-01', '2003-01-01', '2004-01-01', '2005-01-01'],
'series1' : [34, 30, 35.6, 33.3, 38.1],
'series2' : [4, 5.5, 5.6, 6.3, 5.1],
'series3' : [100, 100.5, 100.6, 100.2, 100.1]}
df = pd.DataFrame(dataset).set_index('date')
print(df)

# Adjust Bayesian Ridge
regr5 = linear_model.BayesianRidge()
obj_DeepMTS = ns.DeepMTS(regr5, lags = 1, n_hidden_features=5)
obj_DeepMTS.fit(df)
print(obj_DeepMTS.predict())

# with credible intervals
print(obj_DeepMTS.predict(return_std=True, level=80))

print(obj_DeepMTS.predict(return_std=True, level=95))
class DiscreteTokenMTS(nnetsauce.MTS):
 12class DiscreteTokenMTS(MTS):
 13    """
 14    MTS for discrete token forecasting via nearest-neighbor in embedding space.
 15
 16    Maps continuous predictions to discrete tokens using nearest-neighbor lookup
 17    in a vocabulary (embedding space). Supports probabilistic decoding with
 18    temperature-controlled softmax and uncertainty quantification in token space.
 19
 20    Parameters
 21    ----------
 22    obj : object
 23        Base learner with fit() and predict() methods
 24
 25    vocab : np.ndarray of shape (vocab_size, n_series)
 26        Token vocabulary - each row is a token embedding vector
 27
 28    metric : {'euclidean', 'cosine'}, default='euclidean'
 29        Distance metric for nearest-neighbor lookup
 30
 31    return_mode : {'token_id', 'token_vector', 'both', 'probs'}, default='token_id'
 32        Output format:
 33        - 'token_id': integer token indices
 34        - 'token_vector': token embedding vectors
 35        - 'both': single DataFrame with token_id + dimensions
 36        - 'probs': probability distribution over all tokens
 37
 38    softmax_temperature : float, default=1.0
 39        Temperature for softmax when return_mode='probs'
 40        Lower values (0.1-0.5) → sharper distributions (more deterministic)
 41        Higher values (2.0-10.0) → smoother distributions (more exploratory)
 42
 43    normalize_vocab : bool, default=False
 44        Whether to center and scale vocabulary to zero mean, unit variance
 45
 46    **mts_kwargs : dict
 47        Additional parameters passed to MTS base class
 48
 49    Attributes
 50    ----------
 51    vocab : np.ndarray
 52        Normalized vocabulary (if normalize_vocab=True)
 53
 54    vocab_mean_ : np.ndarray
 55        Mean used for normalization (if normalize_vocab=True)
 56
 57    vocab_std_ : np.ndarray
 58        Std used for normalization (if normalize_vocab=True)
 59
 60    discretization_errors_ : pd.DataFrame or None
 61        Distances from predictions to nearest tokens
 62
 63    Warnings
 64    --------
 65    - Prediction intervals (lower/upper) are NOT discretized - only the mean
 66    - For uncertainty in token space, use predict_token_distribution()
 67    - Vocabulary quality strongly affects results - use diagnose_vocabulary()
 68
 69    Examples
 70    --------
 71    >>> # Basic token prediction
 72    >>> vocab = np.random.randn(100, 10)  # 100 tokens, 10 dimensions
 73    >>> model = DiscreteTokenMTS(
 74    ...     obj=Ridge(),
 75    ...     vocab=vocab,
 76    ...     lags=5,
 77    ...     return_mode='token_id'
 78    ... )
 79    >>> model.fit(X_train)
 80    >>> tokens = model.predict(h=10)
 81
 82    >>> # Probabilistic with temperature control
 83    >>> model = DiscreteTokenMTS(
 84    ...     obj=Ridge(),
 85    ...     vocab=vocab,
 86    ...     lags=5,
 87    ...     return_mode='probs',
 88    ...     softmax_temperature=1.5
 89    ... )
 90    >>> probs = model.predict(h=10)  # Returns probability distributions
 91
 92    >>> # Uncertainty-aware token distributions
 93    >>> freqs, entropy, mode = model.predict_token_distribution(
 94    ...     h=10,
 95    ...     replications=100
 96    ... )
 97    """
 98
 99    def __init__(
100        self,
101        obj,
102        vocab,
103        metric="euclidean",
104        return_mode="token_id",
105        softmax_temperature=1.0,
106        normalize_vocab=False,
107        **mts_kwargs,
108    ):
109        super().__init__(obj, **mts_kwargs)
110
111        # Convert and validate vocabulary
112        self.vocab_original = np.asarray(vocab, dtype=np.float64)
113        self._validate_vocabulary()
114
115        self.vocab_size = self.vocab_original.shape[0]
116        self.vocab_mean_ = None
117        self.vocab_std_ = None
118        self.normalize_vocab = normalize_vocab
119
120        # Normalize if requested
121        if normalize_vocab:
122            self._normalize_vocabulary()
123        else:
124            self.vocab = self.vocab_original.copy()
125
126        # Validate and set metric
127        assert metric in [
128            "euclidean",
129            "cosine",
130        ], "metric must be 'euclidean' or 'cosine'"
131        self.metric = metric
132        self.distance_func = (
133            euclidean_distances if metric == "euclidean" else cosine_distances
134        )
135
136        # Validate and set return mode
137        assert return_mode in [
138            "token_id",
139            "token_vector",
140            "both",
141            "probs",
142        ], "return_mode must be 'token_id', 'token_vector', 'both', or 'probs'"
143        self.return_mode = return_mode
144
145        # Validate temperature
146        assert softmax_temperature > 0, "softmax_temperature must be positive"
147        self.softmax_temperature = softmax_temperature
148
149        # Initialize error tracking
150        self.discretization_errors_ = None
151
152    def _validate_vocabulary(self):
153        """Comprehensive vocabulary validation"""
154        # Check shape
155        assert (
156            self.vocab_original.ndim == 2
157        ), "vocab must be 2D array (vocab_size, n_series)"
158        assert (
159            self.vocab_original.shape[0] > 0
160        ), "vocab must have at least one token"
161
162        # Check for NaN/Inf
163        if np.any(np.isnan(self.vocab_original)) or np.any(
164            np.isinf(self.vocab_original)
165        ):
166            raise ValueError("Vocabulary contains NaN or Inf values")
167
168        # Check for duplicates
169        unique_rows = np.unique(self.vocab_original, axis=0)
170        if len(unique_rows) < len(self.vocab_original):
171            n_duplicates = len(self.vocab_original) - len(unique_rows)
172            warnings.warn(
173                f"Vocabulary contains {n_duplicates} duplicate vectors. "
174                "This reduces effective vocabulary size.",
175                UserWarning,
176            )
177
178        # Check for near-duplicates
179        if len(self.vocab_original) > 1:
180            dists = euclidean_distances(self.vocab_original)
181            np.fill_diagonal(dists, np.inf)
182            min_dist = dists.min()
183
184            if min_dist < 1e-6:
185                warnings.warn(
186                    f"Vocabulary contains very close vectors (min distance: {min_dist:.2e}). "
187                    "Consider increasing token diversity.",
188                    UserWarning,
189                )
190
191    def _normalize_vocabulary(self):
192        """Center and scale vocabulary"""
193        self.vocab_mean_ = self.vocab_original.mean(axis=0)
194        self.vocab_std_ = self.vocab_original.std(axis=0) + 1e-8
195        self.vocab = (self.vocab_original - self.vocab_mean_) / self.vocab_std_
196
197    def fit(self, X, **kwargs):
198        """
199        Fit model and validate vocabulary dimensions match data.
200
201        Parameters
202        ----------
203        X : array-like of shape (n_samples, n_series)
204            Training data
205
206        **kwargs : dict
207            Additional parameters passed to parent fit
208
209        Returns
210        -------
211        self : object
212            Fitted estimator
213        """
214        # Call parent fit
215        super().fit(X, **kwargs)
216
217        # Validate vocabulary dimensions
218        n_series = X.shape[1] if X.ndim > 1 else 1
219        if self.vocab.shape[1] != n_series:
220            raise ValueError(
221                f"Vocabulary dimension ({self.vocab.shape[1]}) must match "
222                f"number of series ({n_series})"
223            )
224
225        # Additional check for cosine distance
226        if self.metric == "cosine":
227            norms = np.linalg.norm(self.vocab, axis=1)
228            zero_vectors = norms < 1e-10
229            if np.any(zero_vectors):
230                raise ValueError(
231                    f"Vocabulary contains {zero_vectors.sum()} zero/near-zero vectors. "
232                    "Cosine distance requires non-zero vectors."
233                )
234
235        return self
236
237    def _vectorized_map_to_tokens(self, continuous_preds):
238        """
239        Vectorized token mapping for efficiency.
240
241        Parameters
242        ----------
243        continuous_preds : np.ndarray of shape (h, n_series)
244            Continuous predictions
245
246        Returns
247        -------
248        result : depends on return_mode
249        errors : np.ndarray
250            Distances to nearest tokens
251        """
252        # Normalize predictions if vocabulary was normalized
253        if self.normalize_vocab:
254            continuous_preds = (
255                continuous_preds - self.vocab_mean_
256            ) / self.vocab_std_
257
258        # Compute all distances at once
259        dists = self.distance_func(continuous_preds, self.vocab)
260
261        # Find nearest tokens
262        nearest_indices = np.argmin(dists, axis=1)
263        min_dists = dists[np.arange(len(dists)), nearest_indices]
264
265        if self.return_mode == "token_id":
266            return nearest_indices, min_dists
267
268        elif self.return_mode == "token_vector":
269            token_vecs = self.vocab[nearest_indices]
270            # Denormalize if vocabulary was normalized
271            if self.normalize_vocab:
272                token_vecs = token_vecs * self.vocab_std_ + self.vocab_mean_
273            return token_vecs, min_dists
274
275        elif self.return_mode == "both":
276            # Return combined array: [token_id, dim_0, dim_1, ...]
277            token_ids = nearest_indices.reshape(-1, 1)
278            token_vecs = self.vocab[nearest_indices]
279            # Denormalize if vocabulary was normalized
280            if self.normalize_vocab:
281                token_vecs = token_vecs * self.vocab_std_ + self.vocab_mean_
282            combined = np.column_stack([token_ids, token_vecs])
283            return combined, min_dists
284
285        elif self.return_mode == "probs":
286            # Softmax of negative distances
287            probs = softmax(-dists / self.softmax_temperature, axis=1)
288            return probs, min_dists
289
290    def predict(
291        self,
292        h=5,
293        level=95,
294        quantiles=None,
295        return_discretization_error=False,
296        **kwargs,
297    ):
298        """
299        Generate discrete token predictions.
300
301        Parameters
302        ----------
303        h : int, default=5
304            Forecast horizon
305
306        level : int, default=95
307            Confidence level (only affects continuous forecasts)
308
309        quantiles : list of float, optional
310            Quantile levels
311
312        return_discretization_error : bool, default=False
313            If True, return (predictions, errors) tuple
314
315        **kwargs : dict
316            Additional parameters for parent predict
317
318        Returns
319        -------
320        predictions : pd.DataFrame
321            Discrete predictions. Format depends on return_mode:
322            - 'token_id': single column 'token_id'
323            - 'token_vector': columns 'dim_0', 'dim_1', ...
324            - 'both': columns 'token_id', 'dim_0', 'dim_1', ...
325            - 'probs': columns 'token_0_prob', 'token_1_prob', ...
326
327        errors : pd.DataFrame (if return_discretization_error=True)
328            Discretization errors (distances to nearest tokens)
329
330        Warnings
331        --------
332        When prediction intervals are requested but only mean is discretized,
333        a warning is issued. Use predict_token_distribution() for uncertainty
334        in token space.
335        """
336        # Get continuous predictions from parent
337        continuous_result = super().predict(
338            h=h, level=level, quantiles=quantiles, **kwargs
339        )
340
341        # FIXED: Robust type detection using duck typing
342        if hasattr(continuous_result, "_fields"):  # Namedtuple
343            if (
344                hasattr(continuous_result, "sims")
345                and continuous_result.sims is not None
346            ):
347                # Simulation-based forecast
348                return self._discretize_simulations(
349                    continuous_result.sims, return_discretization_error
350                )
351            elif hasattr(continuous_result, "mean"):
352                # Interval-based forecast - warn about information loss
353                warnings.warn(
354                    "Prediction intervals cannot be meaningfully discretized. "
355                    "Only mean predictions are converted to tokens. "
356                    "Use predict_token_distribution(replications=N) for "
357                    "uncertainty in token space.",
358                    UserWarning,
359                )
360                return self._discretize_dataframe(
361                    continuous_result.mean, return_discretization_error
362                )
363        elif isinstance(continuous_result, pd.DataFrame):
364            # Deterministic forecast
365            return self._discretize_dataframe(
366                continuous_result, return_discretization_error
367            )
368        else:
369            raise NotImplementedError(
370                f"Unhandled predict output type: {type(continuous_result)}"
371            )
372
373    def _discretize_dataframe(self, df, return_error=False):
374        """Discretize a continuous prediction DataFrame"""
375        # Use vectorized mapping
376        result, errors = self._vectorized_map_to_tokens(df.values)
377
378        # FIXED: Always return single DataFrame (even for 'both' mode)
379        if self.return_mode == "probs":
380            result_df = pd.DataFrame(
381                result,
382                index=df.index,
383                columns=[f"token_{i}_prob" for i in range(self.vocab_size)],
384            )
385        elif self.return_mode == "both":
386            # Combined format: token_id + dimensions
387            columns = ["token_id"] + [
388                f"dim_{i}" for i in range(self.vocab.shape[1])
389            ]
390            result_df = pd.DataFrame(result, index=df.index, columns=columns)
391            result_df["token_id"] = result_df["token_id"].astype(int)
392        elif self.return_mode == "token_id":
393            result_df = pd.DataFrame(
394                result.reshape(-1, 1), index=df.index, columns=["token_id"]
395            )
396        else:  # 'token_vector'
397            result_df = pd.DataFrame(
398                result,
399                index=df.index,
400                columns=[f"dim_{i}" for i in range(self.vocab.shape[1])],
401            )
402
403        if return_error:
404            error_df = pd.DataFrame(
405                errors.reshape(-1, 1),
406                index=df.index,
407                columns=["discretization_error"],
408            )
409            self.discretization_errors_ = error_df
410            return result_df, error_df
411
412        return result_df
413
414    def _discretize_simulations(self, sims, return_error=False):
415        """Discretize simulation paths"""
416        discrete_sims = []
417        all_errors = []
418
419        for sim_df in sims:
420            result, errors = self._vectorized_map_to_tokens(sim_df.values)
421
422            if self.return_mode == "probs":
423                discrete_df = pd.DataFrame(
424                    result,
425                    index=sim_df.index,
426                    columns=[f"token_{i}_prob" for i in range(self.vocab_size)],
427                )
428            elif self.return_mode == "both":
429                columns = ["token_id"] + [
430                    f"dim_{i}" for i in range(self.vocab.shape[1])
431                ]
432                discrete_df = pd.DataFrame(
433                    result, index=sim_df.index, columns=columns
434                )
435                discrete_df["token_id"] = discrete_df["token_id"].astype(int)
436            elif self.return_mode == "token_id":
437                discrete_df = pd.DataFrame(
438                    result.reshape(-1, 1),
439                    index=sim_df.index,
440                    columns=["token_id"],
441                )
442            else:  # 'token_vector'
443                discrete_df = pd.DataFrame(
444                    result,
445                    index=sim_df.index,
446                    columns=[f"dim_{i}" for i in range(self.vocab.shape[1])],
447                )
448
449            discrete_sims.append(discrete_df)
450
451            if return_error:
452                error_df = pd.DataFrame(
453                    errors.reshape(-1, 1),
454                    index=sim_df.index,
455                    columns=["discretization_error"],
456                )
457                all_errors.append(error_df)
458
459        if return_error:
460            return tuple(discrete_sims), tuple(all_errors)
461        return tuple(discrete_sims)
462
463    # ========== NEW: Uncertainty Quantification in Token Space ==========
464
465    def predict_top_k(self, h=5, k=5, **kwargs):
466        """
467        Predict top-k most probable tokens per timestep.
468
469        Parameters
470        ----------
471        h : int
472            Forecast horizon
473        k : int
474            Number of top tokens to return
475        **kwargs : dict
476            Additional parameters for parent predict
477
478        Returns
479        -------
480        predictions : pd.DataFrame
481            Columns: token_1, prob_1, token_2, prob_2, ..., token_k, prob_k
482        """
483        continuous_result = super().predict(h=h, **kwargs)
484
485        # Handle different return types
486        if hasattr(continuous_result, "mean"):
487            preds = continuous_result.mean.values
488            index = continuous_result.mean.index
489        elif isinstance(continuous_result, pd.DataFrame):
490            preds = continuous_result.values
491            index = continuous_result.index
492        else:
493            raise ValueError("Cannot extract continuous predictions")
494
495        # Compute probabilities
496        dists = self.distance_func(preds, self.vocab)
497        probs = softmax(-dists / self.softmax_temperature, axis=1)
498
499        # Get top-k
500        top_k_indices = np.argsort(probs, axis=1)[:, -k:][:, ::-1]
501        top_k_probs = np.take_along_axis(probs, top_k_indices, axis=1)
502
503        # Format as DataFrame
504        columns = []
505        data = []
506        for i in range(k):
507            columns.extend([f"token_{i+1}", f"prob_{i+1}"])
508            data.append(top_k_indices[:, i])
509            data.append(top_k_probs[:, i])
510
511        return pd.DataFrame(np.column_stack(data), index=index, columns=columns)
512
513    def predict_token_distribution(self, h=5, replications=100, **kwargs):
514        """
515        Generate token probability distribution from simulation ensemble.
516
517        This method provides meaningful uncertainty quantification in token space
518        by discretizing multiple simulation paths and computing token frequencies.
519
520        Parameters
521        ----------
522        h : int
523            Forecast horizon
524        replications : int
525            Number of simulation paths
526        **kwargs : dict
527            Additional parameters for parent predict
528
529        Returns
530        -------
531        frequencies : pd.DataFrame
532            Token frequencies across simulations
533            Columns: token_0_freq, token_1_freq, ..., token_V_freq
534
535        entropy : pd.Series
536            Shannon entropy per timestep (uncertainty measure)
537
538        mode_tokens : pd.DataFrame
539            Most frequent token per timestep
540
541        Examples
542        --------
543        >>> freqs, entropy, mode = model.predict_token_distribution(h=10, replications=100)
544        >>> # High entropy → uncertain prediction
545        >>> uncertain_steps = entropy[entropy > 2.0]
546        >>> # Use mode tokens for point predictions
547        >>> predictions = mode['mode_token'].values
548        """
549        # Force simulation mode
550        kwargs["replications"] = replications
551        continuous_result = super().predict(h=h, **kwargs)
552
553        # Extract simulations
554        if (
555            hasattr(continuous_result, "sims")
556            and continuous_result.sims is not None
557        ):
558            sims = continuous_result.sims
559            index = continuous_result.mean.index
560        else:
561            raise ValueError(
562                "predict_token_distribution requires simulation-based forecasting. "
563                "Ensure replications > 0 and type_pi supports simulations."
564            )
565
566        # Discretize all paths
567        all_tokens = []
568        for sim in sims:
569            tokens, _ = self._vectorized_map_to_tokens(sim.values)
570            if self.return_mode == "probs":
571                # For probs mode, get argmax token
572                tokens = np.argmax(tokens, axis=1)
573            elif self.return_mode == "both":
574                # Extract token_id column
575                tokens = tokens[:, 0].astype(int)
576            elif self.return_mode == "token_vector":
577                # Map back to token IDs
578                dists = self.distance_func(tokens, self.vocab)
579                tokens = np.argmin(dists, axis=1)
580            # else: token_id mode, already correct
581
582            all_tokens.append(tokens)
583
584        all_tokens = np.array(all_tokens)  # (replications, h)
585
586        # Compute frequency distribution
587        h_actual = all_tokens.shape[1]
588        token_freqs = np.zeros((h_actual, self.vocab_size))
589
590        for t in range(h_actual):
591            unique, counts = np.unique(all_tokens[:, t], return_counts=True)
592            token_freqs[t, unique] = counts / replications
593
594        # Compute entropy
595        epsilon = 1e-10
596        entropy = -np.sum(token_freqs * np.log(token_freqs + epsilon), axis=1)
597
598        # Get mode
599        mode_tokens = np.argmax(token_freqs, axis=1)
600
601        # Package results
602        freq_df = pd.DataFrame(
603            token_freqs,
604            index=index,
605            columns=[f"token_{i}_freq" for i in range(self.vocab_size)],
606        )
607
608        entropy_series = pd.Series(entropy, index=index, name="entropy")
609
610        mode_df = pd.DataFrame(mode_tokens, index=index, columns=["mode_token"])
611
612        return freq_df, entropy_series, mode_df
613
614    # ========== Utility Methods ==========
615
616    def tokens_to_vectors(self, token_ids):
617        """Convert token IDs to embedding vectors (in original scale)"""
618        token_ids = np.asarray(token_ids).astype(int)
619        assert np.all(
620            (token_ids >= 0) & (token_ids < self.vocab_size)
621        ), f"Token IDs must be in range [0, {self.vocab_size-1}]"
622        vectors = self.vocab[token_ids]
623        # Denormalize if vocabulary was normalized
624        if self.normalize_vocab:
625            vectors = vectors * self.vocab_std_ + self.vocab_mean_
626        return vectors
627
628    def get_token_neighbors(self, token_id, k=5):
629        """Find k nearest neighbors of a token"""
630        assert (
631            0 <= token_id < self.vocab_size
632        ), f"token_id must be in range [0, {self.vocab_size-1}]"
633
634        token_vec = self.vocab[token_id].reshape(1, -1)
635        dists = self.distance_func(token_vec, self.vocab).flatten()
636
637        sorted_indices = np.argsort(dists)
638        sorted_indices = sorted_indices[sorted_indices != token_id][:k]
639
640        return pd.DataFrame(
641            {"neighbor_id": sorted_indices, "distance": dists[sorted_indices]}
642        )
643
644    def compute_vocab_coverage(self, predictions):
645        """Compute vocabulary usage statistics"""
646        if "token_id" not in predictions.columns:
647            raise ValueError("predictions must have 'token_id' column")
648
649        token_ids = predictions["token_id"].values
650        unique_tokens = np.unique(token_ids)
651        freq = pd.Series(token_ids).value_counts().sort_index()
652
653        return {
654            "unique_tokens": len(unique_tokens),
655            "coverage_pct": 100 * len(unique_tokens) / self.vocab_size,
656            "token_frequencies": freq,
657            "most_common_token": freq.idxmax() if len(freq) > 0 else None,
658            "least_common_token": freq.idxmin() if len(freq) > 0 else None,
659        }
660
661    def diagnose_vocabulary(self):
662        """
663        Comprehensive vocabulary quality diagnostics.
664
665        Returns
666        -------
667        report : dict
668            Quality metrics including distances, condition number, coverage
669        """
670        # Use original vocabulary for diagnostics to get meaningful statistics
671        vocab_to_diagnose = self.vocab_original
672
673        report = {
674            "vocab_size": self.vocab_size,
675            "embedding_dim": vocab_to_diagnose.shape[1],
676            "normalized": self.normalize_vocab,
677        }
678
679        # Pairwise distances
680        dists = euclidean_distances(vocab_to_diagnose)
681        np.fill_diagonal(dists, np.inf)
682
683        report["min_pairwise_distance"] = dists.min()
684        report["max_pairwise_distance"] = dists.max()
685        report["mean_pairwise_distance"] = dists[dists != np.inf].mean()
686
687        # Condition number
688        U, s, Vt = np.linalg.svd(vocab_to_diagnose, full_matrices=False)
689        report["condition_number"] = s.max() / (s.min() + 1e-10)
690
691        # Coverage volume
692        ranges = vocab_to_diagnose.max(axis=0) - vocab_to_diagnose.min(axis=0)
693        report["coverage_volume"] = np.prod(ranges)
694
695        # Duplicates
696        unique_rows = np.unique(vocab_to_diagnose, axis=0)
697        report["duplicate_count"] = len(vocab_to_diagnose) - len(unique_rows)
698
699        return report
700
701    def print_vocabulary_report(self):
702        """Print human-readable vocabulary diagnostics"""
703        report = self.diagnose_vocabulary()
704
705        print("=" * 60)
706        print("VOCABULARY QUALITY REPORT")
707        print("=" * 60)
708        print(f"Vocabulary size: {report['vocab_size']} tokens")
709        print(f"Embedding dimension: {report['embedding_dim']}")
710        print(f"\nPairwise Distances:")
711        print(f"  Min:  {report['min_pairwise_distance']:.6f}")
712        print(f"  Mean: {report['mean_pairwise_distance']:.6f}")
713        print(f"  Max:  {report['max_pairwise_distance']:.6f}")
714        print(f"\nVocabulary Health:")
715        print(f"  Condition number: {report['condition_number']:.2f}")
716        if report["condition_number"] > 1000:
717            print(
718                "  ⚠️  WARNING: High condition number may indicate redundant tokens"
719            )
720        print(f"  Duplicate tokens: {report['duplicate_count']}")
721        if report["duplicate_count"] > 0:
722            print("  ⚠️  WARNING: Duplicates reduce effective vocabulary size")
723        print(f"  Coverage volume: {report['coverage_volume']:.2e}")
724        print("=" * 60)

MTS for discrete token forecasting via nearest-neighbor in embedding space.

Maps continuous predictions to discrete tokens using nearest-neighbor lookup in a vocabulary (embedding space). Supports probabilistic decoding with temperature-controlled softmax and uncertainty quantification in token space.

Parameters

obj : object Base learner with fit() and predict() methods

vocab : np.ndarray of shape (vocab_size, n_series) Token vocabulary - each row is a token embedding vector

metric : {'euclidean', 'cosine'}, default='euclidean' Distance metric for nearest-neighbor lookup

return_mode : {'token_id', 'token_vector', 'both', 'probs'}, default='token_id' Output format: - 'token_id': integer token indices - 'token_vector': token embedding vectors - 'both': single DataFrame with token_id + dimensions - 'probs': probability distribution over all tokens

softmax_temperature : float, default=1.0 Temperature for softmax when return_mode='probs' Lower values (0.1-0.5) → sharper distributions (more deterministic) Higher values (2.0-10.0) → smoother distributions (more exploratory)

normalize_vocab : bool, default=False Whether to center and scale vocabulary to zero mean, unit variance

**mts_kwargs : dict Additional parameters passed to MTS base class

Attributes

vocab : np.ndarray Normalized vocabulary (if normalize_vocab=True)

vocab_mean_ : np.ndarray Mean used for normalization (if normalize_vocab=True)

vocab_std_ : np.ndarray Std used for normalization (if normalize_vocab=True)

discretization_errors_ : pd.DataFrame or None Distances from predictions to nearest tokens

Warnings

  • Prediction intervals (lower/upper) are NOT discretized - only the mean
  • For uncertainty in token space, use predict_token_distribution()
  • Vocabulary quality strongly affects results - use diagnose_vocabulary()

Examples

>>> # Basic token prediction
>>> vocab = np.random.randn(100, 10)  # 100 tokens, 10 dimensions
>>> model = DiscreteTokenMTS(
...     obj=Ridge(),
...     vocab=vocab,
...     lags=5,
...     return_mode='token_id'
... )
>>> model.fit(X_train)
>>> tokens = model.predict(h=10)
>>> # Probabilistic with temperature control
>>> model = DiscreteTokenMTS(
...     obj=Ridge(),
...     vocab=vocab,
...     lags=5,
...     return_mode='probs',
...     softmax_temperature=1.5
... )
>>> probs = model.predict(h=10)  # Returns probability distributions
>>> # Uncertainty-aware token distributions
>>> freqs, entropy, mode = model.predict_token_distribution(
...     h=10,
...     replications=100
... )
def fit(self, X, **kwargs):
197    def fit(self, X, **kwargs):
198        """
199        Fit model and validate vocabulary dimensions match data.
200
201        Parameters
202        ----------
203        X : array-like of shape (n_samples, n_series)
204            Training data
205
206        **kwargs : dict
207            Additional parameters passed to parent fit
208
209        Returns
210        -------
211        self : object
212            Fitted estimator
213        """
214        # Call parent fit
215        super().fit(X, **kwargs)
216
217        # Validate vocabulary dimensions