🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

Data Drift 및 Model Drift 감지 완벽 가이드 - 슬라이드 1/8
A

AI Generated

2025. 11. 30. · 16 Views

Data Drift 및 Model Drift 감지 완벽 가이드

머신러닝 모델을 운영하다 보면 시간이 지나면서 성능이 저하되는 현상을 경험하게 됩니다. 이 가이드에서는 데이터 드리프트와 모델 드리프트가 무엇인지, 그리고 이를 어떻게 감지하고 대응하는지 초급자도 이해할 수 있도록 설명합니다.


목차

  1. 데이터_드리프트의_개념
  2. 모델_드리프트의_이해
  3. PSI를_활용한_드리프트_정량화
  4. Evidently_라이브러리_활용하기
  5. 다변량_드리프트_감지
  6. 드리프트_대응_전략
  7. 드리프트_모니터링_대시보드_구축

1. 데이터 드리프트의 개념

어느 날 김개발 씨는 6개월 전에 배포한 상품 추천 모델의 성능이 이상하게 떨어졌다는 보고를 받았습니다. 분명히 배포 당시에는 정확도가 95%였는데, 지금은 78%밖에 되지 않는다고 합니다.

코드를 한 줄도 건드리지 않았는데 왜 이런 일이 벌어진 걸까요?

데이터 드리프트는 모델이 학습할 때 사용한 데이터와 실제 운영 환경에서 들어오는 데이터의 분포가 달라지는 현상입니다. 마치 여름에 맞춰 만든 아이스크림 판매 예측 모델이 겨울이 되자 엉뚱한 예측을 내놓는 것과 같습니다.

세상은 끊임없이 변하고, 데이터도 그와 함께 변하기 때문에 이런 현상이 발생합니다.

다음 코드를 살펴봅시다.

import numpy as np
from scipy import stats

# 학습 당시의 데이터 분포
train_data = np.array([25, 30, 28, 32, 27, 29, 31, 26, 33, 28])

# 현재 운영 환경에서 들어오는 데이터
production_data = np.array([45, 52, 48, 55, 50, 47, 53, 49, 51, 46])

# KS 검정으로 두 분포가 같은지 확인합니다
statistic, p_value = stats.ks_2samp(train_data, production_data)

# p-value가 0.05보다 작으면 분포가 달라졌다고 판단합니다
if p_value < 0.05:
    print(f"데이터 드리프트 감지! (p-value: {p_value:.4f})")
else:
    print("데이터 분포가 안정적입니다.")

김개발 씨는 입사 1년 차 머신러닝 엔지니어입니다. 회사에서 처음으로 배포한 추천 모델이 잘 작동해서 뿌듯해하던 차에, 갑자기 성능 저하 알림을 받게 되었습니다.

당황한 김개발 씨는 코드를 샅샅이 뒤져봤지만 버그는 어디에도 없었습니다. 선배 개발자 박시니어 씨가 다가와 물었습니다.

"혹시 데이터 드리프트 모니터링은 해봤어요?" 데이터 드리프트란 정확히 무엇일까요? 쉽게 비유하자면, 이것은 마치 서울 날씨에 맞춰 옷을 준비했는데 갑자기 제주도로 발령이 난 것과 같습니다.

서울에서는 완벽했던 겨울 코트가 제주도의 따뜻한 겨울에는 맞지 않는 것처럼, 학습할 때의 데이터 패턴이 현재 들어오는 데이터와 맞지 않으면 모델도 엉뚱한 예측을 내놓게 됩니다. 왜 이런 현상이 발생할까요?

세상은 끊임없이 변화하기 때문입니다. 사용자의 취향이 바뀌고, 시장 트렌드가 변하고, 계절이 바뀝니다.

코로나19 팬데믹처럼 예상치 못한 사건이 발생하면 사람들의 행동 패턴이 완전히 달라지기도 합니다. 이 모든 변화가 데이터에 반영되고, 결국 모델 성능에 영향을 미칩니다.

데이터 드리프트를 감지하는 대표적인 방법 중 하나가 바로 **KS 검정(Kolmogorov-Smirnov test)**입니다. 이 방법은 두 개의 데이터 분포가 통계적으로 같은지 다른지를 판단해줍니다.

위 코드를 한 줄씩 살펴보겠습니다. 먼저 학습 당시의 데이터와 현재 운영 환경의 데이터를 준비합니다.

예제에서는 간단하게 숫자 배열로 표현했지만, 실제로는 사용자 나이, 구매 금액, 클릭 횟수 같은 피처들이 될 것입니다. 그 다음 stats.ks_2samp 함수를 호출하여 두 분포를 비교합니다.

