tisthemachinelearner

1from .base import BaseModel
2from .classifier import Classifier
3from .regressor import Regressor
4from .finitedifftrainer import FiniteDiffRegressor
5
6__all__ = ["BaseModel", "Classifier", "Regressor", "FiniteDiffRegressor"]
class BaseModel(sklearn.base.BaseEstimator):
  8class BaseModel(BaseEstimator):
  9    """
 10    Base class for dynamically loading and wrapping scikit-learn models.
 11    """
 12    # Custom parameters that should only be passed to nnetsauce models
 13    CUSTOM_PARAMS = [
 14        'n_hidden_features',
 15        'activation_name',
 16        'bias',
 17        'dropout',
 18        'direct_link',
 19        'n_clusters',
 20        'cluster_encode',
 21        'type_clust'
 22    ]
 23
 24    def __init__(self, base_model, custom=False, **kwargs):
 25        """
 26        Initialize a scikit-learn model dynamically.
 27
 28        Parameters:
 29
 30        - base_model (str): The class name of the scikit-learn model (e.g., 'LogisticRegression').
 31
 32        - custom (bool): Whether the model is a custom nnetsauce model.
 33
 34        - **kwargs: Additional parameters to pass to the model constructor.
 35
 36        """
 37        modules = [
 38            "linear_model",
 39            "ensemble",
 40            "neural_network",
 41            "svm",
 42            "neighbors",
 43            "tree",
 44            "discriminant_analysis",
 45            "gaussian_process",
 46            "naive_bayes",
 47            "kernel_ridge",
 48            "gbdt_regressor",
 49            "gbdt_classifier"
 50        ] 
 51
 52        self.base_model = base_model
 53        self.custom = custom
 54
 55        # Split kwargs into base and custom parameters
 56        self.base_kwargs = {k: v for k, v in kwargs.items() if k not in self.CUSTOM_PARAMS}
 57        self.custom_kwargs = {k: v for k, v in kwargs.items() if k in self.CUSTOM_PARAMS}
 58
 59        # Initialize only the base model here
 60        self.model = self._load_model(base_model, modules)(**self.base_kwargs)
 61        
 62        # Custom model wrapping is handled in derived classes
 63
 64    def _load_model(self, base_model, modules):
 65        """
 66        Load a model class from scikit-learn modules.
 67
 68        Parameters:
 69
 70        - base_model (str): The class name of the scikit-learn model.
 71
 72        - modules (list): List of scikit-learn submodules to search.
 73
 74        Returns:
 75
 76        - class: The loaded scikit-learn model class.
 77
 78        """
 79        for module_name in modules:
 80            try:
 81                module = importlib.import_module(f"sklearn.{module_name}")
 82                return getattr(module, base_model)
 83            except (ImportError, AttributeError) as e:    
 84                try:                     
 85                    return getattr(ub, base_model)
 86                except (ImportError, AttributeError) as e:
 87                    continue        
 88
 89        raise ImportError(f"Model '{base_model}' not found in scikit-learn modules.")
 90
 91    def fit(self, X, y, **kwargs):
 92        """
 93        Fit the model to the training data.
 94
 95        Parameters:
 96
 97        - X (array-like): Training data features.
 98
 99        - y (array-like): Target values.
100
101        - **kwargs: Additional parameters to pass to the 
102        model fit method.
103
104        """
105        self.model.fit(X, y, **kwargs)
106        return self
107
108    def predict(self, X, **kwargs):
109        """
110        Predict using the trained model.
111
112        Parameters:
113
114        - X (array-like): Input data.
115
116        - **kwargs: Additional parameters to pass to the model predict method.
117
118        Returns:
119
120        - array-like: Predictions.
121
122        """
123        return self.model.predict(X, **kwargs)
124
125    def score(self, X, y, **kwargs):
126        """
127        Return the score of the model on the given test data and labels.
128
129        Parameters:
130
131        - X (array-like): Test data features.
132
133        - y (array-like): True labels.
134
135        - **kwargs: Additional parameters to pass to the model score method.
136        
137        Returns:
138        
139        - float: The score.
140        """
141        return self.model.score(X, y, **kwargs)

