Skip to content

Commit 551661d

Browse files
steenrotsmanstijn
andauthored
[ENH]Use n_jobs parameter in KNeighborsTimeSeriesClassifier. (#2687)
* Use n_jobs parameter in KNeighborsTimeSeriesClassifier. * Don't convert proba dtype. * Remove is_fitted checks, update docstring. * Implement parallel backend param. * Correct keyword argument for Parallel. * Add check_n_jobs. --------- Co-authored-by: stijn <s.j.rotman@tilburguniversity.edu>
1 parent 4fab159 commit 551661d

File tree

1 file changed

+36
-25
lines changed

1 file changed

+36
-25
lines changed

aeon/classification/distance_based/_time_series_neighbors.py

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
from typing import Callable, Union
1414

1515
import numpy as np
16+
from joblib import Parallel, delayed
1617

1718
from aeon.classification.base import BaseClassifier
1819
from aeon.distances import get_distance_function
20+
from aeon.utils.validation import check_n_jobs
1921

2022
WEIGHTS_SUPPORTED = ["uniform", "distance"]
2123

@@ -46,11 +48,15 @@ class KNeighborsTimeSeriesClassifier(BaseClassifier):
4648
n_timepoints)`` as input and returns a float.
4749
distance_params : dict, default = None
4850
Dictionary for metric parameters for the case that distance is a str.
49-
n_jobs : int, default = None
51+
n_jobs : int, default = 1
5052
The number of parallel jobs to run for neighbors search.
5153
``None`` means 1 unless in a :obj:`joblib.parallel_backend` context.
5254
``-1`` means using all processors.
53-
for more details. Parameter for compatibility purposes, still unimplemented.
55+
parallel_backend : str, ParallelBackendBase instance or None, default=None
56+
Specify the parallelisation backend implementation in joblib, if None
57+
a ‘prefer’ value of “threads” is used by default. Valid options are
58+
“loky”, “multiprocessing”, “threading” or a custom backend.
59+
See the joblib Parallel documentation for more details.
5460
5561
Raises
5662
------
@@ -85,11 +91,13 @@ def __init__(
8591
n_neighbors: int = 1,
8692
weights: Union[str, Callable] = "uniform",
8793
n_jobs: int = 1,
94+
parallel_backend: str = None,
8895
) -> None:
8996
self.distance = distance
9097
self.distance_params = distance_params
9198
self.n_neighbors = n_neighbors
9299
self.n_jobs = n_jobs
100+
self.parallel_backend = parallel_backend
93101

94102
self._distance_params = distance_params
95103
if self._distance_params is None:
@@ -140,16 +148,11 @@ def _predict_proba(self, X):
140148
The class probabilities of the input samples. Classes are ordered
141149
by lexicographic order.
142150
"""
143-
preds = np.zeros((len(X), len(self.classes_)))
144-
for i in range(len(X)):
145-
idx, weights = self._kneighbors(X[i])
146-
for id, w in zip(idx, weights):
147-
predicted_class = self.y_[id]
148-
preds[i, predicted_class] += w
149-
150-
preds[i] = preds[i] / np.sum(preds[i])
151-
152-
return preds
151+
n_jobs = check_n_jobs(self.n_jobs)
152+
preds = Parallel(n_jobs=n_jobs, backend=self.parallel_backend)(
153+
delayed(self._proba_row)(x) for x in X
154+
)
155+
return np.array(preds)
153156

154157
def _predict(self, X):
155158
"""
@@ -167,19 +170,27 @@ def _predict(self, X):
167170
y : array of shape (n_cases)
168171
Class labels for each data sample.
169172
"""
170-
self._check_is_fitted()
171-
172-
preds = np.empty(len(X), dtype=self.classes_.dtype)
173-
for i in range(len(X)):
174-
scores = np.zeros(len(self.classes_))
175-
idx, weights = self._kneighbors(X[i])
176-
for id, w in zip(idx, weights):
177-
predicted_class = self.y_[id]
178-
scores[predicted_class] += w
179-
180-
preds[i] = self.classes_[np.argmax(scores)]
181-
182-
return preds
173+
n_jobs = check_n_jobs(self.n_jobs)
174+
preds = Parallel(n_jobs=n_jobs, backend=self.parallel_backend)(
175+
delayed(self._predict_row)(x) for x in X
176+
)
177+
return np.array(preds, dtype=self.classes_.dtype)
178+
179+
def _proba_row(self, x):
180+
scores = self._predict_scores(x)
181+
return scores / np.sum(scores)
182+
183+
def _predict_row(self, x):
184+
scores = self._predict_scores(x)
185+
return self.classes_[np.argmax(scores)]
186+
187+
def _predict_scores(self, x):
188+
scores = np.zeros(len(self.classes_))
189+
idx, weights = self._kneighbors(x)
190+
for id, weight in zip(idx, weights):
191+
predicted_class = self.y_[id]
192+
scores[predicted_class] += weight
193+
return scores
183194

184195
def _kneighbors(self, X):
185196
"""

0 commit comments

Comments
 (0)