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,106 @@
|
|
|
1
|
+
# Author:
|
|
2
|
+
# Bill Sousa
|
|
3
|
+
#
|
|
4
|
+
# License: BSD 3 clause
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from typing_extensions import Any
|
|
10
|
+
from .._type_aliases import DaskKFoldType
|
|
11
|
+
|
|
12
|
+
from dask import compute
|
|
13
|
+
|
|
14
|
+
from ._fold_splitter import _fold_splitter as _dask_fold_splitter
|
|
15
|
+
from pybear.model_selection.GSTCV._GSTCV._fit._fold_splitter import \
|
|
16
|
+
_fold_splitter as _sk_fold_splitter
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _estimator_fit_params_helper(
|
|
21
|
+
_data_len: int,
|
|
22
|
+
_fit_params: dict[str, Any],
|
|
23
|
+
_KFOLD: DaskKFoldType
|
|
24
|
+
) -> dict[int, dict[str, Any]]:
|
|
25
|
+
"""This module customizes the estimator's fit params for each pass
|
|
26
|
+
of cv, to be passed at fit time for the respective fold.
|
|
27
|
+
|
|
28
|
+
This is being done via a dictionary keyed by fold index, whose values
|
|
29
|
+
are dictionaries holding the respective fit params for that fold. In
|
|
30
|
+
particular, this is designed to perform splitting on any fit param
|
|
31
|
+
whose length matches the number of examples in the data, so that the
|
|
32
|
+
contents of that fit param are matched correctly to the train fold
|
|
33
|
+
of data concurrently being passed to fit. Other params that are not
|
|
34
|
+
split are simply replicated into each dictionary inside the helper.
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
_data_len : int
|
|
39
|
+
The number of examples in the full data set.
|
|
40
|
+
_fit_params : dict[str, Any]
|
|
41
|
+
All the fit params passed to GSTCVDask fit for the estimator.
|
|
42
|
+
_KFOLD : DaskKFoldType
|
|
43
|
+
The KFold indices that were used to create the train / test
|
|
44
|
+
splits of data.
|
|
45
|
+
|
|
46
|
+
Returns
|
|
47
|
+
-------
|
|
48
|
+
_fit_params_helper : dict[int, dict[str, Any]]
|
|
49
|
+
A dictionary of customized fit params for each pass of cv.
|
|
50
|
+
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
# validation ** * ** * ** * ** * ** * ** * ** * ** * ** * ** * ** *
|
|
54
|
+
try:
|
|
55
|
+
float(_data_len)
|
|
56
|
+
if isinstance(_data_len, bool):
|
|
57
|
+
raise Exception
|
|
58
|
+
if not int(_data_len) == _data_len:
|
|
59
|
+
raise Exception
|
|
60
|
+
_data_len = int(_data_len)
|
|
61
|
+
if not _data_len > 0:
|
|
62
|
+
raise Exception
|
|
63
|
+
except:
|
|
64
|
+
raise TypeError(f"'data_len' must be an integer greater than 0")
|
|
65
|
+
|
|
66
|
+
assert isinstance(_fit_params, dict)
|
|
67
|
+
assert all(map(isinstance, list(_fit_params), (str for i in _fit_params)))
|
|
68
|
+
|
|
69
|
+
assert isinstance(_KFOLD, list), f"{type(_KFOLD)=}"
|
|
70
|
+
assert all(map(isinstance, _KFOLD, (tuple for _ in _KFOLD)))
|
|
71
|
+
# END validation ** * ** * ** * ** * ** * ** * ** * ** * ** * ** *
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
_fit_params_helper = {}
|
|
75
|
+
|
|
76
|
+
for f_idx, (train_idxs, test_idxs) in enumerate(_KFOLD):
|
|
77
|
+
|
|
78
|
+
_fit_params_helper[f_idx] = {}
|
|
79
|
+
|
|
80
|
+
for _fit_param_key, _fit_param_value in _fit_params.items():
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
iter(_fit_param_value)
|
|
84
|
+
if isinstance(_fit_param_value, (dict, str)):
|
|
85
|
+
raise Exception
|
|
86
|
+
if [*compute(len(_fit_param_value))][0] != _data_len:
|
|
87
|
+
raise Exception
|
|
88
|
+
# remember we only care about the train fold.
|
|
89
|
+
# the fit_param may not be a dask object. try with
|
|
90
|
+
# dask _fold_splitter, if that fails, try sk _fold_splitter
|
|
91
|
+
try:
|
|
92
|
+
_fit_params_helper[f_idx][_fit_param_key] = \
|
|
93
|
+
_dask_fold_splitter(train_idxs, test_idxs, _fit_param_value)[0][0]
|
|
94
|
+
except:
|
|
95
|
+
_fit_params_helper[f_idx][_fit_param_key] = \
|
|
96
|
+
_sk_fold_splitter(train_idxs, test_idxs, _fit_param_value)[0][0]
|
|
97
|
+
except:
|
|
98
|
+
_fit_params_helper[f_idx][_fit_param_key] = _fit_param_value
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
return _fit_params_helper
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Author:
|
|
2
|
+
# Bill Sousa
|
|
3
|
+
#
|
|
4
|
+
# License: BSD 3 clause
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from typing_extensions import Union
|
|
10
|
+
|
|
11
|
+
from .._type_aliases import (
|
|
12
|
+
DaskSlicerType,
|
|
13
|
+
DaskSplitType,
|
|
14
|
+
DaskXType,
|
|
15
|
+
DaskYType
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
import dask.array as da
|
|
19
|
+
import dask.dataframe as ddf
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _fold_splitter(
|
|
24
|
+
train_idxs: DaskSlicerType,
|
|
25
|
+
test_idxs: DaskSlicerType,
|
|
26
|
+
*data_objects: Union[DaskXType, DaskYType]
|
|
27
|
+
) -> tuple[DaskSplitType, ...]:
|
|
28
|
+
"""Split given data objects into train / test pairs using the given
|
|
29
|
+
train and test indices.
|
|
30
|
+
|
|
31
|
+
The train and test indices independently slice the given data objects;
|
|
32
|
+
the entire data object need not be consumed in a train / test split
|
|
33
|
+
and the splits can also possibly share indices. Standard indexing
|
|
34
|
+
rules apply. Returns a tuple whose length is equal to the number of
|
|
35
|
+
data objects passed, holding tuples of the train / test splits for
|
|
36
|
+
the respective data objects. `train_idxs` and `test_idxs` must be 1D
|
|
37
|
+
vectors of indices, not booleans.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
train_idxs : DaskSlicerType
|
|
42
|
+
1D vector of row indices used to slice train sets out of every
|
|
43
|
+
given data object.
|
|
44
|
+
test_idxs : DaskSlicerType
|
|
45
|
+
1D vector of row indices used to slice test sets out of every
|
|
46
|
+
given data object.
|
|
47
|
+
*data_objects : Union[DaskXType, DaskYType]
|
|
48
|
+
The data objects to slice. Need not be of equal size, and need
|
|
49
|
+
not be completely consumed in the train / test splits. However,
|
|
50
|
+
standard indexing rules apply when slicing by `train_idxs` and
|
|
51
|
+
`test_idxs`.
|
|
52
|
+
|
|
53
|
+
Returns
|
|
54
|
+
-------
|
|
55
|
+
SPLITS : tuple[DaskSplitType, ...]
|
|
56
|
+
Return the train / test splits for the given data objects in the
|
|
57
|
+
order passed in a tuple of tuples, each inner tuple containing a
|
|
58
|
+
train/test pair.
|
|
59
|
+
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
SPLITS = []
|
|
64
|
+
for _data in data_objects:
|
|
65
|
+
|
|
66
|
+
# the compute()s need to be here for ddf slicing to work
|
|
67
|
+
if isinstance(_data, da.core.Array):
|
|
68
|
+
_data_train = _data[train_idxs]
|
|
69
|
+
_data_test = _data[test_idxs]
|
|
70
|
+
elif isinstance(_data, ddf.DataFrame):
|
|
71
|
+
_data_train = _data.loc[train_idxs.compute(), :]
|
|
72
|
+
_data_test = _data.loc[test_idxs.compute(), :]
|
|
73
|
+
elif isinstance(_data, ddf.Series):
|
|
74
|
+
_data_train = _data[train_idxs.compute()]
|
|
75
|
+
_data_test = _data[test_idxs.compute()]
|
|
76
|
+
else:
|
|
77
|
+
raise TypeError(f"disallowed container '{type(_data)}'")
|
|
78
|
+
|
|
79
|
+
SPLITS.append(tuple((_data_train, _data_test)))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
return tuple(SPLITS)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Author:
|
|
2
|
+
# Bill Sousa
|
|
3
|
+
#
|
|
4
|
+
# License: BSD 3 clause
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from typing import (
|
|
10
|
+
Iterator,
|
|
11
|
+
Optional
|
|
12
|
+
)
|
|
13
|
+
from .._type_aliases import (
|
|
14
|
+
DaskXType,
|
|
15
|
+
DaskYType,
|
|
16
|
+
DaskKFoldType
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
import time
|
|
20
|
+
|
|
21
|
+
from dask_ml.model_selection import KFold as dask_KFold
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _get_kfold(
|
|
26
|
+
_X: DaskXType,
|
|
27
|
+
_n_splits: int,
|
|
28
|
+
_iid: bool,
|
|
29
|
+
_verbose: int,
|
|
30
|
+
_y: Optional[DaskYType] = None
|
|
31
|
+
) -> Iterator[DaskKFoldType]:
|
|
32
|
+
"""Use dask_ml KFold to get train / test splits when cv is passed as
|
|
33
|
+
an integer.
|
|
34
|
+
|
|
35
|
+
KFold uses the number of rows in `_X` and `_n_splits` to determine
|
|
36
|
+
the indices in each train / test split. 'y' is optional in dask_ml
|
|
37
|
+
KFold. If passed, the number of rows in `_X` and `_y` must be equal.
|
|
38
|
+
|
|
39
|
+
*** IMPORTANT!!!
|
|
40
|
+
This function can be called multiple times within a single param grid
|
|
41
|
+
permutation, first to fit, again to get test score, then again if
|
|
42
|
+
`return_train_score`. Therefore, it must return the same indices for
|
|
43
|
+
each call. The only things that should cause indices to be different
|
|
44
|
+
are `_n_splits` and the number of rows in `_X`. Since this is dask
|
|
45
|
+
KFold, there is the wildcard of the `_iid` setting. If `_iid` is
|
|
46
|
+
False -- meaning the data is known to have some non-random grouping
|
|
47
|
+
along axis 0 -- via the 'shuffle' argument KFold will generate
|
|
48
|
+
indices that sample across chunks to randomize the data in the
|
|
49
|
+
splits. In that case, fix the 'random_state' parameter to make
|
|
50
|
+
selection repeatable. If `_iid` is True, 'shuffle' is False,
|
|
51
|
+
'random_state' can be None, and the splits should be repeatable.
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
_X : DaskXType
|
|
56
|
+
The data to be split.
|
|
57
|
+
_n_splits : int
|
|
58
|
+
The number of splits to produce; the number of split pairs
|
|
59
|
+
yielded by the returned generator object.
|
|
60
|
+
_iid : bool
|
|
61
|
+
True, the examples in X are distributed randomly; False,
|
|
62
|
+
there is some kind of non-random ordering of the examples in X.
|
|
63
|
+
_verbose : int
|
|
64
|
+
A number from 0 to 10 indicating the amount of information to
|
|
65
|
+
display to screen during the grid search trials. 0 means no
|
|
66
|
+
output, 10 means full output.
|
|
67
|
+
_y : Optional[DaskYType]
|
|
68
|
+
The target the data is being fit against, to be split in the
|
|
69
|
+
same way as the data.
|
|
70
|
+
|
|
71
|
+
Returns
|
|
72
|
+
-------
|
|
73
|
+
KFOLD : Iterator[DaskKFoldType]
|
|
74
|
+
A generator object yielding pairs of train test indices as
|
|
75
|
+
da.core.Array[int].
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
# validation ** * ** * ** * ** * ** * ** * ** * ** * ** * ** * ** * ** *
|
|
80
|
+
# 25_04_29 NOT VALIDATING X & y HERE ANYMORE. LET KFold RAISE.
|
|
81
|
+
assert isinstance(_n_splits, int)
|
|
82
|
+
assert _n_splits > 1
|
|
83
|
+
assert isinstance(_iid, bool)
|
|
84
|
+
try:
|
|
85
|
+
float(_verbose)
|
|
86
|
+
except:
|
|
87
|
+
raise AssertionError(f"'_verbose' must be numeric")
|
|
88
|
+
assert _verbose >= 0
|
|
89
|
+
# END validation ** * ** * ** * ** * ** * ** * ** * ** * ** * ** * ** *
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
split_t0 = time.perf_counter()
|
|
93
|
+
# KFold keeps the same chunks ax X
|
|
94
|
+
# as of 25_04_29 dask_KFold only accepts da array for X
|
|
95
|
+
KFOLD = dask_KFold(
|
|
96
|
+
n_splits=_n_splits,
|
|
97
|
+
shuffle=not _iid,
|
|
98
|
+
random_state=7 if not _iid else None,
|
|
99
|
+
# shuffle is on if not iid. must use random_state so that later
|
|
100
|
+
# calls for train score get same splits.
|
|
101
|
+
).split(_X, _y)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if _verbose >= 5:
|
|
105
|
+
print(f'split time = {time.perf_counter() - split_t0: ,.3g} s')
|
|
106
|
+
|
|
107
|
+
del split_t0
|
|
108
|
+
|
|
109
|
+
return KFOLD
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Author:
|
|
2
|
+
# Bill Sousa
|
|
3
|
+
#
|
|
4
|
+
# License: BSD 3 clause
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from typing import Literal
|
|
10
|
+
from typing_extensions import (
|
|
11
|
+
Any,
|
|
12
|
+
Union
|
|
13
|
+
)
|
|
14
|
+
from .._type_aliases import (
|
|
15
|
+
DaskXType,
|
|
16
|
+
DaskYType
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
import numbers
|
|
20
|
+
import time
|
|
21
|
+
import warnings
|
|
22
|
+
|
|
23
|
+
from pybear.model_selection.GSTCV._type_aliases import ClassifierProtocol
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _parallelized_fit(
|
|
28
|
+
_f_idx: int,
|
|
29
|
+
_X_train: DaskXType,
|
|
30
|
+
_y_train: DaskYType,
|
|
31
|
+
_estimator: ClassifierProtocol,
|
|
32
|
+
_grid: dict[str, Any],
|
|
33
|
+
_error_score: Union[numbers.Real, Literal['raise']],
|
|
34
|
+
**_fit_params
|
|
35
|
+
) -> tuple[ClassifierProtocol, float, bool]:
|
|
36
|
+
"""Estimator fit method designed for dask parallelism.
|
|
37
|
+
|
|
38
|
+
Special exception handling on fit.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
_f_idx : int
|
|
43
|
+
The zero-based fold index of the train partition used in this
|
|
44
|
+
fit; parallelism occurs over the different folds.
|
|
45
|
+
_X_train : DaskXType
|
|
46
|
+
A train partition of the data being fit.
|
|
47
|
+
_y_train : DaskYType
|
|
48
|
+
The corresponding train partition of the target for the X train
|
|
49
|
+
partition.
|
|
50
|
+
_estimator : ClassifierProtocol
|
|
51
|
+
Any scikit-style classifier, having `fit`, `predict_proba`,
|
|
52
|
+
`get_params`, and `set_params` methods (the `score` method is
|
|
53
|
+
not necessary, as GSTCVDask never calls it.) This includes,
|
|
54
|
+
but is not limited to, dask_ml, XGBoost dask, and LGBM dask
|
|
55
|
+
classifiers.
|
|
56
|
+
_grid : dict[str, Any]
|
|
57
|
+
The hyperparameter values to be used during this fit. One
|
|
58
|
+
permutation of all the grid search permutations.
|
|
59
|
+
_error_score : Union[numbers.Real, Literal['raise']]
|
|
60
|
+
If a training fold excepts during fitting, the exception can be
|
|
61
|
+
allowed to raise by passing the 'raise' literal. Otherwise,
|
|
62
|
+
passing a number-like will cause the exception to be handled,
|
|
63
|
+
allowing the grid search to proceed, and the given number carries
|
|
64
|
+
through scoring tabulations in place of the missing score(s).
|
|
65
|
+
**_fit_params : dict[str, Any]
|
|
66
|
+
Dictionary of fit_param:value pairs to be passed to the
|
|
67
|
+
estimator's `fit` method. `fit_params` must have been processed
|
|
68
|
+
by :func:`_estimator_fit_params_helper` so that any `fit_param`
|
|
69
|
+
that has length == (n_samples in X and y) is split in the same
|
|
70
|
+
way as X and y.
|
|
71
|
+
|
|
72
|
+
Returns
|
|
73
|
+
-------
|
|
74
|
+
__ : tuple[_estimator, _fit_time, _fit_excepted]
|
|
75
|
+
_estimator : EstimatorProtocol
|
|
76
|
+
The fit estimator
|
|
77
|
+
_fit_time : float
|
|
78
|
+
The seconds elapsed when performing the fit
|
|
79
|
+
_fit_excepted : bool
|
|
80
|
+
True if the fit excepted and '_error_score' was not 'raise';
|
|
81
|
+
False if the fit ran successfully.
|
|
82
|
+
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# validation ** * ** * ** * ** * ** * ** * ** * ** * ** * ** * ** *
|
|
87
|
+
|
|
88
|
+
assert isinstance(_f_idx, int)
|
|
89
|
+
assert isinstance(_grid, dict)
|
|
90
|
+
assert isinstance(_error_score, (str, numbers.Real))
|
|
91
|
+
|
|
92
|
+
# END validation ** * ** * ** * ** * ** * ** * ** * ** * ** * ** * *
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
_fit_excepted = False
|
|
96
|
+
|
|
97
|
+
_X_train = _X_train.persist()
|
|
98
|
+
_y_train = _y_train.persist()
|
|
99
|
+
|
|
100
|
+
t0_fit = time.perf_counter()
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
_estimator.fit(_X_train, _y_train, **_fit_params)
|
|
104
|
+
except BrokenPipeError:
|
|
105
|
+
raise BrokenPipeError # FOR PYTEST ONLY
|
|
106
|
+
except Exception as f:
|
|
107
|
+
if _error_score == 'raise':
|
|
108
|
+
raise ValueError(
|
|
109
|
+
f"estimator excepted during fitting on {_grid}, cv fold "
|
|
110
|
+
f"index {_f_idx} --- {f}"
|
|
111
|
+
)
|
|
112
|
+
else:
|
|
113
|
+
_fit_excepted = True
|
|
114
|
+
warnings.warn(
|
|
115
|
+
f'\033[93mfit excepted on {_grid}, cv fold index {_f_idx}\033[0m'
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
_fit_time = time.perf_counter() - t0_fit
|
|
119
|
+
|
|
120
|
+
del t0_fit
|
|
121
|
+
|
|
122
|
+
return _estimator, _fit_time, _fit_excepted
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
@@ -0,0 +1,228 @@
|
|
|
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
|
+
ThresholdsWIPType,
|
|
14
|
+
MaskedHolderType
|
|
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 \
|
|
27
|
+
import _val_predict_proba
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _parallelized_scorer(
|
|
32
|
+
_X_test: DaskXType,
|
|
33
|
+
_y_test: DaskYType,
|
|
34
|
+
_FIT_OUTPUT_TUPLE: tuple[ClassifierProtocol, float, bool],
|
|
35
|
+
_f_idx: int,
|
|
36
|
+
_SCORER_DICT: ScorerWIPType,
|
|
37
|
+
_THRESHOLDS: ThresholdsWIPType,
|
|
38
|
+
_error_score: Union[numbers.Real, None],
|
|
39
|
+
_verbose: int
|
|
40
|
+
) -> tuple[MaskedHolderType, MaskedHolderType]:
|
|
41
|
+
|
|
42
|
+
# dont adjust the spacing, is congruent with train scorer
|
|
43
|
+
|
|
44
|
+
"""Using the estimators fit on each train fold, use `predict_proba`
|
|
45
|
+
and _X_tests to generate _y_preds and score against the corresponding
|
|
46
|
+
_y_tests using all of the scorers and thresholds.
|
|
47
|
+
|
|
48
|
+
Builds one fold layer of the TEST_FOLD_x_THRESHOLD_x_SCORER__SCORE
|
|
49
|
+
and TEST_FOLD_x_THRESHOLD_x_SCORER__SCORE_TIME cubes.
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
_X_test : DaskXType
|
|
54
|
+
A test partition of the data, matched up with the estimator that
|
|
55
|
+
was trained on the complementary train set.
|
|
56
|
+
_y_test : DaskYType
|
|
57
|
+
The corresponding test partition of the target for the `_X_test`
|
|
58
|
+
partition.
|
|
59
|
+
_FIT_OUTPUT_TUPLE : tuple[ClassifierProtocol, float, bool]
|
|
60
|
+
A tuple holding the fitted estimator, the fit time (not needed
|
|
61
|
+
here), and the fit_excepted boolean (needed here.)
|
|
62
|
+
_f_idx : int
|
|
63
|
+
The zero-based split index of the test partition used here;
|
|
64
|
+
parallelism occurs over the different splits.
|
|
65
|
+
_SCORER_DICT : ScorerWIPType
|
|
66
|
+
A dictionary with scorer name as keys and the scorer callables
|
|
67
|
+
as values. The scorer callables are scoring metrics (or similar),
|
|
68
|
+
not make_scorer.
|
|
69
|
+
_THRESHOLDS : ThresholdsWIPType
|
|
70
|
+
For the current search permutation, there was a mother param
|
|
71
|
+
grid that contained a 'thresholds' parameter, that was separated
|
|
72
|
+
from the mother before building cv_results. This is the vector
|
|
73
|
+
of thresholds from the mother that also mothered this search
|
|
74
|
+
permutation.
|
|
75
|
+
_error_score : Union[numbers.Real, Literal['raise']]
|
|
76
|
+
If the training fold complementing this test fold excepted during
|
|
77
|
+
fitting and `error_score` was set to the 'raise' literal, this
|
|
78
|
+
module cannot be reached. Otherwise, a number or number-like was
|
|
79
|
+
passed to `error_score`. If 'fit_excepted' is True, this module
|
|
80
|
+
puts the `error_score` value in every position of the
|
|
81
|
+
TEST_THRESHOLD_x_SCORER__SCORE_LAYER array. If `error_score` is
|
|
82
|
+
set to np.nan, that layer is also masked. Every value in
|
|
83
|
+
TEST_THRESHOLD_x_SCORER__SCORE_TIME_LAYER is set to np.nan and
|
|
84
|
+
masked.
|
|
85
|
+
_verbose : int
|
|
86
|
+
A number from 0 to 10 that indicates the amount of information
|
|
87
|
+
to display to the screen during the grid search process. 0 means
|
|
88
|
+
no output, 10 means maximum output.
|
|
89
|
+
|
|
90
|
+
Returns
|
|
91
|
+
-------
|
|
92
|
+
__ : tuple[np.ma.MaskedArray, np.ma.MaskedArray]
|
|
93
|
+
TEST_THRESHOLD_x_SCORER__SCORE_LAYER : MaskedHolderType
|
|
94
|
+
Masked array of shape (n_thresholds, n_scorers) holding the
|
|
95
|
+
scores for each scorer on each threshold for one fold of
|
|
96
|
+
test data.
|
|
97
|
+
|
|
98
|
+
TEST_THRESHOLD_x_SCORER__SCORE_TIME_LAYER : MaskedHolderType
|
|
99
|
+
Masked array of shape (n_thresholds, n_scorers) holding the
|
|
100
|
+
times to score each scorer on each threshold for one fold of
|
|
101
|
+
test data.
|
|
102
|
+
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
# validation ** * ** * ** * ** * ** * ** * ** * ** * ** * ** * ** *
|
|
106
|
+
assert isinstance(_FIT_OUTPUT_TUPLE, tuple)
|
|
107
|
+
assert isinstance(_f_idx, int)
|
|
108
|
+
assert isinstance(_SCORER_DICT, dict)
|
|
109
|
+
assert all(map(callable, _SCORER_DICT.values()))
|
|
110
|
+
assert isinstance(_THRESHOLDS, list)
|
|
111
|
+
assert isinstance(_verbose, (int, float))
|
|
112
|
+
# END validation ** * ** * ** * ** * ** * ** * ** * ** * ** * ** * *
|
|
113
|
+
|
|
114
|
+
if _verbose >= 5:
|
|
115
|
+
print(f"Start scoring fold {_f_idx + 1} test with different thresholds "
|
|
116
|
+
f"and scorers")
|
|
117
|
+
|
|
118
|
+
_estimator_, _fit_time, _fit_excepted = _FIT_OUTPUT_TUPLE
|
|
119
|
+
|
|
120
|
+
TEST_THRESHOLD_x_SCORER__SCORE_LAYER: MaskedHolderType = \
|
|
121
|
+
np.ma.zeros((len(_THRESHOLDS), len(_SCORER_DICT)), dtype=np.float64)
|
|
122
|
+
TEST_THRESHOLD_x_SCORER__SCORE_LAYER.mask = True
|
|
123
|
+
|
|
124
|
+
TEST_THRESHOLD_x_SCORER__SCORE_TIME_LAYER: MaskedHolderType = \
|
|
125
|
+
np.ma.zeros((len(_THRESHOLDS), len(_SCORER_DICT)), dtype=np.float64)
|
|
126
|
+
TEST_THRESHOLD_x_SCORER__SCORE_TIME_LAYER.mask = True
|
|
127
|
+
|
|
128
|
+
# IF A FOLD EXCEPTED DURING FIT, ALL THE THRESHOLDS AND SCORERS
|
|
129
|
+
# IN THAT FOLD LAYER GET SET TO error_score.
|
|
130
|
+
# SCORE TIME CANT BE TAKEN SINCE SCORING WONT BE DONE SO ALSO MASK THAT
|
|
131
|
+
if _fit_excepted:
|
|
132
|
+
TEST_THRESHOLD_x_SCORER__SCORE_LAYER[:, :] = _error_score
|
|
133
|
+
if _error_score is np.nan:
|
|
134
|
+
TEST_THRESHOLD_x_SCORER__SCORE_LAYER[:, :] = np.ma.masked
|
|
135
|
+
|
|
136
|
+
TEST_THRESHOLD_x_SCORER__SCORE_TIME_LAYER[:, :] = np.nan
|
|
137
|
+
TEST_THRESHOLD_x_SCORER__SCORE_TIME_LAYER[:, :] = np.ma.masked
|
|
138
|
+
|
|
139
|
+
if _verbose >= 5:
|
|
140
|
+
print(f'fold {_f_idx + 1} excepted during fit, unable to score')
|
|
141
|
+
|
|
142
|
+
return (TEST_THRESHOLD_x_SCORER__SCORE_LAYER,
|
|
143
|
+
TEST_THRESHOLD_x_SCORER__SCORE_TIME_LAYER)
|
|
144
|
+
|
|
145
|
+
# v v v only accessible if fit() did not except v v v
|
|
146
|
+
|
|
147
|
+
_X_test = _X_test.persist()
|
|
148
|
+
_y_test = _y_test.persist()
|
|
149
|
+
|
|
150
|
+
pp0_time = time.perf_counter()
|
|
151
|
+
_predict_proba = _estimator_.predict_proba(_X_test)[:, -1].ravel()
|
|
152
|
+
_val_predict_proba(
|
|
153
|
+
_predict_proba,
|
|
154
|
+
_X_test.shape[0] if hasattr(_X_test, 'shape') else len(_X_test)
|
|
155
|
+
)
|
|
156
|
+
pp_time = time.perf_counter() - pp0_time
|
|
157
|
+
del pp0_time
|
|
158
|
+
|
|
159
|
+
if _verbose >= 5:
|
|
160
|
+
print(f'fold {_f_idx + 1} test predict_proba time = {pp_time: ,.3g} s')
|
|
161
|
+
del pp_time
|
|
162
|
+
|
|
163
|
+
# GET SCORES FOR ALL SCORERS & THRESHOLDS ##########################
|
|
164
|
+
|
|
165
|
+
_test_fold_score_t0 = time.perf_counter()
|
|
166
|
+
for thresh_idx, _threshold in enumerate(_THRESHOLDS):
|
|
167
|
+
|
|
168
|
+
_y_pred_t0 = time.perf_counter()
|
|
169
|
+
_y_test_pred = (_predict_proba >= _threshold).astype(np.uint8)
|
|
170
|
+
_ypt = time.perf_counter() - _y_pred_t0
|
|
171
|
+
del _y_pred_t0
|
|
172
|
+
if _verbose == 10:
|
|
173
|
+
print(f"fold {_f_idx+1} test thresholding time = {_ypt: ,.3g} s")
|
|
174
|
+
del _ypt
|
|
175
|
+
|
|
176
|
+
for s_idx, scorer_key in enumerate(_SCORER_DICT):
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
test_scorer_t0 = time.perf_counter()
|
|
187
|
+
_score = _SCORER_DICT[scorer_key](_y_test, _y_test_pred)
|
|
188
|
+
test_scorer_score_time = time.perf_counter() - test_scorer_t0
|
|
189
|
+
del test_scorer_t0
|
|
190
|
+
TEST_THRESHOLD_x_SCORER__SCORE_LAYER[thresh_idx, s_idx] = _score
|
|
191
|
+
TEST_THRESHOLD_x_SCORER__SCORE_TIME_LAYER[thresh_idx, s_idx] = \
|
|
192
|
+
test_scorer_score_time
|
|
193
|
+
|
|
194
|
+
if _verbose >= 8:
|
|
195
|
+
print(f"fold {_f_idx + 1} '{scorer_key}' test score time = "
|
|
196
|
+
f"{test_scorer_score_time: ,.3g} s")
|
|
197
|
+
|
|
198
|
+
del _score, test_scorer_score_time
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
tfst = time.perf_counter() - _test_fold_score_t0
|
|
202
|
+
del _test_fold_score_t0
|
|
203
|
+
|
|
204
|
+
# END GET SCORE FOR ALL SCORERS & THRESHOLDS #######################
|
|
205
|
+
|
|
206
|
+
if _verbose >= 5:
|
|
207
|
+
print(f'End scoring fold {_f_idx + 1} test with different thresholds and '
|
|
208
|
+
f'scorers')
|
|
209
|
+
_ = _f_idx + 1
|
|
210
|
+
|
|
211
|
+
print(f'fold {_} total test thresh & score wall time = {tfst: ,.3g} s')
|
|
212
|
+
_ast = tfst / len(_SCORER_DICT) / len(_THRESHOLDS)
|
|
213
|
+
print(f'fold {_} avg test thresh & score wall time = {_ast: ,.3g} s')
|
|
214
|
+
del _ast
|
|
215
|
+
|
|
216
|
+
__ = TEST_THRESHOLD_x_SCORER__SCORE_TIME_LAYER
|
|
217
|
+
print(f'fold {_} total test actual scoring time = {__.sum(): ,.3g} s')
|
|
218
|
+
print(f'fold {_} avg test actual scoring time = {__.mean(): ,.3g} s')
|
|
219
|
+
del _, __
|
|
220
|
+
|
|
221
|
+
del _X_test, _y_test, _predict_proba, _y_test_pred, tfst
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
return (TEST_THRESHOLD_x_SCORER__SCORE_LAYER,
|
|
225
|
+
TEST_THRESHOLD_x_SCORER__SCORE_TIME_LAYER)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
|