Base class for dynamically loading and wrapping scikit-learn models.

def fit(self, X, y, **kwargs):
 91    def fit(self, X, y, **kwargs):
 92        """
 93        Fit the model to the training data.
 94
 95        Parameters:
 96
 97        - X (array-like): Training data features.
 98
 99        - y (array-like): Target values.
100
101        - **kwargs: Additional parameters to pass to the 
102        model fit method.
103
104        """
105        self.model.fit(X, y, **kwargs)
106        return self

Fit the model to the training data.

Parameters:

  • X (array-like): Training data features.

  • y (array-like): Target values.

  • **kwargs: Additional parameters to pass to the model fit method.

def predict(self, X, **kwargs):
108    def predict(self, X, **kwargs):
109        """
110        Predict using the trained model.
111
112        Parameters:
113
114        - X (array-like): Input data.
115
116        - **kwargs: Additional parameters to pass to the model predict method.
117
118        Returns:
119
120        - array-like: Predictions.
121
122        """
123        return self.model.predict(X, **kwargs)

Predict using the trained model.

Parameters:

  • X (array-like): Input data.

  • **kwargs: Additional parameters to pass to the model predict method.

Returns:

  • array-like: Predictions.
def score(self, X, y, **kwargs):
125    def score(self, X, y, **kwargs):
126        """
127        Return the score of the model on the given test data and labels.
128
129        Parameters:
130
131        - X (array-like): Test data features.
132
133        - y (array-like): True labels.
134
135        - **kwargs: Additional parameters to pass to the model score method.
136        
137        Returns:
138        
139        - float: The score.
140        """
141        return self.model.score(X, y, **kwargs)

Return the score of the model on the given test data and labels.

Parameters:

  • X (array-like): Test data features.

  • y (array-like): True labels.

  • **kwargs: Additional parameters to pass to the model score method.

Returns:

  • float: The score.
class Classifier(tisthemachinelearner.BaseModel, sklearn.base.ClassifierMixin):
 8class Classifier(BaseModel, ClassifierMixin):
 9    """
10    Wrapper for scikit-learn classifier models.
11
12    Parameters:
13    - model_name (str): The name of the scikit-learn classifier model.
14    - **kwargs: Additional parameters to pass to the scikit-learn model.
15
16    Examples:
17    
18        ```python
19        from sklearn.model_selection import train_test_split
20        from sklearn.datasets import load_breast_cancer
21        from tisthemachinelearner import Classifier
22
23        X, y = load_breast_cancer(return_X_y=True)
24        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
25
26        clf = Classifier("LogisticRegression", random_state=42)
27        clf.fit(X_train, y_train)
28        print(clf.predict(X_test))
29        print(clf.score(X_test, y_test))
30
31        clf = Classifier("RandomForestClassifier", n_estimators=100, random_state=42)
32        clf.fit(X_train, y_train)
33        print(clf.predict(X_test))
34        print(clf.score(X_test, y_test))
35        ```
36    """
37    def __init__(self, base_model, custom=False, calibrate=False, **kwargs):
38        super().__init__(base_model, custom, **kwargs)
39        if self.custom:
40            self.model = ns.CustomClassifier(self.model, **self.custom_kwargs)
41        self.calibrate = calibrate
42        if self.calibrate: 
43            raise NotImplementedError
44
45    def fit(self, X, y, **kwargs):
46        """Fit the model."""
47        super().fit(X, y, **kwargs)
48        if self.calibrate:
49            self.model = WrappedCalibratedClassifier(self.model, method='sigmoid', cv='prefit')
50            self.model.fit(X, y)
51        return self
52    
53    def predict_proba(self, X):
54        """
55        Predict class probabilities for the input data.
56
57        Parameters:
58        - X (array-like): Input data features.
59
60        Returns:
61        - array-like: Predicted class probabilities.
62        """
63        return self.model.predict_proba(X)

