Skip to content

Commit 43f8af0

Browse files
notaryanramaniTonyBagnallpatrickzibMatthewMiddlehurstaadya940
authored
[ENH] Implemented COPOD for anomaly detection (#2202)
* Implemented COPOD for anomaly detection * fix: type annotations for python3.9 * fix: changed method decorator * test: soft dependencies check * [DOC] Add JMLR paper to readme (#2203) * add paper * add paper * [DOC, ENH] Add WEASEL v2 and Human Activity Challenge Papers (#2204) * add WEASEL v2 paper * add ECML challenge * [ENH] Update remaining `registry` files (#2198) * registry refactor * all_estimators * comments and qc * fixes * fixes * exclude sklearn in docs * examples * examples * [MNT] Changes in preparation for update to `numpy` 2 (#1813) * numpy 2 * Update pyproject.toml * bound * scipy bound * correct method * not 2.1.0 * comment deps * comment deps * comment deps * make numpy 2.0 compatible * restore current numpy bound * revert to main pyproject * Empty commit for CI --------- Co-authored-by: aadya940 <[email protected]> Co-authored-by: Tony Bagnall <[email protected]> Co-authored-by: Tony Bagnall <[email protected]> Co-authored-by: TonyBagnall <[email protected]> * [ENH] Split up rocket estimators (#2207) * split up rocket * test params * test params * test params * more fixes for float_ (#2215) * Implemented COPOD for anomaly detection * update: added new test cases * changes: clean up --------- Co-authored-by: Tony Bagnall <[email protected]> Co-authored-by: Patrick Schäfer <[email protected]> Co-authored-by: Matthew Middlehurst <[email protected]> Co-authored-by: aadya940 <[email protected]> Co-authored-by: Tony Bagnall <[email protected]> Co-authored-by: TonyBagnall <[email protected]>
1 parent 8281d25 commit 43f8af0

File tree

4 files changed

+151
-0
lines changed

4 files changed

+151
-0
lines changed

aeon/anomaly_detection/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
"STOMP",
1010
"LeftSTAMPi",
1111
"IsolationForest",
12+
"COPOD",
1213
]
1314

15+
from aeon.anomaly_detection._copod import COPOD
1416
from aeon.anomaly_detection._dwt_mlead import DWT_MLEAD
1517
from aeon.anomaly_detection._iforest import IsolationForest
1618
from aeon.anomaly_detection._kmeans import KMeansAD

aeon/anomaly_detection/_copod.py

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""COPOD for anomaly detection."""
2+
3+
__maintainer__ = []
4+
__all__ = ["COPOD"]
5+
6+
from typing import Union
7+
8+
import numpy as np
9+
10+
from aeon.anomaly_detection._pyodadapter import PyODAdapter
11+
from aeon.utils.validation._dependencies import _check_soft_dependencies
12+
13+
14+
class COPOD(PyODAdapter):
15+
"""COPOD for anomaly detection.
16+
17+
This class implements the COPOD using PyODAdadpter to be used in the aeon framework.
18+
The parameter `n_jobs` is passed to COPOD model from PyOD, `window_size` and
19+
`stride` are used to construct the sliding windows.
20+
21+
.. list-table:: Capabilities
22+
:stub-columns: 1
23+
* - Input data format
24+
- univariate and multivariate
25+
* - Output data format
26+
- anomaly scores
27+
* - Learning Type
28+
- unsupervised or semi-supervised
29+
30+
Parameters
31+
----------
32+
n_jobs : int, default=1
33+
The number of jobs to run in parallel for the COPOD model.
34+
35+
window_size : int, default=10
36+
Size of the sliding window.
37+
38+
stride : int, default=1
39+
Stride of the sliding window.
40+
"""
41+
42+
_tags = {
43+
"capability:multivariate": True,
44+
"capability:univariate": True,
45+
"capability:missing_values": False,
46+
"fit_is_empty": False,
47+
"python_dependencies": ["pyod"],
48+
}
49+
50+
def __init__(self, n_jobs: int = 1, window_size: int = 10, stride: int = 1):
51+
_check_soft_dependencies(*self._tags["python_dependencies"])
52+
from pyod.models.copod import COPOD
53+
54+
model = COPOD(n_jobs=n_jobs)
55+
self.n_jobs = n_jobs
56+
super().__init__(model, window_size=window_size, stride=stride)
57+
58+
def _fit(self, X: np.ndarray, y: Union[np.ndarray, None] = None) -> None:
59+
super()._fit(X, y)
60+
61+
def _predict(self, X: np.ndarray) -> np.ndarray:
62+
return super()._predict(X)
63+
64+
def _fit_predict(
65+
self, X: np.ndarray, y: Union[np.ndarray, None] = None
66+
) -> np.ndarray:
67+
return super()._fit_predict(X, y)
68+
69+
@classmethod
70+
def get_test_params(cls, parameter_set="default") -> dict:
71+
"""Return testing parameter settings for the estimator.
72+
73+
Parameters
74+
----------
75+
parameter_set : str, default="default"
76+
Name of the set of test parameters to return, for use in tests. If no
77+
special parameters are defined for a value, will return `"default"` set.
78+
79+
Returns
80+
-------
81+
params : dict or list of dict, default={}
82+
Parameters to create testing instances of the class.
83+
Each dict are parameters to construct an "interesting" test instance, i.e.,
84+
`MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
85+
`create_test_instance` uses the first (or only) dictionary in `params`.
86+
"""
87+
return {}
+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""Tests for the COPOD class."""
2+
3+
import numpy as np
4+
import pytest
5+
6+
from aeon.anomaly_detection import COPOD
7+
from aeon.testing.data_generation import make_example_1d_numpy
8+
from aeon.utils.validation._dependencies import _check_soft_dependencies
9+
10+
11+
@pytest.mark.skipif(
12+
not _check_soft_dependencies("pyod", severity="none"),
13+
reason="required soft dependency PyOD not available",
14+
)
15+
def test_copod_default():
16+
"""Test COPOD."""
17+
series = make_example_1d_numpy(n_timepoints=80, random_state=0)
18+
series[50:58] -= 2
19+
20+
copod = COPOD(window_size=10, stride=1)
21+
pred = copod.fit_predict(series, axis=0)
22+
23+
assert pred.shape == (80,)
24+
assert pred.dtype == np.float_
25+
assert 50 <= np.argmax(pred) <= 60
26+
27+
28+
@pytest.mark.skipif(
29+
not _check_soft_dependencies("pyod", severity="none"),
30+
reason="required soft dependency PyOD not available",
31+
)
32+
def test_copod_pyod_parameters():
33+
"""Test parameters are correctly passed to the PyOD model."""
34+
params = {"n_jobs": 2}
35+
copod = COPOD(**params)
36+
37+
assert copod.pyod_model.n_jobs == params["n_jobs"]
38+
39+
40+
@pytest.mark.skipif(
41+
not _check_soft_dependencies("pyod", severity="none"),
42+
reason="required soft dependency PyOD not available",
43+
)
44+
def test_aeon_copod_with_pyod_copod():
45+
"""Test COPOD with PyOD COPOD."""
46+
from pyod.models.copod import COPOD as PyODCOPOD
47+
48+
series = make_example_1d_numpy(n_timepoints=100, random_state=0)
49+
series[20:30] -= 2
50+
51+
# fit and predict with aeon COPOD
52+
copod = COPOD(window_size=1, stride=1)
53+
copod_preds = copod.fit_predict(series)
54+
55+
# fit and predict with PyOD COPOD
56+
_series = series.reshape(-1, 1)
57+
pyod_copod = PyODCOPOD()
58+
pyod_copod.fit(_series)
59+
pyod_copod_preds = pyod_copod.decision_function(_series)
60+
61+
assert np.allclose(copod_preds, pyod_copod_preds)

docs/api_reference/anomaly_detection.rst

+1
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Detectors
6969
:toctree: auto_generated/
7070
:template: class.rst
7171

72+
COPOD
7273
DWT_MLEAD
7374
IsolationForest
7475
KMeansAD

0 commit comments

Comments
 (0)