13
13
from typing import Callable , Union
14
14
15
15
import numpy as np
16
+ from joblib import Parallel , delayed
16
17
17
18
from aeon .classification .base import BaseClassifier
18
19
from aeon .distances import get_distance_function
20
+ from aeon .utils .validation import check_n_jobs
19
21
20
22
WEIGHTS_SUPPORTED = ["uniform" , "distance" ]
21
23
@@ -46,11 +48,15 @@ class KNeighborsTimeSeriesClassifier(BaseClassifier):
46
48
n_timepoints)`` as input and returns a float.
47
49
distance_params : dict, default = None
48
50
Dictionary for metric parameters for the case that distance is a str.
49
- n_jobs : int, default = None
51
+ n_jobs : int, default = 1
50
52
The number of parallel jobs to run for neighbors search.
51
53
``None`` means 1 unless in a :obj:`joblib.parallel_backend` context.
52
54
``-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.
54
60
55
61
Raises
56
62
------
@@ -85,11 +91,13 @@ def __init__(
85
91
n_neighbors : int = 1 ,
86
92
weights : Union [str , Callable ] = "uniform" ,
87
93
n_jobs : int = 1 ,
94
+ parallel_backend : str = None ,
88
95
) -> None :
89
96
self .distance = distance
90
97
self .distance_params = distance_params
91
98
self .n_neighbors = n_neighbors
92
99
self .n_jobs = n_jobs
100
+ self .parallel_backend = parallel_backend
93
101
94
102
self ._distance_params = distance_params
95
103
if self ._distance_params is None :
@@ -140,16 +148,11 @@ def _predict_proba(self, X):
140
148
The class probabilities of the input samples. Classes are ordered
141
149
by lexicographic order.
142
150
"""
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 )
153
156
154
157
def _predict (self , X ):
155
158
"""
@@ -167,19 +170,27 @@ def _predict(self, X):
167
170
y : array of shape (n_cases)
168
171
Class labels for each data sample.
169
172
"""
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
183
194
184
195
def _kneighbors (self , X ):
185
196
"""
0 commit comments