tsod: Anomaly Detection for time series data
220915 ์์ฑ
<๋ณธ ๋ธ๋ก๊ทธ๋ DHI/tsod ๋๊ณผ dhi ๋์ github๋ฅผ ์ฐธ๊ณ ํด์ ๊ณต๋ถํ๋ฉฐ ์์ฑํ์์ต๋๋ค :-) >
tsod: Anomaly Detection for time series data. — tsod documentation
dhi.github.io
GitHub - DHI/tsod: Anomaly Detection for time series data
Anomaly Detection for time series data. Contribute to DHI/tsod development by creating an account on GitHub.
github.com
๐ tsod ๋?
- ์ด์ ํ์์ ๊ฒฝ๊ณ ์กฐ๊ฑด ๋๋ ์ค์๊ฐ ๊ฒฐ์ ์์คํ ์ผ๋ก ์์น ์๋ฎฌ๋ ์ด์ ์์ง์ ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ๊ธฐ ์ ์ ์๋์ผ๋ก ๊ฐ์ง๋๊ณ ๋ณด๋ค ์คํ ๊ฐ๋ฅํ ๊ฐ์ผ๋ก ๋์ฒด๋์ด์ผ ํจ
- ์๊ณ์ด์ ์ด์ ๊ฐ์ง๋ฅผ ์ํ ๊ฐ๋จํ๊ณ ์ผ๊ด๋ API
- ์๊ณ์ด ๋ฐ์ดํฐ์ฉ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- ์๊ณ์ด ํ์์ ํญ์ Series
- ์ด๋ค ๊ฒฝ์ฐ์๋ DatetimeIndex
- ๋๊ฐ์ง ์ ํ ๊ฐ์ง
- Outlier detection (unsupervised anomaly detection)
- ํ๋ จ ๋ฐ์ดํฐ๋ ์ด์์น, ์ฆ ๋๋ถ๋ถ์ ๋ค๋ฅธ ๊ด์ฐฐ์์ ๋ฉ๋ฆฌ ๋จ์ด์ง ๊ด์ฐฐ์ ํฌํจ
- ์ด์๊ฐ ๊ฐ์ง๊ธฐ๋ ์ ์ฌํ๊ณ ์๋ก ๊ฐ๊น์ด ํ์ต ๋ฐ์ดํฐ์ ๊ด์ธก๊ฐ์ ์ง์คํ๋ ค๊ณ ํ๊ณ ๋ ๋ฉ๋ฆฌ ์๋ ๊ด์ธก๊ฐ์ ๋ฌด์
- Novelty detection (semi-supervised anomaly detection)
- ํ๋ จ ๋ฐ์ดํฐ๋ "์ ์"์ผ๋ก ๊ฐ์ฃผ๋๋ฉฐ ์ด์๊ฐ์ ์ํด ์ค์ผ๋์ง ์์
- ์๋ก์ด ํ ์คํธ ๋ฐ์ดํฐ ๊ด์ฐฐ์ ์ด์์น๋ก ๋ถ๋ฅ๋ ์ ์์ผ๋ฉฐ ์ด๋ฌํ ๋งฅ๋ฝ์์ "novelty" ์ด๋ผ ๋ถ๋ฆผ
- Outlier detection (unsupervised anomaly detection)
๐ ์ฝ๋ ๊ตฌํ
1๏ธโฃ ํ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ & ํจํค์น ๋ก๋
- ํ์ํ tsod ์ค์น
!pip install tsod # from PyPI
!pip install https://github.com/DHI/tsod/archive/main.zip # dev version
- ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ก๋
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import tsod
2๏ธโฃ ๋ฐ์ดํฐ ๋ก๋
df = pd.read_csv("https://raw.githubusercontent.com/DHI/tsod/main/tests/data/example.csv", parse_dates=True, index_col=0)
df.head()
series = df.value
- Series ํํ๋ก ๋ฐ์ดํฐ ๊ฐ๊ณ ์ค๊ธฐ
type(series)
3๏ธโฃ ๊ฐ์ง๊ธฐ ๋ก๋
- ๊ฐ์ง๊ธฐ๋ฅผ ์ ํ ( RangeDetector or ConstantValueDetector )
- ๋ฒ์๋ฅผ ๋ฒ์ด๋ ๊ฐ์ ๊ฐ์ง
- tsod.RangeDetector(min_value=- inf, max_value=inf, quantiles=None)
- min_value (float) : ์ต์๊ฐ ์๊ณ๊ฐ
- max_value (float) : ์ต๋๊ฐ ์๊ณ๊ฐ
- quantiles (list[2]) : ๊ธฐ๋ณธ quantiles [0, 1] (์ต์๊ฐ ๋ฐ ์ต๋๊ฐ๊ณผ ๋์ผ)
- ์ด์ ๊ฐ์ง detect()
- ์ด์ ๊ฐ์ง
- detect(data: Union[pandas.core.series.Series, pandas.core.frame.DataFrame])
- data (pd.Series) : ๊ฐ๋ฅํ ๋ณ์น์ด ์๋ ์๊ณ์ด ๋ฐ์ดํฐ
- Returns : bool์ด ์๋ ์๊ณ์ด, True == anomaly
- Return type : pd.Series
rd = tsod.RangeDetector(min_value=0.01, max_value=2.0)
res = rd.detect(series)
series[res]
plt.plot(series)
plt.plot(series[res], 'ro',label='Anomaly')
plt.legend()
3๏ธโฃ Constant value
- ConstantValueDetector()
- ๋ ์ค๋ ๊ธฐ๊ฐ ๋์ ์์ ๊ฐ์ ๊ฐ์ง
- ์ผ๋ฐ์ ์ผ๋ก ์ผ์ ํ ์์ค์์ ๋ฉ์ถ๋ ์ผ์ ์ค๋ฅ๋ก ์ธํด ๋ฐ์
- tsod.ConstantValueDetector(window_size: int = 3, threshold: float = 1e-07)
- window_size ( int ) : ์ฐฝ ๋ฒ์๋ [(i - window_size):(i + window_size)]์ด๋ฏ๋ก ๋ฐฐ์ด ์์์ ์๋ก ๊ณ์ฐ๋๋ ์ฐฝ์ ์ ๋ฐ (์ด์์ผ๋ก ๊ฐ์ฃผํ ์ต์ ์ฐฝ, ๊ธฐ๋ณธ๊ฐ 3)
- ์๊ณ๊ฐ ( float ) : ์ด์๊ฐ์ ํ์ํ๊ธฐ ์ํ ์๊ณ๊ฐ (๋ฎ์ ์๊ณ๊ฐ์ ๊ฐ์ด ์ด์๊ฐ์ผ๋ก ๊ฐ์ฃผ๋๋ ๋ฒ์๋ฅผ "์ข๊ฒ, ๊ธฐ๋ณธ๊ฐ=3.0)
cd = tsod.ConstantValueDetector()
res = cd.detect(series)
series[res]
plt.plot(series)
plt.plot(series[res], 'ro',label='Anomaly')
plt.legend()
4๏ธโฃ Combination
- CombinedDetector()
- ๊ฐ์ง๊ธฐ๋ฅผ ๊ฒฐํฉ
- ์ฌ๋ฌ ๊ฐ์ง ์ด์ ํ์ง ์ ๋ต์ ๊ฒฐํฉ๋ ํ์ง๊ธฐ๋ก ๊ฒฐํฉํ๋ ๊ฒ์ด ๊ฐ๋ฅ
- tsod.CombinedDetector(detectors)
combined = tsod.CombinedDetector([tsod.RangeDetector(max_value=2.0),
tsod.ConstantValueDetector()])
res = combined.detect(series)
series[res]
plt.plot(series)
plt.plot(series[res], 'ro',label='Anomaly')
plt.legend()
5๏ธโฃ Constant Gradient
- ConstantGradientDetector()
- ์ผ์ ํ ๊ธฐ์ธ๊ธฐ๋ฅผ ๊ฐ์ง
- ๊ธด ๊ฐ๊ฒฉ์ ๋ํ ์ ํ ๋ณด๊ฐ์ผ๋ก ์ธํด ๋ฐ์
- tsod.ConstantGradientDetector(window_size: int = 3)
- window_size ( int ) : ์ด์์ผ๋ก ๊ฐ์ฃผํ ์ต์ ์ฐฝ, ๊ธฐ๋ณธ๊ฐ 3
cgd = tsod.ConstantGradientDetector()
res = cgd.detect(series)
plt.figure(figsize=(16,4))
plt.plot(series)
plt.plot(series[res], 'ro',label='Anomaly')
plt.legend()
6๏ธโฃ Gradient
- GradientDetector()
- ๊ธ๊ฒฉํ ๋ณํ ๊ฐ์ง
- tsod.GradientDetector(max_gradient=inf, direction='both')
- max_gradient ( float ) : ์ด๋น ์ต๋ ๋ณํ์จ, ๊ธฐ๋ณธ np.inf
- direction ( str ) : ์์, ์์ ๋๋ ๋ ๋ค, ๊ธฐ๋ณธ๊ฐ='both'
magd = tsod.GradientDetector()
magd.fit(series[0:10])
res = magd.detect(series)
series[res]
plt.figure(figsize=(16,4))
plt.plot(series)
plt.plot(series[res], 'ro',label='Anomaly')
plt.legend()
plt.title(magd)
7๏ธโฃ Rolling standard deviation
- ๊ฐ์์ค๋ฌ์ด ํฐ ๋ณํ๋ฅผ ๊ฐ์งํ๋ ๋ฐ ์ฌ์ฉ
normal_data = pd.Series(np.random.normal(size=100,scale=0.3) + 10.0*np.sin(np.linspace(0,2*np.pi,num=100)))
abnormal_data = pd.Series(np.random.normal(size=20,scale=5.0) + normal_data.iloc[-1])
all_data = pd.concat([normal_data,abnormal_data,normal_data[21:]],ignore_index=True)
all_data[150]= 5.0
all_data.plot()
- ๋ชจ๋ ๋ฐ์ดํฐ๊ฐ ํ์ฉ ๊ฐ๋ฅํ ๋ฒ์ ๋ด์ ์์ง๋ง ๋ณ๋์ด ์์๋ณด๋ค ํฌ๋ฏ๋ก ๋น์ ์
rsd = tsod.RollingStandardDeviationDetector(window_size=10, center=True)
rsd.fit(normal_data)
res = rsd.detect(all_data)
all_data[res]
plt.figure(figsize=(16,4))
plt.plot(all_data)
plt.plot(all_data[res], 'ro',label='Anomaly')
plt.legend()
plt.title(rsd)
8๏ธโฃ Diff
- ๊ฒฝ๊ณผ ์๊ฐ์ ๊ณ ๋ คํ์ง ์๊ณ ๊ธ๊ฒฉํ ๋ณํ๋ฅผ ๊ฐ์ง
drd = tsod.DiffDetector()
drd.fit(normal_data)
res = drd.detect(all_data)
all_data[res]
plt.figure(figsize=(16,4))
plt.plot(all_data)
plt.plot(all_data[res], 'ro',label='Anomaly')
plt.legend()
plt.title(drd)