이 함수는 두 가지 값을 반환합니다. statistic은 두 분포가 얼마나 다른지를 나타내는 수치이고, p-value는 두 분포가 같다는 가설이 참일 확률입니다.

p-value가 0.05보다 작으면 두 분포가 다르다고 판단할 수 있습니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 이커머스 플랫폼에서 상품 추천 모델을 운영한다고 가정해봅시다. 매일 또는 매주 단위로 새로 들어온 데이터와 학습 데이터를 비교하는 파이프라인을 구축합니다.

드리프트가 감지되면 슬랙 알림을 보내거나 자동으로 재학습을 트리거하는 방식으로 대응할 수 있습니다. 하지만 주의할 점도 있습니다.

모든 변화가 나쁜 것은 아닙니다. 계절에 따른 자연스러운 변화일 수도 있고, 마케팅 캠페인의 성공으로 인한 일시적인 변화일 수도 있습니다.

따라서 드리프트가 감지되었다고 무조건 재학습을 하기보다는, 비즈니스 맥락을 함께 고려해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 조언대로 데이터 드리프트를 점검해보니, 사용자들의 평균 연령대가 학습 당시보다 10살 이상 낮아져 있었습니다. 최근 마케팅 팀에서 진행한 MZ세대 타겟 캠페인이 대성공을 거두면서 사용자 구성이 완전히 달라진 것이었습니다.

실전 팁

💡 - KS 검정 외에도 PSI(Population Stability Index), Jensen-Shannon Divergence 등 다양한 방법이 있으니 데이터 특성에 맞게 선택하세요

  • 모든 피처를 개별적으로 모니터링하기보다는 중요한 피처 위주로 모니터링하는 것이 효율적입니다

2. 모델 드리프트의 이해

데이터 드리프트에 대해 알게 된 김개발 씨는 모니터링 시스템을 열심히 구축했습니다. 그런데 어느 날 박시니어 씨가 또 다른 질문을 던졌습니다.

"데이터 드리프트랑 모델 드리프트는 다른 거 알지?" 김개발 씨는 고개를 갸웃거렸습니다.

모델 드리프트는 입력 데이터와 타겟 변수 사이의 관계가 변하는 현상입니다. 데이터 드리프트가 입력의 변화라면, 모델 드리프트는 입력과 출력 사이의 규칙이 변하는 것입니다.

마치 어제까지는 비가 오면 우산 판매가 늘었는데, 오늘부터는 비가 와도 우산 판매가 늘지 않는 것과 같습니다.

다음 코드를 살펴봅시다.

import numpy as np
from sklearn.metrics import accuracy_score
from datetime import datetime, timedelta

# 주간 단위로 모델 성능을 추적합니다
def track_model_performance(model, weekly_data, weekly_labels):
    predictions = model.predict(weekly_data)
    accuracy = accuracy_score(weekly_labels, predictions)
    return accuracy

# 시간에 따른 성능 변화를 모니터링합니다
performance_history = []
baseline_accuracy = 0.95  # 배포 당시 성능

for week in range(10):
    # 실제로는 해당 주의 데이터와 실제 결과를 가져옵니다
    current_accuracy = baseline_accuracy - (week * 0.02)  # 예시: 점진적 하락
    performance_history.append(current_accuracy)

    # 성능이 10% 이상 하락하면 경고를 발생시킵니다
    if (baseline_accuracy - current_accuracy) / baseline_accuracy > 0.1:
        print(f"Week {week}: 모델 드리프트 경고! 정확도: {current_accuracy:.2%}")

박시니어 씨는 화이트보드에 그림을 그리며 설명을 시작했습니다. "데이터 드리프트와 모델 드리프트는 비슷해 보이지만 근본적으로 다른 문제야." 데이터 드리프트가 입력값 자체의 분포가 변하는 것이라면, 모델 드리프트는 입력과 출력 사이의 관계가 변하는 것입니다.

좀 더 쉽게 비유하자면, 데이터 드리프트는 수험생들의 평균 공부 시간이 변한 것이고, 모델 드리프트는 공부 시간과 성적 사이의 상관관계가 변한 것입니다. 왜 이런 구분이 중요할까요?

대응 방법이 다르기 때문입니다. 데이터 드리프트는 새로운 데이터로 모델을 재학습하면 어느 정도 해결됩니다.

하지만 모델 드리프트는 더 근본적인 문제입니다. 피처 엔지니어링을 다시 해야 할 수도 있고, 심지어 모델 아키텍처 자체를 바꿔야 할 수도 있습니다.

