🤖

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

⚠️

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

이미지 로딩 중...

모델 드리프트 감지 및 대응 전략 완벽 가이드 - 슬라이드 1/8
A

AI Generated

2025. 11. 30. · 21 Views

모델 드리프트 감지 및 대응 전략 완벽 가이드

머신러닝 모델이 시간이 지나면서 성능이 떨어지는 현상인 드리프트를 감지하고 대응하는 방법을 다룹니다. Evidently AI, Alibi Detect 등 실무 도구와 자동화 전략을 초급 개발자 눈높이에서 설명합니다.


목차

  1. Data_Drift와_Concept_Drift_개념
  2. Evidently_AI로_드리프트_감지
  3. Alibi_Detect_활용하기
  4. 통계적_테스트_적용
  5. 자동_알림_시스템_구축
  6. 재학습_트리거_설정
  7. AB_테스팅으로_검증

1. Data Drift와 Concept Drift 개념

김개발 씨는 6개월 전에 배포한 추천 모델의 성능 지표를 확인하다가 깜짝 놀랐습니다. 분명히 배포 당시에는 정확도가 92%였는데, 지금은 78%까지 떨어져 있었습니다.

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

**드리프트(Drift)**란 머신러닝 모델이 학습했던 데이터와 현재 입력되는 데이터 사이에 차이가 생기는 현상입니다. 마치 어제의 날씨를 기반으로 내일 날씨를 예측하려는 것과 같습니다.

세상은 변하는데 모델은 과거에 머물러 있으면 당연히 예측이 빗나갈 수밖에 없습니다.

다음 코드를 살펴봅시다.

# 드리프트 유형을 이해하기 위한 간단한 예시
import numpy as np

# 학습 당시의 데이터 분포 (6개월 전)
train_ages = np.random.normal(loc=35, scale=10, size=1000)  # 평균 35세
train_income = np.random.normal(loc=50000, scale=15000, size=1000)

# 현재 들어오는 데이터 분포 (Data Drift 발생)
current_ages = np.random.normal(loc=25, scale=8, size=1000)  # 평균 25세로 변화
current_income = np.random.normal(loc=45000, scale=20000, size=1000)

# 분포 변화 확인
print(f"학습 데이터 평균 나이: {train_ages.mean():.1f}")
print(f"현재 데이터 평균 나이: {current_ages.mean():.1f}")
print(f"차이: {abs(train_ages.mean() - current_ages.mean()):.1f}세")

김개발 씨는 입사 1년 차 ML 엔지니어입니다. 첫 프로젝트로 고객 이탈 예측 모델을 만들었고, 성공적으로 배포까지 마쳤습니다.

그런데 반년이 지난 어느 날, 마케팅팀에서 연락이 왔습니다. "요즘 예측이 잘 안 맞는 것 같아요." 선배 개발자 박시니어 씨가 김개발 씨 옆에 앉았습니다.

"혹시 드리프트에 대해 들어봤어요?" 김개발 씨는 고개를 저었습니다. "아니요, 그게 뭔가요?" 박시니어 씨가 설명을 시작했습니다.

"우리가 모델을 학습시킬 때 사용한 데이터가 있잖아요. 그런데 시간이 지나면 세상이 변해요.

사용자들의 행동 패턴도 바뀌고, 시장 상황도 달라지죠." 쉽게 비유하자면 드리프트는 마치 오래된 지도를 들고 새로운 도시를 여행하는 것과 같습니다. 10년 전 지도에는 없던 건물이 생기고, 있던 도로가 사라지기도 합니다.

지도 자체는 변하지 않았지만, 현실이 달라졌기 때문에 길을 잃게 되는 것입니다. 드리프트는 크게 두 가지로 나뉩니다.

첫 번째는 Data Drift입니다. 이것은 입력 데이터의 분포 자체가 변하는 경우입니다.

예를 들어 쇼핑몰 모델을 학습할 때는 주 고객층이 30~40대였는데, 마케팅 전략이 바뀌면서 20대 고객이 급증했다면 이것이 Data Drift입니다. 두 번째는 Concept Drift입니다.

이것은 더 까다롭습니다. 입력과 출력 사이의 관계 자체가 변하는 것입니다.

코로나19 팬데믹을 생각해보세요. 이전에는 "집에서 보내는 시간이 길면 온라인 쇼핑을 많이 한다"는 패턴이 없었는데, 갑자기 이 관계가 생겼습니다.

위의 코드를 살펴보겠습니다. train_ages는 모델 학습 당시의 나이 분포로, 평균 35세입니다.

