매직코드

이상치탐지를 하게 된 이유

이번에 진행하는 프로젝트는 대기 중 화학물질의 농도를 보고 악취인지 아닌지 판단해야하는 프로젝트인데, 라벨값이 없기 때문에 지도학습이 어려워 이상치탐지를 먼저 수행해봤다. 하나의 이상치탐지만 진행하는 경우 악취임을 판단하는 적절한 임계값을 지정하는데 근거가 부족할 수 있기 때문에 다양한 방법론을 사용하려고 했다.

 

Iforest

iforest는 isolation forest로 트리기반 앙상블 기법중 하나다.

iforest를 제공하는 패키지가 2개정도 있는데 나는 pyod에서 제공하는 iforest를 사용했다. 공식문서

 

iforest의 작동 방식은 아래와 같다.

1) 임의의 변수와 임의의 기준점으로 데이터를 나눈다.

2) 이상치라고 생각되는 데이터를 포함한 부분에서 또다시 임의의 기준을 가지고 데이터를 나눈다.

3) 이상치라고 생각되는 데이터가 고립될 때 까지 데이터를 나눈다.

4) 이상치라고 생각되는 데이터들이 몇번만에 고립되는지에 따라 이상치인지 아닌지 판별한다.

5) 데이터들이 고립되는 평균값을 구하고 Anomaly score를 계산한다.

 

iforest 작동방식

 

Anomaly Score

Iforest 수행 시 데이터가 더 빠르게 고립될수록 더 높은 anomaly score를 가진다.

anomaly score은 0~1사이의 값으로 계산된다.

그 많은 데이터들 중에서 단한번의 split으로 이상치데이터로 판별된다면 그 데이터는 확실히 다른 데이터와는 다른 값을 가지고 있기 때문이다.

 

Anomaly Score 수식

 

위와 같은 수식으로 Anomaly Score를 계산한다.

 

  • 이상치라고 생각되는 데이터가 한번만에 나누어진 경우
    이상치가 한번에 나눠진다면 전체 데이터의 평균 split 횟수는 0이 된다.
    -E(h(x))가 0이 되기 때문에 식은 2의0제곱이 되고 Anomaly Score 값은 1이 된다.
  • 전체 데이터 평균 split 횟수 = 이상치 데이터 평균 split 횟수, 두 split이 동일한 경우
    -E(h(x))와 c(n)이 동일한 값이기 때문에 식은 2의 -1제곱이 되고 Anomaly Score 값은 0.5가 된다.
  • 데이터를 계속 나누어도 데이터 고립이 안되는 경우
    데이터가 고립될 때 까지 split을 진행하기 때문에 이 때 -E(h(x))는 -∞ 가 된다.
    식은 2의 -∞ 제곱이 되고 Anomaly Score 값은 0이 된다.

import pandas as pd
from pyod.models.iforest import Iforest

df = pd.read_csv('./train.csv')

model = Iforest()
model.fit(df)
scores = model.decision_scores_
df['scores'] = scores

 

이와같이 간단한 코드로 이상치탐지를 진행할 수 있다.

이 때 score 값들을 보고 수동적으로 threshold를 잡아줄 수 있다.

데이터마다 score가 나오는 범위가 다르기때문에 딱 0.5를 기준으로 할 수는 없고 본인 데이터에서 나오는 score의 최솟값, 최댓값을 보고 그 사이 값으로 threshold를 결정하면 될 것 같다.

 

 

fbprophet

fbprophet은 facebook에서 만든 시계열 관련 패키지이다. 공식문서

원래는 시계열예측, Trend 분석, Seasonality & Holiday 경향 분석 등을 제공한다.

이 모델의 장점은 null값이 많아도 알아서 null을 유추하여 예측을 한다는 점이다.

 

나는 prophet 모델이 예측하고 난 다음 시각화를 할 때 예측범위를 벗어난 데이터들을 이상치라고 봤다.

 

# pip install fbprophet

import pandas as pd
from fbprophet import Prophet

# 이 때 df에는 'ds'컬럼과 'y'컬럼만 존재해야함
# 'ds'컬럼은 datetime
# 'y'컬럼은 예측하고자 하는 라벨컬럼
df = pd.read_csv('./train.csv')

model = Prophet(seasonality_model='nultiplicative',
				yearly_seasonality=False,
                weekly_seasonality=True,
                daily_seasonality=True)
model.fit(df)

# 1일단위로 예측하여 fill na
df_fillna = model.make_future_dataframe(periods=1, freq='D')

# model predict
df_predict = model.predict(df_fillna)

# 시각화
model.plot(df_predict)

 

예측을 모두 수행한 이후 시각화를 통해 이상치를 눈으로 파악하는 방법으로 검은색 점이 실제 데이터값, 파란색이 예측값 및 예측오차범위를 포함한다. 따라서 파란색 범위를 넘어가는 경우 이상치라고 판단했다.

 

아래의 경우 y축이 잘 안보이는데 파란색 범위가 0~50 사이에 존재하고 이 범위를 넘는 경우 이상치라고 볼 수 있다.

따라서 threshold 값을 50으로 설정할 수 있다.

 

prophet 모델 결과 시각화

 

 

ADTK

ADTK는 Anomaly Detection Toolkit의 약자로 이상치탐지를 위해 만들어진 패키지다.

이상치탐지를 할 수 있는 경우의 수가 많고, 시각화도 잘 되어있는 편이다.

자세한 함수들은 공식문서를 참조하면 된다.

 

여러가지 함수 중에서 Persist Anomaly Detection을 사용해보았다.

Persist Anomaly Detection은 단 하나의 변수에 대해서 시계열값을 이전값과 비교하여 데이터가 비정상적으로 변화하는 것을 감지하는 방법으로 단기척도에 유리하다.

 

# pip install adtk

import pandas as pd
from adtk.data import validate_series
from adtk.visualization import plot
from adtk.detector import PersistAD

# 이 때 df는 datetime은 index로, 라벨컬럼 하나만 있어야함
df = pd.read_csv('./train.csv')

data = validate_series(df)
model = PersistAD(c=0.3, side='positive')
anomalies = model.fit_detect(data)

# 시각화
plot(data, anomaly=anomalies,
    ts_lindewidth=1,
    ts_markersize=3,
    anomaly_color='red')

 

 

시각화 결과 80% 정도는 이상치에 대해 anomaly라고 표시한 것 같다.

PersistAD는 단 하나의 변수에 대해서만 이상치탐지를 진행할 수 있는데 만약 서로 영향을 주는 여러개의 변수 데이터를 포함하여 이상치탐지를 하고싶다면 다변량 이상치탐지를 해주는 MinClusterDetector를 사용할 수 있다.

 

오늘 소개한 세가지 방법 모두 공식문서에 잘 설명이 되어있기 때문에 사용하기 어려움이 없을 것이다.

profile

매직코드

@개발법사

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!