Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions Examples - capabilities.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "bf37b854-6b40-4c37-932d-7d0ba55e3d00",
"metadata": {},
"outputs": [],
"source": [
"from ats import logger\n",
"logger.setup('INFO')"
]
},
{
"cell_type": "markdown",
"id": "b0de2247-6a80-4617-9c37-ae34b2042d25",
"metadata": {},
"source": [
"# Anomaly detector capabilities\n",
"\n",
"This example shows the capabilities of some models."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "4c9ebc5e-fb9b-4859-ab5a-e611ee5e07c8",
"metadata": {},
"outputs": [],
"source": [
"from ats.anomaly_detectors.base import AnomalyDetector\n",
"from ats.anomaly_detectors.naive.minmax import MinMaxAnomalyDetector\n",
"from ats.anomaly_detectors.stat.periodic_average import PeriodicAverageAnomalyDetector\n",
"from ats.anomaly_detectors.stat.robust import NHARAnomalyDetector\n",
"from ats.anomaly_detectors.ml.linear_regression import LinearRegressionAnomalyDetector\n",
"from ats.anomaly_detectors.ml.ifsom import IFSOMAnomalyDetector\n",
"from ats.anomaly_detectors.dl.lstm import LSTMAnomalyDetector"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "92e6e5bb-e746-4acd-8156-1ef28a8208dd",
"metadata": {},
"outputs": [],
"source": [
"anomaly_detectors = [MinMaxAnomalyDetector,\n",
" PeriodicAverageAnomalyDetector,\n",
" NHARAnomalyDetector,\n",
" LinearRegressionAnomalyDetector,\n",
" IFSOMAnomalyDetector,\n",
" LSTMAnomalyDetector]"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "d59cba53-0687-47bd-aa16-0956e95cd34d",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"MinMaxAnomalyDetector\n",
"{'mode': 'unsupervised', 'streaming': False, 'context': 'series', 'granularity': 'point', 'multivariate': True, 'scope': 'specific'}\n",
"\n",
"PeriodicAverageAnomalyDetector\n",
"{'mode': 'semisupervised', 'streaming': True, 'context': 'window', 'granularity': 'point', 'multivariate': True, 'scope': 'agnostic'}\n",
"\n",
"NHARAnomalyDetector\n",
"{'mode': 'unsupervised', 'streaming': False, 'context': 'series', 'granularity': 'point', 'multivariate': 'only', 'scope': 'specific'}\n",
"\n",
"LinearRegressionAnomalyDetector\n",
"{'mode': 'semisupervised', 'streaming': True, 'context': 'window', 'granularity': 'point', 'multivariate': False, 'scope': 'agnostic'}\n",
"\n",
"IFSOMAnomalyDetector\n",
"{'mode': 'unsupervised', 'streaming': False, 'context': 'dataset', 'granularity': 'series', 'multivariate': False, 'scope': 'specific'}\n",
"\n",
"LSTMAnomalyDetector\n",
"{'mode': 'semisupervised', 'streaming': True, 'context': 'window', 'granularity': 'point', 'multivariate': True, 'scope': 'agnostic'}\n"
]
}
],
"source": [
"for anomaly_detector in anomaly_detectors:\n",
" print()\n",
" print(anomaly_detector.__name__)\n",
" print(anomaly_detector.capabilities)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
38 changes: 38 additions & 0 deletions ats/anomaly_detectors/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,46 @@
import logging
logger = logging.getLogger(__name__)


class classproperty:
def __init__(self, func):
self.func = func

def __get__(self, obj, cls):
return self.func(cls)


class AnomalyDetector():

allowed_capabilities = {
'mode': {'unsupervised', 'semi-supervised', 'weakly-supervised', 'supervised'},
'streaming': {True, False},
'context': {'point', 'window', 'series', 'dataset'},
'granularity': {'series', 'point', 'variable'},
'multivariate': {True, False, 'only'},
'scope': {'specific', 'agnostic'},
}

@classproperty
def capabilities(cls):
raise NotImplementedError(f'Capabilities are not set for {cls.__name__}')

def __new__(cls, *args, **kwargs):
cls._validate_capabilities(cls.capabilities, cls.allowed_capabilities)
return super().__new__(cls, *args, **kwargs)

@classmethod
def _validate_capabilities(cls, capabilities, allowed_capabilities):
missing = set(allowed_capabilities) - set(capabilities)
if missing:
raise ValueError(f'Missing required capabilities: {sorted(missing)} for {cls.__name__}')
for key, value in capabilities.items():
if key not in allowed_capabilities:
raise ValueError(f'Unknown capability: "{key}" for {cls.__name__}')
if value not in allowed_capabilities[key]:
raise ValueError(f'Invalid value "{value}" for capability "{key}" for {cls.__name__}')


#========================
# Helpers
#========================
Expand Down
10 changes: 10 additions & 0 deletions ats/anomaly_detectors/dl/lstm.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,14 @@
from timeseria.models.anomaly_detectors import LSTMAnomalyDetector as TimeseriaLSTMAnomalyDetector

class LSTMAnomalyDetector(TimeseriaAnomalyDetector):

capabilities = {
'mode': 'semi-supervised',
'streaming': True,
'context': 'window',
'granularity': 'point',
'multivariate': True,
'scope': 'agnostic'
}

model_class = TimeseriaLSTMAnomalyDetector
9 changes: 9 additions & 0 deletions ats/anomaly_detectors/ml/ifsom.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ class IFSOMAnomalyDetector(AnomalyDetector):
features computed using FATS in order to identify anomalous series within a data set.
"""

capabilities = {
'mode': 'unsupervised',
'streaming': False,
'context': 'dataset',
'granularity': 'series',
'multivariate': False,
'scope': 'specific'
}

@staticmethod
def _wide_df_to_timeseries_df_with_anomaly_labels(wide_df, anomaly_col="outliers"):
"""
Expand Down
10 changes: 10 additions & 0 deletions ats/anomaly_detectors/ml/linear_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,14 @@
from timeseria.models.anomaly_detectors import LinearRegressionAnomalyDetector as TimeseriaLinearRegressionAnomalyDetector

class LinearRegressionAnomalyDetector(TimeseriaAnomalyDetector):

capabilities = {
'mode': 'semi-supervised',
'streaming': True,
'context': 'window',
'granularity': 'point',
'multivariate': False,
'scope': 'agnostic'
}

model_class = TimeseriaLinearRegressionAnomalyDetector
9 changes: 9 additions & 0 deletions ats/anomaly_detectors/naive/minmax.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@

class MinMaxAnomalyDetector(AnomalyDetector):

capabilities = {
'mode': 'unsupervised',
'streaming': False,
'context': 'series',
'granularity': 'point',
'multivariate': True,
'scope': 'specific'
}

@AnomalyDetector.apply_method
def apply(self, data, inplace=False):

Expand Down
10 changes: 10 additions & 0 deletions ats/anomaly_detectors/stat/periodic_average.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,14 @@
from timeseria.models.anomaly_detectors import PeriodicAverageAnomalyDetector as TimeseriaPeriodicAverageAnomalyDetector

class PeriodicAverageAnomalyDetector(TimeseriaAnomalyDetector):

capabilities = {
'mode': 'semi-supervised',
'streaming': True,
'context': 'window',
'granularity': 'point',
'multivariate': True,
'scope': 'agnostic'
}

model_class = TimeseriaPeriodicAverageAnomalyDetector
9 changes: 9 additions & 0 deletions ats/anomaly_detectors/stat/robust.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ class _COMNHARAnomalyDetector(AnomalyDetector):
Statistically robust anomaly detector based on COM, HAR, and NHAR methodologies.
"""

capabilities = {
'mode': 'unsupervised',
'streaming': False,
'context': 'series',
'granularity': 'point',
'multivariate': 'only',
'scope': 'specific'
}

def __init__(self, fq=2 * np.pi / 30, fw=2 * np.pi / 7, trend=2, methods=('COM', 'HAR', 'NHAR')):
self.fq = fq
self.fw = fw
Expand Down