본문 바로가기

프로그래밍/AI:ML:DL

[번역] 이동 중앙값 분해를 통한 비정상 탐지 Dectect Anomaly with Moving Median Decomposition

반응형

이동 중앙값 분해를 통한 비정상 탐지

https://anomaly.io/anomaly-detection-moving-median-decomposition/


2016 년 1 월 12 일




시계열 분해는 시계열을 계절적(seasonal) 시계열, 트랜드 시계열, 랜덤 잔여 시계열로 나눕니다. 트렌드와 랜덤 시계열은 둘 다 비정상을 감지하는 데 사용될 수 있습니다. 그러나 이미 비정상적인 시계열에서 비정상(anomaly)을 탐지하는 것은 쉽지 않습니다.


우선 결론


비정상적인 시계열로 작업하기 :


  • 이동 평균 분해를 통한 이상 탐지는 작동하지 않음
  • 이동 중앙값 분해를 통한 이상 탐지는 동작함.

 


이동 평균의 문제


R의 시계열 분해 포스팅에서 우리는 알고리즘이 이동평균을 이용하여 시계열의 추세를 추출하고 있음을 배웠습니다. 이것은 비정상값을 포함하지 않은 시계열에서는 완벽하게 동작합니다. 하지만, 비정상값이 있는 상황에서는 이동 평균 또한 예외적인 비정상값(outlier)의 영향을 받습니다. 우선, 이동 평균을 이용한 분해를 사용하여 비정상을 감지해 봅니다. 하지만 이 방법은 잘 작동하지 않으므로  더 나은 결과를 얻기 위해 이동 중앙값을 사용한 분해를 사용하여 비정상을 감지 합니다.


데이터에 관하여 :  webTraffic.csv  는 103 주 (거의 2 년) 동안의 일별 페이지 뷰 기록입니다. 재미를 더하기 위해 위해, 랜덤성을 추가할 것입니다. 시계열을 보면 주말 트래픽이 적기 때문에 7일의 주기적 계절성을 명확하게 볼 수 있습니다. 계절 시계열을 분해하려면 계절성 시간주기가 필요합니다. 이 예에서는 계절성이 7 일이라는 것을 알고 있습니다. 미리 알 수 없는 경우에도 시계열의 계절성을 구할 수 있습니다. (푸리에 변환) 마지막으로, 우리는 시계열 분해가 가산적인지 아니면 곱셈인지를 알아야합니다. 우리의 웹 트래픽은 곱셈적입니다.


웹 트래픽을 요약하면 다음과 같습니다.


  • 7 일의 계절성 (103 주 동안)
  • 곱셈적 시계열
  • webTraffic.csv 다운로드 (다운로드는 원문 링크 이용 바랍니다.)



set.seed(4)

data <- read.csv("webTraffic.csv", sep = ",", header = T)

days = as.numeric(data$Visite)

for (i in 1:45 ) {

 pos = floor(runif(1, 1, 50))

 days[i*15+pos] = days[i*15+pos]^1.2

}

days[510+pos] = 0

plot(as.ts(days))




이동 평균 분해  (나쁜 결과)


더 진행하기 위해선 우선 데이터 를 가져와야합니다. 또 시계열 분해가 어떻게 작동하는지 미리 이해해 두는 것이 좋습니다. R의 통계 패키지는 편리한 시계열 분해함수(decompose)를 제공합니다.


1 - 시계열 분해


시계열이 비정상적(anomalous)이기 때문에, 분해를 통해 구해진 추세(trend) 또한 완전히 잘못 나옵니다. 실제로 비정상요소들이 평균에 포함되어 버립니다.



# install required library

install.packages("FBN")

library(FBN)


decomposed_days = decompose(ts(days, frequency = 7), "multiplicative")

plot(decomposed_days)



2 - 정규분포로 min & max 찾기


(분해된) 랜덤 잡음에서 비정상을 검출하기 위해 정규 분포를 적용 할 수 있습니다. 표준 편차의 4배 밖의 값을 이상치(outlier)로 간주합시다.



random = decomposed_days$random

min = mean(random, na.rm = T) - 4*sd(random, na.rm = T)

max = mean(random, na.rm = T) + 4*sd(random, na.rm = T)


plot(as.ts(as.vector(random)), ylim = c(-0.5,2.5))

abline(h=max, col="#e15f3f", lwd=2)

abline(h=min, col="#e15f3f", lwd=2)



3 - 비정상(anomaly) 플롯팅하기