Wrapper for scikit-learn classifier models.

Parameters:

  • model_name (str): The name of the scikit-learn classifier model.
  • **kwargs: Additional parameters to pass to the scikit-learn model.

Examples:

from sklearn.model_selection import train_test_split
    from sklearn.datasets import load_breast_cancer
    from tisthemachinelearner import Classifier
 
X, y = load_breast_cancer(return_X_y=True) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
clf = Classifier("LogisticRegression", random_state=42) clf.fit(X_train, y_train) print(clf.predict(X_test)) print(clf.score(X_test, y_test))
clf = Classifier("RandomForestClassifier", n_estimators=100, random_state=42) clf.fit(X_train, y_train) print(clf.predict(X_test)) print(clf.score(X_test, y_test))

def fit(self, X, y, **kwargs):
45    def fit(self, X, y, **kwargs):
46        """Fit the model."""
47        super().fit(X, y, **kwargs)
48        if self.calibrate:
49            self.model = WrappedCalibratedClassifier(self.model, method='sigmoid', cv='prefit')
50            self.model.fit(X, y)
51        return self

Fit the model.

def predict_proba(self, X):
53    def predict_proba(self, X):
54        """
55        Predict class probabilities for the input data.
56
57        Parameters:
58        - X (array-like): Input data features.
59
60        Returns:
61        - array-like: Predicted class probabilities.
62        """
63        return self.model.predict_proba(X)

Predict class probabilities for the input data.

Parameters:

  • X (array-like): Input data features.

Returns:

  • array-like: Predicted class probabilities.
class Regressor(tisthemachinelearner.BaseModel, sklearn.base.RegressorMixin):
 7class Regressor(BaseModel, RegressorMixin):
 8    """
 9    Wrapper for scikit-learn regressor models.
10
11    Parameters:
12
13    - model_name (str): The name of the scikit-learn regressor model.
14    
15    - **kwargs: Additional parameters to pass to the scikit-learn model.
16
17    Examples:
18        ```python
19        from sklearn.model_selection import train_test_split
20        from sklearn.datasets import load_diabetes
21        from tisthemachinelearner import Regressor
22
23        X, y = load_diabetes(return_X_y=True)
24        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
25
26        reg = Regressor("LinearRegression")
27        reg.fit(X_train, y_train)
28        print(reg.predict(X_test))
29        print(reg.score(X_test, y_test))
30
31        reg = Regressor("RidgeCV", alphas=[0.01, 0.1, 1, 10])
32        reg.fit(X_train, y_train)
33        print(reg.predict(X_test))
34        print(np.sqrt(np.mean((reg.predict(X_test) - y_test) ** 2)))
35        ```
36    """
37    def __init__(self, base_model, custom=False, **kwargs):
38        super().__init__(base_model, custom, **kwargs)
39        if self.custom:
40            self.model = ns.CustomRegressor(self.model, **self.custom_kwargs)
41
42    def fit(self, X, y, **kwargs):
43        """Fit the model."""
44        super().fit(X, y, **kwargs)
45        if self.custom:
46            self.model.fit(X, y)
47        return self

Wrapper for scikit-learn regressor models.

Parameters:

  • model_name (str): The name of the scikit-learn regressor model.

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

Examples:

from sklearn.model_selection import train_test_split
    from sklearn.datasets import load_diabetes
    from tisthemachinelearner import Regressor
 
X, y = load_diabetes(return_X_y=True) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
reg = Regressor("LinearRegression") reg.fit(X_train, y_train) print(reg.predict(X_test)) print(reg.score(X_test, y_test))
reg = Regressor("RidgeCV", alphas=[0.01, 0.1, 1, 10]) reg.fit(X_train, y_train) print(reg.predict(X_test)) print(np.sqrt(np.mean((reg.predict(X_test) - y_test) ** 2)))

def fit(self, X, y, **kwargs):
42    def fit(self, X, y, **kwargs):
43        """Fit the model."""
44        super().fit(X, y, **kwargs)
45        if self.custom:
46            self.model.fit(X, y)
47        return self