그런데 current_ages를 보면 평균이 25세로 바뀌었습니다. 이런 변화가 바로 Data Drift입니다.

모델은 35세 중심의 데이터로 학습했는데, 갑자기 25세 데이터가 들어오니 예측이 빗나가는 것입니다. 실제 현업에서 드리프트가 발생하는 상황은 다양합니다.

계절 변화로 인한 소비 패턴 변화, 경쟁사의 신제품 출시, 경제 위기, 새로운 트렌드의 등장 등이 있습니다. 특히 추천 시스템이나 금융 사기 탐지 모델은 드리프트에 매우 취약합니다.

중요한 점은 드리프트가 발생해도 모델이 에러를 뱉지 않는다는 것입니다. 조용히 틀린 예측을 계속 내놓습니다.

그래서 모니터링이 필수입니다. 모델 성능이 서서히 떨어지고 있다는 사실을 늦게 알수록 비즈니스 손실은 커집니다.

김개발 씨가 물었습니다. "그러면 어떻게 해야 하나요?" 박시니어 씨가 미소 지었습니다.

"다행히 좋은 도구들이 많아요. 다음으로 Evidently AI에 대해 알아볼까요?"

실전 팁

💡 - 모델 배포 시점의 데이터 통계를 반드시 저장해두세요. 나중에 비교 기준이 됩니다.

  • Data Drift와 Concept Drift를 구분해서 모니터링해야 올바른 대응 전략을 세울 수 있습니다.

2. Evidently AI로 드리프트 감지

박시니어 씨의 조언을 듣고 김개발 씨는 드리프트 감지 도구를 찾아보기 시작했습니다. 구글링을 하다 보니 Evidently AI라는 이름이 계속 눈에 띄었습니다.

오픈소스인 데다 시각화까지 예쁘게 해준다니, 한번 사용해보기로 했습니다.

Evidently AI는 ML 모델의 데이터 품질과 드리프트를 모니터링하는 오픈소스 도구입니다. 마치 자동차 계기판처럼 모델의 건강 상태를 한눈에 보여줍니다.

설치가 간단하고 시각적 리포트를 생성해주어 비개발자도 이해하기 쉽습니다.

다음 코드를 살펴봅시다.

# Evidently AI로 Data Drift 감지하기
from evidently.report import Report
from evidently.metric_preset import DataDriftPreset
import pandas as pd

# 학습 데이터 (reference)
reference_data = pd.DataFrame({
    'age': [35, 42, 28, 55, 38, 45, 33, 50, 29, 41],
    'income': [50000, 65000, 42000, 80000, 55000, 70000, 48000, 75000, 40000, 62000],
    'purchase_count': [5, 8, 3, 12, 6, 9, 4, 10, 2, 7]
})

# 현재 데이터 (current) - 분포가 달라짐
current_data = pd.DataFrame({
    'age': [22, 25, 21, 28, 24, 26, 23, 27, 20, 25],
    'income': [35000, 38000, 32000, 45000, 36000, 40000, 34000, 42000, 30000, 37000],
    'purchase_count': [2, 3, 1, 4, 2, 3, 2, 4, 1, 3]
})

# 드리프트 리포트 생성
report = Report(metrics=[DataDriftPreset()])
report.run(reference_data=reference_data, current_data=current_data)
report.save_html('drift_report.html')

김개발 씨는 터미널을 열고 pip install evidently를 입력했습니다. 설치가 순식간에 끝났습니다.

"오픈소스라 무료인 데다 설치도 간단하네요." Evidently AI의 핵심 개념은 reference datacurrent data의 비교입니다. reference data는 모델이 학습할 때 사용한 데이터이고, current data는 지금 실시간으로 들어오는 데이터입니다.

이 둘을 비교해서 분포가 얼마나 달라졌는지 측정합니다. 마치 건강검진을 생각해보면 됩니다.

작년 검진 결과가 reference이고, 올해 검진 결과가 current입니다. 혈압이 120에서 160으로 올랐다면 뭔가 문제가 생긴 것이죠.

Evidently도 같은 방식으로 데이터의 건강 상태를 체크합니다. 위 코드에서 DataDriftPreset()은 Evidently가 제공하는 사전 설정 메트릭입니다.

이것을 사용하면 각 컬럼별로 분포 변화를 자동으로 분석합니다. 숫자형 컬럼은 통계적 검정을, 범주형 컬럼은 카이제곱 검정을 적용합니다.

report.run() 메서드가 실행되면 Evidently는 내부적으로 여러 통계 테스트를 수행합니다. Kolmogorov-Smirnov 검정, Wasserstein 거리 등을 계산해서 두 분포가 통계적으로 유의미하게 다른지 판단합니다.

