Skip to content

Commit

Permalink
[ENH] parallelization support and config for forecasting performance …
Browse files Browse the repository at this point in the history
…metrics (#5813)

This PR adds the option to parallelize forecasting performance metrics
via `set_config`.

The method is simply passing on the config to `vectorize_est`, if set.

Tests are added. Docstrings do not need to be added, as apprently the
option is already inherited from `BaseObject` (mistakenly before this
PR, but now correctly).
  • Loading branch information
fkiraly committed Jan 24, 2024
1 parent 19e249b commit c0a9ea3
Show file tree
Hide file tree
Showing 2 changed files with 20 additions and 2 deletions.
10 changes: 10 additions & 0 deletions sktime/performance_metrics/forecasting/_classes.py
Expand Up @@ -273,12 +273,17 @@ def _evaluate_vectorized(self, y_true, y_pred, **kwargs):
y_pred : VectorizedDF
non-time-like instances of y_true, y_pred must be identical
"""
backend = dict()
backend["backend"] = self.get_config()["backend:parallel"]
backend["backend_params"] = self.get_config()["backend:parallel:params"]

eval_result = y_true.vectorize_est(
estimator=self.clone(),
method="_evaluate",
varname_of_self="y_true",
args={**kwargs, "y_pred": y_pred},
colname_default=self.name,
**backend,
)

if isinstance(self.multioutput, str) and self.multioutput == "raw_values":
Expand All @@ -305,13 +310,18 @@ def _evaluate_by_index_vectorized(self, y_true, y_pred, **kwargs):
y_pred : VectorizedDF
non-time-like instances of y_true, y_pred must be identical
"""
backend = dict()
backend["backend"] = self.get_config()["backend:parallel"]
backend["backend_params"] = self.get_config()["backend:parallel:params"]

eval_result = y_true.vectorize_est(
estimator=self.clone().set_params(**{"multilevel": "uniform_average"}),
method="_evaluate_by_index",
varname_of_self="y_true",
args={**kwargs, "y_pred": y_pred},
colname_default=self.name,
return_type="list",
**backend,
)

eval_result = y_true.reconstruct(eval_result)
Expand Down
12 changes: 10 additions & 2 deletions sktime/performance_metrics/tests/test_metrics_classes.py
Expand Up @@ -14,6 +14,7 @@
from sktime.utils._testing.hierarchical import _make_hierarchical
from sktime.utils._testing.panel import _make_panel
from sktime.utils._testing.series import _make_series
from sktime.utils.parallel import _get_parallel_test_fixtures

metric_classes = getmembers(_classes, isclass)

Expand All @@ -24,6 +25,9 @@

MULTIOUTPUT = ["uniform_average", "raw_values", "numpy"]

# list of parallelization backends to test
BACKENDS = _get_parallel_test_fixtures("config")


@pytest.mark.parametrize("n_columns", [1, 2])
@pytest.mark.parametrize("multioutput", MULTIOUTPUT)
Expand Down Expand Up @@ -76,12 +80,13 @@ def test_metric_output_direct(metric, multioutput, n_columns):
assert np.allclose(res[1], res[2])


@pytest.mark.parametrize("backend", BACKENDS)
@pytest.mark.parametrize("n_columns", [1, 2])
@pytest.mark.parametrize(
"multilevel", ["uniform_average", "uniform_average_time", "raw_values"]
)
@pytest.mark.parametrize("multioutput", MULTIOUTPUT)
def test_metric_hierarchical(multioutput, multilevel, n_columns):
def test_metric_hierarchical(multioutput, multilevel, n_columns, backend):
"""Test hierarchical input for metrics."""
# create numpy weights based on n_columns
if multioutput == "numpy":
Expand All @@ -94,6 +99,7 @@ def test_metric_hierarchical(multioutput, multilevel, n_columns):
y_true = _make_hierarchical(random_state=42, n_columns=n_columns)

metric = MeanSquaredError(multioutput=multioutput, multilevel=multilevel)
metric.set_config(**backend)

res = metric(
y_true=y_true,
Expand Down Expand Up @@ -183,10 +189,11 @@ def test_metric_output_by_instance(metric, multioutput, n_columns):
assert (res.index == y_true.index).all()


@pytest.mark.parametrize("backend", BACKENDS)
@pytest.mark.parametrize("n_columns", [1, 2])
@pytest.mark.parametrize("multilevel", ["uniform_average", "raw_values"])
@pytest.mark.parametrize("multioutput", MULTIOUTPUT)
def test_metric_hierarchical_by_index(multioutput, multilevel, n_columns):
def test_metric_hierarchical_by_index(multioutput, multilevel, n_columns, backend):
"""Test hierarchical input for metrics."""
# create numpy weights based on n_columns
if multioutput == "numpy":
Expand All @@ -199,6 +206,7 @@ def test_metric_hierarchical_by_index(multioutput, multilevel, n_columns):
y_true = _make_hierarchical(random_state=42, n_columns=n_columns)

metric = MeanSquaredError(multioutput=multioutput, multilevel=multilevel)
metric.set_config(**backend)

res = metric.evaluate_by_index(
y_true=y_true,
Expand Down

0 comments on commit c0a9ea3

Please sign in to comment.