-
Notifications
You must be signed in to change notification settings - Fork 919
/
Copy pathmultioutput.py
135 lines (113 loc) · 4.58 KB
/
multioutput.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
from typing import Optional
from sklearn.base import is_classifier
from sklearn.multioutput import MultiOutputRegressor as sk_MultiOutputRegressor
from sklearn.multioutput import _fit_estimator
from sklearn.utils.multiclass import check_classification_targets
from sklearn.utils.parallel import Parallel, delayed
from sklearn.utils.validation import (
_check_method_params,
has_fit_parameter,
validate_data,
)
from darts.logging import get_logger, raise_log
logger = get_logger(__name__)
class MultiOutputRegressor(sk_MultiOutputRegressor):
"""
:class:`sklearn.utils.multioutput.MultiOutputRegressor` with a modified ``fit()`` method that also slices
validation data correctly. The validation data has to be passed as parameter ``eval_set`` in ``**fit_params``.
"""
def __init__(
self,
*args,
eval_set_name: Optional[str] = None,
eval_weight_name: Optional[str] = None,
**kwargs,
):
super().__init__(*args, **kwargs)
self.eval_set_name_ = eval_set_name
self.eval_weight_name_ = eval_weight_name
self.estimators_ = None
self.n_features_in_ = None
self.feature_names_in_ = None
def fit(self, X, y, sample_weight=None, **fit_params):
"""Fit the model to data, separately for each output variable.
Parameters
----------
X : {array-like, sparse matrix} of shape (n_samples, n_features)
The input data.
y : {array-like, sparse matrix} of shape (n_samples, n_outputs)
Multi-output targets. An indicator matrix turns on multilabel
estimation.
sample_weight : array-like of shape (n_samples, n_outputs), default=None
Sample weights. If `None`, then samples are equally weighted.
Only supported if the underlying regressor supports sample
weights.
**fit_params : dict of string -> object
Parameters passed to the ``estimator.fit`` method of each step.
.. versionadded:: 0.23
Returns
-------
self : object
Returns a fitted instance.
"""
if not hasattr(self.estimator, "fit"):
raise_log(
ValueError("The base estimator should implement a fit method"),
logger=logger,
)
y = validate_data(self.estimator, X="no_validation", y=y, multi_output=True)
if is_classifier(self):
check_classification_targets(y)
if y.ndim == 1:
raise_log(
ValueError(
"`y` must have at least two dimensions for multi-output regression but has only one."
),
logger=logger,
)
if sample_weight is not None and (
sample_weight.ndim == 1 or sample_weight.shape[1] != y.shape[1]
):
raise_log(
ValueError("`sample_weight` must have the same dimensions as `y`."),
logger=logger,
)
if sample_weight is not None and not self.supports_sample_weight:
raise_log(
ValueError("Underlying estimator does not support sample weights."),
logger=logger,
)
fit_params_validated = _check_method_params(X, fit_params)
eval_set = fit_params_validated.pop(self.eval_set_name_, None)
eval_weight = fit_params_validated.pop(self.eval_weight_name_, None)
self.estimators_ = Parallel(n_jobs=self.n_jobs)(
delayed(_fit_estimator)(
self.estimator,
X,
y[:, i],
sample_weight=sample_weight[:, i]
if sample_weight is not None
else None,
**(
{self.eval_set_name_: [eval_set[i]]} if eval_set is not None else {}
),
**(
{self.eval_weight_name_: [eval_weight[i]]}
if eval_weight is not None
else {}
),
**fit_params_validated,
)
for i in range(y.shape[1])
)
if hasattr(self.estimators_[0], "n_features_in_"):
self.n_features_in_ = self.estimators_[0].n_features_in_
if hasattr(self.estimators_[0], "feature_names_in_"):
self.feature_names_in_ = self.estimators_[0].feature_names_in_
return self
@property
def supports_sample_weight(self) -> bool:
"""
Whether model supports sample weight for training.
"""
return has_fit_parameter(self.estimator, "sample_weight")