Anomaly Detection (이상 탐지)¶
메타 정보¶
| 항목 | 내용 |
|---|---|
| 분류 | Unsupervised / Semi-Supervised / Data Quality |
| 핵심 논문 | "Isolation Forest" (ICDM 2008), "Deep One-Class Classification" (ICML 2018), "A Unifying Review of Deep and Shallow Anomaly Detection" (IEEE 2021) |
| 주요 저자 | Fei Tony Liu, Kai Ming Ting, Zhi-Hua Zhou (Isolation Forest); Lukas Ruff et al. (Deep SVDD); Varun Chandola et al. (Survey) |
| 핵심 개념 | 정상 데이터의 분포에서 벗어난 관측치(outlier, novelty)를 탐지 |
| 관련 분야 | Fraud Detection, Predictive Maintenance, Network Intrusion, Quality Control |
정의¶
Anomaly Detection은 데이터셋에서 대다수의 정상(normal) 패턴과 현저히 다른 관측치를 식별하는 작업이다. 이상치(anomaly)는 통계적 분포, 거리, 밀도, 또는 학습된 표현 공간에서 정상 데이터와 구별된다.
핵심 가정: 이상치는 (1) 전체 데이터의 극소수를 차지하고, (2) 정상 데이터와 특성이 상이하다.
이상 탐지의 분류 체계¶
이상의 유형¶
Point Anomaly (점 이상)
개별 데이터 포인트가 나머지와 현저히 다름
예: 신용카드 거래에서 갑작스러운 고액 결제
Contextual Anomaly (맥락 이상)
특정 맥락에서만 이상으로 판단됨
예: 여름에 난방비가 급증 (겨울이면 정상)
Collective Anomaly (집합 이상)
개별적으로는 정상이나 모이면 이상 패턴
예: 네트워크에서 특정 시간대에 반복되는 접속 패턴
학습 패러다임¶
감독 학습 (Supervised)
정상 + 이상 레이블 모두 존재
-> 분류 문제로 접근 (class imbalance 처리 필요)
제한: 이상 레이블 확보가 현실적으로 어려움
준지도 학습 (Semi-Supervised)
정상 데이터만으로 학습 (One-Class Learning)
-> 정상 분포를 모델링하고, 벗어나면 이상으로 판단
대표: One-Class SVM, Deep SVDD, Autoencoder
비지도 학습 (Unsupervised)
레이블 없이 데이터 구조만으로 탐지
-> 밀도, 거리, 고립성 등 데이터 고유 특성 활용
대표: Isolation Forest, LOF, DBSCAN
주요 알고리즘¶
1. 통계 기반 (Statistical)¶
데이터가 특정 분포를 따른다는 가정 하에 이상치를 탐지한다.
| 방법 | 원리 | 장점 | 한계 |
|---|---|---|---|
| Z-Score | 평균에서 표준편차 기준 거리 | 단순, 해석 용이 | 정규분포 가정 |
| IQR (Interquartile Range) | Q1 - 1.5IQR ~ Q3 + 1.5IQR 범위 밖 | 분포 가정 불필요 | 다변량 부적합 |
| Mahalanobis Distance | 공분산 고려한 거리 | 상관관계 반영 | 정규분포 가정, 고차원 불안정 |
| Grubbs' Test | 통계 검정 기반 단일 이상치 탐지 | 이론적 근거 명확 | 하나씩만 탐지 |
import numpy as np
from scipy import stats
# Z-Score 기반 이상 탐지
def detect_zscore(data, threshold=3.0):
z_scores = np.abs(stats.zscore(data))
return z_scores > threshold
# Mahalanobis Distance
def mahalanobis_anomaly(X, threshold_percentile=97.5):
mean = np.mean(X, axis=0)
cov = np.cov(X.T)
cov_inv = np.linalg.inv(cov)
distances = []
for x in X:
diff = x - mean
d = np.sqrt(diff @ cov_inv @ diff)
distances.append(d)
threshold = np.percentile(distances, threshold_percentile)
return np.array(distances) > threshold
2. 거리/밀도 기반 (Distance/Density)¶
데이터 포인트 간의 거리 또는 지역 밀도를 활용한다.
LOF (Local Outlier Factor)¶
Breunig et al. (2000)이 제안. 각 포인트의 지역 밀도를 이웃의 밀도와 비교한다.
LOF 계산 과정:
1. k-거리 (k-distance):
포인트 p에서 k번째 가까운 이웃까지의 거리
2. 도달 거리 (reachability distance):
reach-dist_k(p, o) = max(k-distance(o), d(p, o))
-> 너무 가까운 이웃의 영향을 평활화
3. 지역 도달 밀도 (local reachability density):
lrd_k(p) = 1 / (avg reach-dist of k-neighbors)
4. LOF 계산:
LOF_k(p) = avg(lrd_k(o) / lrd_k(p)) for o in N_k(p)
LOF ~ 1 : 이웃과 밀도가 유사 (정상)
LOF >> 1 : 이웃보다 밀도가 낮음 (이상)
LOF << 1 : 이웃보다 밀도가 높음 (정상)
kNN Distance¶
k번째 이웃까지의 거리 또는 k개 이웃의 평균 거리를 이상 점수로 사용한다. LOF보다 단순하지만 밀도 변화에 덜 적응적이다.
from sklearn.neighbors import LocalOutlierFactor
# LOF
lof = LocalOutlierFactor(n_neighbors=20, contamination=0.05)
labels = lof.fit_predict(X) # -1: anomaly, 1: normal
scores = -lof.negative_outlier_factor_ # 높을수록 이상
3. Isolation Forest¶
Liu, Ting, Zhou (ICDM 2008)이 제안한 알고리즘으로, 이상 탐지에 대한 근본적으로 다른 접근법이다. 정상 데이터를 프로파일링하는 대신, 이상치가 고립(isolate)하기 쉽다는 성질을 직접 활용한다.
핵심 직관:
이상치는 정상 데이터보다 "고립하기 쉽다"
-> 무작위 분할 시 적은 횟수만에 분리됨
정상 데이터: 이상 데이터:
+------------------+ +------------------+
| . . . . . . . | | . . . . . . . |
| . . . . . . . | | . . . . . . . |
| . . . . . . . | | . . . . . . . |
| . . . . . . . | | . . . x . . . | <- x를 분리하려면
| . . . . . . . | | . . . . . . . | 많은 분할 필요
+------------------+ +------------------+
+------------------+
| |
| * | <- *는 한두 번의
| . . . . . . . | 분할로 분리 가능
| . . . . . . . |
+------------------+
알고리즘¶
Isolation Forest 구축:
1. 서브샘플링: 전체 데이터에서 psi개 샘플 추출 (기본 256)
2. Isolation Tree 구축:
a. 무작위로 특성(feature) 하나 선택
b. 해당 특성의 min~max 사이에서 무작위 분할점 선택
c. 분할점 기준으로 좌/우 노드로 데이터 분배
d. 각 노드에 1개 포인트만 남거나 max_depth 도달 시 종료
3. 앙상블: t개의 Isolation Tree 구축 (기본 100)
이상 점수 (Anomaly Score):
s(x, n) = 2^(-E(h(x)) / c(n))
E(h(x)) : 모든 트리에서 x의 평균 경로 길이
c(n) : BST 평균 경로 길이 (정규화 상수)
s -> 1 : 이상 (경로가 짧음)
s -> 0.5 : 판단 어려움
s -> 0 : 정상 (경로가 김)
특성¶
| 특성 | 설명 |
|---|---|
| 시간 복잡도 | O(t * psi * log(psi)) -- 선형에 가까움 |
| 메모리 | O(t * psi) -- 서브샘플링으로 효율적 |
| 스케일링 | 고차원, 대규모 데이터에 적합 |
| 파라미터 | n_estimators (트리 수), max_samples (서브샘플 크기), contamination |
from sklearn.ensemble import IsolationForest
# Isolation Forest
iso_forest = IsolationForest(
n_estimators=100,
max_samples=256,
contamination=0.05,
random_state=42
)
labels = iso_forest.fit_predict(X) # -1: anomaly, 1: normal
scores = -iso_forest.decision_function(X) # 높을수록 이상
4. One-Class SVM¶
Scholkopf et al. (2001). 데이터를 고차원 특성 공간에 매핑한 후, 원점에서 최대 마진으로 분리하는 초평면을 찾는다.
One-Class SVM 원리:
입력 공간 커널 매핑 특성 공간
. . . phi(x) . .
. . . ------> . . .
. * 커널 . *. .
\---/
초평면으로
원점과 분리
목적 함수:
min (1/2)||w||^2 + (1/(v*n)) * sum(xi_i) - rho
s.t. w . phi(x_i) >= rho - xi_i, xi_i >= 0
v (nu): 이상치 비율의 상한/서포트 벡터 비율의 하한
from sklearn.svm import OneClassSVM
# One-Class SVM (RBF 커널)
ocsvm = OneClassSVM(kernel='rbf', gamma='scale', nu=0.05)
ocsvm.fit(X_train) # 정상 데이터만
labels = ocsvm.predict(X_test) # -1: anomaly, 1: normal
5. Autoencoder 기반¶
정상 데이터로 Autoencoder를 학습시킨 후, 재구성 오차(reconstruction error)가 큰 데이터를 이상으로 판단한다.
Autoencoder 이상 탐지:
입력 x --[Encoder]--> z (잠재 표현) --[Decoder]--> x_hat (재구성)
정상 데이터: x ≈ x_hat -> ||x - x_hat||^2 작음
이상 데이터: x ≠ x_hat -> ||x - x_hat||^2 큼
재구성 오차 분포:
정상: |###########|
이상: |####|
------+---------------+---------> 재구성 오차
낮음 threshold 높음
Variational Autoencoder (VAE)¶
VAE는 잠재 공간에 확률적 구조를 부여하여 더 견고한 이상 탐지가 가능하다. 이상 점수로 재구성 오차 + KL divergence 또는 ELBO를 사용한다.
import torch
import torch.nn as nn
class AnomalyAutoencoder(nn.Module):
def __init__(self, input_dim, latent_dim=32):
super().__init__()
self.encoder = nn.Sequential(
nn.Linear(input_dim, 128),
nn.ReLU(),
nn.Linear(128, 64),
nn.ReLU(),
nn.Linear(64, latent_dim)
)
self.decoder = nn.Sequential(
nn.Linear(latent_dim, 64),
nn.ReLU(),
nn.Linear(64, 128),
nn.ReLU(),
nn.Linear(128, input_dim)
)
def forward(self, x):
z = self.encoder(x)
x_hat = self.decoder(z)
return x_hat
def anomaly_score(self, x):
x_hat = self.forward(x)
return torch.mean((x - x_hat) ** 2, dim=1)
# 학습 (정상 데이터만)
model = AnomalyAutoencoder(input_dim=30)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
criterion = nn.MSELoss()
for epoch in range(100):
x_hat = model(X_train_normal)
loss = criterion(x_hat, X_train_normal)
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 추론
scores = model.anomaly_score(X_test).detach().numpy()
threshold = np.percentile(scores, 95)
anomalies = scores > threshold
6. Deep SVDD (Deep Support Vector Data Description)¶
Ruff et al. (ICML 2018). One-Class SVM의 딥러닝 확장으로, 신경망이 정상 데이터를 특성 공간의 하나의 초구(hypersphere) 중심 근처에 매핑하도록 학습한다.
Deep SVDD 원리:
입력 공간 특성 공간 (신경망 매핑)
. . . . . . .
. . * . --phi--> . c . c: 중심
. . . . . . .
. . . |R| R: 반경
경계 밖 = 이상
목적 함수:
min (1/n) * sum(||phi(x_i; W) - c||^2) + (lambda/2) * ||W||^2
정상 데이터: c 근처에 매핑 -> 손실 작음
이상 데이터: c에서 멀리 매핑 -> 손실 큼 (탐지)
7. 시계열 이상 탐지¶
시계열 데이터는 시간적 의존성이 존재하므로 특화된 방법이 필요하다.
| 방법 | 원리 | 적용 |
|---|---|---|
| ARIMA Residuals | 예측 잔차의 이상 패턴 | 단변량 시계열 |
| Prophet Anomaly | 트렌드/계절성 분해 후 잔차 분석 | 비즈니스 시계열 |
| LSTM-AE | LSTM Autoencoder로 시퀀스 재구성 | 다변량 시계열 |
| Transformer-based | Self-attention 기반 시계열 이상 탐지 | 장기 의존성 |
| Matrix Profile | 서브시퀀스 간 거리 프로파일 | 모티프/디스코드 탐지 |
# LSTM Autoencoder for Time Series Anomaly Detection
import torch.nn as nn
class LSTMAutoencoder(nn.Module):
def __init__(self, n_features, hidden_dim=64, n_layers=2):
super().__init__()
self.encoder = nn.LSTM(
n_features, hidden_dim, n_layers, batch_first=True
)
self.decoder = nn.LSTM(
hidden_dim, hidden_dim, n_layers, batch_first=True
)
self.output_layer = nn.Linear(hidden_dim, n_features)
def forward(self, x):
# x: (batch, seq_len, n_features)
_, (h, c) = self.encoder(x)
# Decoder: 인코더의 마지막 hidden state를 반복
seq_len = x.size(1)
decoder_input = h[-1].unsqueeze(1).repeat(1, seq_len, 1)
decoder_out, _ = self.decoder(decoder_input)
reconstruction = self.output_layer(decoder_out)
return reconstruction
방법 선택 가이드¶
데이터 특성에 따른 알고리즘 선택:
데이터 크기?
|
+-- 소규모 (< 10K)
| |
| +-- 고차원? -- Yes --> One-Class SVM (RBF)
| | -- No --> LOF / kNN
|
+-- 중규모 (10K ~ 1M)
| |
| +-- 해석성 필요? -- Yes --> Isolation Forest
| | -- No --> Autoencoder
|
+-- 대규모 (> 1M)
|
+-- Isolation Forest (서브샘플링으로 확장)
+-- Streaming: Half-Space Trees, RRCF
| 상황 | 추천 방법 | 이유 |
|---|---|---|
| 정형 데이터, 빠른 프로토타입 | Isolation Forest | 파라미터 적음, 빠름, 견고함 |
| 비선형 복잡한 패턴 | Autoencoder / VAE | 표현 학습 능력 |
| 이미지 이상 | CNN Autoencoder, PatchCore | 공간적 패턴 캡처 |
| 시계열 | LSTM-AE, Transformer | 시간적 의존성 모델링 |
| 그래프/네트워크 | GNN-based, Spectral | 구조적 이상 탐지 |
| 레이블 일부 존재 | Deep SAD, DevNet | 준지도 학습 활용 |
| 해석성 중요 | Isolation Forest + SHAP | 특성 기여도 설명 가능 |
평가 지표¶
이상 탐지는 극단적 클래스 불균형(정상 >> 이상)이므로 accuracy는 부적절하다.
| 지표 | 설명 | 용도 |
|---|---|---|
| AUROC | ROC 곡선 아래 면적 | 전반적 분리 능력 |
| AUPRC | PR 곡선 아래 면적 | 불균형 데이터에 더 적합 |
| F1-Score | Precision과 Recall의 조화 평균 | 임계값 설정 후 평가 |
| Precision@k | 상위 k개 중 실제 이상 비율 | 실무에서 리소스 할당 |
AUROC vs AUPRC:
극단적 불균형 (이상 0.1%):
AUROC = 0.95 -> "좋아 보이지만..."
AUPRC = 0.30 -> 실제로는 많은 오탐 존재
-> AUPRC가 더 현실적인 평가
from sklearn.metrics import roc_auc_score, average_precision_score
# 평가
auroc = roc_auc_score(y_true, anomaly_scores)
auprc = average_precision_score(y_true, anomaly_scores)
print(f"AUROC: {auroc:.4f}, AUPRC: {auprc:.4f}")
실전 파이프라인 예시¶
import numpy as np
import pandas as pd
from sklearn.ensemble import IsolationForest
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import classification_report
# 1. 데이터 로드 및 전처리
data = pd.read_csv("transactions.csv")
features = ['amount', 'frequency', 'time_since_last', 'distance']
X = data[features].values
# 2. 스케일링 (거리/밀도 기반 방법에 중요)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 3. 다중 모델 앙상블
from sklearn.neighbors import LocalOutlierFactor
# 모델 1: Isolation Forest
iso = IsolationForest(contamination=0.02, random_state=42)
iso_scores = -iso.fit(X_scaled).decision_function(X_scaled)
# 모델 2: LOF
lof = LocalOutlierFactor(n_neighbors=20, contamination=0.02)
lof.fit_predict(X_scaled)
lof_scores = -lof.negative_outlier_factor_
# 4. 점수 정규화 및 앙상블
from sklearn.preprocessing import MinMaxScaler
iso_norm = MinMaxScaler().fit_transform(iso_scores.reshape(-1, 1)).ravel()
lof_norm = MinMaxScaler().fit_transform(lof_scores.reshape(-1, 1)).ravel()
# 가중 평균 앙상블
ensemble_scores = 0.5 * iso_norm + 0.5 * lof_norm
# 5. 임계값 설정 및 탐지
threshold = np.percentile(ensemble_scores, 98)
anomalies = ensemble_scores > threshold
print(f"탐지된 이상: {anomalies.sum()} / {len(anomalies)}")
최근 동향 (2024-2025)¶
| 트렌드 | 설명 |
|---|---|
| LLM 기반 이상 탐지 | 텍스트 로그, 코드 등에서 LLM의 맥락 이해력 활용 |
| Foundation Model for AD | 다양한 도메인에 전이 가능한 범용 이상 탐지 모델 |
| Self-Supervised Pretext | 보조 태스크(회전 예측, 대조 학습)로 정상 표현 학습 |
| Graph Neural Network AD | 그래프 구조 데이터(소셜 네트워크, 금융 거래)에서의 이상 탐지 |
| Explainable AD | SHAP, attention 기반 이상 원인 설명 |
| Few-Shot AD | 극소량의 이상 샘플로 탐지 성능 향상 |
참고 문헌¶
- Liu, F. T., Ting, K. M., & Zhou, Z.-H. (2008). Isolation Forest. ICDM 2008.
- Breunig, M. M., et al. (2000). LOF: Identifying Density-Based Local Outliers. SIGMOD 2000.
- Scholkopf, B., et al. (2001). Estimating the Support of a High-Dimensional Distribution. Neural Computation.
- Ruff, L., et al. (2018). Deep One-Class Classification. ICML 2018.
- Chandola, V., Banerjee, A., & Kumar, V. (2009). Anomaly Detection: A Survey. ACM Computing Surveys.
- Pang, G., et al. (2021). Deep Learning for Anomaly Detection: A Review. ACM Computing Surveys.
- Schmidl, S., et al. (2022). Anomaly Detection in Time Series: A Comprehensive Evaluation. VLDB 2022.