실제 사례를 들어보겠습니다. 코로나19 팬데믹 초기에 많은 항공권 가격 예측 모델들이 무용지물이 되었습니다.

과거에는 유가, 계절, 경쟁사 가격 등으로 항공권 가격을 꽤 정확하게 예측할 수 있었습니다. 하지만 팬데믹으로 인해 이런 관계가 완전히 깨져버렸습니다.

유가가 폭락해도 항공권 가격은 오히려 올랐고, 성수기에도 수요가 없었습니다. 위 코드는 간단한 모델 드리프트 감지 시스템을 보여줍니다.

핵심 아이디어는 시간에 따른 성능 변화를 추적하는 것입니다. 배포 당시의 성능을 기준선으로 잡고, 매주 또는 매일 현재 성능을 측정합니다.

성능이 일정 수준 이상 떨어지면 경고를 발생시킵니다. 여기서 중요한 점은 실제 정답 레이블이 필요하다는 것입니다.

데이터 드리프트는 입력 데이터만 있으면 감지할 수 있지만, 모델 드리프트를 정확하게 감지하려면 예측값과 실제값을 비교해야 합니다. 이것이 실무에서 모델 드리프트 감지가 더 어려운 이유 중 하나입니다.

실무에서는 어떻게 해결할까요? 많은 회사들이 일정 기간의 지연된 레이블을 활용합니다.

예를 들어 대출 부실 예측 모델의 경우, 대출 실행 후 3개월이 지나야 실제로 부실이 발생했는지 알 수 있습니다. 따라서 3개월 전 예측과 현재의 실제 결과를 비교하여 모델 성능을 평가합니다.

주의해야 할 점도 있습니다. 성능이 일시적으로 하락했다고 해서 반드시 모델 드리프트인 것은 아닙니다.

데이터 품질 이슈, 시스템 장애, 또는 단순한 통계적 변동일 수도 있습니다. 따라서 여러 번 연속으로 성능 저하가 관측될 때 드리프트로 판단하는 것이 좋습니다.

김개발 씨는 박시니어 씨의 설명을 들으며 깨달았습니다. 자신이 만든 모니터링 시스템은 데이터 드리프트만 감지하고 있었던 것입니다.

모델 드리프트까지 감지하려면 실제 결과 데이터를 수집하고 성능을 지속적으로 추적하는 시스템이 추가로 필요했습니다.

실전 팁

💡 - 모델 드리프트 감지에는 실제 레이블이 필요하므로, 레이블 수집 파이프라인을 미리 구축해두세요

  • 성능 하락의 원인이 데이터 드리프트인지 모델 드리프트인지 구분하면 더 효과적으로 대응할 수 있습니다

3. PSI를 활용한 드리프트 정량화

김개발 씨는 KS 검정으로 드리프트를 감지하는 데는 성공했지만, 새로운 고민이 생겼습니다. "드리프트가 발생했다는 건 알겠는데, 얼마나 심각한 건지는 어떻게 알 수 있을까요?" 박시니어 씨는 웃으며 대답했습니다.

"PSI라는 지표를 써보면 어떨까?"

**PSI(Population Stability Index)**는 두 분포 사이의 차이를 하나의 숫자로 정량화해주는 지표입니다. 0에 가까울수록 분포가 안정적이고, 값이 클수록 변화가 심합니다.

일반적으로 0.1 미만이면 안정, 0.1~0.25면 주의, 0.25 이상이면 심각한 드리프트로 판단합니다.

다음 코드를 살펴봅시다.

import numpy as np

def calculate_psi(expected, actual, buckets=10):
    # 데이터를 동일한 구간으로 나눕니다
    breakpoints = np.percentile(expected, np.linspace(0, 100, buckets + 1))
    breakpoints[0] = -np.inf
    breakpoints[-1] = np.inf

    # 각 구간별 비율을 계산합니다
    expected_counts = np.histogram(expected, breakpoints)[0] / len(expected)
    actual_counts = np.histogram(actual, breakpoints)[0] / len(actual)

    # 0으로 나누는 것을 방지합니다
    expected_counts = np.clip(expected_counts, 0.001, None)
    actual_counts = np.clip(actual_counts, 0.001, None)

    # PSI 공식을 적용합니다
    psi = np.sum((actual_counts - expected_counts) * np.log(actual_counts / expected_counts))
    return psi

# 사용 예시
train_feature = np.random.normal(100, 15, 1000)
production_feature = np.random.normal(110, 20, 1000)

psi_value = calculate_psi(train_feature, production_feature)
print(f"PSI: {psi_value:.4f}")

