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/__init__.py +18 -0
- path_boost/_path_boost.py +1096 -0
- path_boost/_version.py +24 -0
- path_boost/utils/__init__.py +2 -0
- path_boost/utils/classes/__init__.py +0 -0
- path_boost/utils/classes/additive_model_wrapper.py +301 -0
- path_boost/utils/classes/additive_model_wrapper_classifier.py +394 -0
- path_boost/utils/classes/extended_boosting_matrix.py +596 -0
- path_boost/utils/classes/interfaces/__init__.py +0 -0
- path_boost/utils/classes/interfaces/interface_base_learner.py +30 -0
- path_boost/utils/classes/interfaces/interface_selector.py +27 -0
- path_boost/utils/classes/sequential_path_boost.py +1023 -0
- path_boost/utils/classes/sequential_path_boost_classifier.py +840 -0
- path_boost/utils/cross_validation.py +49 -0
- path_boost/utils/cyclic_path_boost_utils.py +76 -0
- path_boost/utils/datasets_for_examples/__init__.py +2 -0
- path_boost/utils/datasets_for_examples/generate_example_dataset.py +304 -0
- path_boost/utils/discovery.py +217 -0
- path_boost/utils/plots_functions.py +153 -0
- path_boost/utils/validate_data.py +223 -0
- path_boost/utils/variable_importance_according_to_path_boost.py +341 -0
- path_boost-2.1.0.dist-info/METADATA +174 -0
- path_boost-2.1.0.dist-info/RECORD +26 -0
- path_boost-2.1.0.dist-info/WHEEL +5 -0
- path_boost-2.1.0.dist-info/licenses/LICENSE +21 -0
- path_boost-2.1.0.dist-info/top_level.txt +1 -0
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
|
|
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)
|