Luminol을 알려면 Bitmap이 뭔지를 알아야함 (깃허브 살펴보면, bitmap 논문 참조했다고 주석에 나옴 http://alumni.cs.ucr.edu/~ratana/KumarN.pdf)

<1차이해>

우선 Bitmap은 카오스게임방식으로 패턴을 나타낸다. 이것은 단순한 숫자를 문자로 변환하여 패턴을 한눈에 살펴볼수 있도록 하는 알고리즘입니다. 하지만 이 알고리즘을 적용하기위해서는 이산 시퀀스형태로 만들어야하는데 우리가 사용하고자 하는 데이터는 시계열 실수 데이터. 이런 시계열 데이터를 이산 시퀀스로 변환해주는것이 바로 SAX알고리즘입니다 SAX 알고리즘은 타임시리즈 실수 데이터를 동일한 크기의 구간으로 나눕니다. 나눈 구간의 평균값을 계산한다음 그 구간을 전부다 그 평균값으로 대체해버리는 방식으로 타임시리즈 데이터를 이산시퀀스로 변환해줍니다. 그리하여 연속된 실수값 타임시리즈값을 → 일정 구간으로 나누어 평균값으로 바꾸어버려 이산시퀀스로 만들고 → 구간별로 baabccbc같은 문자로 패턴을 얻을수 있다. → 얻어낸 문자패턴을 카오스게임에 적용하여 시각화할수 있습니다.

anomaly detection을 하는방법은 다음과 같습니다. 1) 타임시리즈는 lead 윈도우와 lag 윈도우로 나뉜다. 두개의 크기는 다를수 있으며 lead window는 예측하고자하는 window, lag window는 선행 윈도우로 대체적으로 lead window이전의값을 의미합니다. 이 각각의 window에 속한 타임시리즈는 앞에서처럼 sax알고리즘을 사용하여 문자열로 바꾸고 → 문자열로 바꾼값을 quad tree안에 집어넣어서 정리하여 두개의 bitmap를 생성합니다.→ 두개 비트맵사이의 거리는 아래식으로 구합니다. (이 구한 숫자가 바로 이상탐지값이고, 이 숫자 높을수록 이상치값이 높다고 판단합니다.) 이 과정을 쭉 반복하면서 이상치값을 구하고 새롭게 구한 이상치값으로 재구성한 그래프를 내놓은게 바로 luminol 입니다.

 

코드를 살펴본결과 Luminol울 수행할때 lead window, lag window 의 디폴트 크기는 전체 데이터 길이의 0.2/16 즉 약 1.25% 를 차지하도록 설정해줍니다. 그리고 너무 작은 데이터사이즈나 너무 큰 데이터 사이즈의 경우를 대비해서 min,max사이즈도 정해두었습니다. 아래코드처럼 chunk_size(=윈도우크기)를 사용자가 직접 설정하지 않으면 디폴트값으로 진행되는것으로 확인하였습니다.

그리고 lag window와 future_window(=lead window)값을 비교하는 함수를 살펴보니 위논문에서 구한 식과 동일한것을 확인하였습니다.

 

<2차이해>

 