드리프트가 발생했다는 사실만으로는 충분하지 않습니다. 얼마나 심각한지, 즉시 조치해야 하는지 아니면 조금 더 지켜봐도 되는지를 판단할 수 있어야 합니다.

이때 유용한 것이 바로 PSI입니다. PSI는 원래 금융권에서 신용 스코어카드의 안정성을 평가하기 위해 개발된 지표입니다.

하지만 그 유용성 덕분에 지금은 머신러닝 분야 전반에서 드리프트 정량화에 널리 사용되고 있습니다. PSI의 계산 원리를 살펴보겠습니다.

먼저 데이터를 여러 구간(버킷)으로 나눕니다. 그 다음 각 구간에 속하는 데이터의 비율을 기준 분포와 현재 분포에서 각각 계산합니다.

마지막으로 이 비율 차이를 로그 스케일로 합산합니다. 위 코드를 자세히 살펴보겠습니다.

np.percentile 함수를 사용하여 기준 분포를 동일한 비율의 구간들로 나눕니다. 10개의 버킷을 사용하면 각 구간에 약 10%의 데이터가 포함됩니다.

그 다음 np.histogram으로 각 구간별 데이터 개수를 세고, 전체 개수로 나누어 비율을 구합니다. 중요한 부분은 np.clip 함수입니다.

어떤 구간에 데이터가 하나도 없으면 비율이 0이 되고, 이후 로그 계산에서 문제가 발생합니다. 따라서 최솟값을 0.001로 제한하여 이 문제를 방지합니다.

실제 업무에서는 PSI 값을 어떻게 해석할까요? 일반적인 기준은 다음과 같습니다.

PSI가 0.1 미만이면 분포가 안정적이므로 특별한 조치가 필요 없습니다. 0.1에서 0.25 사이면 주의가 필요합니다.

원인을 분석하고 추이를 지켜봐야 합니다. 0.25 이상이면 심각한 드리프트로 판단하고, 즉시 원인 분석과 모델 재학습을 검토해야 합니다.

하지만 이 기준이 절대적인 것은 아닙니다. 비즈니스 도메인과 모델의 중요도에 따라 임계값을 조정할 수 있습니다.

예를 들어 의료 진단 모델처럼 오류의 영향이 큰 경우에는 더 낮은 임계값을 적용하여 민감하게 대응할 수 있습니다. 김개발 씨는 자신이 담당하는 추천 모델의 주요 피처 5개에 대해 PSI를 계산하는 대시보드를 만들었습니다.

매일 아침 대시보드를 확인하며 어떤 피처에서 드리프트가 발생하고 있는지, 그 심각도는 어느 정도인지 한눈에 파악할 수 있게 되었습니다.

실전 팁

💡 - 버킷 수는 보통 10개를 사용하지만, 데이터 양에 따라 조정할 수 있습니다

  • PSI는 범주형 변수에도 적용할 수 있습니다. 이 경우 각 범주가 하나의 버킷이 됩니다

4. Evidently 라이브러리 활용하기

PSI 계산 함수를 직접 구현한 김개발 씨는 뿌듯했지만, 곧 현실의 벽에 부딪혔습니다. 피처가 100개가 넘는 모델에서 각각의 PSI를 계산하고, 시각화하고, 알림까지 보내려니 작업량이 만만치 않았습니다.

"혹시 이런 걸 한 번에 해주는 라이브러리가 없을까요?"

Evidently는 머신러닝 모델 모니터링을 위한 오픈소스 라이브러리입니다. 데이터 드리프트, 모델 드리프트, 데이터 품질 등을 자동으로 분석하고 시각화된 리포트를 생성해줍니다.

몇 줄의 코드로 복잡한 드리프트 분석을 수행할 수 있어 실무에서 매우 유용합니다.

다음 코드를 살펴봅시다.

from evidently.report import Report
from evidently.metric_preset import DataDriftPreset
import pandas as pd

# 학습 데이터와 운영 데이터를 준비합니다
reference_data = pd.DataFrame({
    'age': [25, 30, 35, 28, 32, 27, 29, 31, 26, 33],
    'income': [3000, 4500, 5000, 3500, 4800, 3200, 4000, 4700, 3100, 5200],
    'purchase_count': [5, 8, 12, 6, 10, 7, 9, 11, 4, 15]
})

current_data = pd.DataFrame({
    'age': [22, 24, 26, 23, 25, 21, 27, 24, 22, 28],
    'income': [2500, 2800, 3000, 2600, 2900, 2400, 3200, 2700, 2500, 3100],
    'purchase_count': [3, 4, 5, 3, 4, 2, 6, 4, 3, 5]
})

