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,918 @@
|
|
|
1
|
+
# Author:
|
|
2
|
+
# Bill Sousa
|
|
3
|
+
#
|
|
4
|
+
# License: BSD 3 clause
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from typing import (
|
|
10
|
+
Callable,
|
|
11
|
+
Iterable,
|
|
12
|
+
Optional
|
|
13
|
+
)
|
|
14
|
+
from typing_extensions import (
|
|
15
|
+
Any,
|
|
16
|
+
Union
|
|
17
|
+
)
|
|
18
|
+
from ._type_aliases import (
|
|
19
|
+
DaskXType,
|
|
20
|
+
DaskYType,
|
|
21
|
+
DaskKFoldType,
|
|
22
|
+
DaskSplitType,
|
|
23
|
+
DaskSchedulerType
|
|
24
|
+
)
|
|
25
|
+
from pybear.model_selection.GSTCV._type_aliases import (
|
|
26
|
+
ClassifierProtocol,
|
|
27
|
+
ErrorScoreType,
|
|
28
|
+
ParamGridInputType,
|
|
29
|
+
ParamGridsInputType,
|
|
30
|
+
ScorerInputType,
|
|
31
|
+
ThresholdsInputType,
|
|
32
|
+
ThresholdsWIPType,
|
|
33
|
+
MaskedHolderType,
|
|
34
|
+
NDArrayHolderType
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
from copy import deepcopy
|
|
39
|
+
import numbers
|
|
40
|
+
|
|
41
|
+
from dask import compute
|
|
42
|
+
import distributed
|
|
43
|
+
|
|
44
|
+
from ._validation._validation import _validation
|
|
45
|
+
from ._validation._y import _val_y
|
|
46
|
+
|
|
47
|
+
from ._param_conditioning._scheduler import _cond_scheduler
|
|
48
|
+
|
|
49
|
+
from .._GSTCVDask._fit._get_kfold import _get_kfold as _dask_get_kfold
|
|
50
|
+
from .._GSTCVDask._fit._fold_splitter import _fold_splitter as _dask_fold_splitter
|
|
51
|
+
from .._GSTCVDask._fit._estimator_fit_params_helper import \
|
|
52
|
+
_estimator_fit_params_helper as _dask_estimator_fit_params_helper
|
|
53
|
+
from .._GSTCVDask._fit._parallelized_fit import _parallelized_fit
|
|
54
|
+
from .._GSTCVDask._fit._parallelized_scorer import _parallelized_scorer
|
|
55
|
+
from .._GSTCVDask._fit._parallelized_train_scorer import _parallelized_train_scorer
|
|
56
|
+
|
|
57
|
+
from pybear.model_selection.GSTCV._GSTCVMixin._GSTCVMixin import _GSTCVMixin
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class GSTCVDask(_GSTCVMixin):
|
|
62
|
+
"""Exhaustive cross-validated search over a grid of hyperparameter
|
|
63
|
+
values and decision thresholds for a binary classifier.
|
|
64
|
+
|
|
65
|
+
The optimal hyperparameters and decision threshold selected are
|
|
66
|
+
those that maximize the average score (and minimize the average
|
|
67
|
+
loss) of the held-out data (test sets).
|
|
68
|
+
|
|
69
|
+
pybear `GSTCVDask` is intended to closely parallel the interface and
|
|
70
|
+
user-experience of dask_ml and scikit-learn `GridSearchCV`. Users
|
|
71
|
+
who are familiar with those GridSearch implementations should find
|
|
72
|
+
that `GSTCVDask` differs with respect to 4 things:
|
|
73
|
+
|
|
74
|
+
1) the init parameter `thresholds` (which can also be passed as a
|
|
75
|
+
parameter to `param_grid`)
|
|
76
|
+
|
|
77
|
+
2) additional columns in the :attr:`cv_results_` attribute to report
|
|
78
|
+
the best thresholds for each scorer
|
|
79
|
+
|
|
80
|
+
3) a new post-run attribute, :attr:`best_threshold_`, which informs
|
|
81
|
+
about the overall best threshold
|
|
82
|
+
|
|
83
|
+
4) callables passed to `scoring` SHOULD NOT be wrapped in
|
|
84
|
+
'make_scorer' as would be done with `GridSearchCV`. Pass scoring
|
|
85
|
+
callables in raw metric form. See the dask_ml and scikit-learn docs
|
|
86
|
+
for more information about 'make_scorer' and 'metrics'. Also see the
|
|
87
|
+
'Parameters' section of the `GSTCVDask` docs.
|
|
88
|
+
|
|
89
|
+
Users who are familiar with the dask_ml implementation of GridSearch
|
|
90
|
+
should focus on the above 4 areas in the `GSTCVDask` 'Parameters' and
|
|
91
|
+
'Attributes' sections of the docs for mastery of `GSTCVDask`.
|
|
92
|
+
|
|
93
|
+
`GSTCVDask` implements `fit`, `predict_proba`, `predict`, `score`,
|
|
94
|
+
`get_params`, and `set_params` methods. It also implements
|
|
95
|
+
`decision_function`, `predict_log_proba`, `score_samples`,
|
|
96
|
+
`transform` and `inverse_transform` if they are exposed by the
|
|
97
|
+
classifier passed to `estimator`.
|
|
98
|
+
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
estimator : object
|
|
102
|
+
Required. Must be a binary classifier that conforms to the
|
|
103
|
+
scikit-learn estimator API interface. The classifier must have
|
|
104
|
+
the `fit`, `set_params`, `get_params`, and `predict_proba`
|
|
105
|
+
methods. If the classifier does not have `predict_proba`, try
|
|
106
|
+
to wrap with `CalibratedClassifierCV`. The classifier does not
|
|
107
|
+
need a `score` method, as `GSTCVDask` never accesses the
|
|
108
|
+
estimator `score` method because it always uses a 0.5 threshold.
|
|
109
|
+
|
|
110
|
+
`GSTCVDask` does not strictly prohibit non-dask classifiers, but
|
|
111
|
+
is explicitly designed for use with dask objects (estimators,
|
|
112
|
+
arrays, and dataframes.) pybear `GSTCV` is recommended for
|
|
113
|
+
non-dask classifiers.
|
|
114
|
+
param_grid : Union[ParamGridInputType, ParamGridsInputType]
|
|
115
|
+
Required. A dictionary with hyperparameters names (str) as keys
|
|
116
|
+
and list-likes of respective settings to try as values. Can also
|
|
117
|
+
be a list-like of such dictionaries, and the grids spanned by
|
|
118
|
+
each are explored. This enables searching over any combination
|
|
119
|
+
of hyperparameter settings.
|
|
120
|
+
thresholds : ThresholdsInputType, default=None
|
|
121
|
+
The decision threshold search grid to use when performing the
|
|
122
|
+
hyperparameter search. Other `GridSearchCV` modules only use the
|
|
123
|
+
conventional decision threshold for binary classifiers, which is
|
|
124
|
+
0.5. This module can search over any set of decision threshold
|
|
125
|
+
values in the 0 to 1 interval (inclusive) in the same manner as
|
|
126
|
+
any other hyperparameter while performing the grid search.
|
|
127
|
+
|
|
128
|
+
The thresholds value passed via the init parameter can be None,
|
|
129
|
+
a single number from 0 to 1 (inclusive) or a list-like of such
|
|
130
|
+
numbers. If None, (and thresholds are not passed directly inside
|
|
131
|
+
the param grid(s)), the default threshold grid is used,
|
|
132
|
+
numpy.linspace(0, 1, 21).
|
|
133
|
+
|
|
134
|
+
Thresholds may also be passed to individual param grids via a
|
|
135
|
+
'thresholds' key. However, when passed directly to a param grid,
|
|
136
|
+
'thresholds' cannot be None or a single number, it must be a
|
|
137
|
+
list-like of numbers as is normally done with param grids.
|
|
138
|
+
|
|
139
|
+
Because `thresholds` can be passed in 2 different ways, there
|
|
140
|
+
is a hierarchy that dictates which thresholds are used during
|
|
141
|
+
searching and scoring. Any threshold values passed directly
|
|
142
|
+
within a param grid always supersede any passed (or not passed)
|
|
143
|
+
to the `thresholds` init parameter. When a param grid does not
|
|
144
|
+
have any thresholds passed inside it, the value passed to the
|
|
145
|
+
init parameter is used -- if a value was not passed to the init
|
|
146
|
+
parameter, then the `GSTCVDask` default value is used.
|
|
147
|
+
|
|
148
|
+
When one scorer is used, the best threshold is always exposed
|
|
149
|
+
and is accessible via the :attr:`best_threshold_` attribute.
|
|
150
|
+
When multiple scorers are used, the `best_threshold_` attribute
|
|
151
|
+
is only exposed when a string value is passed to `refit`. The
|
|
152
|
+
best threshold is never reported in the :attr:`best_params_`
|
|
153
|
+
attribute, even if thresholds were passed via a param grid; the
|
|
154
|
+
best threshold is only available via the `best_threshold_`
|
|
155
|
+
attribute. Another way to discover the best threshold for each
|
|
156
|
+
scorer is by inspection of the :attr:`cv_results_` attribute.
|
|
157
|
+
|
|
158
|
+
The scores reported for test data in `cv_results_` are those for
|
|
159
|
+
the best threshold. Also note that when `return_train_score` is
|
|
160
|
+
True, the scores returned for the train data are only for the
|
|
161
|
+
best threshold found for the test data. That is, every threshold
|
|
162
|
+
is scored on the test data, the best score is found, and the
|
|
163
|
+
best threshold is the threshold corresponding to the best score.
|
|
164
|
+
That best score and best threshold is reported for the test data.
|
|
165
|
+
Then when scoring train data, only the best threshold is scored
|
|
166
|
+
and reported in `cv_results_`.
|
|
167
|
+
scoring : ScorerInputType, default='accuracy'
|
|
168
|
+
Strategy to evaluate the performance of the cross-validated model
|
|
169
|
+
on the test set (and also train set, if `return_train_score` is
|
|
170
|
+
True.)
|
|
171
|
+
|
|
172
|
+
For any number of scorers, scoring can be a dictionary with
|
|
173
|
+
user-assigned scorer names as keys and callables as values. See
|
|
174
|
+
below for clarification on allowed callables.
|
|
175
|
+
|
|
176
|
+
For a single scoring metric, a single string or a single callable
|
|
177
|
+
is also allowed. Valid strings that can be passed are 'accuracy',
|
|
178
|
+
'balanced_accuracy', 'average_precision', 'f1', 'precision', and
|
|
179
|
+
'recall'.
|
|
180
|
+
|
|
181
|
+
For evaluating multiple metrics, scoring can be a vector-like of
|
|
182
|
+
unique strings, containing a combination of the allowed strings.
|
|
183
|
+
|
|
184
|
+
The default scorer of the estimator cannot be used by this module
|
|
185
|
+
because the decision threshold cannot be manipulated. Therefore,
|
|
186
|
+
`scoring` cannot accept a None argument.
|
|
187
|
+
|
|
188
|
+
About the scorer callable:
|
|
189
|
+
This module's scorers differ from other GSCV implementations
|
|
190
|
+
in an important way. Some of those implementations accept
|
|
191
|
+
'make_scorer' functions, e.g. `sklearn.metrics.make_scorer`, but
|
|
192
|
+
this module cannot accept this. 'make_scorer' implicitly assumes
|
|
193
|
+
a decision threshold of 0.5, but this module needs to be able to
|
|
194
|
+
calculate predictions based on any user-entered threshold.
|
|
195
|
+
Therefore, in place of 'make_scorer' functions, this module uses
|
|
196
|
+
scoring metrics directly (whereas they would otherwise be passed
|
|
197
|
+
to 'make_scorer'.) An example of a valid scoring metric is
|
|
198
|
+
`sklearn.metrics.accuracy_score`.
|
|
199
|
+
|
|
200
|
+
Additionally, this module can accept any scoring function that
|
|
201
|
+
has signature (y_true, y_pred) and returns a single number. Note
|
|
202
|
+
that, when using a custom scorer, the scorer should return a
|
|
203
|
+
single value. Metric functions returning a list/array of values
|
|
204
|
+
can be wrapped in multiple scorers that return one value each.
|
|
205
|
+
|
|
206
|
+
This module cannot directly accept scorer kwargs and pass them
|
|
207
|
+
to scorers. To pass kwargs to your scoring metric, create a
|
|
208
|
+
wrapper with signature (y_true, y_pred) around the metric and
|
|
209
|
+
hard-code the kwargs into the metric, e.g.,
|
|
210
|
+
|
|
211
|
+
def your_metric_wrapper(y_true, y_pred):
|
|
212
|
+
return your_metric(y_true, y_pred, **hard_coded_kwargs)
|
|
213
|
+
iid : Optional[bool], default=True
|
|
214
|
+
`iid` is ignored when `cv` is an iterable. Indicates whether
|
|
215
|
+
the data's examples are believed to have random distribution
|
|
216
|
+
(True) or if the examples are organized non-randomly in some
|
|
217
|
+
way (False). If the data is not iid, KFold will cross chunk
|
|
218
|
+
boundaries when reading the data in an attempt to randomize it;
|
|
219
|
+
this can be an expensive process. Otherwise, if the data is iid,
|
|
220
|
+
KFold can handle the data as contiguous chunks which is much
|
|
221
|
+
more efficient.
|
|
222
|
+
refit : Optional[Union[bool, str, Callable]], default=True
|
|
223
|
+
After the grid search is done, fit the whole dataset on the
|
|
224
|
+
estimator using the best found hyperparameters and expose this
|
|
225
|
+
fitted estimator via the :attr:`best_estimator_` attribute. Also,
|
|
226
|
+
when the estimator is refit on the best hyperparameters, the
|
|
227
|
+
`GSTCVDask` instance itself becomes the best estimator, exposing
|
|
228
|
+
the :meth:`predict_proba`, :meth:`predict`, and :meth:`score`
|
|
229
|
+
methods (and possibly others.) When refit is not performed, the
|
|
230
|
+
search simply finds the best hyperparameters and exposes them via
|
|
231
|
+
the `best_params_` attribute (unless there are multiple scorers
|
|
232
|
+
and `refit` is False, in which case information about the grid
|
|
233
|
+
search is only available via the `cv_results_` attribute.)
|
|
234
|
+
|
|
235
|
+
The values accepted by `refit` depend on the scoring scheme, that
|
|
236
|
+
is, whether a single or multiple scorers are used. In all cases,
|
|
237
|
+
`refit` can be boolean False (to disable refit), a string that
|
|
238
|
+
indicates the name of the scorer to use (when there is only one
|
|
239
|
+
scorer there is only one possible string value), or a callable.
|
|
240
|
+
See below for more information about the refit callable. When
|
|
241
|
+
one scorer is used, `refit` can be boolean True or False, but
|
|
242
|
+
boolean True cannot be used when there is more than one scorer.
|
|
243
|
+
|
|
244
|
+
Where there are considerations other than maximum score in
|
|
245
|
+
choosing a best estimator, `refit` can be set to a function that
|
|
246
|
+
takes in `cv_results_` and returns :attr:`best_index_` (an
|
|
247
|
+
integer). In that case, the returned `best_index_` sets both the
|
|
248
|
+
`best_estimator_` and `best_params_` attributes. When more than
|
|
249
|
+
one scorer is used, the :attr:`best_score_` and `best_threshold_`
|
|
250
|
+
attributes will not be available, but are available if there is
|
|
251
|
+
only one scorer.
|
|
252
|
+
|
|
253
|
+
See the `scoring` parameter to know more about multiple metric
|
|
254
|
+
evaluation.
|
|
255
|
+
cv : Optional[Union[numbers.Integral, Iterable, None]], default=None
|
|
256
|
+
Sets the cross-validation splitting strategy.
|
|
257
|
+
|
|
258
|
+
Possible inputs for cv are:
|
|
259
|
+
|
|
260
|
+
1) None, to use the default 5-fold cross validation,
|
|
261
|
+
2) an integer, must be 2 or greater, to specify the number of
|
|
262
|
+
folds in a KFold split,
|
|
263
|
+
3) an iterable yielding pairs of (train, test) split indices as
|
|
264
|
+
arrays.
|
|
265
|
+
|
|
266
|
+
For passed iterables:
|
|
267
|
+
This module will convert generators to lists. No validation is
|
|
268
|
+
done beyond verifying that it is an iterable that contains pairs
|
|
269
|
+
of iterables. `GSTCVDask` will catch out of range indices and
|
|
270
|
+
raise an error but any validation beyond that is up to the user
|
|
271
|
+
outside of `GSTCVDask`.
|
|
272
|
+
verbose : Optional[numbers.Real], default=0
|
|
273
|
+
The amount of verbosity to display to screen during the grid
|
|
274
|
+
search. Accepts integers from 0 to 10. 0 means no information
|
|
275
|
+
displayed to the screen, 10 means full verbosity. Non-numbers
|
|
276
|
+
are rejected. Boolean False is set to 0, boolean True is set to
|
|
277
|
+
10. Negative numbers are rejected. Numbers greater than 10 are
|
|
278
|
+
set to 10. Floats are rounded to integers.
|
|
279
|
+
error_score : Optional[ErrorScoreType], default='raise'
|
|
280
|
+
Score to assign if the estimator raises an error while fitting
|
|
281
|
+
on a train fold. If set to ‘raise’, the error is raised. If a
|
|
282
|
+
numeric value is given, a warning is raised and the error score
|
|
283
|
+
value is inserted into the subsequent calculations in place of
|
|
284
|
+
the missing value(s). This parameter does not affect the refit
|
|
285
|
+
step, which will always raise the error.
|
|
286
|
+
return_train_score : Optional[bool]
|
|
287
|
+
If False, the `cv_results_` attribute will not include training
|
|
288
|
+
scores. If True, the train data is scored using all the scorers
|
|
289
|
+
at the best respective threshold(s) found for the test data.
|
|
290
|
+
Computing training scores is used to assess conditions of under-
|
|
291
|
+
or over-fittedness. However, computing the scores on the training
|
|
292
|
+
set can be computationally expensive and is not required to
|
|
293
|
+
select the hyperparameters that yield the best performance.
|
|
294
|
+
scheduler : Optional[Union[Client, Scheduler, None]], default=None
|
|
295
|
+
A passed scheduler supersedes all other external schedulers.
|
|
296
|
+
When a scheduler is explicitly passed, `GSTCVDask` does not
|
|
297
|
+
perform any validation or verification but allows that to be
|
|
298
|
+
handled by dask at compute time.
|
|
299
|
+
|
|
300
|
+
If 'None' is passed (the default), `GSTCVDask` looks for an
|
|
301
|
+
external context manager or global scheduler using `get_client`.
|
|
302
|
+
If one exists, `GSTCVDask` uses that as the scheduler. If an
|
|
303
|
+
external scheduler does not exist, `GSTCVDask` instantiates a
|
|
304
|
+
multiprocessing distributed.Client() (which defaults to
|
|
305
|
+
LocalCluster) with `n_workers=n_jobs` and 1 thread per worker.
|
|
306
|
+
If `n_jobs` is None `GSTCVDask` uses the default Client behavior
|
|
307
|
+
when `n_workers` is set to None.
|
|
308
|
+
|
|
309
|
+
This module intentionally disallows any shorthand methods for
|
|
310
|
+
internally setting up a scheduler (such as strings like 'thread-
|
|
311
|
+
ing' and 'multiprocessing', which are ultimately passed to
|
|
312
|
+
dask.base.get_scheduler.) All of these types of configurations
|
|
313
|
+
should be handled by the user external to the `GSTCVDask` module.
|
|
314
|
+
As much as possible, dask and distributed objects are allowed to
|
|
315
|
+
flow through without any hard-coded input.
|
|
316
|
+
n_jobs : Optional[Union[int, None]], default=None
|
|
317
|
+
Active only if no scheduler is available. That is, if a
|
|
318
|
+
scheduler is not passed to `scheduler`, if no global scheduler
|
|
319
|
+
is available, and if there is no scheduler context manager, only
|
|
320
|
+
then does `n_jobs` become effectual. In this case, `GSTCVDask`
|
|
321
|
+
creates a Client instance using local multiprocessing with
|
|
322
|
+
`n_workers=n_jobs`.
|
|
323
|
+
cache_cv : Optional[bool], default=True
|
|
324
|
+
Indicates if the train/test folds of the data are to be stored
|
|
325
|
+
when first generated, or if the folds are generated from X, y
|
|
326
|
+
and the KFold indices at each point of need. Caching all the
|
|
327
|
+
folds for each split of the data is memory-intensive.
|
|
328
|
+
|
|
329
|
+
Attributes
|
|
330
|
+
----------
|
|
331
|
+
cv_results_ : CVResultsType
|
|
332
|
+
A dictionary with column headers as keys and results as values,
|
|
333
|
+
that can be conveniently converted into a pandas DataFrame.
|
|
334
|
+
Always exposed after fit.
|
|
335
|
+
|
|
336
|
+
Below is an example of `cv_results_` for a logistic classifier,
|
|
337
|
+
with:
|
|
338
|
+
cv=3,
|
|
339
|
+
|
|
340
|
+
param_grid={'C': [1e-5, 1e-4]},
|
|
341
|
+
|
|
342
|
+
thresholds=np.linspace(0,1,21),
|
|
343
|
+
|
|
344
|
+
scoring=['accuracy', 'balanced_accuracy']
|
|
345
|
+
|
|
346
|
+
return_train_score=False
|
|
347
|
+
|
|
348
|
+
on random data.
|
|
349
|
+
|
|
350
|
+
{
|
|
351
|
+
'mean_fit_time': [1.227847, 0.341168]
|
|
352
|
+
|
|
353
|
+
'std_fit_time': [0.374309, 0.445982]
|
|
354
|
+
|
|
355
|
+
'mean_score_time': [0.001638, 0.001676]
|
|
356
|
+
|
|
357
|
+
'std_score_time': [0.000551, 0.000647]
|
|
358
|
+
|
|
359
|
+
'param_C': [0.00001, 0.0001]
|
|
360
|
+
|
|
361
|
+
'params': [{'C': 1e-05}, {'C': 0.0001}]
|
|
362
|
+
|
|
363
|
+
'best_threshold_accuracy': [0.5, 0.51]
|
|
364
|
+
|
|
365
|
+
'split0_test_accuracy': [0.785243, 0.79844]
|
|
366
|
+
|
|
367
|
+
'split1_test_accuracy': [0.80228, 0.814281]
|
|
368
|
+
|
|
369
|
+
'split2_test_accuracy': [0.805881, 0.813381]
|
|
370
|
+
|
|
371
|
+
'mean_test_accuracy': [0.797801, 0.808701]
|
|
372
|
+
|
|
373
|
+
'std_test_accuracy': [0.009001, 0.007265]
|
|
374
|
+
|
|
375
|
+
'rank_test_accuracy': [2, 1]
|
|
376
|
+
|
|
377
|
+
'best_threshold_balanced_accuracy': [0.5, 0.51]
|
|
378
|
+
|
|
379
|
+
'split0_test_balanced_accuracy': [0.785164, 0.798407]
|
|
380
|
+
|
|
381
|
+
'split1_test_balanced_accuracy': [0.802188, 0.814252]
|
|
382
|
+
|
|
383
|
+
'split2_test_balanced_accuracy': [0.805791, 0.813341]
|
|
384
|
+
|
|
385
|
+
'mean_test_balanced_accuracy': [0.797714, 0.808667]
|
|
386
|
+
|
|
387
|
+
'std_test_balanced_accuracy': [0.008995, 0.007264]
|
|
388
|
+
|
|
389
|
+
'rank_test_balanced_accuracy': [2, 1]
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
Slicing across the dictionary values gives the results for a
|
|
393
|
+
single permutation of grid search. That is, indexing into all of
|
|
394
|
+
the masked arrays at position zero gives the result for the
|
|
395
|
+
first permutation of the grid search, index 1 contains the
|
|
396
|
+
results for the second permutation, and so forth.
|
|
397
|
+
|
|
398
|
+
The key 'params' is used to store a list of parameter settings
|
|
399
|
+
dictionaries for all the hyperparameter candidates. That is,
|
|
400
|
+
the 'params' key holds all the possible permutations of
|
|
401
|
+
hyperparameters for the given search grid(s).
|
|
402
|
+
|
|
403
|
+
'mean_fit_time', 'std_fit_time', 'mean_score_time' and
|
|
404
|
+
'std_score_time' are all in seconds.
|
|
405
|
+
|
|
406
|
+
For single-metric evaluation, the scores for the single scorer
|
|
407
|
+
are available in the `cv_results_` dict at the keys ending with
|
|
408
|
+
'_score'. For multi-metric evaluation, the scores for all the
|
|
409
|
+
scorers are available in the `cv_results_` dict at the keys
|
|
410
|
+
ending with that scorer’s name ('_<scorer_name>'). E.g.,
|
|
411
|
+
‘split0_test_precision’, ‘mean_train_precision’, etc.
|
|
412
|
+
best_estimator_ : object
|
|
413
|
+
The estimator that was chosen by the search, i.e. the estimator
|
|
414
|
+
which gave the highest score (or smallest loss) on the held-out
|
|
415
|
+
(test) data. Only exposed when `refit` is not False; see the
|
|
416
|
+
`refit` parameter for more information on allowed values.
|
|
417
|
+
best_score_ : float
|
|
418
|
+
The mean of the scores of the hold out (test) cv folds for the
|
|
419
|
+
best estimator with the best threshold applied. Always exposed
|
|
420
|
+
when there is one scorer, or when `refit` is specified as a
|
|
421
|
+
string for 2+ scorers.
|
|
422
|
+
best_params_ : dict[str, Any]
|
|
423
|
+
The dictionary found in the :attr:`cv_results_` 'params'
|
|
424
|
+
column in the :attr:`best_index_` position, which gives the
|
|
425
|
+
hyperparameter settings that resulted in the highest mean score
|
|
426
|
+
(best_score_) on the hold out (test) data with the best threshold
|
|
427
|
+
applied.
|
|
428
|
+
|
|
429
|
+
`best_params_` never holds the best threshold. Access the
|
|
430
|
+
best threshold via the :attr:`best_threshold_` attribute (if
|
|
431
|
+
available) or the `cv_results_` attribute.
|
|
432
|
+
|
|
433
|
+
`best_params_` is always exposed when there is one scorer, or
|
|
434
|
+
when `refit` is not False for 2+ scorers.
|
|
435
|
+
best_index_ : int
|
|
436
|
+
The index of the `cv_results_` arrays which corresponds to the
|
|
437
|
+
best hyperparameter settings. Always exposed when there is one
|
|
438
|
+
scorer, or when `refit` is not False for 2+ scorers.
|
|
439
|
+
scorer_ : dict
|
|
440
|
+
Scorer metric(s) used on the held out data to choose the best
|
|
441
|
+
hyperparameters for the model. Always exposed after fit.
|
|
442
|
+
|
|
443
|
+
This attribute holds the validated scoring dictionary which maps
|
|
444
|
+
the scorer key to the scorer metric callable, i.e., a dictionary
|
|
445
|
+
of {scorer_name: scorer_metric}.
|
|
446
|
+
n_splits_ : int
|
|
447
|
+
The number of cross-validation splits (folds). Always exposed
|
|
448
|
+
after fit.
|
|
449
|
+
refit_time_ : float
|
|
450
|
+
Seconds elapsed when refitting the best model on the whole
|
|
451
|
+
dataset. Only exposed when `refit` is not False.
|
|
452
|
+
|
|
453
|
+
multimetric_ : bool
|
|
454
|
+
Whether several scoring metrics were used. False if one scorer
|
|
455
|
+
was used, otherwise True. Always exposed after fit.
|
|
456
|
+
classes_
|
|
457
|
+
n_features_in_
|
|
458
|
+
feature_names_in_
|
|
459
|
+
best_threshold_ : float
|
|
460
|
+
The threshold that, along with the hyperparameter values found
|
|
461
|
+
in `best_params_`, yields the highest score for the given
|
|
462
|
+
estimator and data.
|
|
463
|
+
|
|
464
|
+
The best threshold is only available conditionally via the
|
|
465
|
+
`best_threshold_` attribute. When one scorer is used, the best
|
|
466
|
+
threshold found is always exposed. When multiple scorers are
|
|
467
|
+
used, the `best_threshold_` attribute is only exposed when a
|
|
468
|
+
string value is passed to `refit`. Another way to find the best
|
|
469
|
+
threshold for each scorer and overall is by inspection of
|
|
470
|
+
`cv_results_`.
|
|
471
|
+
|
|
472
|
+
Notes
|
|
473
|
+
-----
|
|
474
|
+
|
|
475
|
+
**Type Aliases**
|
|
476
|
+
|
|
477
|
+
class ClassifierProtocol(Protocol):
|
|
478
|
+
def fit(self, X: Any, y: Any) -> Self
|
|
479
|
+
|
|
480
|
+
def get_params(self, **kwargs) -> dict[str, Any]
|
|
481
|
+
|
|
482
|
+
def set_params(self, **kwargs) -> Self
|
|
483
|
+
|
|
484
|
+
def predict_proba(self, X: Any) -> Any
|
|
485
|
+
|
|
486
|
+
ParamGridInputType:
|
|
487
|
+
dict[str, Sequence[Any]]
|
|
488
|
+
|
|
489
|
+
ParamGridsInputType:
|
|
490
|
+
Sequence[ParamGridInputType]
|
|
491
|
+
|
|
492
|
+
ThresholdsInputType:
|
|
493
|
+
Union[None, numbers.Real, Sequence[numbers.Real]]
|
|
494
|
+
|
|
495
|
+
DaskSlicerType:
|
|
496
|
+
dask.array.core.Array
|
|
497
|
+
|
|
498
|
+
DaskKFoldType:
|
|
499
|
+
tuple[DaskSlicerType, DaskSlicerType]
|
|
500
|
+
|
|
501
|
+
ScorerNameTypes:
|
|
502
|
+
Literal[
|
|
503
|
+
'accuracy',
|
|
504
|
+
'balanced_accuracy',
|
|
505
|
+
'average_precision',
|
|
506
|
+
'f1',
|
|
507
|
+
'precision',
|
|
508
|
+
'recall'
|
|
509
|
+
]
|
|
510
|
+
|
|
511
|
+
ScorerCallableType:
|
|
512
|
+
Callable[[Iterable, Iterable], numbers.Real]
|
|
513
|
+
|
|
514
|
+
ScorerInputType:
|
|
515
|
+
Union[
|
|
516
|
+
ScorerNameTypes,
|
|
517
|
+
Sequence[ScorerNameTypes],
|
|
518
|
+
ScorerCallableType,
|
|
519
|
+
dict[str, ScorerCallableType]
|
|
520
|
+
]
|
|
521
|
+
|
|
522
|
+
RefitCallableType:
|
|
523
|
+
Callable[[CVResultsType], numbers.Integral]
|
|
524
|
+
|
|
525
|
+
RefitType:
|
|
526
|
+
Union[bool, ScorerNameTypes, RefitCallableType]
|
|
527
|
+
|
|
528
|
+
DaskSchedulerType:
|
|
529
|
+
Union[distributed.scheduler.Scheduler, distributed.client.Client]
|
|
530
|
+
|
|
531
|
+
DaskXType:
|
|
532
|
+
Iterable
|
|
533
|
+
|
|
534
|
+
DaskYType:
|
|
535
|
+
Union[Sequence[numbers.Integral], None]
|
|
536
|
+
|
|
537
|
+
CVResultsType:
|
|
538
|
+
dict[str, np.ma.masked_array[Any]]
|
|
539
|
+
|
|
540
|
+
FeatureNamesInType:
|
|
541
|
+
numpy.ndarray[str]
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
Examples
|
|
545
|
+
--------
|
|
546
|
+
>>> import numpy as np
|
|
547
|
+
>>> from pybear_dask.model_selection import GSTCVDask
|
|
548
|
+
>>> from dask_ml.linear_model import LogisticRegression
|
|
549
|
+
>>> from dask_ml.datasets import make_classification
|
|
550
|
+
|
|
551
|
+
>>> clf = LogisticRegression(
|
|
552
|
+
... solver='lbfgs',
|
|
553
|
+
... tol=1e-4
|
|
554
|
+
... )
|
|
555
|
+
>>> X, y = make_classification(
|
|
556
|
+
... n_samples=90, n_features=5, random_state=19, chunks=(90, 5)
|
|
557
|
+
... )
|
|
558
|
+
>>> param_grid = {
|
|
559
|
+
... 'C': [1e-4, 1e-3],
|
|
560
|
+
... }
|
|
561
|
+
>>> gstcv = GSTCVDask(
|
|
562
|
+
... estimator=clf,
|
|
563
|
+
... param_grid=param_grid,
|
|
564
|
+
... thresholds=[0.1, 0.5, 0.9],
|
|
565
|
+
... scoring='balanced_accuracy',
|
|
566
|
+
... iid=True,
|
|
567
|
+
... refit=False,
|
|
568
|
+
... cv=3,
|
|
569
|
+
... verbose=0,
|
|
570
|
+
... error_score='raise',
|
|
571
|
+
... return_train_score=False,
|
|
572
|
+
... scheduler=None,
|
|
573
|
+
... n_jobs=None,
|
|
574
|
+
... cache_cv=True
|
|
575
|
+
... )
|
|
576
|
+
>>> gstcv.fit(X, y)
|
|
577
|
+
GSTCVDask(cv=3, estimator=LogisticRegression(solver='lbfgs'),
|
|
578
|
+
param_grid={'C': [0.0001, 0.001]}, refit=False,
|
|
579
|
+
scoring='balanced_accuracy', thresholds=[0.1, 0.5, 0.9])
|
|
580
|
+
|
|
581
|
+
>>> gstcv.best_params_
|
|
582
|
+
{'C': 0.0001}
|
|
583
|
+
|
|
584
|
+
>>> gstcv.best_threshold_
|
|
585
|
+
0.5
|
|
586
|
+
|
|
587
|
+
"""
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def __init__(
|
|
591
|
+
self,
|
|
592
|
+
estimator: ClassifierProtocol,
|
|
593
|
+
param_grid: Union[ParamGridInputType, ParamGridsInputType],
|
|
594
|
+
*,
|
|
595
|
+
thresholds: ThresholdsInputType=None,
|
|
596
|
+
scoring: ScorerInputType='accuracy',
|
|
597
|
+
iid: Optional[bool]=True,
|
|
598
|
+
refit: Optional[Union[bool, str, Callable]] = True,
|
|
599
|
+
cv: Optional[Union[numbers.Integral, Iterable, None]]=None,
|
|
600
|
+
verbose: Optional[numbers.Real]=0,
|
|
601
|
+
error_score: Optional[ErrorScoreType]='raise',
|
|
602
|
+
return_train_score: Optional[bool]=False,
|
|
603
|
+
scheduler: Optional[
|
|
604
|
+
Union[distributed.Client, distributed.scheduler.Scheduler, None]
|
|
605
|
+
]=None,
|
|
606
|
+
n_jobs: Optional[Union[numbers.Integral, None]]=None,
|
|
607
|
+
cache_cv: Optional[bool]=True
|
|
608
|
+
) -> None:
|
|
609
|
+
"""Initialize the `GSTCVDask` instance."""
|
|
610
|
+
|
|
611
|
+
self.estimator = estimator
|
|
612
|
+
self.param_grid = param_grid
|
|
613
|
+
self.thresholds = thresholds
|
|
614
|
+
self.scoring = scoring
|
|
615
|
+
self.n_jobs = n_jobs
|
|
616
|
+
self.cv = cv
|
|
617
|
+
self.refit = refit
|
|
618
|
+
self.verbose = verbose
|
|
619
|
+
self.error_score = error_score
|
|
620
|
+
self.return_train_score = return_train_score
|
|
621
|
+
self.iid = iid
|
|
622
|
+
self.scheduler = scheduler
|
|
623
|
+
self.cache_cv = cache_cv
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def _val_y(self, _y: DaskYType) -> None:
|
|
627
|
+
"""Implements `GSTCVDask` _val_y in methods in `_GSTCVMixin`.
|
|
628
|
+
|
|
629
|
+
See the docs for `GSTCVDask` _val_y.
|
|
630
|
+
|
|
631
|
+
Parameters
|
|
632
|
+
----------
|
|
633
|
+
_y : DaskYType
|
|
634
|
+
The target for the data.
|
|
635
|
+
|
|
636
|
+
Returns
|
|
637
|
+
-------
|
|
638
|
+
None
|
|
639
|
+
|
|
640
|
+
"""
|
|
641
|
+
|
|
642
|
+
# KEEP val y separate
|
|
643
|
+
_val_y(_y)
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
def _val_params(self) -> None:
|
|
647
|
+
"""Validate init params that are unique to `GSTCVDask`.
|
|
648
|
+
|
|
649
|
+
Returns
|
|
650
|
+
-------
|
|
651
|
+
None
|
|
652
|
+
|
|
653
|
+
"""
|
|
654
|
+
|
|
655
|
+
# KEEP val of y separate
|
|
656
|
+
_validation(self.estimator, self.iid, self.cache_cv)
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
def _condition_params(
|
|
660
|
+
self,
|
|
661
|
+
_X: DaskXType,
|
|
662
|
+
_y: DaskYType
|
|
663
|
+
) -> None:
|
|
664
|
+
"""Condition GSTCVDask-only init params into format for internal
|
|
665
|
+
processing.
|
|
666
|
+
|
|
667
|
+
Parameters
|
|
668
|
+
----------
|
|
669
|
+
_X : DaskXType
|
|
670
|
+
The data.
|
|
671
|
+
_y : DaskYType
|
|
672
|
+
The target for the data.
|
|
673
|
+
|
|
674
|
+
Returns
|
|
675
|
+
-------
|
|
676
|
+
None
|
|
677
|
+
|
|
678
|
+
"""
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
self._scheduler: DaskSchedulerType = \
|
|
682
|
+
_cond_scheduler(self.scheduler, self.n_jobs)
|
|
683
|
+
|
|
684
|
+
self._KFOLD: list[DaskKFoldType]
|
|
685
|
+
if isinstance(self._cv, numbers.Integral):
|
|
686
|
+
self._KFOLD = list(_dask_get_kfold(
|
|
687
|
+
_X, self.n_splits_, self.iid, self._verbose, _y=_y
|
|
688
|
+
))
|
|
689
|
+
else: # _cv is an iterable, _cond_cv should have made list[tuple]
|
|
690
|
+
self._KFOLD = self._cv
|
|
691
|
+
|
|
692
|
+
self._CACHE_CV: Union[None, list[DaskSplitType]] = None
|
|
693
|
+
if self.cache_cv:
|
|
694
|
+
self._CACHE_CV = []
|
|
695
|
+
for (train_idxs, test_idxs) in self._KFOLD:
|
|
696
|
+
self._CACHE_CV.append(_dask_fold_splitter(train_idxs, test_idxs, _X, _y))
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def _fit_all_folds(
|
|
700
|
+
self,
|
|
701
|
+
_X:DaskXType,
|
|
702
|
+
_y:DaskYType,
|
|
703
|
+
_grid:dict[str, Any],
|
|
704
|
+
_fit_params
|
|
705
|
+
) -> list[tuple[ClassifierProtocol, float, bool], ...]:
|
|
706
|
+
"""Fit on each train/test split for one single set of
|
|
707
|
+
hyperparameter values (one permutation of GSCV).
|
|
708
|
+
|
|
709
|
+
Parameters
|
|
710
|
+
----------
|
|
711
|
+
_X : DaskXType
|
|
712
|
+
The data.
|
|
713
|
+
_y : DaskYType
|
|
714
|
+
The target for the data.
|
|
715
|
+
_grid : dict[str, Any]
|
|
716
|
+
The values for the hyperparameters for this permutation of
|
|
717
|
+
grid search.
|
|
718
|
+
|
|
719
|
+
Returns
|
|
720
|
+
-------
|
|
721
|
+
FIT_OUTPUT : list[tuple[ClassifierProtocol, float, bool], ...]
|
|
722
|
+
A list of tuples, one tuple for each fold, with each tuple
|
|
723
|
+
holding the respective fitted estimator for that fold of
|
|
724
|
+
train/test data, the fit time, and a bool indicating whether
|
|
725
|
+
the fit raised an error.
|
|
726
|
+
|
|
727
|
+
"""
|
|
728
|
+
|
|
729
|
+
# **** IMPORTANT NOTES ABOUT estimator & n_jobs ****
|
|
730
|
+
# this is from sklearn notes, where the problem was discovered.
|
|
731
|
+
# there was a (sometimes large, >> 0.10) anomaly in scores
|
|
732
|
+
# when n_jobs was set to (1, None) vs. (-1, 2, 3, 4) when using
|
|
733
|
+
# SK Logistic. While running the fits under a regular for-loop,
|
|
734
|
+
# the SK estimator itself was found to be the cause, from being
|
|
735
|
+
# fit on repeatedly without reconstruct. There appears be some
|
|
736
|
+
# form of state information being retained in the estimator that
|
|
737
|
+
# is altered with fit and alters subsequent fits slightly. there
|
|
738
|
+
# is very little on google and ChatGPT about this (ChatGPT also
|
|
739
|
+
# thinks that 'state' is the problem.) Locking random_state made
|
|
740
|
+
# no difference, and locking other things with randomness (KFold,
|
|
741
|
+
# etc.) made no difference. There was no experimentation done to
|
|
742
|
+
# see if that problem extends beyond SK Logistic or SK. joblib
|
|
743
|
+
# with n_jobs (1, None) behaves exactly the same as a regular
|
|
744
|
+
# for-loop. The solution is to reconstruct the estimator with each
|
|
745
|
+
# fit and fit it once - then the results agree exactly with the
|
|
746
|
+
# results when n_jobs > 1 and with SK gscv. 'estimator' is being
|
|
747
|
+
# rebuilt at each call with the class constructor
|
|
748
|
+
# type(_estimator)(**_estimator.get_params(deep=True)).
|
|
749
|
+
|
|
750
|
+
# must use shallow params to construct estimator
|
|
751
|
+
s_p = self._estimator.get_params(deep=False) # shallow_params
|
|
752
|
+
# must use deep params for pipeline to set GSCV params (depth
|
|
753
|
+
# doesnt matter for an estimator when not pipeline.)
|
|
754
|
+
d_p = self._estimator.get_params(deep=True) # deep_params
|
|
755
|
+
|
|
756
|
+
_fold_fit_params = _dask_estimator_fit_params_helper(
|
|
757
|
+
[*compute(len(_y))][0], # n_samples
|
|
758
|
+
_fit_params,
|
|
759
|
+
self._KFOLD
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
FIT_OUTPUT = []
|
|
763
|
+
for f_idx in range(self.n_splits_):
|
|
764
|
+
|
|
765
|
+
# train only!
|
|
766
|
+
if self.cache_cv:
|
|
767
|
+
_X_y_train = list(zip(*self._CACHE_CV[f_idx]))[0]
|
|
768
|
+
elif not self.cache_cv:
|
|
769
|
+
_X_y_train = list(zip(*_dask_fold_splitter(*self._KFOLD[f_idx], _X, _y)))[0]
|
|
770
|
+
|
|
771
|
+
FIT_OUTPUT.append(
|
|
772
|
+
_parallelized_fit(
|
|
773
|
+
f_idx,
|
|
774
|
+
*_X_y_train,
|
|
775
|
+
type(self._estimator)(**s_p).set_params(**deepcopy(d_p)),
|
|
776
|
+
_grid,
|
|
777
|
+
self.error_score,
|
|
778
|
+
**_fold_fit_params[f_idx]
|
|
779
|
+
)
|
|
780
|
+
)
|
|
781
|
+
|
|
782
|
+
del s_p, d_p, _fold_fit_params, _X_y_train
|
|
783
|
+
|
|
784
|
+
return FIT_OUTPUT
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
def _score_all_folds_and_thresholds(
|
|
788
|
+
self,
|
|
789
|
+
_X:DaskXType,
|
|
790
|
+
_y:DaskYType,
|
|
791
|
+
_FIT_OUTPUT:list[tuple[ClassifierProtocol, float, bool], ...],
|
|
792
|
+
_THRESHOLDS:ThresholdsWIPType
|
|
793
|
+
) -> list[tuple[MaskedHolderType, MaskedHolderType], ...]:
|
|
794
|
+
"""For each fitted estimator associated with each fold, produce
|
|
795
|
+
the y_pred vector for that fold's test data and score it against
|
|
796
|
+
the actual y.
|
|
797
|
+
|
|
798
|
+
Parameters
|
|
799
|
+
----------
|
|
800
|
+
_X : DaskXType
|
|
801
|
+
The data.
|
|
802
|
+
_y : DaskYType
|
|
803
|
+
The target for the data.
|
|
804
|
+
_FIT_OUTPUT : list[tuple[ClassifierProtocol, float, bool], ...]
|
|
805
|
+
A list of tuples, one tuple for each fold, with each tuple
|
|
806
|
+
holding the respective fitted estimator for that fold of
|
|
807
|
+
train/test data, the fit time, and a bool indicating whether
|
|
808
|
+
the fit raised an error.
|
|
809
|
+
_THRESHOLDS : ThresholdsWIPType
|
|
810
|
+
The thresholds for which to calculate scores.
|
|
811
|
+
|
|
812
|
+
Returns
|
|
813
|
+
-------
|
|
814
|
+
TEST_SCORER_OUT : list[tuple[MaskedHolderType, MaskedHolderType], ...]
|
|
815
|
+
TEST_THRESHOLD_x_SCORER__SCORE_LAYER : MaskedHolderType
|
|
816
|
+
Masked array of shape (n_thresholds, n_scorers) holding
|
|
817
|
+
the scores for each scorer on each threshold for one
|
|
818
|
+
fold of test data.
|
|
819
|
+
TEST_THRESHOLD_x_SCORER__SCORE_TIME_LAYER : MaskedHolderType
|
|
820
|
+
Masked array of shape (n_thresholds, n_scorers) holding
|
|
821
|
+
the times to score each scorer on each threshold for one
|
|
822
|
+
fold of test data.
|
|
823
|
+
|
|
824
|
+
"""
|
|
825
|
+
|
|
826
|
+
TEST_SCORER_OUT = []
|
|
827
|
+
for f_idx in range(self.n_splits_):
|
|
828
|
+
|
|
829
|
+
# test only!
|
|
830
|
+
if self.cache_cv:
|
|
831
|
+
_X_y_test = list(zip(*self._CACHE_CV[f_idx]))[1]
|
|
832
|
+
elif not self.cache_cv:
|
|
833
|
+
_X_y_test = list(zip(*_dask_fold_splitter(*self._KFOLD[f_idx], _X, _y)))[1]
|
|
834
|
+
|
|
835
|
+
TEST_SCORER_OUT.append(
|
|
836
|
+
_parallelized_scorer(
|
|
837
|
+
*_X_y_test,
|
|
838
|
+
_FIT_OUTPUT[f_idx],
|
|
839
|
+
f_idx,
|
|
840
|
+
self.scorer_,
|
|
841
|
+
_THRESHOLDS,
|
|
842
|
+
self.error_score,
|
|
843
|
+
self._verbose
|
|
844
|
+
)
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
del _X_y_test
|
|
848
|
+
|
|
849
|
+
return TEST_SCORER_OUT
|
|
850
|
+
# END SCORE ALL FOLDS & THRESHOLDS #################################
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
def _score_train(
|
|
854
|
+
self,
|
|
855
|
+
_X:DaskXType,
|
|
856
|
+
_y:DaskYType,
|
|
857
|
+
_FIT_OUTPUT:list[tuple[ClassifierProtocol, float, bool], ...],
|
|
858
|
+
_BEST_THRESHOLDS_BY_SCORER:NDArrayHolderType
|
|
859
|
+
) -> list[MaskedHolderType]:
|
|
860
|
+
# TRAIN_SCORER_OUT is TRAIN_SCORER__SCORE_LAYER
|
|
861
|
+
"""Using the fitted estimator for each fold, all the scorers,
|
|
862
|
+
and the best thresholds for each scorer, score the train data
|
|
863
|
+
for each fold using all the scorers and the single best threshold
|
|
864
|
+
for the respective scorer.
|
|
865
|
+
|
|
866
|
+
Parameters
|
|
867
|
+
----------
|
|
868
|
+
_X : DaskXType
|
|
869
|
+
The data.
|
|
870
|
+
_y : DaskYType
|
|
871
|
+
The target for the data.
|
|
872
|
+
_FIT_OUTPUT : list[tuple[ClassifierProtocol, float, bool], ...]
|
|
873
|
+
A list of tuples, one tuple for each fold, with each tuple
|
|
874
|
+
holding the respective fitted estimator for that fold of
|
|
875
|
+
train/test data, the fit time, and a bool indicating whether
|
|
876
|
+
the fit raised an error.
|
|
877
|
+
_BEST_THRESHOLDS_BY_SCORER : NDArrayHolderType
|
|
878
|
+
The best threshold for each scorer as found by averaging the
|
|
879
|
+
scores across each fold of test data.
|
|
880
|
+
|
|
881
|
+
Returns
|
|
882
|
+
-------
|
|
883
|
+
TRAIN_SCORER_OUT : list[MaskedHolderType]
|
|
884
|
+
List of masked arrays where each masked array holds the
|
|
885
|
+
scores for a fold of train data using every scorer and the
|
|
886
|
+
best threshold associated with that scorer.
|
|
887
|
+
|
|
888
|
+
"""
|
|
889
|
+
|
|
890
|
+
TRAIN_SCORER_OUT = []
|
|
891
|
+
for f_idx in range(self.n_splits_):
|
|
892
|
+
|
|
893
|
+
# train only!
|
|
894
|
+
if self.cache_cv:
|
|
895
|
+
_X_y_train = list(zip(*self._CACHE_CV[f_idx]))[0]
|
|
896
|
+
elif not self.cache_cv:
|
|
897
|
+
_X_y_train = list(zip(*_dask_fold_splitter(*self._KFOLD[f_idx], _X, _y)))[0]
|
|
898
|
+
|
|
899
|
+
TRAIN_SCORER_OUT.append(
|
|
900
|
+
_parallelized_train_scorer(
|
|
901
|
+
*_X_y_train,
|
|
902
|
+
_FIT_OUTPUT[f_idx],
|
|
903
|
+
f_idx,
|
|
904
|
+
self.scorer_,
|
|
905
|
+
_BEST_THRESHOLDS_BY_SCORER,
|
|
906
|
+
self.error_score,
|
|
907
|
+
self._verbose
|
|
908
|
+
)
|
|
909
|
+
)
|
|
910
|
+
|
|
911
|
+
del _X_y_train
|
|
912
|
+
|
|
913
|
+
return TRAIN_SCORER_OUT
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
|