분포 거리 측정 (Part 4) - 실무 적용과 Detection 시스템
· 약 11분
KL, JSD, Wasserstein을 실제 시스템에 적용하는 방법. Drift Detection, Anomaly Detection, Model Monitoring 구현 패턴과 실무 체크리스트.
들어가며
지난 세 편에서 KL Divergence, JSD, Wasserstein Distance의 이론적 배경을 살펴봤습니다. 이제 가장 중요한 질문이 남았습니다. "실제로 어떻게 쓰는가?"
이번 글에서는 분포 비교 기법을 실제 시스템에 적용하는 구체적인 패턴들을 다룹니다. Feature drift detection, anomaly detection, model monitoring 등 바로 활용할 수 있는 내용에 집중합니다.
1. 분포 비교가 필요한 실무 상황
주요 사용 사례
┌─────────────────────────────────────────────────────────┐
│ Distribution Comparison Use Cases │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Data │ │ Model │ │ Anomaly │ │
│ │ Drift │ │ Monitoring│ │ Detection │ │
│ │ │ │ │ │ │ │
│ │ Input │ │ Prediction │ │ Behavior │ │
│ │ Change │ │ Tracking │ │ Pattern │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ A/B Test │ │ Quality │ │
│ │ Analysis │ │ Control │ │
│ │ │ │ │ │
│ │ Group │ │ Batch │ │
│ │ Compare │ │ Compare │ │
│ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
- Data Drift: 입력 분포 변화 감지
- Model Monitoring: 예측 분포 변화 추적
- Anomaly Detection: 행동 패턴 이상 탐지
- A/B Test Analysis: 그룹 간 분포 비교
- Quality Control: 배치 간 품질 비교
공통 패턴
대부분의 경우 다음 구조를 따릅니다:
1. Baseline 분포 구축 (정상 상태)
↓
2. 현재 분포 추 정 (실시간 또는 배치)
↓
3. 거리/발산 계산
↓
4. Threshold 기반 판단
↓
5. 알림 또는 액션
2. Feature Distribution Drift Detection
문제 상황
ML 모델은 학습 데이터와 유사한 분포의 입력을 기대합니다. 시간이 지나면서 입력 데이터의 분포가 변하면(drift), 모델 성능이 저하될 수 있습니다.
Training time: Production time:
∩ ∩
_| |_ _| |_
────────────── ──────────────
Feature X Feature X
(mean: 100) (mean: 120)
↓
Drift detected! Retrain model?
- 학습 시점 평균: 100 → 운영 시점 평균: 120으로 분포 이동 (Drift 발생)
구현 패턴
class FeatureDriftDetector:
function initialize(n_bins, threshold):
self.n_bins = n_bins
self.threshold = threshold
self.baseline_histogram = null
self.bin_edges = null
function fit_baseline(baseline_data):
# Baseline 데이터로 히스토그램 생성
self.baseline_histogram, self.bin_edges = histogram(
baseline_data,
bins=self.n_bins,
density=true
)
# Smoothing (zero 방지)
self.baseline_histogram = self.baseline_histogram + 1e-10
self.baseline_histogram = normalize(self.baseline_histogram)
function check_drift(current_data):
# 현재 데이터의 히스토그램
current_histogram, _ = histogram(
current_data,
bins=self.bin_edges, # 동일한 bin 사용
density=true
)
current_histogram = current_histogram + 1e-10
current_histogram = normalize(current_histogram)
# JSD 계산 (대칭, bounded)
jsd_value = compute_jsd(self.baseline_histogram, current_histogram)
# 판단
return {
"jsd": jsd_value,
"is_drift": jsd_value > self.threshold,
"severity": classify_severity(jsd_value)
}
function classify_severity(jsd_value):
if jsd_value > 2 * self.threshold:
return "high"
else if jsd_value > self.threshold:
return "medium"
else:
return "low"
