스코어에서 알림(Alert)으로: POT, SPOT, Oracle 임계값
요약
이상 감지 모델의 점수를 실제 알림(alert)으로 변환하려면 적절한 임계값 설정이 필수적입니다. 본 글은 연속적인 이상 점수(anomaly score)를 0/1 알림으로 바꾸는 세 가지 개념인 POT, SPOT, Oracle 임계값을 설명합니다. 특히 극치 이론(EVT) 기반의 POT와 스트리밍 버전인 SPOT을 통해 정상 데이터의 오른쪽 꼬리를 추정하는 방법을 다룹니다.
핵심 포인트
- 이상 점수를 알림으로 변환하려면 반드시 임계값 설정이 필요하다.
- POT (Peaks Over Threshold)는 극치 이론을 사용해 정상 데이터의 꼬리 부분만 모델링한다.
- SPOT은 POT의 스트리밍 버전으로, 데이터를 읽으면서 임계값을 실시간 업데이트할 수 있다.
- 임계값 설정 시 단순히 최대값이나 분위수를 쓰는 것보다 외삽(extrapolation)이 중요하다.
놓치기 쉬운 마지막 단계
이상 감지 모델이 알려주는 것은 '각 시점이 얼마나 이상한가'라는 점수일 뿐이며, '어디부터를 이상으로 볼 것인가'는 알려주지 않습니다. 연속적인(continuous) anomaly score를 0/1의 알림(alert)으로 변환하려면 임계값(threshold)이 필요합니다. 논문에서 보고되는 F1 값은 이 임계값에 크게 좌우됩니다.
본 글에서는 임계값과 관련된 세 가지 개념을 정리합니다.
- POT (Peaks Over Threshold): 극치 이론(Extreme Value Theory)을 사용하여 정상 점수(normal score)의 꼬리 부분만 보고 임계값을 자동으로 결정하는 방법 -
- SPOT: POT의 스트리밍 버전. 데이터를 읽으면서 임계값을 업데이트합니다. TranAD나 OmniAnomaly 등 많은 시계열 이상 감지 기법의 공식 코드에서 사용됩니다 -
- Oracle 임계값: 테스트 레이블을 보고 선택한 최적의 임계값입니다. 실제 운영에는 사용할 수 없지만, '문제가 모델에 있는지, 아니면 임계값에 있는지'를 구분하는 데 도움이 됩니다.
본문 예시는 제가 UCR 데이터로 TranAD를 재현했을 때의 실험 결과입니다.
임계값을 쉽게 정할 수 없는 이유
정상 데이터 자체도 가끔 큰 점수를 내기 때문입니다. 예를 들어, 정상적인 학습 데이터 1000개의 재구성 오차(reconstruction error)가 대부분 0.005–0.030에 머물러 있어도, 가끔 0.041, 0.043, 0.046, 0.050 같은 값이 나옵니다. 이들 역시 정상적인 점수입니다.
대부분의 정상점 소수의 정상점
██████████████████████▇▅▃▂
0.00 0.02 0.04 0.05 →
즉, 진짜 질문은 정상 데이터의 오른쪽 꼬리가 어디까지 늘어나는가입니다. 알림 라인은 그 꼬리 바깥쪽에 그어져야 합니다.
가장 단순한 방법은 학습 점수의 최대값이나 높은 분위수(quantile)를 사용하는 것입니다. 하지만 샘플 수에는 한계가 있습니다. 학습점이 1600개밖에 없다면, 10만 분의 1인 분위수는
필요한 것은 외삽(extrapolation), 즉 관측된 꼬리 모양으로부터 그 너머를 추정하는 것입니다. 이것이 극치 이론(EVT)과 POT의 역할입니다.
POT: 꼬리만 모델링하기
POT (Peaks Over Threshold)의 개념은 한마디로 말하면, 정상 데이터 내에서 이미 극단적인 점을 사용하여 '더욱 극단하고 알림으로 삼아야 할 라인'을 추정하는 것입니다. 과정은 5단계입니다.
1단계: 꼬리의 시작점 학습 점수의 높은 분위수를 취합니다. 예를 들어
2단계: peaks 계산.
u = 0.040
├─ 0.041 → 초과분 0.001
├─ 0.043 → 초과분 0.003
...
여기서의 peak은 '점수의 최고값'이 아니라, 초기 임계값을 초과한 부분의 길이를 의미합니다.
3단계: peaks에 GPD 적용. Pickands–Balkema–de Haan 정리에 따르면,
밀도 함수는 외울 필요가 없습니다. 매개변수는 단 2개입니다.
4단계: 위험(Risk)
5단계: 알림 라인 학습점 수를
이 됩니다.
여기서 특히 주의해야 할 점은, 입니다.
| 기호 | 역할 | 예시 값 |
|---|---|---|
| 어디부터를 꼬리로 다룰지 | 0.040 | |
| peaks | ||
| 0.001, 0.003, 0.006, 0.010 | ||
| 꼬리의 모양과 스케일 | 0, 0.005 | |
| 어디까지 희귀한 정상의 극단값을 허용할지 | ||
| 최종적인 이론상의 알림 라인 | 0.0745 |
비유하자면, 도쿄 여름의 극심한 고온을 조사하는 것과 같습니다. 35℃가
SPOT: 데이터를 읽으면서 업데이트하기
POT은 일회성 계산이며, 특정 데이터로
핵심은 중간의 분기점입니다. 포함되지 않습니다. 이상으로 인해 임계값이 높아지는 것을 방지하기 위함입니다.
GPD의
실례: TranAD의 0.0402는 어디서 오는가
UCR의 trace 136, seed 1에서는, TranAD 공식 코드가 내놓은 POT 임계값은 0.0402였습니다. 소스 코드를 추적해보면, 교과서적인 POT/SPOT과는 세 가지 점에서 다릅니다.
1. 꼬리의 시작점이 매우 높다. src/constants.py
에서 UCR에 설정된 매개변수는 다음과 같습니다.
TranAD는 level = 0.99935를 사용하고, 그 외의 모델은 0.993을 사용합니다. spot.py
여기서 시작점(starting point)은 단순한 경험 분위수점입니다.
S = np.sort(self.init_data)
self.init_threshold = S[int(level * n_init)]
136개의 학습 데이터 포인트가 1600개이므로, [중략] 그리고 꼬리 부분에는 피크가 하나밖에 없습니다. 단 하나의 지점으로 GPD를 추정해도 통계적인 의미는 거의 없습니다. 참고로,
pot_eval의 인자 level은 실제로는 사용되지 않으며, 작동하는 것은 lm[0]입니다. 추정에 실패할 경우, 이 값에 0.999를 곱하여 재시도합니다. 2. 진정한 의미의 스트리밍 업데이트는 수행하지 않는다. pot_eval이 호출하는 것은 s.run(dynamic=False)이며, 이 분기 로직은 다음과 같습니다.
if not dynamic:
if self.data[i] > self.init_threshold and with_alarm:
self.extreme_quantile = self.init_threshold
...
테스트 스코어와 비교하는 것은 3. 최종적인 임계값은 평균을 사용한다.
pot_th = np.mean(ret['thresholds']) * lm[1]
thresholds에는 각 테스트 시점의 [중략]
세 점을 연결하면 다음과 같습니다.
학습 스코어 1600점 → 정렬 → 두 번째로 큰 값을 u로 설정
→ GPD를 사용하여 z_q 계산 (처음 몇 점에만 사용됨)
→ 테스트 스코어가 처음으로 u를 초과하면, 임계값은 u로 고정
...
즉, 이 구현에서는 POT 임계값이 실질적으로 '학습 스코어의 두 번째로 큰 값'에 가깝고, 극값 이론(Extreme Value Theory)을 통한 외삽(extrapolation)은 거의 작동하지 않습니다. 임계값이 seed에 민감한 것도 이 때문입니다. 초기화가 바뀌면 학습 스코어의 마지막 1, 2개의 값도 바뀝니다. 확인하고 싶다면, 실험에서 저장된 학습 스코어를 정렬하여 두 번째로 큰 값이 0.0402 근처인지 보면 충분합니다.
또 강조하고 싶은 것은, 이 과정에서 사용되는 것은 학습 스코어만이며, 테스트 레이블은 전혀 보지 않았다는 것입니다. 0.0402는 F1을 보면서 조정된 값이 아닙니다.
Oracle 임계값: 정답을 본 후의 상한선 (Upper Bound)
Oracle은 알고리즘이 아니라 진단을 위한 실험입니다. 특정 스코어를 고정한 다음, 가능한 모든 임계값을 시도하여 테스트 레이블로 F1을 계산하고, 그 중 가장 큰 값을 취합니다.
후보(candidates)는 테스트 구간에 나타난 모든 스코어 값입니다. 제 실험에서는 시리즈별로 4499–6300개의 후보가 있었습니다. 실제 운영 환경에서 얻을 수 없는 정보를 사용하기 때문에 oracle(신탁)이라고 불립니다.
비유하자면, POT은 시험 전에 합격선을 정하는 것과 같습니다. Oracle은 시험 후에 누가 합격해야 하는지 알고 난 뒤, 합격선을 0점에서 100점까지 모두 시도해보고, 두 그룹을 가장 잘 나눌 수 있는 선을 찾는 것과 같습니다. 그래도 나눌 수 없다면, 문제는 합격선이 아니라 시험 점수 자체에 있습니다.
왜 상한선이 되는가
최종 예측 규칙이 '스칼라 임계값 1개 + 동일한 point-adjust'인 한, POT은 매개변수를 어떻게 조정하든 결국 임계값을 하나만 선택하게 됩니다. Oracle은 모든 임계값을 시도하기 때문에, POT이 이를 능가할 수는 없습니다.
이 실험에서는 두 가지 세부 사항을 확인했습니다.
point-adjust가 공식과 일치하는 것. 공식의 임계값으로 예측을 재계산하고, 공식 코드가 저장한 결과와 비교했을 때 240/240 그룹 모두 일치했습니다. -
'더 크다(greater than)'와 '이상(greater than or equal to)'의 차이는 상한선에 영향을 미치지 않는 것. 공식 코드는 score > T를 사용하지만, '어떤 값보다 큰'은 '다음으로 큰 스코어 값 이상'과 같습니다.
240 그룹 모두에서 Oracle F1 ≥ POT F1이 성립하며, 이는 구현의 타당성 검증에도 도움이 됩니다.
실패 원인 분리: 임계값 때문인가, 점수(Score) 때문인가
POT와 Oracle을 비교하면 두 가지 종류의 실패를 구별할 수 있습니다.
- POT가 낮고 Oracle이 높은 경우 → 점수의 순위는 문제가 없으며, 임계값을 선택하는 방식이 잘못됨 -
POT도 Oracle도 낮은 경우 → 점수 자체가 이상과 정상을 구분하지 못함. 어떤 임계값 기법을 사용해도 해결할 수 없음
TranAD 공식 코드의 seed 1에 대한 UCR 135–138 결과입니다.
| 시리즈 | 이상점 개수 | POT 임계값 | POT F1 (TP / FP) | Oracle 임계값 | Oracle F1 (TP / FP) |
|---|---|---|---|---|---|
| 135 | 12 | 0.1065 | 0 (0 / 94) | 0.0150 | 0.0064 (12 / 3753) |
| ... | |||||
| 136과 137은 전자의 유형입니다. 점수의 순위는 거의 완벽하며, POT가 라인을 약간 낮게 그었을 뿐입니다. |
정상점 13개
↓↓↓
정상 정상 정상 |xxxxxx| 이상 이상 이상
...
135는 후자의 유형입니다. 정답을 보고 난 후에도, 12개의 이상점을 포착하려면 3753개의 정상점을 동시에 오탐지하게 됩니다. 60회 실행에서, 135의 테스트 구간에서 이상 구간 최고 점수를 넘은 정상점은 중앙값으로 2303.5개였습니다. 이는 임계값으로 해결할 수 있는 문제가 아닙니다.
138도 후자에 가깝지만, 135만큼 깔끔한 예는 아닙니다. 실제로는 19점이 변형되었는데 공식 레이블은 10점만 붙어 있어, 레이블 자체가 평가에 영향을 미칩니다.
주의해야 할 점은, Oracle이 보여주는 것은 그 점수에 대한 상한선일 뿐이며, 모델의 이론적 상한선이 아니라는 것입니다. seed나 학습 환경이 바뀌면 점수와 상한선 모두 바뀝니다. 논문에 쓸 때는
논의

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn ML의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기