pybear-dask 0.2.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.
- pybear_dask/__init__.py +19 -0
- pybear_dask/_version.py +38 -0
- pybear_dask/base/__init__.py +24 -0
- pybear_dask/base/_is_classifier.py +186 -0
- pybear_dask/model_selection/GSTCV/_GSTCVDask/GSTCVDask.py +918 -0
- pybear_dask/model_selection/GSTCV/_GSTCVDask/__init__.py +5 -0
- pybear_dask/model_selection/GSTCV/_GSTCVDask/_fit/__init__.py +5 -0
- pybear_dask/model_selection/GSTCV/_GSTCVDask/_fit/_estimator_fit_params_helper.py +106 -0
- pybear_dask/model_selection/GSTCV/_GSTCVDask/_fit/_fold_splitter.py +86 -0
- pybear_dask/model_selection/GSTCV/_GSTCVDask/_fit/_get_kfold.py +117 -0
- pybear_dask/model_selection/GSTCV/_GSTCVDask/_fit/_parallelized_fit.py +127 -0
- pybear_dask/model_selection/GSTCV/_GSTCVDask/_fit/_parallelized_scorer.py +228 -0
- pybear_dask/model_selection/GSTCV/_GSTCVDask/_fit/_parallelized_train_scorer.py +226 -0
- pybear_dask/model_selection/GSTCV/_GSTCVDask/_param_conditioning/__init__.py +5 -0
- pybear_dask/model_selection/GSTCV/_GSTCVDask/_param_conditioning/_scheduler.py +88 -0
- pybear_dask/model_selection/GSTCV/_GSTCVDask/_type_aliases.py +43 -0
- pybear_dask/model_selection/GSTCV/_GSTCVDask/_validation/__init__.py +5 -0
- pybear_dask/model_selection/GSTCV/_GSTCVDask/_validation/_cache_cv.py +37 -0
- pybear_dask/model_selection/GSTCV/_GSTCVDask/_validation/_dask_estimator.py +84 -0
- pybear_dask/model_selection/GSTCV/_GSTCVDask/_validation/_iid.py +38 -0
- pybear_dask/model_selection/GSTCV/_GSTCVDask/_validation/_validation.py +54 -0
- pybear_dask/model_selection/GSTCV/_GSTCVDask/_validation/_y.py +91 -0
- pybear_dask/model_selection/GSTCV/__init__.py +5 -0
- pybear_dask/model_selection/__init__.py +28 -0
- pybear_dask/model_selection/autogridsearch/AutoGSTCVDask.py +71 -0
- pybear_dask/model_selection/autogridsearch/AutoGridSearchCVDask.py +70 -0
- pybear_dask/model_selection/autogridsearch/__init__.py +19 -0
- pybear_dask/model_selection/autogridsearch/_autogridsearch_wrapper/__init__.py +5 -0
- pybear_dask/model_selection/autogridsearch/_autogridsearch_wrapper/_refit_can_be_skipped.py +100 -0
- pybear_dask-0.2.0.dist-info/LICENSE +28 -0
- pybear_dask-0.2.0.dist-info/METADATA +253 -0
- pybear_dask-0.2.0.dist-info/RECORD +33 -0
- pybear_dask-0.2.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# Author:
|
|
2
|
+
# Bill Sousa
|
|
3
|
+
#
|
|
4
|
+
# License: BSD 3 clause
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from typing_extensions import Union
|
|
10
|
+
from pybear.model_selection.GSTCV._type_aliases import (
|
|
11
|
+
ScorerWIPType,
|
|
12
|
+
ClassifierProtocol,
|
|
13
|
+
MaskedHolderType,
|
|
14
|
+
NDArrayHolderType
|
|
15
|
+
)
|
|
16
|
+
from .._type_aliases import (
|
|
17
|
+
DaskXType,
|
|
18
|
+
DaskYType
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
import numbers
|
|
22
|
+
import time
|
|
23
|
+
|
|
24
|
+
import numpy as np
|
|
25
|
+
|
|
26
|
+
from pybear.model_selection.GSTCV._GSTCVMixin._validation._predict_proba import _val_predict_proba
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _parallelized_train_scorer(
|
|
31
|
+
_X_train: DaskXType,
|
|
32
|
+
_y_train: DaskYType,
|
|
33
|
+
_FIT_OUTPUT_TUPLE: tuple[ClassifierProtocol, float, bool],
|
|
34
|
+
_f_idx: int,
|
|
35
|
+
_SCORER_DICT: ScorerWIPType,
|
|
36
|
+
_BEST_THRESHOLDS_BY_SCORER: NDArrayHolderType,
|
|
37
|
+
_error_score: Union[numbers.Real, None],
|
|
38
|
+
_verbose: int
|
|
39
|
+
) -> MaskedHolderType:
|
|
40
|
+
|
|
41
|
+
# dont adjust the spacing, is congruent with test scorer
|
|
42
|
+
|
|
43
|
+
"""Using the estimators fit on each train fold, use `predict_proba`
|
|
44
|
+
and _X_trains to generate _y_preds and score against the corresponding
|
|
45
|
+
_y_trains using all of the scorers.
|
|
46
|
+
|
|
47
|
+
Fill one layer of the TRAIN_FOLD_x_SCORER__SCORE.
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
_X_train : DaskXType
|
|
52
|
+
A train partition of the data that was fit.
|
|
53
|
+
_y_train : DaskYType
|
|
54
|
+
The corresponding train partition of the target for the X train
|
|
55
|
+
partition.
|
|
56
|
+
_FIT_OUTPUT_TUPLE : tuple[ClassifierProtocol, float, bool]
|
|
57
|
+
A tuple holding the fitted estimator, the fit time (not needed
|
|
58
|
+
here), and the fit_excepted boolean (needed here.)
|
|
59
|
+
_f_idx : int
|
|
60
|
+
The zero-based split index of the train partition used here;
|
|
61
|
+
parallelism occurs over the different splits.
|
|
62
|
+
_SCORER_DICT : ScorerWIPType
|
|
63
|
+
A dictionary with scorer name as keys and the scorer callables
|
|
64
|
+
as values. The scorer callables are scoring metrics (or similar),
|
|
65
|
+
not make_scorer.
|
|
66
|
+
_BEST_THRESHOLDS_BY_SCORER : NDArrayHolderType:
|
|
67
|
+
After all of the fold / threshold / scorer combinations are
|
|
68
|
+
scored, the folds are averaged and the threshold with the maximum
|
|
69
|
+
score for each scorer is found. This vector has length n_scorers
|
|
70
|
+
and in each position holds a float indicating the threshold
|
|
71
|
+
value that is the best threshold for that scorer.
|
|
72
|
+
_error_score : Union[numbers.Real, Literal['raise']]
|
|
73
|
+
If this training fold excepted during fitting and `error_score`
|
|
74
|
+
was set to the 'raise' literal, this module cannot be reached.
|
|
75
|
+
Otherwise, a number or number-like was passed to `error_score`.
|
|
76
|
+
If 'fit_excepted' is True, this module puts the `error_score`
|
|
77
|
+
value in every position of the TRAIN_SCORER__SCORE_LAYER vector.
|
|
78
|
+
If `error_score` is set to np.nan, that layer is also masked.
|
|
79
|
+
_verbose : int
|
|
80
|
+
A number from 0 to 10 that indicates the amount of information
|
|
81
|
+
to display to the screen during the grid search process. 0 means
|
|
82
|
+
no output, 10 means maximum output.
|
|
83
|
+
|
|
84
|
+
Returns
|
|
85
|
+
-------
|
|
86
|
+
TRAIN_SCORER__SCORE_LAYER : MaskedHolderType
|
|
87
|
+
Masked array of shape (n_scorers, ). The score for this fold of
|
|
88
|
+
train data using every scorer and the best threshold associated
|
|
89
|
+
with that scorer.
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
# validation ** * ** * ** * ** * ** * ** * ** * ** * ** * ** * ** *
|
|
103
|
+
assert isinstance(_FIT_OUTPUT_TUPLE, tuple)
|
|
104
|
+
assert isinstance(_f_idx, int)
|
|
105
|
+
assert isinstance(_SCORER_DICT, dict)
|
|
106
|
+
assert all(map(callable, _SCORER_DICT.values()))
|
|
107
|
+
assert isinstance(_BEST_THRESHOLDS_BY_SCORER, np.ndarray)
|
|
108
|
+
assert isinstance(_verbose, (int, float))
|
|
109
|
+
# END validation ** * ** * ** * ** * ** * ** * ** * ** * ** * ** * *
|
|
110
|
+
|
|
111
|
+
if _verbose >= 5:
|
|
112
|
+
print(f"Start scoring fold {_f_idx + 1} train with different scorers")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
_estimator_, _fit_time, _fit_excepted = _FIT_OUTPUT_TUPLE
|
|
116
|
+
|
|
117
|
+
TRAIN_SCORER__SCORE_LAYER: MaskedHolderType = \
|
|
118
|
+
np.ma.zeros(len(_SCORER_DICT), dtype=np.float64)
|
|
119
|
+
TRAIN_SCORER__SCORE_LAYER.mask = True
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# IF A FOLD EXCEPTED DURING FIT, PUT IN error_score.
|
|
127
|
+
# IF error_score==np.nan, ALSO MASK THIS LAYER
|
|
128
|
+
if _fit_excepted:
|
|
129
|
+
TRAIN_SCORER__SCORE_LAYER[:] = _error_score
|
|
130
|
+
if _error_score is np.nan:
|
|
131
|
+
TRAIN_SCORER__SCORE_LAYER[:] = np.ma.masked
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if _verbose >= 5:
|
|
137
|
+
print(f'fold {_f_idx + 1} excepted during fit, unable to score')
|
|
138
|
+
|
|
139
|
+
return TRAIN_SCORER__SCORE_LAYER
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# v v v only accessible if fit() did not except v v v
|
|
143
|
+
|
|
144
|
+
_X_train = _X_train.persist()
|
|
145
|
+
_y_train = _y_train.persist()
|
|
146
|
+
|
|
147
|
+
pp0_time = time.perf_counter()
|
|
148
|
+
_predict_proba = _estimator_.predict_proba(_X_train)[:, -1].ravel()
|
|
149
|
+
_val_predict_proba(
|
|
150
|
+
_predict_proba,
|
|
151
|
+
_X_train.shape[0] if hasattr(_X_train, 'shape') else len(_X_train)
|
|
152
|
+
)
|
|
153
|
+
pp_time = time.perf_counter() - pp0_time
|
|
154
|
+
del pp0_time
|
|
155
|
+
|
|
156
|
+
if _verbose >= 5:
|
|
157
|
+
print(f'fold {_f_idx + 1} train predict_proba time = {pp_time: ,.3g} s')
|
|
158
|
+
del pp_time
|
|
159
|
+
|
|
160
|
+
# GET SCORES FOR ALL SCORERS #######################################
|
|
161
|
+
|
|
162
|
+
_train_fold_score_t0 = time.perf_counter()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
for s_idx, scorer_key in enumerate(_SCORER_DICT):
|
|
174
|
+
|
|
175
|
+
_y_pred_t0 = time.perf_counter()
|
|
176
|
+
_y_train_pred = (_predict_proba >= _BEST_THRESHOLDS_BY_SCORER[s_idx])
|
|
177
|
+
_ypt = time.perf_counter() - _y_pred_t0
|
|
178
|
+
del _y_pred_t0
|
|
179
|
+
if _verbose == 10:
|
|
180
|
+
print(f"fold {_f_idx+1} train thresholding time = {_ypt: ,.3g} s")
|
|
181
|
+
del _ypt
|
|
182
|
+
|
|
183
|
+
train_scorer_t0 = time.perf_counter()
|
|
184
|
+
_score = _SCORER_DICT[scorer_key](_y_train, _y_train_pred)
|
|
185
|
+
train_scorer_score_time = time.perf_counter() - train_scorer_t0
|
|
186
|
+
del train_scorer_t0
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
TRAIN_SCORER__SCORE_LAYER[s_idx] = _score
|
|
190
|
+
|
|
191
|
+
if _verbose >= 8:
|
|
192
|
+
print(f"fold {_f_idx+1} '{scorer_key}' train score time = "
|
|
193
|
+
f"{train_scorer_score_time: ,.3g} s")
|
|
194
|
+
|
|
195
|
+
del _score, train_scorer_score_time
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
tfst = time.perf_counter() - _train_fold_score_t0
|
|
199
|
+
del _train_fold_score_t0
|
|
200
|
+
|
|
201
|
+
# END GET SCORE FOR ALL SCORERS ####################################
|
|
202
|
+
|
|
203
|
+
if _verbose >= 5:
|
|
204
|
+
print(f'End scoring fold {_f_idx + 1} train with different scorers')
|
|
205
|
+
|
|
206
|
+
_ = _f_idx + 1
|
|
207
|
+
|
|
208
|
+
print(f'fold {_} total train thresh & score wall time = {tfst: ,.3g} s')
|
|
209
|
+
_ast = tfst / len(_SCORER_DICT)
|
|
210
|
+
print(f'fold {_} avg train thresh & score wall time = {_ast: ,.3g} s')
|
|
211
|
+
del _ast
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
del _X_train, _y_train, _predict_proba, _y_train_pred, tfst
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
return TRAIN_SCORER__SCORE_LAYER
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Author:
|
|
2
|
+
# Bill Sousa
|
|
3
|
+
#
|
|
4
|
+
# License: BSD 3 clause
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from typing_extensions import Union
|
|
10
|
+
from .._type_aliases import DaskSchedulerType
|
|
11
|
+
|
|
12
|
+
import contextlib
|
|
13
|
+
import numbers
|
|
14
|
+
|
|
15
|
+
from distributed import (
|
|
16
|
+
Client,
|
|
17
|
+
get_client
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _cond_scheduler(
|
|
23
|
+
_scheduler: Union[DaskSchedulerType, None],
|
|
24
|
+
_n_jobs: Union[numbers.Integral, None]
|
|
25
|
+
) -> DaskSchedulerType:
|
|
26
|
+
"""Set the dask scheduler.
|
|
27
|
+
|
|
28
|
+
The passed scheduler supersedes all other external schedulers. If
|
|
29
|
+
"None" was passed to the scheduler kwarg of GSTCVDask (the default),
|
|
30
|
+
look for an external context manager or global scheduler using
|
|
31
|
+
`get_client`. If one exists, use nullcontext as an internal context
|
|
32
|
+
manager to not interfere with the external scheduler. If "None" was
|
|
33
|
+
passed to the scheduler kwarg of `GSTCVDask` and there is no external
|
|
34
|
+
scheduler, instantiate `distributed.Client`, which defaults to
|
|
35
|
+
`LocalCluster`, with `n_workers`=`n_jobs` and 1 thread per worker.
|
|
36
|
+
If `n_jobs` is None, uses the default `distributed.Client` behavior
|
|
37
|
+
when `n_workers` is set to None.
|
|
38
|
+
|
|
39
|
+
If a scheduler is passed, this module does not perform any validation
|
|
40
|
+
but allows that to be handled by dask at compute time.
|
|
41
|
+
|
|
42
|
+
This module intentionally deviates from the dask_ml API, and
|
|
43
|
+
disallows any shorthand methods for setting up a scheduler (such
|
|
44
|
+
as strings like 'threading' and 'multiprocessing', which are
|
|
45
|
+
ultimately passed to dask.base.get_scheduler.) All of these types
|
|
46
|
+
of configurations should be handled by the user external to the
|
|
47
|
+
`GSTCVDask` module. As much as possible, dask and distributed
|
|
48
|
+
objects are allowed to flow through without any hard-coded input.
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
_scheduler : Union[SchedulerType, None]
|
|
53
|
+
_scheduler to be validated and used for compute
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
_scheduler : DaskSchedulerType
|
|
58
|
+
Validated, instantiated scheduler
|
|
59
|
+
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if _scheduler is None:
|
|
64
|
+
# if there is no hard scheduler...
|
|
65
|
+
try:
|
|
66
|
+
# ...try to get an existing client...
|
|
67
|
+
get_client()
|
|
68
|
+
# if a client is available (either an external context manager
|
|
69
|
+
# or a default scheduler in an outer scope) let that scheduler
|
|
70
|
+
# take precedence. set _scheduler to nullcontext for empty
|
|
71
|
+
# internal context managers.
|
|
72
|
+
_scheduler = contextlib.nullcontext()
|
|
73
|
+
except ValueError:
|
|
74
|
+
# ...if no external client and no hard scheduler (client),
|
|
75
|
+
# create a new one
|
|
76
|
+
_scheduler = Client(n_workers=_n_jobs, threads_per_worker=1)
|
|
77
|
+
|
|
78
|
+
else:
|
|
79
|
+
# if there is a hard scheduler, that supersedes all.
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
return _scheduler
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Author:
|
|
2
|
+
# Bill Sousa
|
|
3
|
+
#
|
|
4
|
+
# License: BSD 3 clause
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from typing import (
|
|
10
|
+
ContextManager,
|
|
11
|
+
Iterable,
|
|
12
|
+
Sequence
|
|
13
|
+
)
|
|
14
|
+
from typing_extensions import (
|
|
15
|
+
TypeAlias,
|
|
16
|
+
Union
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
import numbers
|
|
20
|
+
|
|
21
|
+
import dask
|
|
22
|
+
import distributed
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
DaskXType: TypeAlias = Iterable
|
|
27
|
+
DaskYType: TypeAlias = Union[Sequence[numbers.Integral], None]
|
|
28
|
+
|
|
29
|
+
DaskSlicerType: TypeAlias = dask.array.core.Array
|
|
30
|
+
|
|
31
|
+
DaskKFoldType: TypeAlias = tuple[DaskSlicerType, DaskSlicerType]
|
|
32
|
+
|
|
33
|
+
DaskSplitType: TypeAlias = tuple[DaskXType, DaskYType]
|
|
34
|
+
|
|
35
|
+
DaskSchedulerType: TypeAlias = Union[
|
|
36
|
+
distributed.scheduler.Scheduler,
|
|
37
|
+
distributed.client.Client,
|
|
38
|
+
ContextManager # nullcontext
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Author:
|
|
2
|
+
# Bill Sousa
|
|
3
|
+
#
|
|
4
|
+
# License: BSD 3 clause
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _val_cache_cv(
|
|
10
|
+
_cache_cv: bool
|
|
11
|
+
) -> None:
|
|
12
|
+
"""Validate `cache_cv`.
|
|
13
|
+
|
|
14
|
+
`cache_cv` can only be boolean. Indicates if the train/test folds
|
|
15
|
+
are to be stored once first generated, or if the folds are generated
|
|
16
|
+
from X and y with the KFold indices at each point of use.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
_cache_cv : bool
|
|
21
|
+
To be validated
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
None
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if not isinstance(_cache_cv, bool):
|
|
31
|
+
raise TypeError(f"'cache_cv' must be a bool")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Author:
|
|
2
|
+
# Bill Sousa
|
|
3
|
+
#
|
|
4
|
+
# License: BSD 3 clause
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from pybear.model_selection.GSTCV._type_aliases import ClassifierProtocol
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
import warnings
|
|
13
|
+
|
|
14
|
+
from sklearn.pipeline import Pipeline
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _val_dask_estimator(
|
|
19
|
+
_estimator: ClassifierProtocol
|
|
20
|
+
) -> None:
|
|
21
|
+
"""Warn if the estimator is not a dask classifier, either from dask
|
|
22
|
+
itself, or from XGBoost or LightGBM.
|
|
23
|
+
|
|
24
|
+
The `GSTCVDask` module is expected to most likely encounter dask_ml,
|
|
25
|
+
xgboost, and lightgbm dask estimators. The estimator must be passed
|
|
26
|
+
as an instance, not the class itself.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
_estimator : ClassifierProtocol
|
|
31
|
+
the estimator to be validated
|
|
32
|
+
|
|
33
|
+
Returns
|
|
34
|
+
-------
|
|
35
|
+
None
|
|
36
|
+
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# validate estimator ** * ** * ** * ** * ** * ** * ** * ** * ** * **
|
|
41
|
+
|
|
42
|
+
def get_inner_most_estimator(__estimator):
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
if isinstance(__estimator, Pipeline):
|
|
46
|
+
return get_inner_most_estimator(__estimator.steps[-1][-1])
|
|
47
|
+
else:
|
|
48
|
+
return get_inner_most_estimator(__estimator.estimator)
|
|
49
|
+
except:
|
|
50
|
+
return __estimator
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
__estimator = get_inner_most_estimator(_estimator)
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
_module = sys.modules[__estimator.__class__.__module__].__file__
|
|
57
|
+
except:
|
|
58
|
+
raise AttributeError(f"'{__estimator.__class__.__name__}' is not "
|
|
59
|
+
f"a valid classifier")
|
|
60
|
+
|
|
61
|
+
# 24_08_04 change raise to warn
|
|
62
|
+
# to allow XGBClassifier, reference errors associated with
|
|
63
|
+
# DaskXGBClassifier and dask GridSearch CV
|
|
64
|
+
# 25_06_28 no longer checking for non-dask estimator
|
|
65
|
+
# __ = str(_module).lower()
|
|
66
|
+
# if 'dask_ml' not in __:
|
|
67
|
+
# warnings.warn(f"'{__estimator.__class__.__name__}' does not "
|
|
68
|
+
# f"appear to be a dask classifier.")
|
|
69
|
+
# if 'dask' not in __ and 'conftest' not in __: # allow pytest with
|
|
70
|
+
# mock clf
|
|
71
|
+
# raise TypeError(f"'{__estimator.__class__.__name__}' is not a
|
|
72
|
+
# f"dask classifier. GSTCVDask can only accept dask classifiers. "
|
|
73
|
+
# f"\nTo use non-dask classifiers, use the GSTCV package.")
|
|
74
|
+
|
|
75
|
+
del get_inner_most_estimator, __estimator, _module
|
|
76
|
+
|
|
77
|
+
# END validate estimator ** * ** * ** * ** * ** * ** * ** * ** * **
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Author:
|
|
2
|
+
# Bill Sousa
|
|
3
|
+
#
|
|
4
|
+
# License: BSD 3 clause
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _val_iid(
|
|
10
|
+
_iid: bool
|
|
11
|
+
) -> None:
|
|
12
|
+
"""Validate `iid`.
|
|
13
|
+
|
|
14
|
+
`iid` can only be boolean. Indicates whether the data is believed to
|
|
15
|
+
have random distribution of examples (True) or if the data is
|
|
16
|
+
organized non-randomly in some way (False).
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
_iid : bool
|
|
21
|
+
To be validated.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
None
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if not isinstance(_iid, bool):
|
|
31
|
+
raise TypeError(f"'iid' must be a bool")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Author:
|
|
2
|
+
# Bill Sousa
|
|
3
|
+
#
|
|
4
|
+
# License: BSD 3 clause
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from pybear.model_selection.GSTCV._type_aliases import ClassifierProtocol
|
|
10
|
+
|
|
11
|
+
from ....GSTCV._GSTCVDask._validation._dask_estimator import _val_dask_estimator
|
|
12
|
+
from ....GSTCV._GSTCVDask._validation._cache_cv import _val_cache_cv
|
|
13
|
+
from ....GSTCV._GSTCVDask._validation._iid import _val_iid
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _validation(
|
|
18
|
+
_estimator: ClassifierProtocol,
|
|
19
|
+
_iid: bool,
|
|
20
|
+
_cache_cv: bool
|
|
21
|
+
) -> None:
|
|
22
|
+
"""Centralized hub for `GSTCVDask` parameter validation.
|
|
23
|
+
|
|
24
|
+
See the submodules for more information.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
_estimator : ClassifierProtocol
|
|
29
|
+
The estimator passed to `GSTCVDask`.
|
|
30
|
+
_iid : bool
|
|
31
|
+
Whether the data is randomly distributed along the example axis
|
|
32
|
+
or is organized in some non-random way.
|
|
33
|
+
_cache_cv : bool
|
|
34
|
+
Indicates if the train/test folds of the data are to be stored
|
|
35
|
+
when first generated, or if the folds are generated from X, y
|
|
36
|
+
and the KFold indices at each point of need. Caching all the
|
|
37
|
+
folds for each split of the data is memory-intensive.
|
|
38
|
+
|
|
39
|
+
Returns
|
|
40
|
+
-------
|
|
41
|
+
None
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
_val_dask_estimator(_estimator)
|
|
47
|
+
|
|
48
|
+
_val_iid(_iid)
|
|
49
|
+
|
|
50
|
+
_val_cache_cv(_cache_cv)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Author:
|
|
2
|
+
# Bill Sousa
|
|
3
|
+
#
|
|
4
|
+
# License: BSD 3 clause
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from typing import Iterable
|
|
10
|
+
|
|
11
|
+
import numbers
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
from dask import compute
|
|
15
|
+
import dask.array as da
|
|
16
|
+
import dask.dataframe as ddf
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _val_y(
|
|
21
|
+
_y: Iterable[numbers.Integral] # not DaskYType... see the notes.
|
|
22
|
+
) -> None:
|
|
23
|
+
"""Validate `_y`.
|
|
24
|
+
|
|
25
|
+
`_y` must be single label and binary in [0, 1].
|
|
26
|
+
|
|
27
|
+
The validation is considerably looser than what would be for
|
|
28
|
+
DaskYType. This allows *any* 1D container holding 0's and 1's.
|
|
29
|
+
Let the estimator raise an error if there is a problem with the
|
|
30
|
+
container.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
_y : vector-like of shape (n_samples,) or (n_samples, 1)
|
|
35
|
+
The target for the data.
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
None
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# y ** * ** * ** * ** * ** * ** * ** * ** * ** * ** * ** * ** * ** *
|
|
45
|
+
|
|
46
|
+
if hasattr(_y, 'shape'):
|
|
47
|
+
if hasattr(_y, 'compute'):
|
|
48
|
+
y_shape = compute(*_y.shape)
|
|
49
|
+
else:
|
|
50
|
+
y_shape = _y.shape
|
|
51
|
+
else:
|
|
52
|
+
try:
|
|
53
|
+
y_shape = np.array(_y).shape
|
|
54
|
+
if isinstance(_y, set):
|
|
55
|
+
y_shape = np.array(list(_y)).shape
|
|
56
|
+
except:
|
|
57
|
+
raise TypeError(f"'y' must have a 'shape' attribute.")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
_err_msg = (
|
|
61
|
+
f"GSTCVDask can only perform thresholding on vector-like binary "
|
|
62
|
+
f"targets with values in [0,1]. \nPass 'y' as a vector of 0's and 1's."
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if len(y_shape) == 1:
|
|
66
|
+
pass
|
|
67
|
+
elif len(y_shape) == 2:
|
|
68
|
+
if y_shape[1] != 1:
|
|
69
|
+
raise ValueError(_err_msg)
|
|
70
|
+
else:
|
|
71
|
+
raise ValueError(_err_msg)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
if isinstance(_y, da.core.Array):
|
|
75
|
+
_unique = set(da.unique(_y).compute())
|
|
76
|
+
elif isinstance(_y, (ddf.DataFrame, ddf.Series)):
|
|
77
|
+
_unique = set(da.unique(_y.to_dask_array(lengths=True)).compute())
|
|
78
|
+
elif hasattr(_y, 'shape'):
|
|
79
|
+
_unique = set(np.unique(_y))
|
|
80
|
+
else:
|
|
81
|
+
_unique = set(np.unique(list(_y)))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if not _unique.issubset({0, 1}):
|
|
85
|
+
raise ValueError(_err_msg)
|
|
86
|
+
|
|
87
|
+
del _unique
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|