Skip to content

Commit

Permalink
fix: missing tests of anomaly detectors (#2102)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sebastian Schmidl authored Sep 26, 2024
1 parent 3b7579e commit 0c76fa9
Show file tree
Hide file tree
Showing 4 changed files with 17 additions and 19 deletions.
15 changes: 8 additions & 7 deletions aeon/anomaly_detection/_dwt_mlead.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ def _predict(self, X) -> np.ndarray:
if self.quantile_epsilon < 0 or self.quantile_epsilon > 1:
raise ValueError("quantile_epsilon must be in [0, 1]")

X, self.N_, self.M_ = _pad_series(X)
max_level = int(np.log2(self.M_))
X, n, m = _pad_series(X)
max_level = int(np.log2(m))

if self.start_level >= max_level:
raise ValueError(
Expand Down Expand Up @@ -168,7 +168,7 @@ def _predict(self, X) -> np.ndarray:

# aggregate anomaly counts (leaf counters)
point_anomaly_scores = self._push_anomaly_counts_down_to_points(
coef_anomaly_counts
coef_anomaly_counts, m, n
)
return point_anomaly_scores

Expand Down Expand Up @@ -220,19 +220,20 @@ def _reverse_windowing(

return np.sum(mapped, axis=1)

@staticmethod
def _push_anomaly_counts_down_to_points(
self, coef_anomaly_counts: list[np.ndarray]
coef_anomaly_counts: list[np.ndarray], m: int, n: int
) -> np.ndarray:
# sum up counters of detail coeffs (orig. D^l) and approx coeffs (orig. C^l)
anomaly_counts = coef_anomaly_counts[0::2] + coef_anomaly_counts[1::2]

# extrapolate anomaly counts to the original series' points
counter = np.zeros(self.M_)
counter = np.zeros(m)
for ac in anomaly_counts:
counter += ac.repeat(self.M_ // ac.shape[0], axis=0)
counter += ac.repeat(m // ac.shape[0], axis=0)
# set event counters with count < 2 to 0
counter[counter < 2] = 0
return counter[: self.N_]
return counter[:n]

@classmethod
def get_test_params(cls, parameter_set="default"):
Expand Down
10 changes: 6 additions & 4 deletions aeon/anomaly_detection/_pyodadapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from typing import TYPE_CHECKING, Any

import numpy as np
from sklearn import clone

from aeon.anomaly_detection.base import BaseAnomalyDetector
from aeon.utils.validation._dependencies import _check_soft_dependencies
Expand Down Expand Up @@ -114,7 +115,7 @@ def _predict(self, X: np.ndarray) -> np.ndarray:
_X, padding = sliding_windows(
X, window_size=self.window_size, stride=self.stride, axis=0
)
window_anomaly_scores = self.pyod_model.decision_function(_X)
window_anomaly_scores = self.fitted_pyod_model_.decision_function(_X)
point_anomaly_scores = reverse_windowing(
window_anomaly_scores, self.window_size, np.nanmean, self.stride, padding
)
Expand All @@ -127,7 +128,7 @@ def _fit_predict(self, X: np.ndarray, y: np.ndarray | None = None) -> np.ndarray
)
self._inner_fit(_X)

window_anomaly_scores = self.pyod_model.decision_scores_
window_anomaly_scores = self.fitted_pyod_model_.decision_scores_
point_anomaly_scores = reverse_windowing(
window_anomaly_scores, self.window_size, np.nanmean, self.stride, padding
)
Expand All @@ -149,10 +150,11 @@ def _check_params(self, X: np.ndarray) -> None:
)

def _inner_fit(self, X: np.ndarray) -> None:
self.pyod_model.fit(X)
self.fitted_pyod_model_: BaseDetector = clone(self.pyod_model) # type: ignore
self.fitted_pyod_model_.fit(X)

def _inner_predict(self, X: np.ndarray, padding: int) -> np.ndarray:
window_anomaly_scores = self.pyod_model.decision_function(X)
window_anomaly_scores = self.fitted_pyod_model_.decision_function(X)
point_anomaly_scores = reverse_windowing(
window_anomaly_scores, self.window_size, np.nanmean, self.stride, padding
)
Expand Down
5 changes: 2 additions & 3 deletions aeon/anomaly_detection/_stomp.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ def __init__(
p: float = 2.0,
k: int = 1,
):
self.mp: np.ndarray | None = None
self.window_size = window_size
self.ignore_trivial = ignore_trivial
self.normalize = normalize
Expand All @@ -96,15 +95,15 @@ def _predict(self, X: np.ndarray) -> np.ndarray:
import stumpy

self._check_params(X)
self.mp = stumpy.stump(
mp = stumpy.stump(
X[:, 0],
m=self.window_size,
ignore_trivial=self.ignore_trivial,
normalize=self.normalize,
p=self.p,
k=self.k,
)
point_anomaly_scores = reverse_windowing(self.mp[:, 0], self.window_size)
point_anomaly_scores = reverse_windowing(mp[:, 0], self.window_size)
return point_anomaly_scores

def _check_params(self, X: np.ndarray) -> None:
Expand Down
6 changes: 1 addition & 5 deletions aeon/testing/testing_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,8 @@
"RSAST": ["check_fit_deterministic"],
"AEFCNClusterer": ["check_fit_updates_state"],
"AEResNetClusterer": ["check_fit_updates_state"],
"PyODAdapter": ["check_fit_updates_state"],
"SFA": ["check_persistence_via_pickle", "check_fit_deterministic"],
# missed in legacy testing, changes state in predict/transform
"DWT_MLEAD": ["check_non_state_changing_method"],
"STOMP": ["check_non_state_changing_method"],
"FLUSSSegmenter": ["check_non_state_changing_method"],
"InformationGainSegmenter": ["check_non_state_changing_method"],
"GreedyGaussianSegmenter": ["check_non_state_changing_method"],
Expand All @@ -84,9 +81,8 @@
# methods that should not change the state of the estimator, that is, they should
# not change fitted parameters or hyper-parameters. They are also the methods that
# "apply" the fitted estimator to data and useful for checking results.
# NON_STATE_CHANGING_METHODS_ARRAYLIK =
# non-state-changing methods that return an array-like output

# non-state-changing methods that return an array-like output
NON_STATE_CHANGING_METHODS_ARRAYLIKE = (
"predict",
"predict_var",
Expand Down

0 comments on commit 0c76fa9

Please sign in to comment.