Fit the model.

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

Finite difference trainer for nnetsauce models.

Parameters

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

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

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

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

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

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

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

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

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

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

def fit( self, X, y, epochs=10, verbose=True, show_progress=True, sample_weight=None, **kwargs):
162    def fit(self, X, y, epochs=10, verbose=True, show_progress=True, sample_weight=None, **kwargs):
163        """
164        Fit the model using finite difference optimization.
165
166        Parameters
167        ----------
168
169        X : array-like of shape (n_samples, n_features)
170            Training data.
171
172        y : array-like of shape (n_samples,)
173            Target values.
174
175        epochs : int, optional
176            Number of optimization steps (default=10).
177
178        verbose : bool, optional
179            Whether to print progress messages (default=True).
180
181        show_progress : bool, optional
182            Whether to show tqdm progress bar (default=True).
183
184        sample_weight : array-like, optional
185            Sample weights.
186
187        **kwargs
188            Additional keyword arguments.
189
190        Returns
191        -------
192
193        self : object
194            Returns self.
195        """        
196
197        self.model.fit(X, y)
198
199        iterator = tqdm(range(epochs)) if show_progress else range(epochs)
200
201        for epoch in iterator:
202            grad = self._compute_grad(X, y)
203
204            if self.optimizer == 'gd':
205                self.model.W_ -= self.lr * grad
206                self.model.W_ = np.clip(self.model.W_, 0, 1)
207                #print("self.model.W_", self.model.W_)
208
209            elif self.optimizer == 'sgd':
210                # Sample a mini-batch for stochastic gradient
211                n_samples = X.shape[0]
212                idxs = np.random.choice(n_samples, self.batch_size, replace=False)
213                if isinstance(X, pd.DataFrame):
214                    X_batch = X.iloc[idxs,:]
215                else: 
216                    X_batch = X[idxs,:]
217                y_batch = y[idxs]
218                grad = self._compute_grad(X_batch, y_batch)
219
220                self.model.W_ -= self.lr * grad
221                self.model.W_ = np.clip(self.model.W_, 0, 1)
222
223            elif self.optimizer == 'adam':
224                if self.opt_state is None:
225                    self.opt_state = {'m': np.zeros_like(grad), 'v': np.zeros_like(grad), 't': 0}
226                beta1, beta2, eps = 0.9, 0.999, 1e-8
227                self.opt_state['t'] += 1
228                self.opt_state['m'] = beta1 * self.opt_state['m'] + (1 - beta1) * grad
229                self.opt_state['v'] = beta2 * self.opt_state['v'] + (1 - beta2) * (grad ** 2)
230                m_hat = self.opt_state['m'] / (1 - beta1 ** self.opt_state['t'])
231                v_hat = self.opt_state['v'] / (1 - beta2 ** self.opt_state['t'])
232
233                self.model.W_ -= self.lr * m_hat / (np.sqrt(v_hat) + eps)
234                self.model.W_ = np.clip(self.model.W_, 0, 1)
235                #print("self.model.W_", self.model.W_)
236
237            elif self.optimizer == 'cd':  # coordinate descent
238
239                W_shape = self.model.W_.shape
240                W_flat_size = self.model.W_.size
241                W_flat = self.model.W_.flatten()
242                grad_flat = grad.flatten()
243
244                # Update only one coordinate per epoch (cyclic)
245                idx = self._cd_index % W_flat_size
246                W_flat[idx] -= self.lr * grad_flat[idx]
247                # Clip the updated value
248                W_flat[idx] = np.clip(W_flat[idx], 0, 1)
249
250                # Restore W_
251                self.model.W_ = W_flat.reshape(W_shape)
252
253                self._cd_index += 1
254
255            else:
256                raise ValueError(f"Unsupported optimizer: {self.optimizer}")
257
258            loss = self._loss(X, y)
259            self.loss_history_.append(loss)
260
261            if verbose:
262                print(f"Epoch {epoch+1}: Loss = {loss:.6f}")                
263
264        # if sample_weights, else: (must use self.row_index)
265        if sample_weight in kwargs:
266            self.model.fit(
267                X,
268                y,
269                sample_weight=sample_weight[self.index_row_].ravel(),
270                **kwargs
271            )
272
273            return self
274
275        return self