# 드리프트 리포트를 생성합니다
report = Report(metrics=[DataDriftPreset()])
report.run(reference_data=reference_data, current_data=current_data)

# HTML 파일로 저장합니다
report.save_html("drift_report.html")

직접 구현하는 것도 좋지만, 바퀴를 다시 발명할 필요는 없습니다. Evidently는 드리프트 분석을 위해 특별히 설계된 라이브러리로, 많은 기업에서 프로덕션 환경에 사용하고 있습니다.

Evidently의 가장 큰 장점은 단순함입니다. 위 코드에서 볼 수 있듯이, 학습 데이터와 현재 데이터를 준비하고 Report 객체를 생성한 다음 run 메서드를 호출하면 끝입니다.

나머지는 라이브러리가 알아서 처리합니다. DataDriftPreset은 데이터 드리프트 분석에 필요한 모든 것을 포함하고 있습니다.

각 피처별로 적절한 통계 검정을 자동으로 선택합니다. 수치형 변수에는 KS 검정을, 범주형 변수에는 카이제곱 검정을 적용합니다.

또한 분포 히스토그램, 드리프트 점수, 상세 통계량 등을 포함한 종합 리포트를 생성합니다. 생성된 HTML 리포트를 브라우저에서 열면 인터랙티브한 대시보드를 볼 수 있습니다.

각 피처별 드리프트 여부가 색상으로 표시되고, 기준 분포와 현재 분포를 겹쳐서 비교할 수 있습니다. 비개발자인 비즈니스 담당자에게 공유하기에도 좋은 형태입니다.

Evidently는 데이터 드리프트 외에도 다양한 분석을 지원합니다. TargetDriftPreset은 타겟 변수의 드리프트를, DataQualityPreset은 결측값, 중복값, 이상치 등 데이터 품질 이슈를 분석합니다.

ClassificationPerformancePreset은 분류 모델의 성능 지표를 추적합니다. 실무에서는 이 리포트를 CI/CD 파이프라인에 통합하거나, 정기적으로 생성하여 데이터 과학 팀에 공유하는 방식으로 활용합니다.

또한 Evidently는 JSON 형태로도 결과를 내보낼 수 있어서, 이를 파싱하여 알림 시스템과 연동하는 것도 가능합니다. 김개발 씨는 Evidently를 도입한 후 드리프트 모니터링에 들이던 시간을 크게 줄일 수 있었습니다.

매일 자동으로 생성되는 리포트를 통해 전체 피처의 상태를 한눈에 파악하고, 문제가 있는 피처만 집중적으로 분석할 수 있게 되었습니다.

실전 팁

💡 - Evidently는 pip install evidently로 간단히 설치할 수 있습니다

  • Jupyter Notebook에서 report.show()를 사용하면 노트북 내에서 바로 리포트를 확인할 수 있습니다

5. 다변량 드리프트 감지

김개발 씨는 각 피처별로 드리프트를 감지하는 시스템을 잘 구축했습니다. 그런데 이상한 일이 일어났습니다.

개별 피처에서는 드리프트가 감지되지 않는데, 모델 성능은 계속 떨어지고 있었습니다. "각각은 멀쩡한데 왜 전체가 이상하지?"

다변량 드리프트는 개별 피처의 분포는 그대로인데, 피처들 사이의 관계가 변하는 현상입니다. 마치 키와 몸무게 각각의 평균은 그대로인데, 키가 큰 사람이 날씬해지고 키가 작은 사람이 뚱뚱해지는 식으로 상관관계가 뒤바뀌는 것과 같습니다.

PCA나 오토인코더를 활용하여 이런 다변량 드리프트를 감지할 수 있습니다.

다음 코드를 살펴봅시다.

import numpy as np
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

def detect_multivariate_drift(reference_data, current_data, threshold=2.0):
    # 데이터를 표준화합니다
    scaler = StandardScaler()
    ref_scaled = scaler.fit_transform(reference_data)
    curr_scaled = scaler.transform(current_data)

    # PCA로 차원을 축소합니다
    pca = PCA(n_components=2)
    ref_pca = pca.fit_transform(ref_scaled)
    curr_pca = pca.transform(curr_scaled)

    # 기준 분포의 중심점과 평균 거리를 계산합니다
    ref_center = np.mean(ref_pca, axis=0)
    ref_distances = np.linalg.norm(ref_pca - ref_center, axis=1)
    ref_mean_distance = np.mean(ref_distances)

    # 현재 데이터가 기준 중심에서 얼마나 떨어져 있는지 확인합니다
    curr_distances = np.linalg.norm(curr_pca - ref_center, axis=1)
    drift_score = np.mean(curr_distances) / ref_mean_distance

    is_drift = drift_score > threshold
    return is_drift, drift_score

