1프로덕션 모델 모니터링
프로덕션 환경의 ML 모델은 지속적인 모니터링이 필수적입니다. 제조 현장에서는 공정 변화, 원자재 변동, 설비 노화 등으로 인해 모델 성능이 점진적으로 저하될 수 있습니다. 이를 조기에 탐지하고 대응하기 위한 체계적인 모니터링 시스템이 필요합니다.
📊 성능 메트릭
Accuracy, Precision, Recall, F1-Score 등 핵심 성능 지표를 실시간 추적
⏱️ 추론 지연
모델 추론 시간(latency)과 처리량(throughput) 모니터링
💾 리소스 사용
CPU, GPU, 메모리 사용량 및 디스크 I/O 추적
🔍 예측 분포
모델 출력값의 분포 변화 감지
# =====================================================
# 모델 성능 모니터링 시스템
# 핵심 개념: 슬라이딩 윈도우 기반 실시간 성능 추적
# =====================================================
# 1. 초기화 - 모니터링 설정
모니터 생성(모델명, 윈도우_크기=1000):
예측_히스토리 = 빈 리스트
실제값_히스토리 = 빈 리스트
추론시간_히스토리 = 빈 리스트
메트릭_익스포터 초기화 # Prometheus 등
# 2. 예측 기록 - 매 예측마다 호출
예측_기록(예측값, 실제값, 추론시간):
히스토리에 예측값 추가
히스토리에 실제값 추가 (있으면)
히스토리에 추론시간 추가
# 윈도우 크기 초과 시 오래된 데이터 제거
만약 히스토리 길이 > 윈도우_크기:
가장 오래된 항목 제거
# 3. 성능 메트릭 계산
메트릭_계산():
만약 데이터 부족하면: 반환 없음
최근_윈도우 = 히스토리에서 최근 N개 추출
반환:
정확도 = 정답_비율(예측, 실제)
정밀도 = 양성예측_정답비율
재현율 = 실제양성_탐지비율
F1점수 = 정밀도와 재현율의 조화평균
# 4. 성능 추이 분석
성능_추이_분석(윈도우_개수=10):
시간대별로 윈도우를 나누어
각 윈도우의 정확도, F1점수 계산
시계열 데이터로 반환
# === 사용 예시 ===
모니터 = 모니터 생성("불량_분류기")
# 예측 수행 시
시작시간 기록
예측값 = 모델.예측(입력데이터)
추론시간 = 경과시간 측정
모니터.예측_기록(예측값, 실제값, 추론시간)
# 주기적 메트릭 확인
메트릭 = 모니터.메트릭_계산()
출력("현재 정확도:", 메트릭.정확도)
제조 현장에서는 실제 레이블(ground truth)을 즉시 얻기 어려운 경우가 많습니다. 예를 들어 품질 검사 결과는 수 시간 또는 수일 후에 확인될 수 있습니다. 이를 위해 예측 ID와 타임스탬프를 기록하고, 나중에 레이블이 확보되면 소급하여 연결하는 지연 조인(delayed join) 방식을 사용합니다.
2데이터 드리프트 탐지
데이터 드리프트(Data Drift)는 입력 데이터의 분포가 학습 시점과 달라지는 현상입니다. 원자재 공급처 변경, 센서 교체, 공정 조건 변화 등이 원인이 될 수 있으며, 이는 모델 성능 저하로 이어집니다.
# =====================================================
# 데이터 드리프트 탐지 시스템
# 핵심 개념: 학습 데이터 vs 프로덕션 데이터 분포 비교
# =====================================================
# 1. 초기화 - 기준 분포 설정
드리프트탐지기 생성(기준데이터, 피처목록, 임계값):
각 피처별로 기준 통계량 저장:
- 평균, 표준편차
- 백분위수 (0%, 25%, 50%, 75%, 100%)
- 히스토그램
# 2. KS 테스트 (Kolmogorov-Smirnov Test)
# 귀무가설: 두 분포가 동일하다
KS_테스트(현재데이터):
각 피처에 대해:
KS통계량, p값 = 두_분포_비교(기준분포, 현재분포)
만약 p값 < 임계값(0.05):
드리프트_발생 = 참 # 분포가 다름
아니면:
드리프트_발생 = 거짓 # 분포가 유사함
반환: 피처별 드리프트 여부
# 3. PSI (Population Stability Index) 계산
# PSI = Σ[(실제비율 - 기대비율) × ln(실제비율/기대비율)]
PSI_계산(현재데이터, 빈_개수=10):
각 피처에 대해:
동일한 구간으로 히스토그램 생성
기준_비율 = 기준데이터의 각 구간 비율
현재_비율 = 현재데이터의 각 구간 비율
PSI값 = 합계((현재비율 - 기준비율) × log(현재비율/기준비율))
# PSI 해석 기준
만약 PSI < 0.1: 안정
만약 0.1 <= PSI < 0.25: 약간 불안정
만약 PSI >= 0.25: 불안정 (재학습 필요)
반환: 피처별 PSI값과 안정성 상태
# 4. 통합 드리프트 탐지
드리프트_탐지(현재데이터):
KS결과 = KS_테스트(현재데이터)
PSI결과 = PSI_계산(현재데이터)
반환:
드리프트_피처_수 (KS 기준)
드리프트_피처_수 (PSI 기준)
상세_분석_결과
# === 사용 예시 ===
탐지기 = 드리프트탐지기 생성(
기준데이터 = 학습데이터,
피처목록 = ["온도", "압력", "진동", "회전수"]
)
# 일일 점검
일일데이터 = 프로덕션_데이터_조회(날짜="오늘")
결과 = 탐지기.드리프트_탐지(일일데이터)
만약 결과.드리프트_발생:
알림_발송("데이터 드리프트 감지!")
드리프트 탐지는 False Positive(오탐)가 발생할 수 있습니다. 특히 정상적인 계절성 패턴이나 주기적 변동을 드리프트로 오인할 수 있습니다. 임계값을 적절히 조정하고, 여러 통계 테스트를 조합하여 신뢰성을 높이세요.
3모델 드리프트 탐지
모델 드리프트(Model Drift)는 입력 분포는 변하지 않았지만, 입력과 출력 간의 관계가 변화하는 현상입니다. 이를 Concept Drift라고도 합니다. 예를 들어, 동일한 센서 값이더라도 설비 노화로 인해 불량률이 달라질 수 있습니다.
# =====================================================
# 모델 드리프트 탐지 시스템
# 핵심 개념: 성능 저하(Concept Drift) + 예측 분포 변화(Prediction Drift)
# =====================================================
# 1. 초기화 - 기준 성능 설정
모델드리프트모니터 생성(모델명, 기준정확도=0.92, 윈도우크기=500):
성능저하_임계값 = 0.05 # 5% 저하 시 알림
예측_윈도우 = 슬라이딩 윈도우(최대크기=500)
실제값_윈도우 = 슬라이딩 윈도우(최대크기=500)
기준_예측분포 = 없음
# 2. 기준 예측 분포 설정 (학습 시점)
기준분포_설정(학습예측값):
각 클래스별 비율 계산하여 저장
예: {양품: 0.95, 불량: 0.05}
# 3. 성능 저하 탐지 (Concept Drift)
성능저하_탐지():
만약 데이터 부족: 반환 "데이터 부족"
현재정확도 = 정확도계산(실제값_윈도우, 예측_윈도우)
저하율 = (기준정확도 - 현재정확도) / 기준정확도 × 100
만약 저하율 > 임계값:
반환 "드리프트 감지", 상세정보
아니면:
반환 "정상"
# 4. 예측 분포 변화 탐지 (Prediction Drift)
예측분포_변화_탐지():
현재_예측분포 = 최근 예측값의 클래스별 비율
# Jensen-Shannon Divergence: 두 분포의 차이 측정
JS거리 = JS_발산(기준_예측분포, 현재_예측분포)
만약 JS거리 > 0.1:
반환 "분포 변화 감지"
아니면:
반환 "정상"
# === 사용 예시 ===
모니터 = 모델드리프트모니터 생성("품질예측기", 기준정확도=0.92)
모니터.기준분포_설정(학습데이터_예측결과)
# 프로덕션 운영 중
반복 (새로운 데이터가 들어올 때마다):
예측값 = 모델.예측(입력)
모니터.예측_추가(예측값, 실제값)
# 100건마다 드리프트 점검
만약 예측건수 % 100 == 0:
성능결과 = 모니터.성능저하_탐지()
분포결과 = 모니터.예측분포_변화_탐지()
만약 드리프트_감지됨:
알림_발송()
재학습_트리거()
Sudden Drift: 급격한 변화 (설비 교체, 공정 변경)
Gradual Drift: 점진적 변화 (설비 노화, 원자재 품질 저하)
Incremental Drift: 단계적 변화 (계절성, 주기적 패턴)
Recurring Drift: 반복적 변화 (주간/월간 패턴)
각 유형에 맞는 탐지 전략과 재학습 주기를 설정해야 합니다.
4자동 재학습 파이프라인
드리프트가 탐지되면 자동으로 모델을 재학습하는 파이프라인을 구축합니다. 트리거 조건, 데이터 수집, 학습, 검증, 배포까지의 전 과정을 자동화합니다.
# =====================================================
# 자동 재학습 파이프라인
# 핵심 개념: 트리거 → 데이터 수집 → 학습 → 검증 → 배포
# =====================================================
# 1. 파이프라인 초기화
재학습파이프라인 생성(모델명, 데이터소스, 최소샘플수=1000):
개선_임계값 = 0.01 # 최소 1% 성능 개선 필요
# 2. 재학습 트리거 확인
트리거_확인():
트리거_목록 = {드리프트감지, 성능저하, 스케줄, 수동요청}
만약 드리프트_모니터.드리프트_감지됨:
트리거_목록.드리프트감지 = 참
만약 오늘이_일요일: # 주간 정기 재학습
트리거_목록.스케줄 = 참
반환: 트리거 중 하나라도 활성화되면 재학습 필요
# 3. 학습 데이터 수집
데이터_수집(기간=30일):
최근_N일_데이터 = DB에서_조회(레이블_있는_데이터만)
만약 데이터_수 < 최소샘플수:
오류("데이터 부족")
피처, 레이블 = 데이터_분리()
반환: 피처, 레이블
# 4. 새 모델 학습
새_모델_학습(학습데이터, 검증데이터):
모델 = 랜덤포레스트(트리수=100, 최대깊이=10)
모델.학습(학습데이터)
학습_정확도 = 평가(학습데이터)
검증_정확도 = 평가(검증데이터)
실험_추적기에_기록(파라미터, 메트릭, 모델)
반환: 모델, 검증_정확도
# 5. 모델 비교 및 승인
모델_비교(새_모델, 새_정확도, 현재_모델, 현재_정확도):
개선율 = (새_정확도 - 현재_정확도) / 현재_정확도
만약 개선율 >= 임계값:
반환: 승인 (배포 진행)
아니면:
반환: 거부 (현재 모델 유지)
# 6. 모델 배포 (Canary 방식)
모델_배포(새_모델):
모델_저장(경로)
레지스트리에_등록(모델)
Canary_배포(트래픽_비율=10%) # 점진적 배포
반환: 배포_경로
# === 전체 파이프라인 실행 흐름 ===
파이프라인_실행():
# 1단계: 트리거 확인
만약 트리거_확인().재학습_불필요:
종료
# 2단계: 데이터 수집
데이터 = 데이터_수집(30일)
학습셋, 검증셋 = 데이터_분할(비율=8:2)
# 3단계: 새 모델 학습
새_모델, 새_정확도 = 새_모델_학습(학습셋, 검증셋)
# 4단계: 기존 모델과 비교
현재_모델 = 모델_불러오기()
현재_정확도 = 평가(현재_모델, 검증셋)
# 5단계: 승인 및 배포
만약 모델_비교(새_모델, 현재_모델) == 승인:
모델_배포(새_모델)
알림_발송("새 모델 배포 완료")
자동 재학습은 편리하지만 리스크도 있습니다. 잘못된 데이터로 학습하거나, 일시적 노이즈를 학습할 수 있습니다. 반드시 Shadow Mode(병렬 테스트), A/B Testing, Rollback 메커니즘을 구현하여 안전장치를 마련하세요.
5알림과 대시보드
모니터링 시스템은 Prometheus로 메트릭을 수집하고, Grafana로 시각화하며, Alertmanager로 알림을 발송합니다.
# =====================================================
# Prometheus 메트릭 수집 시스템
# 핵심 개념: 실시간 메트릭 → Prometheus → Grafana 시각화
# =====================================================
# 1. 메트릭 유형 정의
메트릭_정의():
# Counter: 누적 카운터 (증가만 가능)
예측_총횟수 = Counter("model_predictions_total")
# Histogram: 분포 측정 (추론 시간의 백분위수 등)
추론_지연시간 = Histogram("model_inference_latency_seconds",
구간=[0.001, 0.01, 0.05, 0.1, 0.5, 1.0])
# Gauge: 현재값 (증가/감소 모두 가능)
현재_정확도 = Gauge("model_accuracy")
드리프트_점수 = Gauge("data_drift_score")
# Info: 메타데이터 (버전 정보 등)
모델_버전정보 = Info("model_version")
# 2. 메트릭 업데이트 함수
메트릭_업데이트(모델명, 버전, 추론시간, 정확도, 드리프트점수들):
# 예측 카운터 1 증가
예측_총횟수.증가(레이블={모델명, 버전})
# 추론 시간 히스토그램에 기록
추론_지연시간.기록(추론시간)
# 현재 정확도 설정
현재_정확도.설정(정확도)
# 피처별 드리프트 점수 설정
각 피처에 대해:
드리프트_점수.설정(피처명, PSI값)
# 버전 정보 업데이트
모델_버전정보.설정(버전, 배포시간)
# === 메인 모니터링 루프 ===
Prometheus_서버_시작(포트=8000) # /metrics 엔드포인트 노출
반복:
# 예측 수행 및 시간 측정
시작시간 = 현재시간()
예측값 = 모델.예측(입력)
추론시간 = 경과시간()
# 현재 성능 메트릭 계산
성능메트릭 = 모니터.메트릭_계산()
드리프트결과 = 탐지기.PSI_계산()
# Prometheus로 메트릭 전송
메트릭_업데이트(
모델명 = "불량_분류기",
버전 = "v2.1.3",
추론시간, 정확도, 드리프트점수
)
대기(1초)
{
"dashboard": {
"title": "ML Model Monitoring",
"panels": [
{
"id": 1,
"title": "Model Accuracy (Real-time)",
"type": "graph",
"targets": [
{
"expr": "model_accuracy{model_name='defect_classifier'}",
"legendFormat": "Accuracy"
}
],
"alert": {
"conditions": [
{
"evaluator": {
"type": "lt",
"params": [0.85]
},
"query": {
"params": ["A", "5m", "now"]
}
}
],
"alertRuleTags": {
"severity": "warning"
},
"message": "Model accuracy dropped below 85%",
"notifications": [
{"uid": "slack-notifications"}
]
}
},
{
"id": 2,
"title": "Inference Latency (P95)",
"type": "graph",
"targets": [
{
"expr": "histogram_quantile(0.95, rate(model_inference_latency_seconds_bucket[5m]))",
"legendFormat": "P95 Latency"
}
]
},
{
"id": 3,
"title": "Data Drift Score (PSI)",
"type": "heatmap",
"targets": [
{
"expr": "data_drift_score{model_name='defect_classifier'}",
"legendFormat": "{{feature}}"
}
]
},
{
"id": 4,
"title": "Predictions per Second",
"type": "stat",
"targets": [
{
"expr": "rate(model_predictions_total[1m])",
"legendFormat": "RPS"
}
]
}
],
"time": {
"from": "now-6h",
"to": "now"
},
"refresh": "10s"
}
}
global:
resolve_timeout: 5m
slack_api_url: 'https://hooks.slack.com/services/YOUR/WEBHOOK/URL'
route:
group_by: ['alertname', 'model_name']
group_wait: 10s
group_interval: 10s
repeat_interval: 12h
receiver: 'ml-ops-team'
routes:
- match:
severity: critical
receiver: 'ml-ops-oncall'
- match:
severity: warning
receiver: 'ml-ops-team'
receivers:
- name: 'ml-ops-team'
slack_configs:
- channel: '#ml-monitoring'
title: 'ML Alert: {{ .GroupLabels.alertname }}'
text: '{{ range .Alerts }}{{ .Annotations.message }}{{ end }}'
- name: 'ml-ops-oncall'
slack_configs:
- channel: '#ml-critical'
title: '🚨 CRITICAL ML Alert'
text: '{{ range .Alerts }}{{ .Annotations.message }}{{ end }}'
pagerduty_configs:
- service_key: 'YOUR_PAGERDUTY_KEY'
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'model_name']
📧 Slack 알림
드리프트 탐지, 성능 저하 시 즉시 Slack 채널로 알림
📱 PagerDuty
Critical 알림은 온콜 엔지니어에게 즉시 전화/SMS
📊 Grafana 대시보드
실시간 메트릭 시각화 및 이력 추적
📝 주간 리포트
자동 생성된 성능 리포트를 이메일로 발송
너무 많은 알림은 오히려 중요한 알림을 놓치게 만듭니다. Alert Grouping(동일 원인의 알림 묶기), Inhibition(상위 알림 발생 시 하위 알림 억제), Silence(유지보수 시간 알림 음소거) 기능을 활용하여 알림 피로도를 줄이세요.
6실전 적용 시나리오
자동차 부품 제조 현장에서 불량 예측 모델을 운영하는 사례를 살펴봅니다.
초기 배포: 학습 데이터 10만 건으로 학습한 모델, 검증 정확도 92%
1개월 후: 원자재 공급처 변경, 데이터 드리프트 탐지 (PSI > 0.2)
→ 자동 재학습 트리거, 최근 1개월 데이터로 재학습
→ 새 모델 정확도 94%, 기존 대비 2% 개선 확인
→ Canary 배포로 10% 트래픽 테스트 후 전체 배포
3개월 후: 성능 저하 탐지 (정확도 92% → 87%)
→ Concept Drift 발생 (설비 노화로 불량 패턴 변화)
→ 긴급 재학습 실행, 노화된 설비의 새로운 불량 패턴 학습
→ 정확도 91%로 회복, 현장 적용
결과: 지속적인 모니터링과 자동 재학습으로 연중 평균 정확도 91% 이상 유지