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]
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))
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
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}
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}
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"
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}
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}
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}
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}
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
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
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}
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
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
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}
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
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
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}
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
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
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}
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.
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}")
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
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}
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}
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
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
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
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`
()
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
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.
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.
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.
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))
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
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}
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}
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
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))
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
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`
()
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
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))
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
... )
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