--- id: wiki-2026-0508-concept-drift title: Concept Drift category: 10_Wiki/Topics status: verified canonical_id: self aliases: [개념 드리프트, data drift, distribution shift, model decay, MLOps monitoring, retraining] duplicate_of: none source_trust_level: A confidence_score: 0.93 verification_status: applied tags: [mlops, concept-drift, data-drift, model-monitoring, retraining, distribution-shift, kl-divergence, evidently] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: Python framework: Evidently / Alibi Detect / Whylogs / scikit-multiflow --- # Concept Drift ## 매 한 줄 > **"매 어제 의 정답 의 매 오늘 의 오답"**. 매 train-time data 의 distribution 의 deviate over time. 매 ML 의 silent killer. 매 monitoring + 매 retraining 의 essential. 매 modern: 매 LLM 의 knowledge cutoff 의 same problem. ## 매 핵심 ### 매 4 type 1. **Sudden / Concept**: 매 abrupt change (COVID). 2. **Gradual**: 매 slow shift (inflation, language). 3. **Seasonal / Recurring**: 매 cyclic. 4. **Incremental**: 매 transition period. ### 매 distinction - **Concept drift**: P(y|X) 변화 — 매 same input 의 다른 output. - **Data drift / Covariate**: P(X) 변화 — 매 input distribution. - **Label drift**: P(y) 변화 — 매 target frequency. - **Prior probability shift**. ### 매 detection #### Statistical - **KS test** (Kolmogorov-Smirnov): 매 univariate. - **Chi-square**: 매 categorical. - **PSI** (Population Stability Index): 매 industry standard. - **KL / JS divergence**: 매 distribution distance. - **Wasserstein**. #### ML-based - **Domain classifier**: 매 train vs current 의 separability. - **DDM** (Drift Detection Method). - **EDDM** (Early DDM). - **Page-Hinkley**. - **ADWIN** (Adaptive Windowing). #### Performance-based - 매 accuracy / loss 의 monitor. - 매 lag (label availability). ### 매 adaptation 1. **Periodic retrain**: 매 cron schedule. 2. **Trigger-based**: 매 drift detect → train. 3. **Online learning**: 매 streaming. 4. **Ensemble** with weight update. 5. **Sliding window**. 6. **Active learning**: 매 critical sample 의 label. ### 매 modern MLOps stack - **Evidently**: 매 drift report. - **Alibi Detect** (Seldon): 매 detection algo. - **Whylogs / WhyLabs**: 매 data profiling. - **NannyML**: 매 model performance estimation. - **Arize / Fiddler / Aporia**: 매 observability platform. ### 매 LLM 의 drift - **Knowledge cutoff**: 매 train date 후의 사실 X. - **Skill drift**: 매 fine-tune 의 base capability lose ([[Catastrophic-Forgetting]]). - **User distribution drift**: 매 new use case 의 emerge. - **Prompt drift**: 매 prompt template 의 stale. ### 매 응용 1. **Fraud detection**: 매 fraud pattern evolve. 2. **Recommendation**: 매 trend shift. 3. **Pricing**: 매 market dynamic. 4. **Spam**: 매 evasion technique. 5. **Medical**: 매 disease pattern. 6. **Demand forecasting**: 매 seasonality + 매 black swan. ## 💻 패턴 ### Evidently drift report ```python from evidently.report import Report from evidently.metric_preset import DataDriftPreset, TargetDriftPreset reference = X_train.copy() current = X_prod_recent.copy() report = Report(metrics=[ DataDriftPreset(), TargetDriftPreset(), ]) report.run(reference_data=reference, current_data=current) report.save_html('drift_report.html') ``` ### PSI (Population Stability Index) ```python import numpy as np def psi(reference, current, n_bins=10): """매 < 0.1: stable. 0.1-0.25: moderate drift. > 0.25: significant.""" breaks = np.percentile(reference, np.linspace(0, 100, n_bins + 1)) breaks[0] = -np.inf breaks[-1] = np.inf ref_pct = np.histogram(reference, breaks)[0] / len(reference) cur_pct = np.histogram(current, breaks)[0] / len(current) # 매 avoid log(0) ref_pct = np.where(ref_pct == 0, 0.0001, ref_pct) cur_pct = np.where(cur_pct == 0, 0.0001, cur_pct) return np.sum((cur_pct - ref_pct) * np.log(cur_pct / ref_pct)) ``` ### KS test ```python from scipy.stats import ks_2samp def detect_drift_ks(reference, current, threshold=0.05): stat, p_value = ks_2samp(reference, current) drifted = p_value < threshold return {'statistic': stat, 'p_value': p_value, 'drifted': drifted} ``` ### Domain classifier ```python from sklearn.linear_model import LogisticRegression from sklearn.model_selection import cross_val_score import numpy as np def domain_classifier_drift(X_ref, X_cur): """매 ref vs current 의 separability.""" X = np.vstack([X_ref, X_cur]) y = np.array([0]*len(X_ref) + [1]*len(X_cur)) auc = cross_val_score(LogisticRegression(max_iter=1000), X, y, scoring='roc_auc', cv=5).mean() # 매 0.5: no drift. 0.7+: significant. return {'auc': auc, 'drifted': auc > 0.7} ``` ### Page-Hinkley (online) ```python class PageHinkley: """매 streaming drift detection.""" def __init__(self, delta=0.005, threshold=50): self.delta = delta self.threshold = threshold self.cumsum = 0 self.min_cumsum = 0 self.n = 0 self.mean = 0 def update(self, x): self.n += 1 self.mean += (x - self.mean) / self.n self.cumsum += x - self.mean - self.delta self.min_cumsum = min(self.min_cumsum, self.cumsum) ph = self.cumsum - self.min_cumsum return ph > self.threshold # 매 drift detected ``` ### Sliding window retrain ```python class SlidingWindowModel: def __init__(self, base_model, window_size=10000): self.base = base_model self.window_size = window_size self.X_window = [] self.y_window = [] def add(self, X, y): self.X_window.extend(X) self.y_window.extend(y) if len(self.X_window) > self.window_size: self.X_window = self.X_window[-self.window_size:] self.y_window = self.y_window[-self.window_size:] def retrain(self): self.base.fit(self.X_window, self.y_window) ``` ### Online learning (river) ```python from river import linear_model, preprocessing, metrics model = (preprocessing.StandardScaler() | linear_model.LogisticRegression()) metric = metrics.ROCAUC() for x, y in stream: y_pred = model.predict_proba_one(x)[True] metric.update(y, y_pred) model.learn_one(x, y) print(metric) # 매 streaming AUC ``` ### Trigger-based retrain pipeline ```python def retrain_if_drift(model, monitoring_data, reference): drift_score = psi(reference['features'], monitoring_data['features']) perf = evaluate(model, monitoring_data) if drift_score > 0.25 or perf < ACCEPTABLE_PERFORMANCE: log(f'Retrain triggered: drift={drift_score:.3f}, perf={perf:.3f}') new_model = train(combined(reference, monitoring_data)) if shadow_test(new_model, model, validation_set) > 0: promote_to_prod(new_model) return new_model return model ``` ### Shadow deployment ```python def shadow_serve(request): """매 prod model + 매 candidate model 의 둘 다 의 inference.""" prod_pred = prod_model.predict(request) candidate_pred = candidate_model.predict(request) log_for_comparison(request, prod_pred, candidate_pred) return prod_pred # 매 user 의 only prod 의 see ``` ### LLM knowledge cutoff handling ```python def llm_with_freshness(query): # 매 1. detect freshness need if needs_recent_info(query): # 매 RAG with recent docs context = search_recent(query, after=cutoff_date) return llm.generate(query, context=context) return llm.generate(query) ``` ## 🤔 결정 기준 | 상황 | Approach | |---|---| | Static domain | Periodic retrain (monthly) | | Fast-moving | Online + drift trigger | | High-stakes | Shadow + canary | | Streaming | Online learning (river) | | Univariate | KS / PSI | | Multivariate | Domain classifier | | LLM | RAG + retrieval | **기본값**: PSI + KS + performance monitor. 매 trigger threshold 의 drift detect 시 retrain. ## 🔗 Graph - 부모: [[MLOps]] · [[Distribution-Shift]] - 변형: [[Data-Drift]] · [[Concept-Drift]] - Detection: [[PSI]] · [[KL-Divergence]] - Tool: [[Evidently]] - Adjacent: [[Catastrophic-Forgetting]] · [[Continual-Learning]] · [[Bias-vs-Variance]] · [[Antifragility]] ## 🤖 LLM 활용 **언제**: 매 production ML monitoring. 매 drift detection. 매 retrain trigger design. 매 LLM RAG freshness. **언제 X**: 매 closed system (no real-world distribution). ## ❌ 안티패턴 - **No monitoring**: 매 silent decay. - **Performance metric 만**: 매 label lag. - **Single threshold**: 매 false alarm. - **Retrain without test**: 매 worse model. - **No baseline / reference**: 매 drift 의 measure X. - **LLM 의 knowledge cutoff 의 ignore**: 매 stale answer. ## 🧪 검증 / 중복 - Verified (Evidently docs, Webb concept drift survey, Gama drift detection). - 신뢰도 A. - Related: [[Catastrophic-Forgetting]] · [[Continual-Learning]] · [[Antifragility]] · [[Causal-Inference]] · [[Benchmarks]]. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — types + detection methods + 매 PSI / KS / domain classifier / Page-Hinkley / online code |