path-boost 2.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
path_boost/_version.py ADDED
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '2.1.0'
22
+ __version_tuple__ = version_tuple = (2, 1, 0)
23
+
24
+ __commit_id__ = commit_id = None
@@ -0,0 +1,2 @@
1
+ # Authors: scikit-learn-contrib developers
2
+ # License: BSD 3 clause
File without changes
@@ -0,0 +1,301 @@
1
+ import pandas as pd
2
+ import copy
3
+ import numpy as np
4
+ import warnings
5
+ import matplotlib.pyplot as plt
6
+ from sklearn.metrics import mean_squared_error, mean_absolute_error
7
+ from .extended_boosting_matrix import ExtendedBoostingMatrix
8
+ from typing import Iterable
9
+ from .interfaces.interface_base_learner import BaseLearnerClassInterface
10
+
11
+
12
+ class AdditiveModelWrapper:
13
+ def __init__(
14
+ self,
15
+ BaseModelClass,
16
+ base_model_class_kwargs,
17
+ learning_rate: float,
18
+ learning_rate_scheduler: callable = None,
19
+ ):
20
+ """
21
+ Initialize the AdditiveModelWrapper.
22
+
23
+ Parameters
24
+ ----------
25
+ BaseModelClass : type
26
+ The class of the base learner to use.
27
+ base_model_class_kwargs : dict
28
+ Keyword arguments to pass to the base learner constructor.
29
+ learning_rate : float
30
+ The initial learning rate.
31
+ learning_rate_scheduler : callable, optional
32
+ A function that takes `initial_lr` and `iteration` as arguments and returns
33
+ the learning rate to use for that iteration. If None, learning rate is constant.
34
+ """
35
+ # Ensure BaseModelClass respects BaseLearnerClassInterface
36
+ if not issubclass(BaseModelClass, BaseLearnerClassInterface):
37
+ raise TypeError(
38
+ f"{BaseModelClass.__name__} must implement BaseLearnerClassInterface"
39
+ )
40
+
41
+ self._last_train_prediction: pd.Series | None = None
42
+
43
+ self.train_mse = []
44
+ self.train_mae = []
45
+ self.eval_sets_mse: list[list[float]] = []
46
+ self.eval_sets_mae: list[list[float]] = []
47
+ self.learning_rate = learning_rate
48
+ self._initial_learning_rate = learning_rate
49
+ self.learning_rate_scheduler = learning_rate_scheduler
50
+ self.base_learners_list: list = []
51
+ self.considered_columns = []
52
+ self.BaseModelClass = BaseModelClass
53
+ self.base_model_class_kwargs = base_model_class_kwargs
54
+
55
+ def fit_one_step(
56
+ self, X: pd.DataFrame, y, best_path, eval_set=None, negative_gradient=None
57
+ ):
58
+ # it fits one step of the boosting
59
+
60
+ # Apply learning rate scheduler if provided
61
+ if self.learning_rate_scheduler is not None:
62
+ iteration = len(self.base_learners_list)
63
+ self.learning_rate = self.learning_rate_scheduler(
64
+ initial_lr=self._initial_learning_rate, iteration=iteration
65
+ )
66
+
67
+ columns_to_keep = ExtendedBoostingMatrix.get_columns_related_to_path(
68
+ best_path, X.columns
69
+ )
70
+ restricted_df = X[columns_to_keep]
71
+ if self.base_model_class_kwargs is not None:
72
+ new_base_learner = self.BaseModelClass(**self.base_model_class_kwargs)
73
+ else:
74
+ new_base_learner = self.BaseModelClass()
75
+
76
+ self.trained_ = True
77
+ if eval_set is not None and not hasattr(self, "_last_eval_set_prediction_"):
78
+ self._last_eval_set_prediction_ = []
79
+ for eval_tuple in eval_set:
80
+ if eval_tuple is None:
81
+ self._last_eval_set_prediction_.append(None)
82
+ else:
83
+ self._last_eval_set_prediction_.append(
84
+ pd.Series(
85
+ np.zeros(len(eval_tuple[0])), index=eval_tuple[0].index
86
+ )
87
+ )
88
+
89
+ if len(self.base_learners_list) == 0:
90
+ # it is the first time we fit it so we do not need to compute the neg gradient
91
+
92
+ self._target_variable_mean_ = []
93
+ self._target_variable_mean_.append(np.array(y).mean())
94
+
95
+ new_y = np.array(y) - self._target_variable_mean_[-1]
96
+
97
+ new_base_learner.fit(restricted_df, new_y)
98
+ self.base_learners_list.append(new_base_learner)
99
+ self.considered_columns.append(columns_to_keep)
100
+ base_learner_prediction = (
101
+ self._target_variable_mean_
102
+ + self.learning_rate
103
+ * pd.Series(new_base_learner.predict(X[columns_to_keep]))
104
+ )
105
+ self._last_train_prediction = base_learner_prediction
106
+
107
+ train_mse = mean_squared_error(y_true=y, y_pred=self._last_train_prediction)
108
+ train_mae = mean_absolute_error(
109
+ y_true=y, y_pred=self._last_train_prediction
110
+ )
111
+
112
+ self.train_mse.append(train_mse)
113
+ self.train_mae.append(train_mae)
114
+
115
+ else:
116
+ # compute the new target (we have to use zeroed_y - true_neg_gradient instead of just zeroed_y, more explained in paper)
117
+ if negative_gradient is None:
118
+ negative_gradient = self._neg_gradient(
119
+ y=y, y_hat=self._last_train_prediction
120
+ )
121
+ new_y = np.array(negative_gradient)
122
+
123
+ self._target_variable_mean_.append(new_y.mean())
124
+ new_y = new_y - self._target_variable_mean_[-1]
125
+
126
+ new_base_learner.fit(restricted_df, new_y)
127
+
128
+ self.base_learners_list.append(new_base_learner)
129
+ self.considered_columns.append(columns_to_keep)
130
+
131
+ base_learner_prediction = self._target_variable_mean_[
132
+ -1
133
+ ] + self.learning_rate * new_base_learner.predict(X[columns_to_keep])
134
+ self._last_train_prediction += base_learner_prediction
135
+
136
+ train_mse = mean_squared_error(y_true=y, y_pred=self._last_train_prediction)
137
+ train_mae = mean_absolute_error(
138
+ y_true=y, y_pred=self._last_train_prediction
139
+ )
140
+
141
+ self.train_mse.append(train_mse)
142
+ self.train_mae.append(train_mae)
143
+
144
+ if eval_set is not None:
145
+ this_iter_eval_set_mse: list[float | None] = [
146
+ None for _ in range(len(eval_set))
147
+ ]
148
+ this_iter_eval_set_mae: list[float | None] = [
149
+ None for _ in range(len(eval_set))
150
+ ]
151
+
152
+ for i, eval_tuple in enumerate(eval_set):
153
+ if eval_tuple is None:
154
+ self._last_eval_set_prediction_[i] = None
155
+ continue
156
+ ebm_df_eval, y_eval = eval_tuple
157
+ assert isinstance(ebm_df_eval, pd.DataFrame)
158
+
159
+ base_learner_prediction = self._target_variable_mean_[
160
+ -1
161
+ ] + self.learning_rate * new_base_learner.predict(
162
+ ebm_df_eval[columns_to_keep]
163
+ )
164
+
165
+ self._last_eval_set_prediction_[i] += base_learner_prediction
166
+ this_iter_eval_set_mse[i] = mean_squared_error(
167
+ y_true=y_eval, y_pred=self._last_eval_set_prediction_[i]
168
+ )
169
+ this_iter_eval_set_mae[i] = mean_absolute_error(
170
+ y_true=y_eval, y_pred=self._last_eval_set_prediction_[i]
171
+ )
172
+
173
+ if len(self.eval_sets_mse) == 0:
174
+ for eval_set_error in this_iter_eval_set_mse:
175
+ self.eval_sets_mse.append([eval_set_error])
176
+ else:
177
+ for i, eval_set_error in enumerate(this_iter_eval_set_mse):
178
+ self.eval_sets_mse[i].append(eval_set_error)
179
+
180
+ if len(self.eval_sets_mae) == 0:
181
+ for eval_set_error in this_iter_eval_set_mae:
182
+ self.eval_sets_mae.append([eval_set_error])
183
+ else:
184
+ for i, eval_set_error in enumerate(this_iter_eval_set_mae):
185
+ self.eval_sets_mae[i].append(eval_set_error)
186
+
187
+ return self
188
+
189
+ def predict(self, X: pd.DataFrame, **kwargs):
190
+ predictions = self.predict_step_by_step(X, **kwargs)
191
+ return predictions[-1]
192
+
193
+ def predict_step_by_step(self, X: pd.DataFrame, **kwargs) -> list[np.array]:
194
+ prediction = []
195
+ last_prediction = np.zeros(len(X))
196
+ for i, base_learner in enumerate(self.base_learners_list):
197
+ chosen_columns = self.considered_columns[i]
198
+ last_prediction += self._target_variable_mean_[
199
+ i
200
+ ] + self.learning_rate * np.array(
201
+ base_learner.predict(X[chosen_columns], **kwargs)
202
+ )
203
+ prediction.append(copy.deepcopy(last_prediction))
204
+ return prediction
205
+
206
+ def evaluate(self, X: pd.DataFrame, y: Iterable, **kwargs) -> list[float]:
207
+ # it returns the evolution of the mse with increasing number of iterations
208
+ predictions = self.predict_step_by_step(X, **kwargs)
209
+ evolution_mse = []
210
+ for prediction in predictions:
211
+ mse = mean_squared_error(y_true=y, y_pred=prediction)
212
+ evolution_mse.append(mse)
213
+ return evolution_mse
214
+
215
+ def get_model(self):
216
+ return self.base_learners_list
217
+
218
+ @staticmethod
219
+ def _neg_gradient(y, y_hat):
220
+ return y - y_hat
221
+
222
+
223
+ # Learning rate schedulers
224
+ def exponential_decay_scheduler(
225
+ initial_lr: float, iteration: int, decay_rate: float = 0.95
226
+ ) -> float:
227
+ """
228
+ Exponential decay learning rate scheduler.
229
+
230
+ lr = initial_lr * decay_rate^iteration
231
+
232
+ Parameters
233
+ ----------
234
+ initial_lr : float
235
+ The initial learning rate.
236
+ iteration : int
237
+ The current iteration number (0-indexed).
238
+ decay_rate : float, default=0.95
239
+ The decay rate per iteration.
240
+
241
+ Returns
242
+ -------
243
+ float
244
+ The learning rate for this iteration.
245
+ """
246
+ return initial_lr * (decay_rate**iteration)
247
+
248
+
249
+ def step_decay_scheduler(
250
+ initial_lr: float, iteration: int, drop_every: int = 10, drop_factor: float = 0.5
251
+ ) -> float:
252
+ """
253
+ Step decay learning rate scheduler.
254
+
255
+ Learning rate drops by drop_factor every drop_every iterations.
256
+
257
+ Parameters
258
+ ----------
259
+ initial_lr : float
260
+ The initial learning rate.
261
+ iteration : int
262
+ The current iteration number (0-indexed).
263
+ drop_every : int, default=10
264
+ Number of iterations between drops.
265
+ drop_factor : float, default=0.5
266
+ Factor by which to multiply the learning rate at each drop.
267
+
268
+ Returns
269
+ -------
270
+ float
271
+ The learning rate for this iteration.
272
+ """
273
+ return initial_lr * (drop_factor ** (iteration // drop_every))
274
+
275
+
276
+ def linear_decay_scheduler(
277
+ initial_lr: float, iteration: int, total_iterations: int = 100, min_lr: float = 0.01
278
+ ) -> float:
279
+ """
280
+ Linear decay learning rate scheduler.
281
+
282
+ Learning rate decays linearly from initial_lr to min_lr over total_iterations.
283
+
284
+ Parameters
285
+ ----------
286
+ initial_lr : float
287
+ The initial learning rate.
288
+ iteration : int
289
+ The current iteration number (0-indexed).
290
+ total_iterations : int, default=100
291
+ Total number of iterations over which to decay.
292
+ min_lr : float, default=0.01
293
+ Minimum learning rate.
294
+
295
+ Returns
296
+ -------
297
+ float
298
+ The learning rate for this iteration.
299
+ """
300
+ decay = (initial_lr - min_lr) / total_iterations
301
+ return max(min_lr, initial_lr - decay * iteration)