개별 피처만 보면 놓칠 수 있는 드리프트가 있습니다. 이것이 바로 다변량 드리프트입니다.

간단한 예를 들어보겠습니다. 대출 심사 모델에서 소득과 부채라는 두 피처를 사용한다고 가정합니다.

학습 데이터에서는 소득이 높은 사람일수록 부채도 많은 경향이 있었습니다. 즉, 양의 상관관계가 있었습니다.

그런데 경기 침체로 인해 고소득자들이 부채를 줄이기 시작하면서, 소득과 부채 사이의 상관관계가 약해졌습니다. 이 상황에서 소득의 평균과 분포는 그대로이고, 부채의 평균과 분포도 그대로입니다.

개별 피처만 보면 드리프트가 없는 것처럼 보입니다. 하지만 두 피처 사이의 관계가 변했기 때문에, 이 관계를 학습한 모델은 더 이상 제대로 작동하지 않습니다.

이런 다변량 드리프트를 감지하는 방법 중 하나가 **PCA(주성분 분석)**를 활용하는 것입니다. PCA는 여러 피처를 몇 개의 주요 성분으로 압축합니다.

이 과정에서 피처들 사이의 관계(공분산)가 반영됩니다. 따라서 PCA로 변환한 공간에서 분포가 달라졌다면, 피처 간 관계가 변했다고 판단할 수 있습니다.

위 코드의 동작 원리를 살펴보겠습니다. 먼저 StandardScaler로 데이터를 표준화합니다.

이는 피처들의 스케일 차이가 결과에 영향을 미치지 않도록 하기 위함입니다. 그 다음 PCA로 2차원으로 축소합니다.

기준 데이터의 중심점을 계산하고, 현재 데이터가 이 중심에서 얼마나 떨어져 있는지를 측정합니다. drift_score는 현재 데이터의 평균 거리를 기준 데이터의 평균 거리로 나눈 값입니다.

이 값이 1에 가까우면 두 분포가 비슷한 것이고, 1보다 훨씬 크면 현재 데이터가 기준 분포의 중심에서 멀리 떨어져 있다는 뜻입니다. 실무에서는 PCA 대신 오토인코더를 사용하기도 합니다.

오토인코더는 비선형적인 관계까지 포착할 수 있어서 더 복잡한 패턴의 드리프트를 감지할 수 있습니다. 기준 데이터로 오토인코더를 학습시킨 다음, 현재 데이터의 재구성 오류가 크면 드리프트가 발생했다고 판단합니다.

김개발 씨는 다변량 드리프트 감지 기능을 추가한 후, 이전에는 발견하지 못했던 미묘한 변화들을 포착할 수 있게 되었습니다. 개별 피처만 봤을 때는 정상이었지만, 실제로는 사용자들의 행동 패턴이 변하고 있었던 것입니다.

실전 팁

💡 - PCA의 n_components는 전체 분산의 95%를 설명하는 수준으로 설정하는 것이 일반적입니다

  • 오토인코더를 사용할 경우 학습 데이터의 재구성 오류 분포를 먼저 파악하고, 이를 기준으로 임계값을 설정하세요

6. 드리프트 대응 전략

드리프트 감지 시스템을 완성한 김개발 씨 앞에 새로운 과제가 놓였습니다. 알림은 매일같이 울리는데, 매번 모델을 재학습해야 할까요?

아니면 무시해도 되는 걸까요? 박시니어 씨는 말했습니다.

"감지하는 것만큼 중요한 게 대응 전략이야."

드리프트에 대응하는 전략은 크게 세 가지로 나눌 수 있습니다. 반응적 재학습은 드리프트가 감지되면 재학습하는 방식이고, 주기적 재학습은 일정 간격으로 재학습하는 방식입니다.

온라인 학습은 새 데이터가 들어올 때마다 실시간으로 모델을 업데이트합니다. 각 전략은 장단점이 있어서 상황에 맞게 선택해야 합니다.

다음 코드를 살펴봅시다.

from datetime import datetime, timedelta
from typing import Callable, Optional