더욱 구체적인 예제로는 https://www.researchgate.net/publication/249889424_Time-series-bitmap_Based_Approach_to_Analyze_Human_Postural_Control_and_Movement_Detection_Strategies_during_Small_Anterior_Perturbations 논문과, luminol github에서 참조하였다는 bitmap논문의 이전버전 논문인 http://alumni.cs.ucr.edu/~ratana/KumarN.pdf 를 참조하였습니다.

  1. 슬라이딩 윈도우방식으로 타임시리즈데이터를 처음부터 2개윈도우창으로 나누어서 끝까지 훝는다. 2개 window는 Lag window와 Lead window(github에서는 future 윈도우)
  2. SAX알고리즘을 통해 타임시리즈 데이터를 문자열로 만들어줍니다. (연속된 실수를 일정단위별로 끊어서 평균값을 구하고 계단형으로 변환, 그 변환된것을 문자열로 변경
  3. 아래 예제에서는 4*4 크기 윈도우를 사용하였고 aaaaaaaa에서 aa가 8개 존재하기 때문에 aa항에 7을 넣어줍니다. aaaa의 경우는 aa가 3개 있기 때문에 aa항에 3을 넣어줍니다.
  4. 이 윈도우를 0-1사이의값으로 변환해줍니다. 왼쪽 figure에서는 0-white, 1-black으로 변환해준다라고 표현하였고, 오른쪽 figure에서는 normalize한다라고 표현하였습니다.
  5. 0-1로 재구성한 4*4그래프 2개값을 가지고 score를 구합니다. 오른쪽 figure를 기준으로 살펴보면 aaaaaaaa 와 aaa의 차이는(lagwindow-futurewindow)^2로 구하며 (1-1)^2=0으로 anomaly score이 0입니다.

github에서 소개하는 bitmap 논문의 이전 버전 논문을 찾아낸본결과(http://alumni.cs.ucr.edu/~ratana/KumarN.pdf) normalize해서 색깔별로 나타내보면 다음과같이 비슷한 타임시리즈 데이터일수록 매우 흡사한 컬러배치를 보인다는것을 알수 있습니다.

moving average가 가장 평균적으로 많이 쓰이는 smoothing 방식입니다. 이는 주어진 데이터 전/후의 일정 개수의 데이터의 평균이나 중간값을 그 데이터의 값으로 추정하는 방법입니다. 그에 반해 savgol filter은 특정구간의 평균/중간값이 아니라, 특정구간을 ax^2 + bx + c 같은 회귀모델을 이용한 smoothing을 수행합니다.

xi의 새로운 값은 양쪽 근방 2n+1 개의 점으로 다항식 회귀한 식으로부터 다시 추정해 내는 것이 핵심 아이디어입니다. 즉, xi 를 포함하고 있는 양쪽 근방 2n+1 개의 점으로 다항식 회귀한 식 Si을 찾고, Si(xi)로 xi 를 대체하는 것입니다. (** 2n+1인이유는 오른쪽으로 n개, 왼쪽으로 n개 , xi 1개해서 2n+1로 셉니다. 그래서 윈도우 갯수는 홀수입니다.) 아래 그림을 예제로 들면 25값을 기준으로 오른쪽으로3개 왼쪽으로3개값을 떼서 7개 데이터를 윈도우로 떼서 봅니다. 7개데이터를 가지고 회귀곡선을 새롭게 그린다음 25에 해당하는 Y값을 새로이 지정해줍니다.

여기서 가장 큰 문제는 xi를 구하기위해 앞뒤 데이터들을 합친 2n+1개 윈도우를 떼서 새로이 다항회귀식(si)를 재구성하는것입니다.

(출처 : https://eigenvector.com/wp-content/uploads/2020/01/SavitzkyGolay.pdf)

Si 회귀함수를 구하고, 그에 맞는 새로운값을 구하는과정은 아래와같이 구해질수 있습니다.

 

 

DTW(Dynamic time warping) :

  • 속도가 다른 2개 시계열 패턴이 들어올때 유사성을 측정하는 알고리즘
  • 0에 가까울수록 그래프가 유사하다고 볼수있음 (0이면 동일한 그래프)
  • 주로 음성인식에 사용됨(두개 목소리를 비교해서 동일인지 확인)
  • 장점 : 두개 매트릭의 길이가 달라도 비교가 가능

 

ED(Euclidean distance) :

  • 유클리드 거리는 정의에 따라 두 시계열 샘플의 해당 관측치 간에 일대일 매핑을 시행하는 두 벡터 간의 유사도 측정에 널리 사용됨
  • 단점 : 두개 간에 길이가 다를경우 오차가 엄청크게 나올수 있음.
  • 이방법을 사용하기위해서는 두개 매트릭을 1) 정규화해주고 2)길이도 맞추어주어야한다.

 

LCSS(Longest common subsequence) 2016

  • 유사성 임계값을 정의하고 결정해서 시계열 유사성을 측정하도록 개발
  • 장점 : 길이차이가 많이나는 시계열에 대해서도 우수한 결과를 보여줌
  • 단점 : 임계값에 따라 결과가 천차만별이 될수있다.

 

DLCSS(Developed Longest Common Subsequence) (2020년)

  • LCSS가 오래된 방법이다보니 더 업데이트한 버전,LCSS, DTW보다 우수하다고 주장
  • 1-Nearest Neighbor 및 k-medoids 클러스터링 기술을 사용

 

TLCC(Time Lagged Cross correlation) & windowed TLCC (2015)

  • 하나의 time series 를 조금씩 shifting 시키면서 데이터 전체 범위에 대해서 pearson 상관계수를 계산하여 나타낸다. pearson,kendal 과는 다르게 두 데이터 사이에서의 인과관계를 파악할수 있다고함.
  • 단점으로는 신호가 동시에 발생함, 데이터가 유사함이라는 기본전제를 깔고간다.
  • 시간에 따라 상세하게 분석을 원한다면 windowed TLCC 를 사용하면 된다고함 ( 데이터를 여러개의 window 로 나눠서 TLCC 분석을 각 window 마다 행함
  •  

1. Rupture

import os
import pandas as pd
import glob
import datetime
import matplotlib.pyplot as plt
import statsmodels.api as sm
import seaborn as sns
import statsmodels.api as sm
from scipy import stats
from sklearn.preprocessing import LabelEncoder
import numpy as np
#pd.options.display.float_format = '{:.10f}'.format #표 전체 다보이
# 안잘리게 설정
pd.set_option('display.max_row', 3000)
pd.set_option('display.max_columns', 1000)

breaks_rpt = []
for i in breaks:
    breaks_rpt.append(Data_F.index[i-1])
#breaks_rpt = pd.to_datetime(breaks_rpt)
breaks_rpt




plt.plot(y, label='data')
print_legend = True
for i in breaks_rpt:
    if print_legend:
        plt.axvline(i, color='red',linestyle='dashed', label='breaks')
        print_legend = False
    else:
        plt.axvline(i, color='red',linestyle='dashed')
plt.grid()
plt.legend()
plt.show()

 

 

 

2. jenkspy

https://github.com/mthh/jenkspy

 

GitHub - mthh/jenkspy: Compute Natural Breaks in Python (Fisher-Jenks algorithm)

Compute Natural Breaks in Python (Fisher-Jenks algorithm) - GitHub - mthh/jenkspy: Compute Natural Breaks in Python (Fisher-Jenks algorithm)

github.com

 

 

 

- Rupture 은 이상치 value 를 뽑아내고

- jenkspy 는 이상치 key 를 뽑아준다.

참조1 :https://github.com/linkedin/luminol

참조2 : https://www.kaggle.com/caesarlupum/anomaly-detection-time-series-linkedin-luminol/notebook

 

import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import os
import gc
import warnings

from scipy.stats import norm
warnings.filterwarnings('ignore')
pd.set_option('max_columns', 150)
pd.set_option('max_rows', 150)

import matplotlib.pyplot as plt
from matplotlib import rcParams
import seaborn as sns
from scipy import stats
#To plot figs on jupyter
%matplotlib inline
# figure size in inches
rcParams['figure.figsize'] = 14,6

import plotly.offline as py
py.init_notebook_mode(connected=True)
import plotly.graph_objs as go
import plotly.tools as tls
import warnings
warnings.filterwarnings('ignore')
pd.set_option('max_columns', 200)
pd.set_option('max_rows', 200)
pd.options.display.float_format = '{:.5f}'.format
## DATASET 1 (ent1, ds1)------------------------------------------------------------------------
mu, sigma = 0.0, 1.0
ent1 = np.zeros((10000))
for i in range(10):
#     print(mu)
    for j in range(1000):
        ent1[1000*i+j] = np.random.normal(mu, sigma)
    mu = mu + 9 - i

a1 = 0.6
a2 = -0.5
ds1 = np.zeros((10000))
ds1[0] = ent1[0]
ds1[1] = ent1[1]
for i in range(2,10000):
    ds1[i] = a1*ds1[i-1] + a2*ds1[i-2] + ent1[i]
## DATASET 2 (ent2 ds2 )------------------------------------------------------------------------
mu = 0.0
ent2 = np.zeros((10000))
for i in range(10):
#     print(mu)
    for j in range(1000):
        sigma = 0.1/(0.01 + (10000 - (i*1000 + j))/10000)
        ent2[1000*i+j] = np.random.normal(mu, sigma)
    mu = mu + 1

a1 = 0.6
a2 = -0.5
ds2 = np.zeros((10000))
ds2[0] = ent1[0]
ds2[1] = ent1[1]
for i in range(2,10000):
    ds2[i] = a1*ds2[i-1] + a2*ds2[i-2] + ent2[i]

## DATASET 3 (ds3) ------------------------------------------------------------------------
mu, sigma1, sigma3 = 0.0, 1.0, 3.0
ds3 = np.zeros((10000))
for i in range(10):
    if i in {0,2,4,6,8}:
        for j in range(1000):
            ds3[1000*i+j] = np.random.normal(mu, sigma1)
    else:
        for j in range(1000):
            ds3[1000*i+j] = np.random.normal(mu, sigma3)
plt.figure(figsize=(16,4))
plt.plot(ent1)
plt.title('Dataset 1  ent1')
plt.ylabel('Values')
plt.xlabel('Count')
plt.legend()

plt.figure(figsize=(16,4))
plt.plot(ent2)
plt.title('Dataset 2  ent2')
plt.ylabel('Values')
plt.xlabel('Count')
plt.legend()

plt.figure(figsize=(16,4))
plt.plot(ds1)
plt.title('Dataset 3  ds1')
plt.ylabel('Values')
plt.xlabel('Count')
plt.legend()

plt.figure(figsize=(16,4))
plt.plot(ds2)
plt.title('Dataset 4  ds2')
plt.ylabel('Values')
plt.xlabel('Count')
plt.legend()

plt.figure(figsize=(16,4))
plt.plot(ds3)
plt.title('Dataset 5  ds3')
plt.ylabel('Values')
plt.xlabel('Count')
plt.legend()

plt.show()

 

import luminol
from luminol import anomaly_detector,correlator
from luminol.anomaly_detector import AnomalyDetector
from luminol.correlator import Correlator

#Luminol - only if module 'luminol was installed'
#data preprocessing for the framework

data = np.array(ent1)
ts_s = pd.Series(data)
ts_dict = ts_s.to_dict()

data2 = np.array(ent2)
ts_s2 = pd.Series(data)
ts_dict2 = ts_s.to_dict()


detector = anomaly_detector.AnomalyDetector(ts_dict)
anomalies = detector.get_anomalies()
anomalies
if anomalies:
    time_period = anomalies[0].get_time_window()
    correlator = correlator.Correlator(ts_dict, ts_dict2, time_period)
    
    
 print(correlator.get_correlation_result().coefficient)
 
 def scoreLuminolALLData(ts_dict):    
    data = np.array(ts_dict)
    ts_s = pd.Series(data)
    ts_dict = ts_s.to_dict()

    detector = anomaly_detector.AnomalyDetector(ts_dict)
    score = detector.get_all_scores()
    score_v = []
    for timestamp, value in score.iteritems():
        score_v.append(value)
#         print(timestamp, value)
    return score_v
dataplot1 = scoreLuminolALLData(ent1)    
dataplot2 = scoreLuminolALLData(ent2) 
dataplot3 = scoreLuminolALLData(ds1)    
dataplot4 = scoreLuminolALLData(ds2)        
dataplot5 = scoreLuminolALLData(ds3) 

dataplot1

더보기
더보기
더보기

dataLUMINOL_dataset1 = np.array(dataplot1)
from scipy import stats
dataLUMINOL_dataset1 = stats.describe(dataplot1)
dataLUMINOL_dataset1

 

qt25_ds1 = np.percentile(dataplot1, 25)  # Q1 백분위 25퍼센트
qt50_ds1 = np.percentile(dataplot1, 50)  # Q2 백분위 25퍼센트
qt75_ds1 = np.percentile(dataplot1, 75)  # Q3 백분위 25퍼센트
qt25_ds1, qt50_ds1, qt75_ds1

 

dfLUMINOL_dataset1 = pd.DataFrame(dataplot1, columns=['Score'])
dfLUMINOL_dataset1.value_counts()
# 이건 퍼센테이지가 왜저렇게 나왔는지 분석하기 위해서 보여준것임. 

 

 

def plot_anomaly_score_low_higt(datascore, data):
    datascore_ = np.array(datascore)
    from scipy import stats
    datascore_ = stats.describe(datascore)
    
    datascore_ = pd.DataFrame(datascore, columns=['Score'])

    delta = np.percentile(datascore, 75)
    print('Threashold ',delta)

    plt.figure(figsize=(16,6))
    plt.plot(data)
    plt.title("data count")        

    plt.figure(figsize=(16,6))
    plt.plot(datascore)
    plt.title("data count")        

    
    plt.figure(figsize=(16,6))
    df_high_data_ = datascore_[datascore_ <= delta]
    df_high_score_ = datascore_[datascore_ > delta]
    
    plt.plot(datascore_.index, datascore_.Score.fillna(1), c='gray', alpha=0.4)
    plt.scatter(df_high_data_.index, df_high_data_.values, label='Inline', s=10)
    plt.scatter(df_high_score_.index, df_high_score_.values, label='Outlier', c='red', s=10)
    plt.margins(x=0.01,y=0.2)
    plt.title('Anomaly Score ')
    plt.ylabel('Score')
    plt.xlabel('Data Count')
    plt.legend()
    plt.show()

 

dataLUMINOL_dataset2 = np.array(dataplot2)
from scipy import stats
dataLUMINOL_dataset2 = stats.describe(dataplot2)
dataLUMINOL_dataset2

qt25_ds2 = np.percentile(dataplot2, 25)  # Q1
qt50_ds2 = np.percentile(dataplot2, 50)  # Q2
qt75_ds2 = np.percentile(dataplot2, 75)  # Q3
qt25_ds2,qt50_ds2, qt75_ds2


dfLUMINOL_dataset2 = pd.DataFrame(dataplot2, columns=['Score'])
plot_anomaly_score_low_higt(dfLUMINOL_dataset2, ent2)

 

dataLUMINOL_dataset4 = np.array(dataplot4)
from scipy import stats
dataLUMINOL_dataset4 = stats.describe(dataplot4)
dataLUMINOL_dataset4


qt25_ds4 = np.percentile(dataplot4, 25)  # Q1
qt50_ds4 = np.percentile(dataplot4, 50)  # Q2
qt75_ds4 = np.percentile(dataplot4, 75)  # Q3
qt25_ds4, qt50_ds4, qt75_ds4

dfLUMINOL_dataset4 = pd.DataFrame(dataplot4, columns=['Score'])
plot_anomaly_score_low_higt(dfLUMINOL_dataset4, ds2)

- kaggle luminol https://www.kaggle.com/caesarlupum/anomaly-detection-time-series-linkedin-luminol

- 위랑 똑같은데 알고리즘만 다름 https://www.kaggle.com/caesarlupum/anomaly-detection-time-series-changefinder#Glipse-Data

- 위에두개 작성한 사람이 cpd 알고리즘만 모아둔거 https://www.kaggle.com/general/128356

- pilly  - 

- rupture https://github.com/deepcharles/ruptures

ㄴ 이건 이상치 value 를 잡아줌 

- jenkspy  https://pypi.org/project/jenkspy/

ㄴ이건 이상치 key 를 잡아줌

 

  • 추세 (Trend) : 증가하는지 감소하는지
  • 계절성 (Seasonality) : 일정한 빈도로 주기적으로 반복되는 패턴( 𝑚 )
  • 잔차(Residual) : 데이터 - 추세(trend) - 계절성(seasonal)

그렇다면 잔차가 불규칙적인 값으로 seaonal 한 값(계절성) 이 유의미한 값이 되기위해서는 잔차(residual)이 다음과 같이 2가지 조건을 모두 만족해야합니다.

 

 

  1. 잔차는 (1) 정규분포이고 ,평균0과 일정한 분산을 가져야함

2. 잔차에 (1) 특정한 흐름이 있으면 안됨, 규칙성이 보이면안됨

 

위에 서술한 잔차의 2가지 조건을 만족하기위해서 총 4가지 방법으로 잔차진단을 수행합니다

잔차진단 ! (4가지)

 

 

pdf

T_잔차진단(백색잡음)_내용_정리.pdf
0.73MB

 

HTML

Export-c87bd7a2-b880-4893-b8fc-f28f28954473.zip
0.52MB

+ Recent posts