생성된 HTML 리포트를 브라우저에서 열면 깔끔한 대시보드가 나타납니다. 각 컬럼별로 드리프트 여부가 빨간색 또는 녹색으로 표시됩니다.

또한 분포를 비교하는 히스토그램도 함께 보여줍니다. 김개발 씨가 리포트를 열어보니 age 컬럼에 빨간 경고가 떠 있었습니다.

"평균 나이가 35세에서 24세로 바뀌었다고 나오네요. 확실히 드리프트가 발생했구나." 실무에서는 이 리포트를 정기적으로 생성하도록 자동화합니다.

매일 아침 어젯밤 들어온 데이터로 리포트를 만들고, 드리프트가 감지되면 슬랙으로 알림을 보내는 식입니다. Evidently는 리포트 외에도 테스트 기능을 제공합니다.

TestSuite를 사용하면 "나이 컬럼의 드리프트가 0.1 이하여야 한다" 같은 조건을 설정하고, 통과 여부를 True/False로 받을 수 있습니다. CI/CD 파이프라인에 넣기 좋습니다.

박시니어 씨가 덧붙였습니다. "Evidently는 시작하기 좋은 도구예요.

하지만 더 세밀한 제어가 필요하면 Alibi Detect도 알아두는 게 좋아요."

실전 팁

💡 - reference data는 모델 학습에 사용한 원본 데이터를 그대로 저장해두세요.

  • 리포트를 HTML 대신 JSON으로 저장하면 프로그래밍 방식으로 처리하기 편합니다.

3. Alibi Detect 활용하기

김개발 씨는 Evidently로 기본적인 드리프트 감지는 할 수 있게 되었습니다. 하지만 프로젝트가 복잡해지면서 더 정교한 감지가 필요해졌습니다.

특히 딥러닝 모델에서 발생하는 미묘한 드리프트를 잡아내고 싶었습니다. 박시니어 씨가 추천한 Alibi Detect를 살펴볼 시간입니다.

Alibi Detect는 Seldon에서 만든 드리프트 감지 라이브러리로, 다양한 통계적 방법과 딥러닝 기반 감지 기법을 제공합니다. 마치 전문 의료 장비처럼 정밀한 진단이 가능합니다.

표 데이터뿐 아니라 이미지, 텍스트 데이터의 드리프트도 감지할 수 있습니다.

다음 코드를 살펴봅시다.

# Alibi Detect로 드리프트 감지하기
from alibi_detect.cd import KSDrift
import numpy as np

# 학습 데이터로 드리프트 감지기 초기화
reference_data = np.random.normal(loc=50, scale=10, size=(1000, 3))
detector = KSDrift(reference_data, p_val=0.05)

# 드리프트가 없는 데이터 테스트
no_drift_data = np.random.normal(loc=50, scale=10, size=(500, 3))
result_no_drift = detector.predict(no_drift_data)
print(f"드리프트 없는 데이터: {result_no_drift['data']['is_drift']}")

# 드리프트가 있는 데이터 테스트
drift_data = np.random.normal(loc=70, scale=15, size=(500, 3))
result_drift = detector.predict(drift_data)
print(f"드리프트 있는 데이터: {result_drift['data']['is_drift']}")
print(f"p-value: {result_drift['data']['p_val']}")

김개발 씨는 pip install alibi-detect를 실행하고 문서를 읽기 시작했습니다. Alibi Detect는 Evidently보다 낮은 수준의 API를 제공하지만, 그만큼 세밀한 제어가 가능했습니다.

Alibi Detect의 가장 큰 장점은 다양한 감지 알고리즘을 선택할 수 있다는 것입니다. 위 코드에서 사용한 KSDrift는 Kolmogorov-Smirnov 검정 기반입니다.

이 외에도 MMDDrift, ChiSquareDrift, TabularDrift 등 상황에 맞는 알고리즘을 고를 수 있습니다. KSDrift를 자세히 살펴보겠습니다.

초기화할 때 reference_data를 넣으면 감지기가 이 데이터의 분포를 기억합니다. 그 후 predict() 메서드로 새 데이터를 전달하면, 기억한 분포와 비교해서 드리프트 여부를 판단합니다.

p_val=0.05는 유의수준입니다. 쉽게 말해 "두 분포가 같을 확률이 5% 미만이면 드리프트로 판단한다"는 의미입니다.

이 값을 낮추면 더 엄격해지고, 높이면 느슨해집니다. 결과에서 is_drift는 불리언 값으로, True면 드리프트가 감지된 것입니다.