class DriftResponseManager:
    def __init__(self, model, retrain_func: Callable):
        self.model = model
        self.retrain_func = retrain_func
        self.last_retrain = datetime.now()
        self.drift_count = 0

    def handle_drift(self, drift_score: float, strategy: str = "reactive"):
        if strategy == "reactive":
            # 드리프트 점수가 임계값을 넘으면 즉시 재학습
            if drift_score > 0.25:
                print("심각한 드리프트 감지 - 즉시 재학습 시작")
                self.model = self.retrain_func()
                self.last_retrain = datetime.now()

        elif strategy == "periodic":
            # 마지막 재학습으로부터 7일이 지났으면 재학습
            if datetime.now() - self.last_retrain > timedelta(days=7):
                print("주기적 재학습 실행")
                self.model = self.retrain_func()
                self.last_retrain = datetime.now()

        elif strategy == "hybrid":
            # 심각한 드리프트이거나 3일 경과시 재학습
            days_since_retrain = (datetime.now() - self.last_retrain).days
            if drift_score > 0.25 or days_since_retrain > 3:
                print(f"하이브리드 재학습 (드리프트: {drift_score:.2f}, 경과일: {days_since_retrain})")
                self.model = self.retrain_func()

드리프트를 감지했다고 해서 항상 재학습이 정답은 아닙니다. 재학습에는 시간과 비용이 들고, 잘못된 타이밍에 재학습하면 오히려 성능이 떨어질 수도 있습니다.

따라서 체계적인 대응 전략이 필요합니다. 첫 번째 전략은 반응적 재학습입니다.

드리프트가 감지되면 그때 재학습을 수행합니다. 장점은 필요할 때만 재학습하므로 리소스를 효율적으로 사용할 수 있다는 것입니다.

단점은 드리프트가 감지된 시점에는 이미 성능 저하가 발생했다는 것입니다. 두 번째 전략은 주기적 재학습입니다.

드리프트 감지와 관계없이 일정 간격(예: 매주, 매월)으로 재학습합니다. 장점은 예측 가능하고 관리하기 쉽다는 것입니다.

단점은 불필요한 재학습이 발생할 수 있고, 급격한 드리프트에 대응이 늦을 수 있다는 것입니다. 세 번째 전략은 온라인 학습입니다.

새로운 데이터가 들어올 때마다 모델을 점진적으로 업데이트합니다. 변화에 실시간으로 대응할 수 있지만, 모든 모델이 온라인 학습을 지원하는 것은 아니고, 노이즈에 민감해질 수 있다는 단점이 있습니다.

위 코드의 DriftResponseManager 클래스는 이런 전략들을 구현한 예시입니다. handle_drift 메서드는 전략에 따라 다르게 동작합니다.

reactive 전략은 드리프트 점수가 0.25를 넘으면 즉시 재학습합니다. periodic 전략은 마지막 재학습으로부터 7일이 지났는지만 확인합니다.

hybrid 전략은 두 조건을 함께 고려합니다. 실무에서는 하이브리드 전략을 가장 많이 사용합니다.

평소에는 주기적으로 재학습하되, 심각한 드리프트가 감지되면 즉시 대응하는 방식입니다. 이렇게 하면 일상적인 변화에는 효율적으로 대응하면서, 급격한 변화에도 빠르게 반응할 수 있습니다.

재학습을 결정할 때 고려해야 할 요소가 몇 가지 있습니다. 첫째, 재학습 비용입니다.

GPU 비용, 소요 시간, 운영 인력의 공수 등을 고려해야 합니다. 둘째, 비즈니스 영향입니다.

모델 성능 저하가 매출에 얼마나 영향을 미치는지 따져봐야 합니다. 셋째, 데이터 품질입니다.

최근 데이터에 이상이 있다면 재학습이 오히려 독이 될 수 있습니다. 김개발 씨는 팀과 논의 끝에 하이브리드 전략을 채택했습니다.

매주 일요일 새벽에 자동으로 재학습이 실행되고, PSI가 0.25를 넘는 심각한 드리프트가 감지되면 담당자에게 알림을 보내 수동으로 재학습 여부를 결정하도록 했습니다.

실전 팁

💡 - 재학습 전후의 성능을 반드시 비교하고, 성능이 개선되었을 때만 새 모델을 배포하세요

  • A/B 테스트를 통해 새 모델의 실제 비즈니스 효과를 검증하는 것도 좋은 방법입니다

7. 드리프트 모니터링 대시보드 구축

드리프트 감지와 대응 전략까지 갖춘 김개발 씨에게 팀장님이 부탁했습니다. "이 내용을 비개발자 동료들도 볼 수 있게 대시보드로 만들어줄 수 있어?" 김개발 씨는 생각했습니다.

"CLI로 결과를 보는 건 나만 편한 거였구나."

모니터링 대시보드는 드리프트 상태를 한눈에 파악할 수 있게 해주는 시각화 도구입니다. 현재 드리프트 점수, 시간에 따른 추이, 피처별 상태 등을 그래프와 표로 보여줍니다.

