ngboost 0.5.10.dev0__tar.gz → 0.6.0__tar.gz
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.
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/PKG-INFO +6 -6
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/api.py +160 -11
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/distns/exponential.py +5 -4
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/distns/laplace.py +4 -2
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/distns/lognormal.py +3 -10
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/distns/normal.py +3 -2
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/distns/t.py +1 -1
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/learners.py +1 -1
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/ngboost.py +217 -25
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/pyproject.toml +10 -7
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/LICENSE +0 -0
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/README.md +0 -0
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/__init__.py +0 -0
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/distns/__init__.py +0 -0
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/distns/beta.py +0 -0
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/distns/beta_bernoulli.py +0 -0
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/distns/betabinomial.py +0 -0
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/distns/categorical.py +0 -0
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/distns/cauchy.py +0 -0
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/distns/distn.py +0 -0
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/distns/gamma.py +0 -0
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/distns/halfnormal.py +0 -0
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/distns/logitnormal.py +0 -0
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/distns/multivariate_normal.py +0 -0
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/distns/poisson.py +0 -0
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/distns/sympy_utils.py +0 -0
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/distns/utils.py +0 -0
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/distns/weibull.py +0 -0
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/evaluation.py +0 -0
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/helpers.py +0 -0
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/manifold.py +0 -0
- {ngboost-0.5.10.dev0 → ngboost-0.6.0}/ngboost/scores.py +0 -0
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: ngboost
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Library for probabilistic predictions via gradient boosting.
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Author: Stanford ML Group
|
|
7
7
|
Author-email: avati@cs.stanford.edu
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.10,<3.15
|
|
9
9
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
13
12
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
13
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Requires-Dist: joblib (>=1.2)
|
|
17
17
|
Requires-Dist: lifelines (>=0.25)
|
|
18
|
-
Requires-Dist: matplotlib (>=3.0
|
|
19
|
-
Requires-Dist: matplotlib (>=3.10.0) ; python_version >= "3.10"
|
|
18
|
+
Requires-Dist: matplotlib (>=3.10.0)
|
|
20
19
|
Requires-Dist: numpy (>=1.21.2) ; python_version < "3.13"
|
|
21
20
|
Requires-Dist: numpy (>=2.1.0) ; python_version >= "3.13"
|
|
22
|
-
Requires-Dist: scikit-learn (>=1.6,<
|
|
21
|
+
Requires-Dist: scikit-learn (>=1.6,<1.7) ; python_version < "3.14"
|
|
22
|
+
Requires-Dist: scikit-learn (>=1.8.0,<2.0) ; python_version >= "3.14"
|
|
23
23
|
Requires-Dist: scipy (>=1.14.1) ; python_version >= "3.13"
|
|
24
24
|
Requires-Dist: scipy (>=1.7.2) ; python_version < "3.13"
|
|
25
25
|
Requires-Dist: sympy (>=1.12)
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"The NGBoost library API"
|
|
2
|
+
|
|
2
3
|
# pylint: disable=too-many-arguments
|
|
3
|
-
|
|
4
|
+
import numpy as np
|
|
5
|
+
from sklearn.base import BaseEstimator, ClassifierMixin
|
|
6
|
+
from sklearn.preprocessing import LabelEncoder
|
|
4
7
|
from sklearn.utils import check_array
|
|
5
8
|
|
|
6
9
|
from ngboost.distns import (
|
|
@@ -31,8 +34,15 @@ class NGBRegressor(NGBoost, BaseEstimator):
|
|
|
31
34
|
A distribution from ngboost.distns, e.g. Normal
|
|
32
35
|
Score : rule to compare probabilistic predictions P̂ to the observed data y.
|
|
33
36
|
A score from ngboost.scores, e.g. LogScore
|
|
34
|
-
Base : base learner to use in the boosting algorithm.
|
|
35
|
-
|
|
37
|
+
Base : base learner(s) to use in the boosting algorithm.
|
|
38
|
+
Pass a single instantiated sklearn regressor, e.g.
|
|
39
|
+
DecisionTreeRegressor(), to use the same learner for
|
|
40
|
+
every distribution parameter. To use different learners
|
|
41
|
+
per distribution parameter, pass a list/tuple of
|
|
42
|
+
instantiated sklearn regressors with length equal to
|
|
43
|
+
Dist.n_params. The sequence order matches the distribution
|
|
44
|
+
parameter order; for example, Normal uses
|
|
45
|
+
[loc_learner, scale_learner].
|
|
36
46
|
natural_gradient : logical flag indicating whether the natural gradient should be used
|
|
37
47
|
n_estimators : the number of boosting iterations to fit
|
|
38
48
|
learning_rate : the learning rate
|
|
@@ -49,12 +59,13 @@ class NGBRegressor(NGBoost, BaseEstimator):
|
|
|
49
59
|
loss has to increase before the algorithm stops early.
|
|
50
60
|
Set to None to disable early stopping and validation.
|
|
51
61
|
None enables running over the full data set.
|
|
62
|
+
n_jobs : see the ``NGBoost`` base class.
|
|
52
63
|
|
|
53
64
|
Output:
|
|
54
65
|
An NGBRegressor object that can be fit.
|
|
55
66
|
"""
|
|
56
67
|
|
|
57
|
-
# pylint: disable=too-many-positional-arguments
|
|
68
|
+
# pylint: disable=too-many-positional-arguments,too-many-locals
|
|
58
69
|
def __init__(
|
|
59
70
|
self,
|
|
60
71
|
Dist=Normal,
|
|
@@ -71,6 +82,7 @@ class NGBRegressor(NGBoost, BaseEstimator):
|
|
|
71
82
|
random_state=None,
|
|
72
83
|
validation_fraction=0.1,
|
|
73
84
|
early_stopping_rounds=None,
|
|
85
|
+
n_jobs=None,
|
|
74
86
|
):
|
|
75
87
|
assert issubclass(
|
|
76
88
|
Dist, RegressionDistn
|
|
@@ -96,6 +108,7 @@ class NGBRegressor(NGBoost, BaseEstimator):
|
|
|
96
108
|
random_state,
|
|
97
109
|
validation_fraction,
|
|
98
110
|
early_stopping_rounds,
|
|
111
|
+
n_jobs,
|
|
99
112
|
)
|
|
100
113
|
|
|
101
114
|
self._estimator_type = "regressor"
|
|
@@ -114,7 +127,7 @@ class NGBRegressor(NGBoost, BaseEstimator):
|
|
|
114
127
|
super().__setstate__(state_dict)
|
|
115
128
|
|
|
116
129
|
|
|
117
|
-
class NGBClassifier(NGBoost, BaseEstimator):
|
|
130
|
+
class NGBClassifier(ClassifierMixin, NGBoost, BaseEstimator):
|
|
118
131
|
"""
|
|
119
132
|
Constructor for NGBoost classification models.
|
|
120
133
|
|
|
@@ -127,8 +140,15 @@ class NGBClassifier(NGBoost, BaseEstimator):
|
|
|
127
140
|
A distribution from ngboost.distns, e.g. Bernoulli
|
|
128
141
|
Score : rule to compare probabilistic predictions P̂ to the observed data y.
|
|
129
142
|
A score from ngboost.scores, e.g. LogScore
|
|
130
|
-
Base : base learner to use in the boosting algorithm.
|
|
131
|
-
|
|
143
|
+
Base : base learner(s) to use in the boosting algorithm.
|
|
144
|
+
Pass a single instantiated sklearn regressor, e.g.
|
|
145
|
+
DecisionTreeRegressor(), to use the same learner for
|
|
146
|
+
every distribution parameter. To use different learners
|
|
147
|
+
per distribution parameter, pass a list/tuple of
|
|
148
|
+
instantiated sklearn regressors with length equal to
|
|
149
|
+
Dist.n_params. The sequence order matches the distribution
|
|
150
|
+
parameter order; for example, Normal uses
|
|
151
|
+
[loc_learner, scale_learner].
|
|
132
152
|
natural_gradient : logical flag indicating whether the natural gradient should be used
|
|
133
153
|
n_estimators : the number of boosting iterations to fit
|
|
134
154
|
learning_rate : the learning rate
|
|
@@ -139,11 +159,18 @@ class NGBClassifier(NGBoost, BaseEstimator):
|
|
|
139
159
|
tol : numerical tolerance to be used in optimization
|
|
140
160
|
random_state : seed for reproducibility. See
|
|
141
161
|
https://stackoverflow.com/questions/28064634/random-state-pseudo-random-number-in-scikit-learn
|
|
162
|
+
validation_fraction: Proportion of training data to set
|
|
163
|
+
aside as validation data for early stopping.
|
|
164
|
+
early_stopping_rounds: The number of consecutive boosting iterations during which the
|
|
165
|
+
loss has to increase before the algorithm stops early.
|
|
166
|
+
Set to None to disable early stopping and validation.
|
|
167
|
+
None enables running over the full data set.
|
|
168
|
+
n_jobs : see the ``NGBoost`` base class.
|
|
142
169
|
Output:
|
|
143
170
|
An NGBClassifier object that can be fit.
|
|
144
171
|
"""
|
|
145
172
|
|
|
146
|
-
# pylint: disable=too-many-positional-arguments
|
|
173
|
+
# pylint: disable=too-many-positional-arguments,too-many-locals
|
|
147
174
|
def __init__(
|
|
148
175
|
self,
|
|
149
176
|
Dist=Bernoulli,
|
|
@@ -158,6 +185,9 @@ class NGBClassifier(NGBoost, BaseEstimator):
|
|
|
158
185
|
verbose_eval=100,
|
|
159
186
|
tol=1e-4,
|
|
160
187
|
random_state=None,
|
|
188
|
+
validation_fraction=0.1,
|
|
189
|
+
early_stopping_rounds=None,
|
|
190
|
+
n_jobs=None,
|
|
161
191
|
):
|
|
162
192
|
assert issubclass(
|
|
163
193
|
Dist, ClassificationDistn
|
|
@@ -175,9 +205,108 @@ class NGBClassifier(NGBoost, BaseEstimator):
|
|
|
175
205
|
verbose_eval,
|
|
176
206
|
tol,
|
|
177
207
|
random_state,
|
|
208
|
+
validation_fraction,
|
|
209
|
+
early_stopping_rounds,
|
|
210
|
+
n_jobs,
|
|
178
211
|
)
|
|
179
212
|
self._estimator_type = "classifier"
|
|
180
213
|
|
|
214
|
+
def _fit_label_encoder(self, Y):
|
|
215
|
+
le = LabelEncoder().fit(Y)
|
|
216
|
+
self._le = le # pylint: disable=attribute-defined-outside-init
|
|
217
|
+
self.classes_ = le.classes_ # pylint: disable=attribute-defined-outside-init
|
|
218
|
+
n_classes = self.Dist.n_params + 1
|
|
219
|
+
if len(self.classes_) != n_classes:
|
|
220
|
+
raise ValueError(
|
|
221
|
+
"NGBClassifier Dist expects "
|
|
222
|
+
f"{n_classes} classes, got {len(self.classes_)}."
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
def __setstate__(
|
|
226
|
+
self, state_dict
|
|
227
|
+
): # pylint: disable=attribute-defined-outside-init
|
|
228
|
+
super().__setstate__(state_dict)
|
|
229
|
+
if not hasattr(self, "classes_"):
|
|
230
|
+
self.classes_ = np.arange(self.Dist.n_params + 1)
|
|
231
|
+
if not hasattr(self, "_le"):
|
|
232
|
+
self._le = LabelEncoder()
|
|
233
|
+
self._le.classes_ = self.classes_
|
|
234
|
+
|
|
235
|
+
# fit and partial_fit pass their arguments on to NGBoost unchanged, so
|
|
236
|
+
# pylint reads the calls as copies of the one in NGBoost.fit.
|
|
237
|
+
# pylint: disable=too-many-positional-arguments,duplicate-code
|
|
238
|
+
def fit(
|
|
239
|
+
self,
|
|
240
|
+
X,
|
|
241
|
+
Y,
|
|
242
|
+
X_val=None,
|
|
243
|
+
Y_val=None,
|
|
244
|
+
sample_weight=None,
|
|
245
|
+
val_sample_weight=None,
|
|
246
|
+
train_loss_monitor=None,
|
|
247
|
+
val_loss_monitor=None,
|
|
248
|
+
early_stopping_rounds=None,
|
|
249
|
+
):
|
|
250
|
+
# Learn the classes on every fit. NGBoost.fit then calls partial_fit,
|
|
251
|
+
# which encodes Y and Y_val with them.
|
|
252
|
+
self._fit_label_encoder(Y)
|
|
253
|
+
super().fit(
|
|
254
|
+
X,
|
|
255
|
+
Y,
|
|
256
|
+
X_val=X_val,
|
|
257
|
+
Y_val=Y_val,
|
|
258
|
+
sample_weight=sample_weight,
|
|
259
|
+
val_sample_weight=val_sample_weight,
|
|
260
|
+
train_loss_monitor=train_loss_monitor,
|
|
261
|
+
val_loss_monitor=val_loss_monitor,
|
|
262
|
+
early_stopping_rounds=early_stopping_rounds,
|
|
263
|
+
)
|
|
264
|
+
return self
|
|
265
|
+
|
|
266
|
+
# pylint: disable=too-many-positional-arguments
|
|
267
|
+
def partial_fit(
|
|
268
|
+
self,
|
|
269
|
+
X,
|
|
270
|
+
Y,
|
|
271
|
+
X_val=None,
|
|
272
|
+
Y_val=None,
|
|
273
|
+
sample_weight=None,
|
|
274
|
+
val_sample_weight=None,
|
|
275
|
+
train_loss_monitor=None,
|
|
276
|
+
val_loss_monitor=None,
|
|
277
|
+
early_stopping_rounds=None,
|
|
278
|
+
):
|
|
279
|
+
if not hasattr(self, "classes_"):
|
|
280
|
+
self._fit_label_encoder(Y)
|
|
281
|
+
Y = self._le.transform(Y)
|
|
282
|
+
if Y_val is not None:
|
|
283
|
+
Y_val = self._le.transform(Y_val)
|
|
284
|
+
super().partial_fit(
|
|
285
|
+
X,
|
|
286
|
+
Y,
|
|
287
|
+
X_val=X_val,
|
|
288
|
+
Y_val=Y_val,
|
|
289
|
+
sample_weight=sample_weight,
|
|
290
|
+
val_sample_weight=val_sample_weight,
|
|
291
|
+
train_loss_monitor=train_loss_monitor,
|
|
292
|
+
val_loss_monitor=val_loss_monitor,
|
|
293
|
+
early_stopping_rounds=early_stopping_rounds,
|
|
294
|
+
)
|
|
295
|
+
return self
|
|
296
|
+
|
|
297
|
+
def predict(self, X, max_iter=None):
|
|
298
|
+
return self.classes_[super().predict(X, max_iter=max_iter)]
|
|
299
|
+
|
|
300
|
+
def staged_predict(self, X, max_iter=None):
|
|
301
|
+
return [
|
|
302
|
+
self.classes_[pred] for pred in super().staged_predict(X, max_iter=max_iter)
|
|
303
|
+
]
|
|
304
|
+
|
|
305
|
+
def score(self, X, Y): # for sklearn
|
|
306
|
+
# Call NGBoost.score directly: super() would reach ClassifierMixin.score,
|
|
307
|
+
# which returns accuracy.
|
|
308
|
+
return NGBoost.score(self, X, self._le.transform(Y))
|
|
309
|
+
|
|
181
310
|
def predict_proba(self, X, max_iter=None):
|
|
182
311
|
"""
|
|
183
312
|
Probability prediction of Y at the points X=x
|
|
@@ -221,8 +350,15 @@ class NGBSurvival(NGBoost, BaseEstimator):
|
|
|
221
350
|
A distribution from ngboost.distns, e.g. LogNormal
|
|
222
351
|
Score : rule to compare probabilistic predictions P̂ to the observed data y.
|
|
223
352
|
A score from ngboost.scores, e.g. LogScore
|
|
224
|
-
Base : base learner to use in the boosting algorithm.
|
|
225
|
-
|
|
353
|
+
Base : base learner(s) to use in the boosting algorithm.
|
|
354
|
+
Pass a single instantiated sklearn regressor, e.g.
|
|
355
|
+
DecisionTreeRegressor(), to use the same learner for
|
|
356
|
+
every distribution parameter. To use different learners
|
|
357
|
+
per distribution parameter, pass a list/tuple of
|
|
358
|
+
instantiated sklearn regressors with length equal to
|
|
359
|
+
Dist.n_params. The sequence order matches the distribution
|
|
360
|
+
parameter order; for example, Normal uses
|
|
361
|
+
[loc_learner, scale_learner].
|
|
226
362
|
natural_gradient : logical flag indicating whether the natural gradient should be used
|
|
227
363
|
n_estimators : the number of boosting iterations to fit
|
|
228
364
|
learning_rate : the learning rate
|
|
@@ -233,11 +369,18 @@ class NGBSurvival(NGBoost, BaseEstimator):
|
|
|
233
369
|
tol : numerical tolerance to be used in optimization
|
|
234
370
|
random_state : seed for reproducibility. See
|
|
235
371
|
https://stackoverflow.com/questions/28064634/random-state-pseudo-random-number-in-scikit-learn
|
|
372
|
+
validation_fraction: Proportion of training data to set
|
|
373
|
+
aside as validation data for early stopping.
|
|
374
|
+
early_stopping_rounds: The number of consecutive boosting iterations during which the
|
|
375
|
+
loss has to increase before the algorithm stops early.
|
|
376
|
+
Set to None to disable early stopping and validation.
|
|
377
|
+
None enables running over the full data set.
|
|
378
|
+
n_jobs : see the ``NGBoost`` base class.
|
|
236
379
|
Output:
|
|
237
380
|
An NGBSurvival object that can be fit.
|
|
238
381
|
"""
|
|
239
382
|
|
|
240
|
-
# pylint: disable=too-many-positional-arguments
|
|
383
|
+
# pylint: disable=too-many-positional-arguments,too-many-locals
|
|
241
384
|
def __init__(
|
|
242
385
|
self,
|
|
243
386
|
Dist=LogNormal,
|
|
@@ -252,6 +395,9 @@ class NGBSurvival(NGBoost, BaseEstimator):
|
|
|
252
395
|
verbose_eval=100,
|
|
253
396
|
tol=1e-4,
|
|
254
397
|
random_state=None,
|
|
398
|
+
validation_fraction=0.1,
|
|
399
|
+
early_stopping_rounds=None,
|
|
400
|
+
n_jobs=None,
|
|
255
401
|
):
|
|
256
402
|
|
|
257
403
|
assert issubclass(
|
|
@@ -278,6 +424,9 @@ class NGBSurvival(NGBoost, BaseEstimator):
|
|
|
278
424
|
verbose_eval,
|
|
279
425
|
tol,
|
|
280
426
|
random_state,
|
|
427
|
+
validation_fraction,
|
|
428
|
+
early_stopping_rounds,
|
|
429
|
+
n_jobs,
|
|
281
430
|
)
|
|
282
431
|
|
|
283
432
|
def __getstate__(self):
|
|
@@ -32,16 +32,17 @@ class ExponentialCRPScore(CRPScore):
|
|
|
32
32
|
def score(self, Y):
|
|
33
33
|
E, T = Y["Event"], Y["Time"]
|
|
34
34
|
score = T + self.scale * (2 * np.exp(-T / self.scale) - 1.5)
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
# a censored observation only scores the CDF up to the censoring time
|
|
36
|
+
score[E == 0] -= (
|
|
37
|
+
0.5 * self.scale[E == 0] * np.exp(-2 * T[E == 0] / self.scale[E == 0])
|
|
37
38
|
)
|
|
38
39
|
return score
|
|
39
40
|
|
|
40
41
|
def d_score(self, Y):
|
|
41
42
|
E, T = Y["Event"], Y["Time"]
|
|
42
43
|
deriv = 2 * np.exp(-T / self.scale) * (self.scale + T) - 1.5 * self.scale
|
|
43
|
-
deriv[E ==
|
|
44
|
-
0.5 * self.scale[E ==
|
|
44
|
+
deriv[E == 0] -= np.exp(-2 * T[E == 0] / self.scale[E == 0]) * (
|
|
45
|
+
0.5 * self.scale[E == 0] + T[E == 0]
|
|
45
46
|
)
|
|
46
47
|
return deriv.reshape((-1, 1))
|
|
47
48
|
|
|
@@ -37,8 +37,10 @@ class LaplaceCRPScore(CRPScore):
|
|
|
37
37
|
D[:, 0] = np.sign(self.loc - Y) * (
|
|
38
38
|
1 - np.exp(-np.abs(Y - self.loc) / self.scale)
|
|
39
39
|
)
|
|
40
|
-
D[:, 1] =
|
|
41
|
-
|
|
40
|
+
D[:, 1] = (
|
|
41
|
+
np.exp(-np.abs(Y - self.loc) / self.scale)
|
|
42
|
+
* (self.scale + np.abs(Y - self.loc))
|
|
43
|
+
- 0.75 * self.scale
|
|
42
44
|
)
|
|
43
45
|
return D
|
|
44
46
|
|
|
@@ -30,11 +30,7 @@ class LogNormalLogScoreCensored(LogScore):
|
|
|
30
30
|
D_cens[:, 0] = -sp.stats.norm.pdf(lT, loc=self.loc, scale=self.scale) / (
|
|
31
31
|
1 - self.dist.cdf(T) + self.eps
|
|
32
32
|
)
|
|
33
|
-
D_cens[:, 1] = (
|
|
34
|
-
-Z
|
|
35
|
-
* sp.stats.norm.pdf(lT, loc=self.loc, scale=self.scale)
|
|
36
|
-
/ (1 - self.dist.cdf(T) + self.eps)
|
|
37
|
-
)
|
|
33
|
+
D_cens[:, 1] = -Z * sp.stats.norm.pdf(Z) / (1 - self.dist.cdf(T) + self.eps)
|
|
38
34
|
|
|
39
35
|
return (1 - E) * D_cens + E * D_uncens
|
|
40
36
|
|
|
@@ -71,12 +67,9 @@ class LogNormalCRPScoreCensored(CRPScore):
|
|
|
71
67
|
Z = (lT - self.loc) / self.scale
|
|
72
68
|
|
|
73
69
|
D = np.zeros((self.loc.shape[0], 2))
|
|
70
|
+
# the censored CRPS is scale * integral of Phi(z)**2 up to Z
|
|
74
71
|
D[:, 0] = E * -(2 * sp.stats.norm.cdf(Z) - 1) + (1 - E) * -(
|
|
75
72
|
sp.stats.norm.cdf(Z) ** 2
|
|
76
|
-
+ 2 * Z * sp.stats.norm.cdf(Z) * sp.stats.norm.pdf(Z)
|
|
77
|
-
+ 2 * sp.stats.norm.pdf(Z) ** 2
|
|
78
|
-
- 2 * sp.stats.norm.cdf(Z) * sp.stats.norm.pdf(Z) ** 2
|
|
79
|
-
- np.sqrt(2 / np.pi) * sp.stats.norm.pdf(np.sqrt(2) * Z)
|
|
80
73
|
)
|
|
81
74
|
D[:, 1] = self.score(Y) + (lT - self.loc) * D[:, 0]
|
|
82
75
|
return D
|
|
@@ -85,7 +78,7 @@ class LogNormalCRPScoreCensored(CRPScore):
|
|
|
85
78
|
I = np.zeros((self.loc.shape[0], 2, 2))
|
|
86
79
|
I[:, 0, 0] = 2
|
|
87
80
|
I[:, 1, 1] = self.scale**2
|
|
88
|
-
I /= 2 * np.sqrt(np.pi)
|
|
81
|
+
I /= 2 * np.sqrt(np.pi) * self.scale[:, np.newaxis, np.newaxis]
|
|
89
82
|
return I
|
|
90
83
|
|
|
91
84
|
|
|
@@ -49,7 +49,8 @@ class NormalCRPScore(CRPScore):
|
|
|
49
49
|
self.var,
|
|
50
50
|
]
|
|
51
51
|
I = I.reshape((self.var.shape[0], 2, 2))
|
|
52
|
-
|
|
52
|
+
# 2 * integral of grad F grad F^T, which scales like 1 / scale
|
|
53
|
+
I = 1 / (2 * np.sqrt(np.pi) * self.scale[:, np.newaxis, np.newaxis]) * I
|
|
53
54
|
return I
|
|
54
55
|
|
|
55
56
|
|
|
@@ -185,7 +186,7 @@ class NormalFixedMeanCRPScore(CRPScore):
|
|
|
185
186
|
return D
|
|
186
187
|
|
|
187
188
|
def metric(self):
|
|
188
|
-
I = np.c_[self.
|
|
189
|
+
I = np.c_[self.scale]
|
|
189
190
|
I = I.reshape((self.var.shape[0], 1, 1))
|
|
190
191
|
I = 1 / (2 * np.sqrt(np.pi)) * I
|
|
191
192
|
return I
|
|
@@ -103,7 +103,7 @@ class TFixedDfLogScore(LogScore):
|
|
|
103
103
|
def metric(self):
|
|
104
104
|
FI = np.zeros((self.var.shape[0], 2, 2))
|
|
105
105
|
FI[:, 0, 0] = (self.df + 1) / ((self.df + 3) * self.var)
|
|
106
|
-
FI[:, 1, 1] =
|
|
106
|
+
FI[:, 1, 1] = 2 * self.df / (self.df + 3)
|
|
107
107
|
return FI
|
|
108
108
|
|
|
109
109
|
|
|
@@ -2,7 +2,7 @@ from sklearn.linear_model import Ridge
|
|
|
2
2
|
from sklearn.tree import DecisionTreeRegressor
|
|
3
3
|
|
|
4
4
|
default_tree_learner = DecisionTreeRegressor(
|
|
5
|
-
criterion="
|
|
5
|
+
criterion="squared_error",
|
|
6
6
|
min_samples_split=2,
|
|
7
7
|
min_samples_leaf=1,
|
|
8
8
|
min_weight_fraction_leaf=0.0,
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
# pylint: disable=unused-variable,invalid-unary-operand-type,attribute-defined-outside-init
|
|
6
6
|
# pylint: disable=redundant-keyword-arg,protected-access,unnecessary-lambda-assignment
|
|
7
7
|
import numpy as np
|
|
8
|
+
from joblib import Parallel, delayed
|
|
9
|
+
from scipy.sparse import issparse
|
|
8
10
|
from sklearn.base import clone
|
|
9
11
|
from sklearn.model_selection import train_test_split
|
|
10
12
|
from sklearn.tree import DecisionTreeRegressor
|
|
@@ -16,6 +18,28 @@ from ngboost.manifold import manifold
|
|
|
16
18
|
from ngboost.scores import LogScore
|
|
17
19
|
|
|
18
20
|
|
|
21
|
+
def _validate_sample_weight(sample_weight, X, input_name="sample_weight"):
|
|
22
|
+
"""Return sample_weight as a 1-D float array with one entry per row of X.
|
|
23
|
+
|
|
24
|
+
Weights are matched to rows by position, so a pandas index is ignored.
|
|
25
|
+
"""
|
|
26
|
+
if sample_weight is None:
|
|
27
|
+
return None
|
|
28
|
+
sample_weight = check_array(
|
|
29
|
+
sample_weight, ensure_2d=False, dtype=np.float64, input_name=input_name
|
|
30
|
+
)
|
|
31
|
+
if sample_weight.ndim != 1:
|
|
32
|
+
raise ValueError(
|
|
33
|
+
f"{input_name} must be 1-D, got an array of shape {sample_weight.shape}"
|
|
34
|
+
)
|
|
35
|
+
if sample_weight.shape[0] != X.shape[0]:
|
|
36
|
+
raise ValueError(
|
|
37
|
+
f"{input_name} must have one entry per row: got {sample_weight.shape[0]} "
|
|
38
|
+
f"entries for {X.shape[0]} rows"
|
|
39
|
+
)
|
|
40
|
+
return sample_weight
|
|
41
|
+
|
|
42
|
+
|
|
19
43
|
class NGBoost:
|
|
20
44
|
"""
|
|
21
45
|
Constructor for all NGBoost models.
|
|
@@ -29,8 +53,15 @@ class NGBoost:
|
|
|
29
53
|
A distribution from ngboost.distns, e.g. Normal
|
|
30
54
|
Score : rule to compare probabilistic predictions P̂ to the observed data y.
|
|
31
55
|
A score from ngboost.scores, e.g. LogScore
|
|
32
|
-
Base : base learner to use in the boosting algorithm.
|
|
33
|
-
|
|
56
|
+
Base : base learner(s) to use in the boosting algorithm.
|
|
57
|
+
Pass a single instantiated sklearn regressor, e.g.
|
|
58
|
+
DecisionTreeRegressor(), to use the same learner for
|
|
59
|
+
every distribution parameter. To use different learners
|
|
60
|
+
per distribution parameter, pass a list/tuple of
|
|
61
|
+
instantiated sklearn regressors with length equal to
|
|
62
|
+
Dist.n_params. The sequence order matches the distribution
|
|
63
|
+
parameter order; for example, Normal uses
|
|
64
|
+
[loc_learner, scale_learner].
|
|
34
65
|
natural_gradient : logical flag indicating whether the natural gradient should be used
|
|
35
66
|
n_estimators : the number of boosting iterations to fit
|
|
36
67
|
learning_rate : the learning rate
|
|
@@ -46,6 +77,18 @@ class NGBoost:
|
|
|
46
77
|
loss has to increase before the algorithm stops early.
|
|
47
78
|
Set to None to disable early stopping and validation.
|
|
48
79
|
None enables running over the full data set.
|
|
80
|
+
n_jobs : number of joblib threads used to fit the per parameter
|
|
81
|
+
base learners in parallel on each boosting round. None
|
|
82
|
+
or 1 (the default) fits them serially. A value above 1,
|
|
83
|
+
or ``-1`` for all cores, speeds up fitting when the
|
|
84
|
+
distribution has many parameters, such as a high
|
|
85
|
+
dimensional MultivariateNormal or classification with
|
|
86
|
+
many classes; on small problems the thread overhead can
|
|
87
|
+
make it slower instead. Threads are used so the shared
|
|
88
|
+
data is not copied per worker; when nesting inside an
|
|
89
|
+
outer parallel job keep the product of the thread counts
|
|
90
|
+
near the core count. The fitted model does not depend on
|
|
91
|
+
n_jobs.
|
|
49
92
|
|
|
50
93
|
|
|
51
94
|
Output:
|
|
@@ -69,6 +112,7 @@ class NGBoost:
|
|
|
69
112
|
random_state=None,
|
|
70
113
|
validation_fraction=0.1,
|
|
71
114
|
early_stopping_rounds=None,
|
|
115
|
+
n_jobs=None,
|
|
72
116
|
):
|
|
73
117
|
self.Dist = Dist
|
|
74
118
|
self.Score = Score
|
|
@@ -87,10 +131,11 @@ class NGBoost:
|
|
|
87
131
|
self.scalings = []
|
|
88
132
|
self.col_idxs = []
|
|
89
133
|
self.tol = tol
|
|
90
|
-
self.random_state =
|
|
134
|
+
self.random_state = random_state
|
|
91
135
|
self.best_val_loss_itr = None
|
|
92
136
|
self.validation_fraction = validation_fraction
|
|
93
137
|
self.early_stopping_rounds = early_stopping_rounds
|
|
138
|
+
self.n_jobs = n_jobs
|
|
94
139
|
|
|
95
140
|
if hasattr(self.Dist, "multi_output"):
|
|
96
141
|
self.multi_output = self.Dist.multi_output
|
|
@@ -120,6 +165,11 @@ class NGBoost:
|
|
|
120
165
|
state_dict["Manifold"] = manifold(state_dict["Score"], state_dict["Dist"])
|
|
121
166
|
self.__dict__ = state_dict
|
|
122
167
|
|
|
168
|
+
def __sklearn_is_fitted__(self):
|
|
169
|
+
# check_is_fitted (used by Pipeline.predict) otherwise looks for
|
|
170
|
+
# attributes that end in "_", and a fitted model has none of those
|
|
171
|
+
return bool(getattr(self, "base_models", None))
|
|
172
|
+
|
|
123
173
|
def fit_init_params_to_marginal(self, Y, sample_weight=None, iters=1000):
|
|
124
174
|
self.init_params = self.Manifold.fit(
|
|
125
175
|
Y
|
|
@@ -143,18 +193,14 @@ class NGBoost:
|
|
|
143
193
|
|
|
144
194
|
if self.minibatch_frac != 1.0:
|
|
145
195
|
sample_size = int(self.minibatch_frac * len(Y))
|
|
146
|
-
idxs = self.
|
|
147
|
-
np.arange(len(Y)), sample_size, replace=False
|
|
148
|
-
)
|
|
196
|
+
idxs = self._rng.choice(np.arange(len(Y)), sample_size, replace=False)
|
|
149
197
|
|
|
150
198
|
if self.col_sample != 1.0:
|
|
151
199
|
if self.col_sample > 0.0:
|
|
152
200
|
col_size = max(1, int(self.col_sample * X.shape[1]))
|
|
153
201
|
else:
|
|
154
202
|
col_size = 0
|
|
155
|
-
col_idx = self.
|
|
156
|
-
np.arange(X.shape[1]), col_size, replace=False
|
|
157
|
-
)
|
|
203
|
+
col_idx = self._rng.choice(np.arange(X.shape[1]), col_size, replace=False)
|
|
158
204
|
|
|
159
205
|
weight_batch = None if sample_weight is None else sample_weight[idxs]
|
|
160
206
|
|
|
@@ -167,13 +213,58 @@ class NGBoost:
|
|
|
167
213
|
params[idxs, :],
|
|
168
214
|
)
|
|
169
215
|
|
|
216
|
+
def _base_learners(self):
|
|
217
|
+
if not isinstance(self.Base, (list, tuple)):
|
|
218
|
+
return [self.Base] * self.Manifold.n_params
|
|
219
|
+
|
|
220
|
+
if len(self.Base) != self.Manifold.n_params:
|
|
221
|
+
raise ValueError(
|
|
222
|
+
"Base must be a single estimator or a sequence with one estimator "
|
|
223
|
+
"per distribution parameter "
|
|
224
|
+
f"({self.Manifold.n_params} for {self.Dist.__name__}); "
|
|
225
|
+
f"got {len(self.Base)}."
|
|
226
|
+
)
|
|
227
|
+
return self.Base
|
|
228
|
+
|
|
170
229
|
def fit_base(self, X, grads, sample_weight=None):
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
230
|
+
base_learners = self._base_learners()
|
|
231
|
+
# Seed only the base learners that have no random_state of their own,
|
|
232
|
+
# drawing from self._rng in this single thread so serial and
|
|
233
|
+
# parallel fits agree. Learners that already fix their own randomness are
|
|
234
|
+
# left untouched, so their RNG stream is not perturbed (this mirrors how
|
|
235
|
+
# scikit learn's forests seed their sub estimators).
|
|
236
|
+
seeds = [
|
|
237
|
+
(
|
|
238
|
+
int(self._rng.randint(np.iinfo(np.int32).max))
|
|
239
|
+
if base.get_params().get("random_state", "set") is None
|
|
240
|
+
else None
|
|
241
|
+
)
|
|
242
|
+
for base in base_learners
|
|
243
|
+
]
|
|
244
|
+
|
|
245
|
+
def _fit_one(base, g, seed):
|
|
246
|
+
learner = clone(base)
|
|
247
|
+
if seed is not None:
|
|
248
|
+
learner.set_params(random_state=seed)
|
|
249
|
+
if sample_weight is None:
|
|
250
|
+
return learner.fit(X, g)
|
|
251
|
+
return learner.fit(X, g, sample_weight=sample_weight)
|
|
252
|
+
|
|
253
|
+
if self.n_jobs in (None, 1):
|
|
174
254
|
models = [
|
|
175
|
-
|
|
255
|
+
_fit_one(base, g, seed)
|
|
256
|
+
for base, g, seed in zip(base_learners, grads.T, seeds)
|
|
176
257
|
]
|
|
258
|
+
else:
|
|
259
|
+
if issparse(X) and X.format in ("csr", "csc", "bsr"):
|
|
260
|
+
# Canonicalize once so the threaded fits do not race on an in
|
|
261
|
+
# place sort_indices of this shared matrix. Only the compressed
|
|
262
|
+
# formats have sortable indices; sklearn copies the rest.
|
|
263
|
+
X.sort_indices()
|
|
264
|
+
models = Parallel(n_jobs=self.n_jobs, backend="threading")(
|
|
265
|
+
delayed(_fit_one)(base, g, seed)
|
|
266
|
+
for base, g, seed in zip(base_learners, grads.T, seeds)
|
|
267
|
+
)
|
|
177
268
|
fitted = np.array([m.predict(X) for m in models]).T
|
|
178
269
|
self.base_models.append(models)
|
|
179
270
|
return fitted
|
|
@@ -235,9 +326,12 @@ class NGBoost:
|
|
|
235
326
|
Y_val : DataFrame object or List or
|
|
236
327
|
numpy array of validation-set outcomes in numeric format
|
|
237
328
|
sample_weight : how much to weigh each example in the training set.
|
|
238
|
-
numpy array of size (n)
|
|
329
|
+
List, pandas Series or numpy array of size (n)
|
|
330
|
+
(defaults to None). Weights are matched to rows by
|
|
331
|
+
position, so the index of a Series is ignored.
|
|
239
332
|
val_sample_weight : how much to weigh each example in the validation set.
|
|
240
|
-
|
|
333
|
+
List, pandas Series or numpy array of size (n_val),
|
|
334
|
+
matched to rows by position (defaults to None)
|
|
241
335
|
train_loss_monitor : a custom score or set of scores to track on the training set
|
|
242
336
|
during training. Defaults to the score defined in the NGBoost
|
|
243
337
|
constructor
|
|
@@ -254,6 +348,11 @@ class NGBoost:
|
|
|
254
348
|
self.base_models = []
|
|
255
349
|
self.scalings = []
|
|
256
350
|
self.col_idxs = []
|
|
351
|
+
# Start a fresh random stream on every fit so that refitting with an
|
|
352
|
+
# integer random_state gives the same model
|
|
353
|
+
self._rng = check_random_state(self.random_state)
|
|
354
|
+
|
|
355
|
+
self.best_val_loss_itr = None
|
|
257
356
|
|
|
258
357
|
return self.partial_fit(
|
|
259
358
|
X,
|
|
@@ -298,9 +397,12 @@ class NGBoost:
|
|
|
298
397
|
Y_val : DataFrame object or List or
|
|
299
398
|
numpy array of validation-set outcomes in numeric format
|
|
300
399
|
sample_weight : how much to weigh each example in the training set.
|
|
301
|
-
numpy array of size (n)
|
|
400
|
+
List, pandas Series or numpy array of size (n)
|
|
401
|
+
(defaults to None). Weights are matched to rows by
|
|
402
|
+
position, so the index of a Series is ignored.
|
|
302
403
|
val_sample_weight : how much to weigh each example in the validation set.
|
|
303
|
-
|
|
404
|
+
List, pandas Series or numpy array of size (n_val),
|
|
405
|
+
matched to rows by position (defaults to None)
|
|
304
406
|
train_loss_monitor : a custom score or set of scores to track on the training set
|
|
305
407
|
during training. Defaults to the score defined in the NGBoost
|
|
306
408
|
constructor
|
|
@@ -320,6 +422,9 @@ class NGBoost:
|
|
|
320
422
|
raise RuntimeError(
|
|
321
423
|
"Base models, scalings, and col_idxs are not the same length"
|
|
322
424
|
)
|
|
425
|
+
if getattr(self, "_rng", None) is None:
|
|
426
|
+
self._rng = check_random_state(self.random_state)
|
|
427
|
+
self._base_learners()
|
|
323
428
|
|
|
324
429
|
# if early stopping is specified, split X,Y and sample weights (if given) into training and validation sets
|
|
325
430
|
# This will overwrite any X_val and Y_val values passed by the user directly.
|
|
@@ -337,7 +442,7 @@ class NGBoost:
|
|
|
337
442
|
X,
|
|
338
443
|
Y,
|
|
339
444
|
test_size=self.validation_fraction,
|
|
340
|
-
random_state=self.
|
|
445
|
+
random_state=self._rng,
|
|
341
446
|
)
|
|
342
447
|
|
|
343
448
|
else:
|
|
@@ -356,7 +461,7 @@ class NGBoost:
|
|
|
356
461
|
Y,
|
|
357
462
|
sample_weight,
|
|
358
463
|
test_size=self.validation_fraction,
|
|
359
|
-
random_state=self.
|
|
464
|
+
random_state=self._rng,
|
|
360
465
|
)
|
|
361
466
|
elif X_val is not None and Y_val is not None:
|
|
362
467
|
if sample_weight is not None and val_sample_weight is None:
|
|
@@ -389,6 +494,7 @@ class NGBoost:
|
|
|
389
494
|
multi_output=self.multi_output,
|
|
390
495
|
y_numeric=True,
|
|
391
496
|
)
|
|
497
|
+
sample_weight = _validate_sample_weight(sample_weight, X)
|
|
392
498
|
|
|
393
499
|
self.n_features = X.shape[1]
|
|
394
500
|
|
|
@@ -406,6 +512,9 @@ class NGBoost:
|
|
|
406
512
|
multi_output=self.multi_output,
|
|
407
513
|
y_numeric=True,
|
|
408
514
|
)
|
|
515
|
+
val_sample_weight = _validate_sample_weight(
|
|
516
|
+
val_sample_weight, X_val, input_name="val_sample_weight"
|
|
517
|
+
)
|
|
409
518
|
val_params = self.pred_param(X_val)
|
|
410
519
|
val_loss_list = []
|
|
411
520
|
best_val_loss = np.inf
|
|
@@ -491,16 +600,65 @@ class NGBoost:
|
|
|
491
600
|
|
|
492
601
|
return self
|
|
493
602
|
|
|
603
|
+
def _set_base_params(self, parameters):
|
|
604
|
+
if not isinstance(self.Base, (list, tuple)):
|
|
605
|
+
self.Base.set_params(**parameters)
|
|
606
|
+
return
|
|
607
|
+
|
|
608
|
+
base_learners = list(self.Base)
|
|
609
|
+
for key, value in parameters.items():
|
|
610
|
+
index, delim, sub_key = key.partition("__")
|
|
611
|
+
try:
|
|
612
|
+
index = int(index)
|
|
613
|
+
except ValueError as exc:
|
|
614
|
+
raise ValueError(
|
|
615
|
+
"Base sequence parameters must use integer positions, "
|
|
616
|
+
f"for example Base__0__max_depth. Got Base__{key}."
|
|
617
|
+
) from exc
|
|
618
|
+
if index < 0 or index >= len(base_learners):
|
|
619
|
+
raise ValueError(
|
|
620
|
+
f"Base sequence index {index} is out of range for "
|
|
621
|
+
f"{len(base_learners)} estimators."
|
|
622
|
+
)
|
|
623
|
+
if delim:
|
|
624
|
+
base_learners[index].set_params(**{sub_key: value})
|
|
625
|
+
else:
|
|
626
|
+
base_learners[index] = value
|
|
627
|
+
self.Base = type(self.Base)(base_learners)
|
|
628
|
+
|
|
494
629
|
def set_params(self, **parameters):
|
|
495
|
-
|
|
496
|
-
|
|
630
|
+
if not parameters:
|
|
631
|
+
return self
|
|
632
|
+
|
|
633
|
+
valid_params = self.get_params(deep=True)
|
|
634
|
+
nested_params = {}
|
|
635
|
+
for key, value in parameters.items():
|
|
636
|
+
key, delim, sub_key = key.partition("__")
|
|
637
|
+
if key not in valid_params:
|
|
638
|
+
local_valid_params = self.get_params(deep=False).keys()
|
|
639
|
+
raise ValueError(
|
|
640
|
+
f"Invalid parameter {key!r} for estimator {self}. "
|
|
641
|
+
f"Valid parameters are: {sorted(local_valid_params)!r}."
|
|
642
|
+
)
|
|
643
|
+
if delim:
|
|
644
|
+
nested_params.setdefault(key, {})[sub_key] = value
|
|
645
|
+
else:
|
|
646
|
+
setattr(self, key, value)
|
|
647
|
+
valid_params[key] = value
|
|
648
|
+
|
|
649
|
+
for key, sub_params in nested_params.items():
|
|
650
|
+
if key == "Base":
|
|
651
|
+
self._set_base_params(sub_params)
|
|
652
|
+
else:
|
|
653
|
+
valid_params[key].set_params(**sub_params)
|
|
497
654
|
return self
|
|
498
655
|
|
|
499
656
|
def get_params(self, deep=True):
|
|
500
657
|
"""
|
|
501
658
|
Parameters
|
|
502
659
|
----------
|
|
503
|
-
deep :
|
|
660
|
+
deep : bool, default=True
|
|
661
|
+
If True, return nested parameters for the base learner or learners.
|
|
504
662
|
Returns
|
|
505
663
|
----------
|
|
506
664
|
params : returns an dictionary of parameters.
|
|
@@ -515,15 +673,45 @@ class NGBoost:
|
|
|
515
673
|
"minibatch_frac": self.minibatch_frac,
|
|
516
674
|
"col_sample": self.col_sample,
|
|
517
675
|
"verbose": self.verbose,
|
|
676
|
+
"verbose_eval": self.verbose_eval,
|
|
677
|
+
"tol": self.tol,
|
|
518
678
|
"random_state": self.random_state,
|
|
519
679
|
"validation_fraction": self.validation_fraction,
|
|
520
680
|
"early_stopping_rounds": self.early_stopping_rounds,
|
|
681
|
+
"n_jobs": self.n_jobs,
|
|
521
682
|
}
|
|
522
683
|
|
|
684
|
+
if not deep:
|
|
685
|
+
return params
|
|
686
|
+
|
|
687
|
+
if isinstance(self.Base, (list, tuple)):
|
|
688
|
+
for i, base in enumerate(self.Base):
|
|
689
|
+
params[f"Base__{i}"] = base
|
|
690
|
+
if hasattr(base, "get_params"):
|
|
691
|
+
for key, value in base.get_params(deep=True).items():
|
|
692
|
+
params[f"Base__{i}__{key}"] = value
|
|
693
|
+
elif hasattr(self.Base, "get_params"):
|
|
694
|
+
for key, value in self.Base.get_params(deep=True).items():
|
|
695
|
+
params[f"Base__{key}"] = value
|
|
696
|
+
|
|
523
697
|
return params
|
|
524
698
|
|
|
525
699
|
def score(self, X, Y): # for sklearn
|
|
526
|
-
|
|
700
|
+
"""
|
|
701
|
+
Score the predicted distributions at the points X=x against the outcomes Y.
|
|
702
|
+
Higher is better, as scikit-learn expects, so this is the negative of the
|
|
703
|
+
average scoring rule: the mean log-likelihood for LogScore and the negative
|
|
704
|
+
mean CRPS for CRPScore.
|
|
705
|
+
|
|
706
|
+
Parameters:
|
|
707
|
+
X : DataFrame object or List or
|
|
708
|
+
numpy array of predictors (n x p) in numeric format.
|
|
709
|
+
Y : numpy array of outcomes (n)
|
|
710
|
+
|
|
711
|
+
Output:
|
|
712
|
+
A float, the negative of the average score
|
|
713
|
+
"""
|
|
714
|
+
return -self.Manifold(self.pred_dist(X)._params).total_score(Y)
|
|
527
715
|
|
|
528
716
|
def pred_dist(self, X, max_iter=None):
|
|
529
717
|
"""
|
|
@@ -616,8 +804,12 @@ class NGBoost:
|
|
|
616
804
|
# Check whether the model is fitted
|
|
617
805
|
if not self.base_models:
|
|
618
806
|
return None
|
|
619
|
-
# Check whether
|
|
620
|
-
if
|
|
807
|
+
# Check whether all base models are DecisionTreeRegressor
|
|
808
|
+
if any(
|
|
809
|
+
not isinstance(model, DecisionTreeRegressor)
|
|
810
|
+
for models in self.base_models
|
|
811
|
+
for model in models
|
|
812
|
+
):
|
|
621
813
|
return None
|
|
622
814
|
# Reshape the base_models
|
|
623
815
|
params_trees = zip(*self.base_models)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "ngboost"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.6.0"
|
|
4
4
|
description = "Library for probabilistic predictions via gradient boosting."
|
|
5
5
|
authors = ["Stanford ML Group <avati@cs.stanford.edu>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -13,8 +13,11 @@ classifiers = [
|
|
|
13
13
|
license = "Apache License 2.0"
|
|
14
14
|
|
|
15
15
|
[tool.poetry.dependencies]
|
|
16
|
-
python = ">=3.
|
|
17
|
-
scikit-learn =
|
|
16
|
+
python = ">=3.10, <3.15"
|
|
17
|
+
scikit-learn = [
|
|
18
|
+
{version = ">=1.6,<1.7", markers = "python_version < '3.14'"},
|
|
19
|
+
{version = ">=1.8.0,<2.0", markers = "python_version >= '3.14'"}
|
|
20
|
+
]
|
|
18
21
|
numpy = [
|
|
19
22
|
{version = ">=1.21.2", markers = "python_version < '3.13'"},
|
|
20
23
|
{version = ">=2.1.0", markers = "python_version >= '3.13'"}
|
|
@@ -26,18 +29,18 @@ scipy = [
|
|
|
26
29
|
tqdm = ">=4.3"
|
|
27
30
|
lifelines = ">=0.25"
|
|
28
31
|
sympy = ">=1.12"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
{version = ">=3.10.0", markers = "python_version >= '3.10'"}
|
|
32
|
-
]
|
|
32
|
+
joblib = ">=1.2"
|
|
33
|
+
matplotlib = ">=3.10.0"
|
|
33
34
|
|
|
34
35
|
[tool.poetry.group.dev.dependencies]
|
|
35
36
|
pytest = "^8.0.0"
|
|
37
|
+
pandas = ">=1.0"
|
|
36
38
|
black = "^24.0.0"
|
|
37
39
|
pre-commit = "^4.0.0"
|
|
38
40
|
isort = "^5.13.0"
|
|
39
41
|
pylint = "^3.2.0"
|
|
40
42
|
flake8 = "^7.1.0"
|
|
43
|
+
skops = ">=0.16"
|
|
41
44
|
|
|
42
45
|
|
|
43
46
|
[build-system]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|