p_val 배열은 각 피처별 p-value를 보여줍니다. 이 값이 0.05보다 작으면 해당 피처에서 드리프트가 발생한 것입니다.

박시니어 씨가 설명을 덧붙였습니다. "Alibi Detect의 진짜 강점은 딥러닝 기반 감지야.

이미지 데이터처럼 복잡한 경우에는 전통적인 통계 검정으로는 한계가 있거든." 실제로 Alibi Detect는 Autoencoder, VAE 기반의 드리프트 감지를 지원합니다. 이미지의 경우 픽셀값 분포를 직접 비교하기보다, 오토인코더로 압축된 잠재 공간에서 비교하는 것이 더 효과적입니다.

또한 온라인 드리프트 감지 기능도 있습니다. 배치 단위가 아니라 데이터가 한 건씩 들어올 때마다 실시간으로 드리프트를 체크할 수 있습니다.

스트리밍 환경에서 유용합니다. 김개발 씨가 물었습니다.

"그러면 Evidently와 Alibi Detect 중에 뭘 써야 하나요?" 박시니어 씨가 답했습니다. "둘 다 써도 돼요.

Evidently로 전체적인 현황을 파악하고, Alibi Detect로 세밀한 분석을 하는 거죠." 실무에서는 Alibi Detect의 감지기를 pickle로 저장해두고 API 서버에서 로드해서 사용합니다. 새 요청이 들어올 때마다 드리프트 점수를 계산하고, 임계값을 넘으면 로그를 남기는 식입니다.

실전 팁

💡 - 피처 수가 많으면 MMDDrift보다 KSDrift가 더 빠릅니다.

  • 딥러닝 모델의 드리프트를 감지할 때는 모델의 마지막 은닉층 출력으로 비교하면 효과적입니다.

4. 통계적 테스트 적용

김개발 씨가 드리프트 감지 도구들을 익히면서 궁금증이 생겼습니다. "이 도구들이 내부적으로 어떻게 드리프트를 판단하는 거지?" 박시니어 씨가 웃으며 말했습니다.

"좋은 질문이에요. 도구에만 의존하지 말고, 원리를 알아야 상황에 맞게 조정할 수 있어요."

드리프트 감지의 핵심은 통계적 가설 검정입니다. 두 데이터셋이 같은 분포에서 왔는지를 통계적으로 판단합니다.

마치 두 주머니에서 꺼낸 구슬의 색깔 비율이 같은지 확인하는 것과 같습니다. 주요 방법으로 KS 검정, 카이제곱 검정, PSI 등이 있습니다.

다음 코드를 살펴봅시다.

# 통계적 테스트로 직접 드리프트 감지하기
from scipy import stats
import numpy as np

def detect_drift_ks(reference, current, threshold=0.05):
    """KS 검정으로 드리프트 감지"""
    statistic, p_value = stats.ks_2samp(reference, current)
    is_drift = p_value < threshold
    return {'is_drift': is_drift, 'p_value': p_value, 'statistic': statistic}

def calculate_psi(reference, current, bins=10):
    """PSI (Population Stability Index) 계산"""
    ref_hist, bin_edges = np.histogram(reference, bins=bins)
    cur_hist, _ = np.histogram(current, bins=bin_edges)

    # 0 방지를 위해 작은 값 추가
    ref_pct = (ref_hist + 0.001) / len(reference)
    cur_pct = (cur_hist + 0.001) / len(current)

    psi = np.sum((cur_pct - ref_pct) * np.log(cur_pct / ref_pct))
    return psi  # PSI > 0.2면 드리프트 의심

# 테스트
ref_data = np.random.normal(50, 10, 1000)
cur_data = np.random.normal(55, 12, 1000)
print(f"KS 검정: {detect_drift_ks(ref_data, cur_data)}")
print(f"PSI: {calculate_psi(ref_data, cur_data):.4f}")

박시니어 씨가 화이트보드 앞에 섰습니다. "통계적 가설 검정의 기본 아이디어는 간단해요.

귀무가설과 대립가설을 세우고, 데이터를 보고 판단하는 거예요." 드리프트 감지에서 귀무가설은 "두 데이터셋이 같은 분포에서 왔다"입니다. 대립가설은 "두 데이터셋이 다른 분포에서 왔다"입니다.

검정 결과 p-value가 충분히 작으면 귀무가설을 기각하고, 드리프트가 있다고 판단합니다. **KS 검정(Kolmogorov-Smirnov test)**은 가장 널리 쓰이는 방법입니다.

두 분포의 누적분포함수(CDF)가 가장 많이 차이나는 지점을 찾습니다. 이 최대 거리가 KS 통계량이 됩니다.

