개념
이상치의 유형
모든 이상치가 같은 것은 아닙니다.
- 점 이상치(Point anomaly): 맥락(context)과 무관하게 단일 데이터 포인트(data point)가 이상합니다. 500도 온도, 평소 50달러를 쓰는 계정의 50,000달러 거래가 예입니다.
- 맥락 이상치(Contextual anomaly): 맥락이 주어졌을 때 이상합니다. 90F는 여름에는 정상이지만 겨울에는 이상치일 수 있습니다.
- 집단 이상치(Collective anomaly): 개별 포인트는 정상일 수 있지만 시퀀스(sequence) 전체가 이상합니다. 로그인 실패 5번은 정상일 수 있지만 50번 연속 실패는 무차별 대입 공격(brute-force attack)일 수 있습니다.
대부분의 방법은 점 이상치를 찾습니다. 맥락 이상치에는 시간이나 위치 같은 맥락 특성(time/location feature)이 필요하고, 집단 이상치에는 시퀀스 인식 방법(sequence-aware method)이 필요합니다.
flowchart TD
A[이상치 유형] --> B[점 이상치(Point Anomaly)]
A --> C[맥락 이상치(Contextual Anomaly)]
A --> D[집단 이상치(Collective Anomaly)]
B --> B1["단일 이상값<br/>온도: 500F"]
C --> C1["맥락 안에서 이상함<br/>1월의 90F"]
D --> D1["이상한 시퀀스<br/>로그인 실패 50회"]
style B fill:#fdd,stroke:#333
style C fill:#ffd,stroke:#333
style D fill:#fdf,stroke:#333
비지도 구조화(Unsupervised Framing)
일반 분류에는 양쪽 클래스 레이블(class label)이 있습니다. 이상치 탐지에는 보통 세 상황 중 하나만 있습니다.
- 완전 비지도(Fully unsupervised): 레이블이 전혀 없습니다. 모든 데이터에 탐지기(detector)를 맞추고, 이상치가 충분히 드물어 "정상" 모델을 망치지 않기를 기대합니다.
- 반지도(Semi-supervised): 깨끗한 정상 데이터만 있습니다. 이 깨끗한 집합에 맞춘 뒤 나머지를 점수화(scoring)합니다. 가능하다면 가장 강력한 설정입니다.
- 약지도(Weakly supervised): 레이블이 붙은 이상치가 조금 있습니다. 학습이 아니라 평가에 사용합니다. 비지도로 학습한 뒤 레이블이 붙은 부분집합(labeled subset)에서 정밀도와 재현율(precision/recall)을 측정합니다.
핵심은 이상치 탐지가 분류와 근본적으로 다르다는 점입니다. 두 클래스 사이의 결정 경계(decision boundary)가 아니라 정상 데이터의 분포(distribution)를 모델링합니다.
지도와 비지도 절충(Supervised vs Unsupervised Tradeoff)
레이블이 붙은 이상치가 있다면 학습에 쓸까요, 평가에만 쓸까요?
지도 방식(Supervised, 분류처럼 다루기):
- 이미 본 이상치 유형을 잘 잡습니다.
- 알려진 이상치 유형에서는 정밀도가 높습니다.
- 새로운 이상치 유형(novel anomaly type)을 완전히 놓칠 수 있습니다.
- 새로운 이상치 유형이 생기면 재학습이 필요합니다.
- 이상치 예시가 충분해야 하지만 보통 부족합니다.
비지도 방식(Unsupervised, 정상을 모델링하고 벗어남을 표시):
- 정상에서 벗어나는 모든 것을 잡으므로 새로운 유형도 잡을 수 있습니다.
- 레이블이 붙은 이상치가 필요 없습니다.
- 거짓 양성률(false positive rate)이 더 높습니다. 특이하다고 모두 나쁜 것은 아닙니다.
- 분포 이동(distribution shift)에 더 강건(robust)합니다.
실무에서는 둘을 결합합니다. 넓은 포괄 범위를 확보하려면 비지도 탐지를 쓰고, 알려진 고우선순위 이상치 유형에는 지도 모델을 쓰며, 애매한 사례는 사람 검토(human review)로 보냅니다.
Z-점수(Z-score) 방법
가장 단순한 접근입니다. 각 특성(feature)의 평균(mean)과 표준편차(standard deviation)를 계산하고, 평균에서 k 표준편차 이상 떨어진 포인트(point)를 표시(flag)합니다.
z_score = (x - mean) / std
anomaly if |z_score| > threshold
기본 임계값(threshold)은 3.0입니다. 가우시안 분포(Gaussian distribution)에서는 정상 데이터의 99.7%가 3 표준편차 안에 들어옵니다.
장점: 단순하고 빠르며 해석 가능합니다. "이 값은 정상보다 4.5 표준편차 멀다"라고 설명할 수 있습니다.
약점: 데이터가 정규분포(normal distribution)라고 가정합니다. 학습 데이터(training data) 안의 이상값(outlier)에 민감합니다. 이상값이 평균을 움직이고 표준편차를 부풀려 자기 자신을 더 찾기 어렵게 만듭니다. 다봉 분포(multimodal distribution)에도 약합니다.
잘 작동하는 경우: 데이터가 대략 종 모양(bell-shaped)인 단일 특성 모니터링(single-feature monitoring)에 좋습니다. 서버 응답 시간(server response time), 제조 허용오차(manufacturing tolerance), 안정적인 기준선(stable baseline)을 가진 센서 판독값(sensor reading)이 예입니다.
실패하는 경우: 기준 온도가 다른 두 사무실처럼 여러 클러스터가 있는 데이터(multi-cluster data), 거래 금액처럼 기울어진 데이터(skewed data), 학습 집합(training set)에 이미 이상값이 섞여 있는 데이터에서는 잘 실패합니다.
사분위 범위(IQR) 방법
Z-점수보다 강건(robust)합니다. 평균과 표준편차 대신 사분위 범위(interquartile range)를 사용합니다.
Q1 = 25th percentile
Q3 = 75th percentile
IQR = Q3 - Q1
lower_bound = Q1 - factor * IQR
upper_bound = Q3 + factor * IQR
anomaly if x < lower_bound or x > upper_bound
기본 계수(factor)는 1.5입니다.
장점: 백분위수(percentile)는 극단값(extreme value)에 덜 영향받으므로 이상값에 강건합니다. 기울어진 분포(skewed distribution)에서도 작동하며 정규성 가정(normality assumption)이 없습니다.
약점: 기본적으로 특성별 단변량 방법(univariate method)입니다. 각 특성만 보면 정상이지만 결합 공간(joint space)에서 이상한 포인트를 잡기 어렵습니다.
실무 메모: 사분위 범위의 1.5 계수는 상자그림(box plot)의 수염(whisker)에 해당합니다. 수염 바깥의 포인트는 잠재적 이상값입니다. 1.5 대신 3.0을 쓰면 더 보수적인 탐지기가 되어 표시되는 포인트가 줄고 거짓 양성(false positive)도 줄어듭니다. 적절한 계수는 거짓 경보(false alarm)를 얼마나 허용할지에 따라 달라집니다.
격리 숲(Isolation Forest)
핵심 직관은 이상치는 적고 다르다는 것입니다. 데이터를 무작위로 분할(random partitioning)하면 이상치는 더 쉽게 격리(isolate)됩니다. 즉 나머지와 분리하는 데 필요한 무작위 분할(random split) 수가 적습니다.
flowchart TD
A[모든 데이터 포인트] --> B{무작위 특성 + 무작위 분할}
B --> C[왼쪽 파티션]
B --> D[오른쪽 파티션]
C --> E{무작위 특성 + 무작위 분할}
E --> F[정상 포인트 - 트리 깊은 곳]
E --> G[더 많은 분할 필요]
D --> H["이상치 - 빠르게 격리됨"]
style H fill:#fdd,stroke:#333
style F fill:#dfd,stroke:#333
동작 방식:
- 많은 무작위 트리(random tree), 즉 격리 숲을 만듭니다.
- 각 노드(node)에서 무작위 특성을 하나 고르고, 그 특성의 최솟값과 최댓값 사이에서 무작위 분할값(random split value)을 고릅니다.
- 모든 포인트가 자기 리프(leaf)에 격리될 때까지 분할합니다.
- 모든 트리에서 평균 경로 길이(average path length)가 짧은 포인트일수록 이상치입니다.
정상 포인트(normal point)는 조밀한 영역(dense region)에 있어 이웃(neighbor)과 분리하려면 많은 분할이 필요합니다. 이상치는 희소한 영역(sparse region)에 있어 한두 번의 분할로도 격리됩니다.
score(x) = 2^(-average_path_length(x) / c(n))
여기서 c(n)은 n개 샘플(sample)에 대한 무작위 이진 탐색 트리(random binary search tree)의 기대 경로 길이(expected path length)입니다. 점수가 1에 가까우면 이상치이고, 0.5 근처면 정상, 0에 가까우면 조밀한 클러스터(dense cluster) 안의 매우 정상적인 포인트입니다.
장점: 분포 가정(distribution assumption)이 없습니다. 고차원 데이터에서도 작동하고, 부분 표본 추출(subsampling) 덕분에 확장성이 좋습니다. 혼합된 특성 유형도 다룰 수 있습니다.
약점: 조밀한 영역 안의 이상치에는 약할 수 있습니다. 이를 마스킹 효과(masking effect)라고도 볼 수 있습니다. 관련 없는 특성(irrelevant feature)이 많으면 무작위 분할 효과가 줄어듭니다.
핵심 하이퍼파라미터(hyperparameter):
n_estimators: 트리의 수입니다. 보통 100이면 충분합니다. 트리가 많을수록 점수는 안정적이지만 계산은 느려집니다.
max_samples: 트리마다 사용할 샘플 수입니다. 원 논문의 기본값은 256입니다. 작은 값은 개별 트리의 정확도를 낮추지만 다양성(diversity)을 높입니다. 격리 숲이 빠른 이유는 각 트리가 데이터의 작은 일부만 보기 때문입니다.
contamination: 예상 이상치 비율(expected fraction of anomalies)입니다. 임계값을 정하는 데만 쓰이며 점수 자체에는 영향을 주지 않습니다.
국소 이상치 계수(Local Outlier Factor; LOF)
국소 이상치 계수는 어떤 포인트 주변의 국소 밀도(local density)를 이웃(neighbor)의 밀도와 비교합니다. 조밀한 영역에 둘러싸인 희소 포인트는 이상치입니다.
동작 방식:
- 각 포인트의 k-최근접 이웃(k-nearest neighbors)을 찾습니다.
- 국소 도달 가능 밀도(local reachability density)를 계산합니다.
- 포인트의 밀도를 이웃의 밀도와 비교합니다.
- 이웃보다 밀도가 훨씬 낮으면 이상값입니다.
국소 이상치 계수 점수가 1에 가까우면 이웃과 비슷한 밀도, 1보다 크면 더 희소한 포인트, 2 이상이면 강한 이상치 후보(likely anomaly)입니다.
"국소(local)"가 중요합니다. 데이터셋에 두 클러스터가 있다고 생각해봅니다. 하나는 1000개 포인트가 있는 조밀한 클러스터이고, 다른 하나는 50개 포인트가 있는 희소한 클러스터입니다. 희소한 클러스터의 가장자리 포인트는 전역적으로는 이상하지 않습니다. 이웃이 50개나 있기 때문입니다. 하지만 바로 주변 이웃보다 훨씬 덜 조밀하다면 국소적으로는 이상합니다. 국소 이상치 계수는 전역 방법(global method)이 놓치는 이 뉘앙스를 포착합니다.
장점: 국소 이상치(local anomaly)를 탐지합니다. 전역적으로는 이상하지 않아도 자기 주변에서는 이상한 포인트를 잡을 수 있습니다. 밀도가 다른 클러스터에도 작동합니다.
약점: 큰 데이터셋에서는 느립니다. 단순 구현은 O(n^2)입니다. k 선택에 민감합니다. 매우 고차원에서는 거리 계산(distance calculation)이 차원의 저주(curse of dimensionality)에 영향을 받아 잘 작동하지 않습니다.
방법 비교
| 방법 | 가정 | 속도 | 고차원 처리 | 국소 이상치 탐지 |
|---|
| Z-점수(Z-score) | 정규분포(normal distribution) | 매우 빠름 | 특성별 가능 | 아니오 |
| 사분위 범위(IQR) | 특성별, 분포 가정 없음 | 매우 빠름 | 특성별 가능 | 아니오 |
| 격리 숲(Isolation Forest) | 없음 | 빠름 | 가능 | 일부 |
| 국소 이상치 계수(LOF) | 거리(distance)가 의미 있어야 함 | 느림 | 약함 | 예 |
평가의 어려움
이상치 탐지기(anomaly detector) 평가는 분류기(classifier) 평가보다 어렵습니다.
- 극단적 클래스 불균형(extreme class imbalance): 이상치가 0.1%면 모든 것을 정상으로 예측해도 정확도(accuracy)가 99.9%입니다. 정확도는 쓸모가 없습니다.
- AUROC가 오해를 줄 수 있음: 불균형(imbalance)이 심하면 실무에서 사용하는 임계값에서 이상치를 거의 못 잡아도 AUROC는 좋아 보일 수 있습니다.
- 더 나은 지표(metric): 상위 k개에서의 정밀도(Precision@k), 정밀도-재현율 곡선 아래 면적(AUPRC), 고정 거짓 양성률(fixed false positive rate)에서의 재현율(recall)을 사용합니다.
flowchart LR
A[원본 데이터] --> B[정상 데이터만으로 학습]
B --> C[테스트 데이터 전체 점수화]
C --> D[이상치 점수로 순위화]
D --> E[Top-K 표시 항목 평가]
E --> F[Precision@K / AUPRC]
style A fill:#f9f,stroke:#333
style F fill:#9f9,stroke:#333
이상치 탐지 파이프라인(Anomaly Detection Pipeline)
실무 작업 흐름은 다음과 같습니다.
- 기준선 데이터(baseline data) 수집: 이상치가 없거나 매우 적다고 아는 기간의 데이터가 이상적입니다.
- 특성 공학(feature engineering): 원시 특성(raw feature)과 함께 롤링 통계(rolling statistic), 시간 특성(time feature), 비율(ratio) 같은 파생 특성을 만듭니다.
- 탐지기 학습: 기준선 데이터에 맞춰 정상을 학습합니다.
- 새 데이터 점수화: 새 관측값(observation)마다 이상치 점수(anomaly score)를 계산합니다.
- 임계값 선택: 점수 컷오프(score cutoff)를 정합니다. 이는 사업적 의사결정(business decision)입니다. 임계값이 높으면 거짓 경보(false alarm)는 줄지만 놓치는 이상치는 늘어납니다.
- 알림과 조사: 표시된 포인트는 사람 검토(human review)나 자동 대응(automated response)으로 보냅니다.
- 피드백 수집: 진짜 이상치인지 거짓 경보인지 기록하고, 그 데이터로 임계값과 탐지기를 시간이 지나며 개선합니다.
이 파이프라인은 끝나지 않습니다. 데이터 분포(data distribution)가 바뀌고, 새로운 이상치 유형이 생기며, 임계값도 조정되어야 합니다. 이상치 탐지는 한 번 만들고 끝나는 모델이 아니라 살아 있는 시스템(living system)으로 다뤄야 합니다.
만들어보기
code/anomaly_detection.py는 Z-점수, 사분위 범위, 격리 숲을 처음부터 구현합니다.
Z-점수 탐지기
def zscore_detect(X, threshold=3.0):
mean = X.mean(axis=0)
std = X.std(axis=0)
std[std == 0] = 1.0
z = np.abs((X - mean) / std)
return z.max(axis=1) > threshold
단순하고 벡터화(vectorized)되어 있습니다. 어떤 특성이든 임계값을 넘으면 그 포인트를 표시합니다.
사분위 범위 탐지기
def iqr_detect(X, factor=1.5):
q1 = np.percentile(X, 25, axis=0)
q3 = np.percentile(X, 75, axis=0)
iqr = q3 - q1
iqr[iqr == 0] = 1.0
lower = q1 - factor * iqr
upper = q3 + factor * iqr
outside = (X < lower) | (X > upper)
return outside.any(axis=1)
격리 숲을 처음부터 구현하기
처음부터 구현한 버전은 특성 공간(feature space)을 무작위로 분할하는 격리 트리(isolation tree)를 만듭니다.
class IsolationTree:
def __init__(self, max_depth):
self.max_depth = max_depth
def fit(self, X, depth=0):
n, p = X.shape
if depth >= self.max_depth or n <= 1:
self.is_leaf = True
self.size = n
return self
self.is_leaf = False
self.feature = np.random.randint(p)
x_min = X[:, self.feature].min()
x_max = X[:, self.feature].max()
if x_min == x_max:
self.is_leaf = True
self.size = n
return self
self.threshold = np.random.uniform(x_min, x_max)
left_mask = X[:, self.feature] < self.threshold
self.left = IsolationTree(self.max_depth).fit(X[left_mask], depth + 1)
self.right = IsolationTree(self.max_depth).fit(X[~left_mask], depth + 1)
return self
포인트를 격리하는 경로 길이가 이상치 점수를 결정합니다. 경로가 짧을수록 더 이상치에 가깝습니다.
IsolationForest 클래스는 여러 트리를 감쌉니다.
class IsolationForest:
def __init__(self, n_estimators=100, max_samples=256, seed=42):
self.n_estimators = n_estimators
self.max_samples = max_samples
def fit(self, X):
sample_size = min(self.max_samples, X.shape[0])
max_depth = int(np.ceil(np.log2(sample_size)))
for _ in range(self.n_estimators):
idx = rng.choice(X.shape[0], size=sample_size, replace=False)
tree = IsolationTree(max_depth=max_depth)
tree.fit(X[idx])
self.trees.append(tree)
def anomaly_score(self, X):
avg_path = average path length across all trees
scores = 2.0 ** (-avg_path / c(max_samples))
return scores
정규화 계수(normalization factor) c(n)은 n개 원소(element)가 있는 이진 탐색 트리에서 실패한 탐색(unsuccessful search)의 기대 경로 길이입니다. 2 * H(n-1) - 2*(n-1)/n과 같고, 여기서 H는 조화수(harmonic number)입니다. 이 정규화 덕분에 서로 다른 데이터셋 크기에서도 점수를 비교할 수 있습니다.
데모 시나리오(Demo Scenarios)
코드는 여러 시나리오를 만듭니다.
- 이상값이 있는 단일 클러스터: 중심(center)에서 멀리 주입된 이상치를 가진 2차원 가우시안 클러스터입니다. 모든 방법이 잘 작동해야 합니다.
- 다봉 데이터(multimodal data): 크기와 밀도(density)가 다른 세 클러스터입니다. 클러스터 사이의 포인트가 이상치입니다. 특성별 범위(per-feature range)가 넓기 때문에 Z-점수가 어려워합니다.
- 고차원 데이터(high-dimensional data): 특성 50개 중 5개에서만 이상치가 다릅니다. 각 방법이 특성 일부에서만 나타나는 이상치를 찾을 수 있는지 확인합니다.
각 데모는 정밀도(precision), 재현율(recall), F1, 상위 k개에서의 정밀도(Precision@k)로 방법들을 비교합니다.
사용해보기
sklearn의 라이브러리 구현을 사용할 때는 다음과 같습니다. 여기서는 처음부터 구현한 코드가 아니라 검증된 구현체를 사용합니다.
from sklearn.ensemble import IsolationForest
from sklearn.neighbors import LocalOutlierFactor
iso = IsolationForest(n_estimators=100, contamination=0.05, random_state=42)
iso.fit(X_train)
predictions = iso.predict(X_test)
lof = LocalOutlierFactor(n_neighbors=20, contamination=0.05, novelty=True)
lof.fit(X_train)
predictions = lof.predict(X_test)
contamination은 이상치로 예상하는 비율을 의미합니다. 올바르게 설정하는 것이 중요합니다. 너무 낮게 잡으면 이상치를 놓치고, 너무 높게 잡으면 거짓 경보가 늘어납니다.
sklearn contamination 파라미터
sklearn의 contamination은 연속형 이상치 점수를 이진 예측(binary prediction)으로 바꾸는 임계값을 정합니다. 내부 점수 자체를 바꾸지는 않습니다.
iso_5 = IsolationForest(contamination=0.05)
iso_10 = IsolationForest(contamination=0.10)
둘의 이상치 점수는 같습니다. 하지만 iso_5는 상위 5%, iso_10은 상위 10%를 표시합니다. 진짜 이상치 비율(true anomaly rate)을 보통 모르기 때문에 contamination을 "auto"로 두고 원시 점수(raw score)를 직접 다룹니다. 거짓 양성(false positive)과 거짓 음성(false negative)의 비용 절충에 따라 직접 임계값을 정합니다.
일종 SVM(One-Class SVM)
일종 SVM(One-Class SVM)은 알아둘 가치가 있는 또 다른 비지도 이상치 탐지기입니다. 커널 트릭(kernel trick)을 사용해 고차원 특성 공간(high-dimensional feature space)에서 정상 데이터 주변의 경계(boundary)를 맞춥니다.
from sklearn.svm import OneClassSVM
oc_svm = OneClassSVM(kernel="rbf", gamma="auto", nu=0.05)
oc_svm.fit(X_train)
predictions = oc_svm.predict(X_test)
nu 파라미터는 이상치 비율(fraction of anomalies)을 근사합니다. 일종 SVM은 작거나 중간 크기의 데이터셋에서는 잘 작동하지만, 커널 행렬(kernel matrix)이 이차적으로(quadratically) 커지기 때문에 매우 큰 데이터에는 확장하기 어렵습니다.
오토인코더 접근(Autoencoder Approach; 미리보기)
오토인코더(Autoencoder)는 데이터를 압축하고 복원하는 신경망(neural network)입니다. 정상 데이터로 학습하면, 테스트 시점(test time)에서 이상치는 정상 패턴(normal pattern)으로 복원하기 어려워 재구성 오차(reconstruction error)가 커집니다. Phase 3의 딥러닝(Deep Learning)에서 더 다루지만 원리는 같습니다. 정상을 모델링하고 거기에서 벗어나는 것을 표시합니다.
앙상블 이상치 탐지(Ensemble Anomaly Detection)
Lesson 11에서 앙상블(ensemble)이 분류 성능을 개선했듯, 여러 이상치 탐지기를 결합하면 탐지가 좋아질 수 있습니다. 가장 단순한 접근은 다음과 같습니다.
- Z-점수, 사분위 범위, 격리 숲, 국소 이상치 계수 같은 여러 탐지기를 실행합니다.
- 각 탐지기의 점수를 [0, 1]로 정규화합니다.
- 정규화된 점수를 평균냅니다.
- 평균 점수가 임계값보다 큰 포인트를 표시합니다.
이 방식은 거짓 양성을 줄입니다. 방법마다 실패 모드(failure mode)가 다르기 때문입니다. 네 방법이 모두 표시한 포인트는 거의 확실히 이상치입니다. 하나만 표시한 포인트는 그 방법 특유의 기묘한 동작(quirk)일 수 있습니다.
더 정교한 앙상블은 각 탐지기의 신뢰도(reliability)에 따라 가중치를 둡니다. 가능하다면 알려진 이상치가 있는 검증 집합(validation set)에서 신뢰도를 측정합니다.
운영 환경 고려사항
- 임계값 표류(threshold drift): 데이터 분포가 바뀌면 고정 임계값이 낡습니다. 이상치 점수 분포를 모니터링하고 주기적으로 조정합니다.
- 알림 피로(alert fatigue): 거짓 경보가 너무 많으면 운영자(operator)가 주의를 기울이지 않습니다. 처음에는 높은 임계값으로 더 적고 더 신뢰할 수 있는 알림부터 시작하고, 신뢰가 쌓이면 낮춥니다.
- 앙상블 접근: 운영 환경(production)에서는 여러 탐지기를 결합합니다. 여러 방법이 동의할 때만 이상치로 표시하면 거짓 양성을 크게 줄일 수 있습니다.
- 특성 공학(feature engineering): 원시 특성만으로는 거의 충분하지 않습니다. 롤링 통계, 비율, 마지막 사건 이후 시간(time-since-last-event), 도메인 특화 특성(domain-specific feature)을 추가합니다. 좋은 특성 집합이 탐지기 선택보다 더 중요할 때가 많습니다.
- 피드백 루프(feedback loop): 운영자가 표시된 항목을 조사하고 확인하거나 기각하면, 그 결과를 시스템에 되돌립니다. 시간이 지나며 레이블 데이터(labeled data)를 축적해 탐지기를 평가하고 개선합니다.
산출물 만들기
이 레슨의 최종 산출물은 다음입니다.
outputs/skill-anomaly-detector.md: 적절한 탐지기를 고르기 위한 의사결정 스킬(decision skill)
code/anomaly_detection.py: Z-점수, 사분위 범위, 격리 숲을 처음부터 구현하고 sklearn과 비교하는 예제
임계값 선택하기
이상치 점수는 연속형(continuous) 값입니다. 이진 결정(binary decision)을 하려면 임계값이 필요합니다. 이는 기술 문제가 아니라 사업적 의사결정(business decision)입니다.
- 사기 탐지(fraud detection): 사기를 놓치는 비용이 큽니다. 지불 거절(chargeback)과 고객 신뢰(customer trust) 문제가 생깁니다. 거짓 경보는 분석가(analyst) 5분의 조사 비용입니다. 임계값을 낮춰 더 많은 사기를 잡고 거짓 경보를 감수합니다.
- 설비 유지보수(equipment maintenance): 거짓 경보는 50,000달러짜리 불필요한 정지(shutdown)일 수 있습니다. 놓친 고장(missed failure)은 500,000달러 수리(repair)일 수 있습니다. 비용 균형에 맞춰 임계값을 정합니다.
두 경우 모두 최적 임계값은 거짓 양성과 거짓 음성의 비용 비율(cost ratio)에 따라 달라집니다. 임계값별 정밀도와 재현율을 그린 뒤 비용 함수(cost function)를 겹쳐서 최소 비용 지점을 고릅니다.
운영 환경(Production)으로 확장하기
실시간 이상치 탐지(real-time anomaly detection)를 운영 환경에 올릴 때는 다음을 고려합니다.
- 배치 학습, 온라인 점수화(batch training, online scoring): 최근 정상 데이터로 모델을 주기적으로 학습합니다. 매일 또는 매주 학습하고, 새 관측값이 들어오면 즉시 점수화합니다.
- 특성 계산 일관성: 학습 때 30일 롤링 통계(rolling statistics)를 썼다면 새 관측값의 특성을 계산할 때도 30일 이력(history)이 필요합니다. 필요한 이력을 버퍼(buffer)로 유지합니다.
- 점수 분포 모니터링: 이상치 점수의 분포를 시간에 따라 추적합니다. 중앙값(median score)이 위로 표류(drift)하면 데이터가 바뀌고 있거나 모델이 낡았을 수 있습니다.
- 설명 가능성(explainability): 이상치로 표시할 때 이유를 말합니다. Z-점수는 "특성 X가 정상보다 4.2 표준편차 높습니다"라고 설명할 수 있습니다. 격리 숲은 "이 포인트는 평균 3.1번의 분할로 격리됐습니다. 정상 포인트는 8.5번이 걸립니다"처럼 설명할 수 있습니다.
연습문제
-
임계값 튜닝(threshold tuning) — 쉬움. Z-점수 탐지기를 임계값 1.0부터 5.0까지 0.5 간격으로 실행합니다. 임계값별 정밀도와 재현율을 그립니다(plot). 데이터에서 최적 지점(sweet spot)은 어디인가요?
-
다변량 이상치(multivariate anomalies) — 중간. 각 특성만 보면 정상이지만 조합으로 보면 이상치인 2차원 데이터를 만듭니다. 예를 들어 주 클러스터(main cluster) 대각선에서 멀리 떨어진 포인트를 만들 수 있습니다. 특성별 Z-점수는 놓치고 격리 숲은 잡는지 확인합니다.
-
국소 이상치 계수를 처음부터 구현하기 — 중간. k-최근접 이웃(k-nearest neighbors)으로 국소 이상치 계수를 구현합니다. sklearn의 LocalOutlierFactor와 비교합니다. k=10과 k=50에서 결과가 어떻게 달라지나요?
-
스트리밍 이상치 탐지(streaming anomaly detection) — 어려움. Z-점수 탐지기를 스트리밍 설정(streaming setting)으로 수정합니다. Welford의 온라인 알고리즘(Welford's online algorithm)으로 진행 중인 평균(running mean)과 분산(variance)을 갱신합니다. 배치 방식의 Z-점수와 비교합니다.
-
현실 데이터 평가(real-world evaluation) — 어려움. 알려진 이상치(known anomaly)가 있는 데이터셋, 예를 들어 Kaggle의 신용카드 사기 데이터(credit card fraud data)에서 네 가지 방법을 Precision@100, Precision@500, AUPRC로 평가합니다. 어떤 방법이 가장 잘 작동하나요? 왜 그런가요?
핵심 용어
| 용어 | 흔한 설명 | 실제 의미 |
|---|
| 이상치(Anomaly) | "이상값, 특이 포인트" | 정상 데이터의 기대 패턴(expected pattern)에서 크게 벗어난 데이터 포인트(data point) |
| 점 이상치(Point anomaly) | "단일 이상값" | 맥락(context)과 무관하게 하나의 관측값(observation)이 이상한 경우 |
| 맥락 이상치(Contextual anomaly) | "정상 값이지만 맥락이 잘못된 경우" | 시간이나 위치 같은 맥락 안에서는 이상하지만 다른 맥락에서는 정상일 수 있는 관측값 |
| 격리 숲(Isolation Forest) | "무작위 분할로 이상값 찾기" | 무작위 트리 앙상블(random tree ensemble)로 정상 포인트보다 적은 분할로 이상치를 격리하는 방법 |
| 국소 이상치 계수(Local Outlier Factor) | "이웃과 밀도 비교" | 이웃보다 국소 밀도가 훨씬 낮은 포인트를 표시하는 방법 |
| Z-점수(Z-score) | "평균에서 몇 표준편차 떨어졌는가" | (x - mean) / std로 중심(center)에서 얼마나 먼지 측정 |
| 사분위 범위(IQR) | "사분위 범위" | Q3 - Q1. 가운데 50% 데이터의 퍼짐(spread)으로 강건한 이상값 탐지(robust outlier detection)에 사용 |
| 오염도(Contamination) | "예상 이상치 비율" | 탐지기가 데이터 중 어느 비율을 이상치로 표시해야 하는지 알려주는 하이퍼파라미터(hyperparameter) |
| 상위 k개 정밀도(Precision@k) | "상위 k개 표시 중 진짜는 몇 개인가" | 가장 의심스러운 k개 포인트에 대해서만 계산한 정밀도 |
| AUPRC | "정밀도-재현율 곡선 아래 면적" | 모든 임계값에서 정밀도-재현율 성능을 요약하는 지표(metric). 불균형 데이터(imbalanced data)에서 AUROC보다 적합 |
더 읽을거리