Fit the model using finite difference optimization.

Parameters

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

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

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

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

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

sample_weight : array-like, optional Sample weights.

**kwargs Additional keyword arguments.

Returns

self : object Returns self.

def predict(self, X, level=95, method='splitconformal', **kwargs):
278    def predict(self, X, level=95, method='splitconformal', **kwargs):
279        """
280        Predict using the trained model.
281
282        Parameters
283        ----------
284
285        X : array-like of shape (n_samples, n_features)
286            Input data.
287
288        level : int, optional
289            Level of confidence for prediction intervals (default=95).
290
291        method : {'splitconformal', 'localconformal'}, optional
292            Method for conformal prediction (default='splitconformal').
293
294        **kwargs
295            Additional keyword arguments. Use `return_pi=True` for prediction intervals,
296            or `return_std=True` for standard deviation estimates.
297
298        Returns
299        -------
300        
301        array or tuple
302            Model predictions, or a tuple with prediction intervals or standard deviations if requested.
303        """
304        if "return_std" in kwargs:
305
306            alpha = 100 - level
307            pi_multiplier = norm.ppf(1 - alpha / 200)
308
309            if len(X.shape) == 1:
310
311                n_features = X.shape[0]
312                new_X = mo.rbind(
313                    X.reshape(1, n_features),
314                    np.ones(n_features).reshape(1, n_features),
315                )
316
317                mean_, std_ = self.model.predict(
318                    new_X, return_std=True
319                )[0]
320
321                preds =  mean_
322                lower =  (mean_ - pi_multiplier * std_)
323                upper =  (mean_ + pi_multiplier * std_)
324
325                DescribeResults = namedtuple(
326                    "DescribeResults", ["mean", "std", "lower", "upper"]
327                )
328
329                return DescribeResults(preds, std_, lower, upper)
330
331            # len(X.shape) > 1
332            mean_, std_ = self.model.predict(
333                X, return_std=True
334            )
335
336            preds =  mean_
337            lower =  (mean_ - pi_multiplier * std_)
338            upper =  (mean_ + pi_multiplier * std_)
339
340            DescribeResults = namedtuple(
341                "DescribeResults", ["mean", "std", "lower", "upper"]
342            )
343
344            return DescribeResults(preds, std_, lower, upper)
345
346        if "return_pi" in kwargs:
347            assert method in (
348                "splitconformal",
349                "localconformal",
350            ), "method must be in ('splitconformal', 'localconformal')"
351            self.pi = ns.PredictionInterval(
352                obj=self,
353                method=method,
354                level=level,
355                type_pi=self.type_pi,
356                replications=self.replications,
357                kernel=self.kernel,
358            )
359
360            if len(self.X_.shape) == 1:
361                if isinstance(X, pd.DataFrame):
362                    self.X_ = pd.DataFrame(
363                        self.X_.values.reshape(1, -1), columns=self.X_.columns
364                    )
365                else:
366                    self.X_ = self.X_.reshape(1, -1)
367                self.y_ = np.array([self.y_])
368
369            self.pi.fit(self.X_, self.y_)
370            # self.X_ = None # consumes memory to keep, dangerous to delete (side effect)
371            # self.y_ = None # consumes memory to keep, dangerous to delete (side effect)
372            preds = self.pi.predict(X, return_pi=True)
373            return preds
374
375        # "return_std" not in kwargs
376        if len(X.shape) == 1:
377
378            n_features = X.shape[0]
379            new_X = mo.rbind(
380                X.reshape(1, n_features),
381                np.ones(n_features).reshape(1, n_features),
382            )
383
384            return (
385                0
386                + self.model.predict(new_X, **kwargs)
387            )[0]
388
389        # len(X.shape) > 1
390        return  self.model.predict(
391            X, **kwargs
392        )

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.