통계량이 크면 두 분포가 다르다는 뜻입니다. 위 코드의 detect_drift_ks 함수를 보겠습니다.

scipy.stats.ks_2samp는 두 샘플에 대한 KS 검정을 수행합니다. 반환값 중 statistic은 KS 통계량이고, p_value는 이 통계량이 우연히 나올 확률입니다.

**PSI(Population Stability Index)**는 금융권에서 특히 많이 사용합니다. 두 분포를 여러 구간으로 나누고, 각 구간의 비율 차이를 계산합니다.

일반적으로 PSI가 0.1 미만이면 안정적, 0.1~0.2면 약간의 변화, 0.2 이상이면 심각한 드리프트로 해석합니다. calculate_psi 함수를 보면 먼저 히스토그램으로 구간별 빈도를 구합니다.

그 후 각 구간의 비율을 계산하고, 로그를 이용한 공식으로 PSI를 산출합니다. 0으로 나누는 것을 방지하기 위해 작은 값을 더하는 것이 중요합니다.

범주형 데이터의 경우에는 카이제곱 검정이 적합합니다. 예를 들어 고객의 직업 분포가 바뀌었는지 확인할 때 사용합니다.

scipy.stats.chi2_contingency 함수로 쉽게 계산할 수 있습니다. 김개발 씨가 고개를 끄덕였습니다.

"그러면 어떤 검정을 써야 하나요?" 박시니어 씨가 정리해주었습니다. "연속형 수치 데이터는 KS 검정, 범주형은 카이제곱, 금융 도메인이면 PSI를 추천해요.

상황에 따라 여러 개를 조합해서 쓰기도 해요." 주의할 점도 있습니다. 샘플 크기가 너무 크면 아주 작은 차이도 통계적으로 유의미하게 나올 수 있습니다.

반대로 너무 작으면 큰 차이도 놓칠 수 있습니다. 통계적 유의성과 실질적 유의성을 구분해야 합니다.

실전 팁

💡 - p-value만 보지 말고 effect size도 함께 확인하세요. 작은 차이가 비즈니스에 큰 영향을 주는지 판단이 필요합니다.

  • 여러 피처를 동시에 검정할 때는 다중 검정 보정(Bonferroni 등)을 고려하세요.

5. 자동 알림 시스템 구축

김개발 씨는 이제 드리프트를 감지하는 방법을 알게 되었습니다. 하지만 매번 수동으로 확인하는 것은 비효율적입니다.

"드리프트가 발생하면 자동으로 알려주는 시스템이 필요해요." 박시니어 씨가 동의했습니다. "맞아요.

MLOps의 핵심은 자동화예요."

자동 알림 시스템은 드리프트가 감지되었을 때 즉시 담당자에게 통보하는 시스템입니다. 마치 집에 설치한 화재 경보기와 같습니다.

불이 나면 자동으로 경보를 울리고, 소방서에 연락까지 해주는 것처럼, 드리프트 알림 시스템도 문제를 자동으로 감지하고 관계자에게 알립니다.

다음 코드를 살펴봅시다.

# 드리프트 감지 및 Slack 알림 시스템
import requests
from datetime import datetime
from evidently.report import Report
from evidently.metric_preset import DataDriftPreset
import json

class DriftAlertSystem:
    def __init__(self, slack_webhook_url):
        self.slack_webhook = slack_webhook_url
        self.alert_history = []

    def check_drift(self, reference_data, current_data):
        """드리프트 검사 및 결과 반환"""
        report = Report(metrics=[DataDriftPreset()])
        report.run(reference_data=reference_data, current_data=current_data)
        result = report.as_dict()

        drift_detected = result['metrics'][0]['result']['dataset_drift']
        drift_share = result['metrics'][0]['result']['drift_share']
        return drift_detected, drift_share

    def send_slack_alert(self, drift_share, drifted_columns):
        """Slack으로 알림 전송"""
        message = {
            "text": f":warning: 드리프트 감지됨!",
            "blocks": [
                {"type": "header", "text": {"type": "plain_text", "text": "모델 드리프트 경고"}},
                {"type": "section", "text": {"type": "mrkdwn",
                    "text": f"*감지 시각:* {datetime.now()}\n*드리프트 비율:* {drift_share:.1%}\n*영향 컬럼:* {', '.join(drifted_columns)}"}}
            ]
        }
        requests.post(self.slack_webhook, json=message)

김개발 씨는 Slack 웹훅 URL을 발급받았습니다. Slack 앱 설정에서 Incoming Webhook을 추가하면 URL을 받을 수 있습니다.