팀 전체가 모델 상태를 공유하고 의사결정에 활용할 수 있어서 MLOps에서 필수적인 요소입니다.

다음 코드를 살펴봅시다.

import json
from datetime import datetime
from typing import Dict, List

class DriftDashboard:
    def __init__(self):
        self.metrics_history: List[Dict] = []

    def log_metrics(self, feature_name: str, psi: float, ks_pvalue: float):
        # 메트릭을 기록합니다
        record = {
            "timestamp": datetime.now().isoformat(),
            "feature": feature_name,
            "psi": psi,
            "ks_pvalue": ks_pvalue,
            "status": self._get_status(psi)
        }
        self.metrics_history.append(record)

    def _get_status(self, psi: float) -> str:
        if psi < 0.1:
            return "stable"
        elif psi < 0.25:
            return "warning"
        else:
            return "critical"

    def get_summary(self) -> Dict:
        # 최신 상태 요약을 반환합니다
        if not self.metrics_history:
            return {"message": "No data available"}

        latest_by_feature = {}
        for record in self.metrics_history:
            latest_by_feature[record["feature"]] = record

        return {
            "total_features": len(latest_by_feature),
            "stable": sum(1 for r in latest_by_feature.values() if r["status"] == "stable"),
            "warning": sum(1 for r in latest_by_feature.values() if r["status"] == "warning"),
            "critical": sum(1 for r in latest_by_feature.values() if r["status"] == "critical")
        }

좋은 모니터링 시스템은 기술적으로 뛰어난 것만으로는 충분하지 않습니다. 정보를 필요로 하는 모든 사람이 쉽게 이해할 수 있어야 합니다.

데이터 과학자뿐만 아니라 프로덕트 매니저, 비즈니스 분석가도 모델 상태를 파악할 수 있어야 합니다. 위 코드의 DriftDashboard 클래스는 대시보드의 핵심 로직을 담고 있습니다.

log_metrics 메서드는 피처별 드리프트 지표를 기록합니다. PSI 값과 KS 검정의 p-value를 함께 저장하고, PSI를 기준으로 상태를 자동으로 분류합니다.

상태는 세 가지로 나뉩니다. stable은 PSI가 0.1 미만으로 안정적인 상태입니다.

warning은 0.1에서 0.25 사이로 주의가 필요한 상태입니다. critical은 0.25 이상으로 즉각적인 조치가 필요한 상태입니다.

이런 상태 분류가 있으면 비전문가도 현재 상황을 직관적으로 파악할 수 있습니다. get_summary 메서드는 현재 모든 피처의 상태를 요약해줍니다.

전체 피처 수와 각 상태별 피처 수를 반환합니다. "전체 50개 피처 중 45개는 안정, 3개는 주의, 2개는 위험" 같은 형태로 한눈에 상황을 파악할 수 있습니다.

실제 대시보드를 구축할 때는 Grafana, Streamlit, Apache Superset 같은 도구를 활용합니다. 위 클래스에서 생성한 데이터를 이런 도구에 연결하면 시각적으로 아름다운 대시보드를 만들 수 있습니다.

대시보드에 포함하면 좋은 요소들이 있습니다. 첫째, 전체 상태 요약입니다.

원형 차트나 게이지 차트로 현재 상태를 한눈에 보여줍니다. 둘째, 시계열 그래프입니다.

시간에 따른 PSI 변화 추이를 보여주어 드리프트가 점진적인지 급격한지 파악할 수 있게 합니다. 셋째, 피처별 상세 테이블입니다.

각 피처의 현재 PSI, 상태, 마지막 업데이트 시간 등을 표로 보여줍니다. 알림 기능도 중요합니다.

critical 상태인 피처가 발생하면 슬랙이나 이메일로 알림을 보내도록 설정합니다. 하지만 너무 민감하게 설정하면 알림 피로가 생기므로, 일정 시간 이상 지속될 때만 알림을 보내는 것이 좋습니다.

김개발 씨는 Streamlit으로 간단한 대시보드를 만들어 팀 내부에 공유했습니다. 매일 아침 팀원들이 대시보드를 확인하며 모델 상태를 논의하는 것이 새로운 루틴이 되었습니다.

덕분에 문제가 발생해도 빠르게 인지하고 대응할 수 있게 되었습니다.

실전 팁

💡 - 대시보드 URL을 팀 위키나 노션에 고정해두면 접근성이 높아집니다

  • 주간 또는 월간 드리프트 리포트를 자동으로 생성하여 이메일로 발송하는 것도 좋은 방법입니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Python#DataDrift#ModelDrift#MLOps#Monitoring#Data Science

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.