표준 편차의 4배 밖의 값을 찾아내서 그 비정상값을 그려 봅시다. 예상했듯이 많은 예외를 발견하지는 못합니다. 이는 분해 알고리즘에 이동 평균을 사용했기 때문입니다.



#find anomaly

position = data.frame(id=seq(1, length(random)), value=random)

anomalyH = position[position$value > max, ]

anomalyH = anomalyH[!is.na(anomalyH$value), ]

anomalyL = position[position$value < min, ]

anomalyL = anomalyL[!is.na(anomalyL$value), ]

anomaly = data.frame(id=c(anomalyH$id, anomalyL$id),

 value=c(anomalyH$value, anomalyL$value))

anomaly = anomaly[!is.na(anomaly$value), ]


plot(as.ts(days))

real = data.frame(id=seq(1, length(days)), value=days)

realAnomaly = real[anomaly$id, ]

points(x = realAnomaly$id, y =realAnomaly$value, col="#e15f3f")




이동 평균 분해  (양호한 결과)


이 두 번째 방법에서는 분해를 수행하지만 이번에는 이동 평균 대신 이동 중앙값을 사용합니다. 이를 위해서 이동 중앙값이 비정상값의 영향을 덜 받는다는 점, 그리고, 시계열 분해가 작동하는 방식을 이해해야했습니다. 다시 데이터를 가져와 봅시다. 이동 중앙값 시계열은 적은 영향만 받으며 비정상치들을 제거합니다.


1 - 이동 중앙값


이동 중앙값 (아래 그림 참조)으로 분해 된 추세는 약간 "날것"에 가깝지만 추세가 더 잘 표현됩니다!



#install library

install.packages("forecast")

library(forecast)

library(stats)


trend = runmed(days, 7)

plot(as.ts(trend))




2 - 정규분포로 min & max를 찾기


분해 공식에서 알 수 있듯이, 원 시계열에서 추세와 계절성을 제거하면 랜덤 노이즈만 남습니다. 이 랜덤 노이즈는 정규적으로 분포할 것이므로, 정규 분포를 적용하여 이상을 탐지 할 수 있습니다. 이전에 보았듯이 표준 편차의 4배 밖의 값은 이상치로 간주 될 수 있습니다. 무작위 시계열에는 여전히 비정상치가 포함되어 있으므로 비정상치는 빼고 표준 편차를 추정해야합니다! 다시 한번 우리는 이동 중앙값을 사용하여 특이치를 제외 할 것입니다.



detrend = days / as.vector(trend)

m = t(matrix(data = detrend, nrow = 7))

seasonal = colMeans(m, na.rm = T)

random = days / (trend * seasonal)

rm_random = runmed(random[!is.na(random)], 3)


min = mean(rm_random, na.rm = T) - 4*sd(rm_random, na.rm = T)

max = mean(rm_random, na.rm = T) + 4*sd(rm_random, na.rm = T)

plot(as.ts(random))

abline(h=max, col="#e15f3f", lwd=2)

abline(h=min, col="#e15f3f", lwd=2)



3 - 비정상치(anomaly) 플롯팅하기


이전과 동일하게 표준 편차의 4배 밖의 값을 비정상으로 표시합니다. 거의 모든 비정상을 감지했기 때문에, 훨씬 잘 작동합니다! 다시 말하면, 이동 중앙값이 (이동 평균에 비해) 예외적인 값에 강하기 때문입니다.



#find anomaly

position = data.frame(id=seq(1, length(random)), value=random)

anomalyH = position[position$value > max, ]

anomalyH = anomalyH[!is.na(anomalyH$value), ]

anomalyL = position[position$value < min, ]

anomalyL = anomalyL[!is.na(anomalyL$value)]

anomaly = data.frame(id=c(anomalyH$id, anomalyL$id),

 value=c(anomalyH$value, anomalyL$value))

points(x = anomaly$id, y =anomaly$value, col="#e15f3f")


plot(as.ts(days))

real = data.frame(id=seq(1, length(days)), value=days)

realAnomaly = real[anomaly$id, ]

points(x = realAnomaly$id, y =realAnomaly$value, col="#e15f3f")



동일한 내용을 다르게 그려봅시다. 파란색 영역 바깥의 값은 비정상적 인 것으로 간주 될 수 있습니다.



high = (trend * seasonal) * max

low = (trend * seasonal) * min

plot(as.ts(days), ylim = c(0,200))

lines(as.ts(high))

lines(as.ts(low))


x = c(seq(1, length(high)), seq(length(high), 1))

y = c(high, rev(low))

length(x)

length(y)

polygon(x,y, density = 50, col='skyblue')



728x90