이 URL로 POST 요청을 보내면 지정한 채널에 메시지가 전송됩니다. 위 코드의 DriftAlertSystem 클래스를 살펴보겠습니다.

생성자에서 Slack 웹훅 URL을 받고, check_drift 메서드에서 Evidently를 이용해 드리프트를 검사합니다. 드리프트가 감지되면 send_slack_alert 메서드가 알림을 보냅니다.

Slack 메시지는 Block Kit을 사용해 구성했습니다. 단순 텍스트보다 구조화된 블록을 사용하면 더 보기 좋은 알림을 만들 수 있습니다.

드리프트 비율, 영향받은 컬럼, 감지 시각 등 핵심 정보를 한눈에 볼 수 있습니다. 박시니어 씨가 추가 팁을 알려주었습니다.

"알림 피로를 조심해야 해요. 너무 자주 알림이 오면 사람들이 무시하게 되거든요." 이를 방지하기 위해 몇 가지 전략이 있습니다.

첫째, 임계값 설정입니다. 아주 작은 드리프트까지 알리면 알림이 너무 많아집니다.

drift_share가 10% 이상일 때만 알림을 보내는 식으로 필터링합니다. 둘째, 쿨다운 기간입니다.

같은 종류의 알림은 일정 시간 동안 다시 보내지 않습니다. 예를 들어 30분 내에 같은 모델에서 드리프트가 여러 번 감지되어도 알림은 한 번만 보냅니다.

셋째, 심각도 분류입니다. 드리프트 정도에 따라 알림 채널을 다르게 합니다.

경미한 드리프트는 모니터링 채널에, 심각한 드리프트는 긴급 채널에 보내고 호출까지 합니다. 이 시스템을 주기적으로 실행하려면 스케줄러가 필요합니다.

간단하게는 Linux cron을 사용할 수 있고, 복잡한 워크플로우는 Apache Airflow나 Prefect를 사용합니다. 김개발 씨가 crontab을 설정했습니다.

매일 새벽 2시에 전날 데이터로 드리프트를 검사하고, 문제가 있으면 Slack으로 알림을 보내는 스크립트를 등록했습니다. 실무에서는 대시보드와 함께 사용하면 효과적입니다.

Grafana나 Datadog에 드리프트 지표를 시각화하고, 임계값을 넘으면 알림이 발생하도록 설정합니다. 이렇게 하면 과거 추이도 함께 볼 수 있습니다.

실전 팁

💡 - Slack 외에도 PagerDuty, Opsgenie 등 온콜 시스템과 연동하면 심각한 드리프트에 빠르게 대응할 수 있습니다.

  • 알림 메시지에 관련 대시보드 링크를 포함시키면 문제 파악이 빨라집니다.

6. 재학습 트리거 설정

드리프트가 감지되면 어떻게 해야 할까요? 김개발 씨가 물었습니다.

"알림을 받았는데, 그 다음에는요?" 박시니어 씨가 답했습니다. "드리프트가 심하면 모델을 다시 학습시켜야 해요.

이것도 자동화할 수 있어요."

재학습 트리거는 드리프트가 일정 수준을 넘으면 자동으로 모델 학습 파이프라인을 실행하는 메커니즘입니다. 마치 자동차의 자동 변속기처럼, 상황에 맞게 알아서 기어를 바꿔주는 것과 같습니다.

사람이 개입하지 않아도 모델이 스스로 최신 상태를 유지합니다.

다음 코드를 살펴봅시다.

# 재학습 트리거 시스템
from datetime import datetime
import subprocess

class RetrainingTrigger:
    def __init__(self, drift_threshold=0.2, min_samples=1000):
        self.drift_threshold = drift_threshold
        self.min_samples = min_samples
        self.last_retrain = None

    def should_retrain(self, drift_score, sample_count, days_since_last):
        """재학습 필요 여부 판단"""
        # 조건 1: 드리프트가 임계값 초과
        high_drift = drift_score > self.drift_threshold

        # 조건 2: 충분한 새 데이터 확보
        enough_data = sample_count >= self.min_samples

        # 조건 3: 최소 재학습 간격 (너무 자주 재학습 방지)
        cooldown_passed = days_since_last >= 7

        return high_drift and enough_data and cooldown_passed

    def trigger_retrain(self, model_name, data_path):
        """재학습 파이프라인 실행"""
        print(f"[{datetime.now()}] 재학습 시작: {model_name}")
        # Airflow DAG 트리거 또는 직접 학습 스크립트 실행
        subprocess.run([
            "python", "train_pipeline.py",
            "--model", model_name,
            "--data", data_path,
            "--reason", "drift_detected"
        ])
        self.last_retrain = datetime.now()

# 사용 예시
trigger = RetrainingTrigger(drift_threshold=0.15)
if trigger.should_retrain(drift_score=0.25, sample_count=5000, days_since_last=10):
    trigger.trigger_retrain("customer_churn_model", "/data/latest/")

박시니어 씨가 중요한 포인트를 짚었습니다. "재학습을 자동화할 때 가장 조심해야 할 것은 무분별한 재학습이에요.

너무 자주 재학습하면 오히려 모델이 불안정해질 수 있어요." 위 코드의 should_retrain 메서드를 보겠습니다. 세 가지 조건을 모두 만족해야 재학습이 트리거됩니다.

첫째, 드리프트 점수가 임계값(0.2)을 넘어야 합니다. 약간의 드리프트는 자연스러운 현상이므로 무시합니다.

둘째, 새로운 데이터가 충분히 쌓여야 합니다. 데이터가 적은 상태에서 재학습하면 오히려 성능이 떨어질 수 있습니다.

최소 1000개 이상의 샘플을 확보한 후에 재학습을 시작합니다. 셋째, 마지막 재학습 후 최소 7일이 지나야 합니다.

쿨다운 기간은 너무 빈번한 재학습을 방지합니다. 매일 재학습하면 컴퓨팅 비용도 많이 들고, 모델 버전 관리도 복잡해집니다.

김개발 씨가 질문했습니다. "재학습할 때 어떤 데이터를 사용해야 하나요?" 이것은 중요한 결정입니다.

몇 가지 전략이 있습니다. 전체 재학습은 처음부터 모든 데이터로 다시 학습하는 것입니다.

가장 단순하지만 시간과 비용이 많이 듭니다. **점진적 학습(Incremental Learning)**은 기존 모델에 새 데이터만 추가로 학습시키는 것입니다.

빠르지만 모든 알고리즘이 지원하지는 않습니다. 슬라이딩 윈도우 방식도 있습니다.

최근 N개월 데이터만 사용해서 학습합니다. 예를 들어 항상 최근 6개월 데이터로 학습하면, 오래된 패턴은 자동으로 잊혀지고 최신 트렌드를 반영합니다.

trigger_retrain 메서드는 실제 학습 파이프라인을 실행합니다. 여기서는 subprocess로 Python 스크립트를 호출했지만, 실무에서는 Airflow DAG를 트리거하거나 Kubeflow Pipeline을 실행하는 식으로 구현합니다.

재학습 후에는 반드시 검증 단계가 필요합니다. 새 모델의 성능이 기존 모델보다 좋은지 확인해야 합니다.

성능이 오히려 떨어졌다면 배포하면 안 됩니다. 이 부분은 다음에 다룰 A/B 테스팅과 연결됩니다.

박시니어 씨가 덧붙였습니다. "재학습 이력을 꼭 기록해두세요.

언제, 왜, 어떤 데이터로 재학습했는지 추적할 수 있어야 해요. MLflow 같은 도구가 도움이 됩니다."

실전 팁

💡 - 재학습 비용과 드리프트로 인한 손실을 비교해서 최적의 임계값을 찾으세요.

  • 긴급 상황을 위해 수동 재학습 트리거 버튼도 만들어두면 좋습니다.

7. AB 테스팅으로 검증

모델을 재학습했습니다. 이제 바로 배포해도 될까요?

김개발 씨는 조심스러웠습니다. "새 모델이 더 좋다는 보장이 있나요?" 박시니어 씨가 고개를 끄덕였습니다.

"그래서 A/B 테스팅이 필요해요. 실제 트래픽으로 검증해봐야 확신할 수 있어요."

A/B 테스팅은 두 개 이상의 모델을 동시에 운영하면서 실제 성과를 비교하는 방법입니다. 마치 음식점에서 새 메뉴를 출시하기 전에 일부 손님에게만 먼저 제공해보고 반응을 살피는 것과 같습니다.

통제된 실험을 통해 새 모델이 정말 더 좋은지 데이터로 증명합니다.

다음 코드를 살펴봅시다.

# A/B 테스팅 시스템
import random
from collections import defaultdict
import numpy as np

class ABTestingSystem:
    def __init__(self, models, traffic_split=None):
        self.models = models  # {'A': model_a, 'B': model_b}
        self.traffic_split = traffic_split or {'A': 0.5, 'B': 0.5}
        self.results = defaultdict(lambda: {'predictions': [], 'actuals': []})

    def route_request(self, user_id):
        """사용자를 일관되게 같은 모델로 라우팅"""
        # user_id 기반 해시로 일관된 할당 보장
        hash_val = hash(user_id) % 100
        cumulative = 0
        for model_name, ratio in self.traffic_split.items():
            cumulative += ratio * 100
            if hash_val < cumulative:
                return model_name
        return list(self.models.keys())[-1]

    def predict(self, user_id, features):
        """할당된 모델로 예측 수행"""
        model_name = self.route_request(user_id)
        model = self.models[model_name]
        prediction = model.predict(features)
        return model_name, prediction

    def record_outcome(self, model_name, prediction, actual):
        """결과 기록"""
        self.results[model_name]['predictions'].append(prediction)
        self.results[model_name]['actuals'].append(actual)

    def evaluate(self):
        """각 모델의 성과 비교"""
        for name, data in self.results.items():
            accuracy = np.mean(np.array(data['predictions']) == np.array(data['actuals']))
            print(f"Model {name}: Accuracy = {accuracy:.4f}, Samples = {len(data['predictions'])}")

박시니어 씨가 A/B 테스팅의 핵심을 설명했습니다. "오프라인 평가 지표가 좋아도 실제 서비스에서는 다를 수 있어요.

사용자 행동은 예측하기 어렵거든요. 그래서 실제 환경에서 검증하는 거예요." 위 코드의 ABTestingSystem 클래스를 살펴보겠습니다.

생성자에서 두 개의 모델(A와 B)과 트래픽 비율을 받습니다. 기본값은 50:50이지만, 새 모델이 불안하면 10:90으로 시작해서 점차 늘려갈 수도 있습니다.

route_request 메서드가 중요합니다. 여기서 일관된 라우팅을 보장합니다.

같은 사용자는 항상 같은 모델을 사용해야 합니다. 그렇지 않으면 사용자 경험이 일관되지 않고, 실험 결과도 오염됩니다.

user_id를 해시해서 결정적으로 모델을 할당합니다. predict 메서드는 라우팅된 모델로 예측을 수행하고, record_outcome 메서드는 나중에 정답이 밝혀졌을 때 결과를 기록합니다.

추천 시스템이라면 사용자가 실제로 클릭했는지, 사기 탐지라면 실제로 사기였는지를 기록합니다. 김개발 씨가 물었습니다.

"얼마나 오래 테스트해야 하나요?" 좋은 질문입니다. A/B 테스트는 통계적 유의성이 확보될 때까지 진행해야 합니다.

샘플이 너무 적으면 우연에 의한 차이인지 실제 차이인지 구분할 수 없습니다. 일반적으로 최소 12주, 그리고 각 그룹에 최소 100010000개의 샘플이 필요합니다.

또한 주중/주말, 월초/월말 등 시간에 따른 변동을 모두 포함해야 공정한 비교가 됩니다. evaluate 메서드에서 단순히 정확도만 비교했지만, 실무에서는 통계적 검정을 추가해야 합니다.

t-검정이나 카이제곱 검정으로 두 모델의 차이가 통계적으로 유의미한지 확인합니다. p-value가 0.05 미만이면 "새 모델이 유의미하게 더 좋다"고 결론 내릴 수 있습니다.

A/B 테스트가 끝나면 어떻게 해야 할까요? 새 모델이 승리했다면 트래픽을 100%로 전환합니다.

이것을 롤아웃이라고 합니다. 점진적으로 50% → 70% → 90% → 100%로 늘려가는 것이 안전합니다.

만약 새 모델이 패배했다면? 귀중한 교훈을 얻은 것입니다.

왜 새 모델이 더 나쁜지 분석하고, 다음 개선에 반영합니다. 실패한 실험도 기록으로 남겨두면 나중에 같은 실수를 피할 수 있습니다.

박시니어 씨가 마무리했습니다. "A/B 테스팅은 MLOps의 완성이에요.

드리프트를 감지하고, 재학습하고, A/B 테스트로 검증하고, 배포하는 이 사이클이 돌아가면 모델이 스스로 진화하는 거예요."

실전 팁

💡 - A/B 테스트 전에 A/A 테스트를 먼저 해보세요. 같은 모델끼리 비교해서 시스템이 정상 작동하는지 확인합니다.

  • 비즈니스 지표(매출, 전환율)와 모델 지표(정확도, AUC)를 함께 모니터링하세요. 모델 지표가 좋아도 비즈니스에 도움이 안 되면 의미가 없습니다.

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

#Python#MLOps#Drift Detection#Evidently#Alibi Detect#MLOps,Drift,Monitoring

댓글 (0)

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