포스트

머신러닝 실전 워크플로 (12) - 모니터링과 드리프트

배포는 끝이 아니라 시작입니다. 데이터 드리프트와 개념 드리프트를 구분하고, 입력과 예측 분포, 성능, 운영 지표를 모니터링하며, 스케줄과 성능 저하로 재학습을 트리거하는 법을 정리한 시리즈 마지막 편입니다.

머신러닝 실전 워크플로 (12) - 모니터링과 드리프트

머신러닝 실전 워크플로 시리즈의 12편입니다. 11편의 “오케스트레이션: Airflow”에 이어집니다.

배포한 모델은 조용히 낡는다

11편에서 Airflow로 데이터 수집부터 학습, 배포까지의 파이프라인을 정해진 시각에 자동으로 돌게 만들었습니다. 이제 모델은 실서비스에 나가 매일 예측을 냅니다. 여기서 프로젝트가 끝난 것처럼 보이지만, 사실은 가장 긴 국면이 이제 막 시작된 지점입니다.

7편에서 세운 오프라인 검증은 그 시점에 가지고 있던 데이터에서의 성능을 측정합니다. 그런데 세상은 계속 움직입니다. 배포 후 실제로 들어오는 데이터는 학습 데이터에서 조금씩 멀어지고, 검증 점수가 좋았던 모델도 시간이 지나면 성능이 떨어집니다. 문제는 이 저하가 ml-basics 13편의 누수처럼 에러를 내지 않는다는 점입니다. 코드는 잘 돌고 API는 200을 응답하는데, 예측만 조용히 틀려집니다. 그래서 모니터링이 필요합니다. 사용자가 알아채기 전에 성능 저하를 먼저 감지하는 것이 목적입니다.

두 가지 드리프트

성능이 떨어지는 근본 원인은 학습 시점과 예측 시점의 분포가 달라지는 것입니다. 이 분포 변화를 드리프트라고 부르고, 어디가 변했느냐에 따라 두 가지로 나눕니다. 구분이 중요한 이유는 대응이 다르기 때문입니다.

데이터 드리프트(공변량 시프트)는 입력의 분포 P(X)가 변하는 것입니다. 입력과 타깃의 관계 P(yX)는 그대로인데, 들어오는 입력 자체가 옮겨갑니다. 택시 예로 들면, 앱 서비스 지역이 확장돼 이전에는 없던 지역의 픽업 요청이 입력에 섞여 들어옵니다. 시간과 날씨, 지역이 수요와 맺는 관계는 그대로지만 입력 분포가 이동한 경우입니다.
개념 드리프트는 입력과 타깃의 관계 P(yX) 자체가 변하는 것입니다. 같은 입력이 다른 답을 냅니다. 재택근무가 늘면서 같은 평일 아침, 같은 날씨인데도 도심 수요가 예전보다 낮아지는 상황이 여기에 해당합니다. 입력은 비슷한데 정답이 달라졌습니다.

이 둘을 나누는 이유는 회복 방법이 다르기 때문입니다. 데이터 드리프트는 새 분포의 데이터를 더해 재학습하면 대개 회복됩니다. 관계는 그대로라 새 입력 영역을 더 채워 주면 되기 때문입니다. 개념 드리프트는 관계 자체가 바뀐 것이라, 옛 데이터로 재학습하면 오히려 틀린 관계를 다시 배웁니다. 최근 레이블을 새로 모아 재학습해야 하고, 심하면 피처나 1편의 문제 정의까지 다시 봐야 합니다.

무엇을 모니터링하나

모니터링 대상은 네 층위로 나뉩니다. 위로 갈수록 레이블 없이 빠르게 얻을 수 있고, 아래로 갈수록 느리지만 정확합니다.

  • 입력 분포: 들어오는 피처의 분포가 학습 데이터와 얼마나 다른가. 레이블이 필요 없어 데이터가 들어오는 즉시 계산할 수 있는 가장 빠른 경보입니다. 뒤에서 볼 PSI, KS 통계, 결측 비율 변화 등으로 봅니다.
  • 예측 분포: 모델이 내놓는 예측의 분포. 예측 평균이나 분산이 갑자기 튀거나, 분류에서 특정 클래스 비율이 급변하면 신호입니다. 이것도 레이블 없이 볼 수 있습니다.
  • 성능 지표: 실제 정답이 도착한 뒤 계산하는 RMSE, MAE, AUC입니다(ml-basics 8편, 7편). 가장 정확하지만 레이블 지연이 있습니다. 택시의 실제 수요는 그 시간대가 지나야 확정되므로, 오늘 예측의 성능은 내일에야 알 수 있습니다.
  • 운영 지표: 지연(latency), 에러율, 처리량, 리소스 사용량입니다. 모델이 맞고 틀리고를 떠나 서비스가 살아 있는지를 봅니다. p99 지연이 튀거나 5xx 비율이 오르면 예측 품질과 무관하게 대응해야 합니다.

핵심은 순서입니다. 성능 지표가 진실에 가장 가깝지만 레이블 지연 때문에 늦게 옵니다. 그 사이 입력과 예측 분포가 조기 경보 역할을 합니다. 분포가 흔들리면 성능 하락을 의심하고, 레이블이 도착하면 성능 지표로 확정합니다.

입력 분포 변화만 보고 성능이 떨어졌다고 단정하지 않는다. 드리프트가 있어도 그 피처가 중요하지 않으면 성능은 유지될 수 있고, 반대로 개념 드리프트는 입력 분포가 그대로여도 성능이 무너진다. 분포는 어디까지나 조기 경보이고, 최종 판단은 레이블이 붙은 성능 지표로 내린다.

드리프트를 수치로: PSI

입력 분포가 “얼마나” 달라졌는지는 숫자로 재야 알림을 걸 수 있습니다. 자주 쓰는 지표가 PSI(Population Stability Index)입니다. 학습 분포의 분위수로 구간을 나눈 뒤, 각 구간에 최근 데이터가 얼마나 들어오는지를 학습 때 비율과 비교합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy as np

def psi(expected, actual, bins=10):
    # 학습 분포(expected)의 분위수로 구간 경계를 잡는다
    edges = np.quantile(expected, np.linspace(0, 1, bins + 1))
    edges[0], edges[-1] = -np.inf, np.inf      # 양 끝을 열어 학습 범위 밖 값도 담는다

    e = np.histogram(expected, edges)[0] / len(expected)   # 학습 분포의 구간별 비율
    a = np.histogram(actual, edges)[0] / len(actual)       # 최근 분포의 구간별 비율
    e, a = np.clip(e, 1e-6, None), np.clip(a, 1e-6, None)   # 0 나눗셈과 log(0) 방지
    return np.sum((a - e) * np.log(a / e))

# 관용적 해석: 0.1 미만이면 안정, 0.1~0.25면 주의, 0.25 이상이면 유의미한 드리프트

수치 피처는 이렇게 구간을 나눠 계산하고, 범주 피처는 카테고리별 비율로 같은 식을 씁니다. 연속 분포의 차이만 보고 싶다면 KS 통계량을, 범주 분포에는 카이제곱을 쓰기도 합니다. 도구로는 Evidently가 피처별 드리프트를 한 번에 리포트로 만들어 줍니다.

1
2
3
4
5
6
from evidently.report import Report
from evidently.metric_preset import DataDriftPreset

report = Report(metrics=[DataDriftPreset()])
report.run(reference_data=train_df, current_data=recent_df)  # 학습 분포 대 최근 분포
report.save_html("drift_report.html")   # 피처별 드리프트 여부와 분포 비교를 HTML로

리포트는 어느 피처가 얼마나 드리프트했는지, 예측 분포가 어떻게 움직였는지를 한눈에 보여 줍니다. 이 수치를 대시보드에 올려 두면 사람이 보기 전에 임계값으로 알림을 걸 수 있고, 그 알림이 다음 절의 재학습 트리거가 됩니다.

재학습을 언제 트리거하나

드리프트를 감지했으면 언젠가는 모델을 다시 학습해야 합니다. 언제 재학습할지를 정하는 방식이 두 가지입니다.

스케줄 기반은 정해진 주기로 무조건 재학습하는 것입니다. 매주 또는 매월처럼 주기를 정해 두고 11편의 Airflow DAG 스케줄에 얹습니다. 단순하고 예측 가능하며 구현이 쉽습니다. 데이터가 꾸준히 흐르고 드리프트가 완만하면 잘 맞습니다. 단점은 필요 없을 때도 돌려 비용이 들고, 주기 사이에 급격한 드리프트가 오면 대응이 늦다는 점입니다.

성능 저하 기반은 모니터링 지표가 임계값을 넘을 때만 재학습하는 것입니다. 앞 절의 PSI가 0.25를 넘거나 성능 지표가 기준 아래로 떨어지면 트리거합니다. 낭비가 없고 급변에 빠르게 반응합니다. 단점은 임계값 설정이 까다롭고, 성능 기반은 레이블 지연 탓에 반응이 늦을 수 있어 분포 기반 트리거로 보완해야 한다는 점입니다.

실전에서는 둘을 겹칩니다. 스케줄로 정기 재학습을 기본으로 깔고, 그 사이에 드리프트나 성능 경보가 뜨면 임시로 재학습을 돌립니다. Airflow에서는 주기 스케줄에 드리프트 게이트를 하나 더해 두 방식을 한 DAG로 합칠 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from airflow.decorators import dag, task
import pendulum

@dag(schedule="@weekly", start_date=pendulum.datetime(2025, 1, 1), catchup=False)
def retrain():

    @task.short_circuit
    def drift_gate():
        score = psi(reference, recent)   # 학습 분포 대 최근 입력 분포
        return score >= 0.25             # 임계 미만이면 이후 태스크를 건너뛴다

    @task
    def train_and_register():
        ...                              # 재학습 후 새 버전을 레지스트리에 등록 (8편)

    drift_gate() >> train_and_register()

retrain()

매주 DAG가 깨어나 드리프트를 확인하고, 임계를 넘을 때만 재학습으로 넘어갑니다. 정기 점검과 조건부 재학습이 한 흐름에 들어가는 셈입니다.

재학습이 곧 배포는 아니다

여기서 한 가지 함정을 짚어야 합니다. 새로 학습한 모델이 자동으로 프로덕션에 올라가서는 안 됩니다. 재학습이 늘 개선인 것은 아니기 때문입니다. 노이즈가 낀 최근 레이블을 학습하거나 일시적 드리프트에 과적합해 오히려 나빠질 수 있습니다.

그래서 새 모델도 7편의 검증을 통과하고 8편의 승격 절차를 거쳐야 합니다. 현재 서비스 중인 챔피언 모델과 새로 학습한 챌린저 모델을 같은 검증셋에서, 가능하면 섀도 트래픽으로 나란히 비교해, 챌린저가 실제로 더 나을 때만 레지스트리의 production 별칭을 옮깁니다. 자동화하는 것은 재학습 트리거까지이고, 승격에는 사람이 지키는 게이트를 둡니다. 이렇게 하면 모니터링이 재학습을 부르고, 재학습이 다시 검증과 승격으로 이어지는 고리가 닫힙니다.

12단계를 한 흐름으로

이 시리즈는 노트북의 모델 하나를 실제로 굴러가는 프로젝트로 감싸는 흐름을 열두 단계로 따라왔습니다. 비즈니스 문제를 ML 문제로 바꿔 성공 지표를 세우고(1편), 재현 가능한 프로젝트 구조를 잡고(2편), 데이터를 버전관리하고(3편), EDA로 품질을 점검하고(4편), 피처를 재현 가능한 파이프라인으로 만들고(5편), 실험을 추적하고(6편), 검증을 정직하게 세우고(7편), 고른 모델을 레지스트리에 등록하고(8편), 서빙으로 예측 요청에 응답하게 하고(9편), 컨테이너로 어디서든 같게 돌게 하고(10편), Airflow로 이 단계들을 스케줄에 얹은(11편) 뒤, 마지막으로 배포한 모델을 모니터링하며 드리프트를 감지해 재학습으로 되돌아옵니다(이번 편). 12편은 다시 1편으로 이어지는 고리입니다. 모니터링이 잡아낸 드리프트가 새로운 재학습을, 때로는 문제 정의의 재검토를 부르기 때문입니다.

NYC 택시에서는

이 열두 단계를 하나의 데이터로 끝까지 구현한 것이 NYC 택시 수요 예측 파이프라인입니다. 각 편이 “실전에서 일반적으로 어떻게 하는가”를 정리했다면, 그 프로젝트는 같은 흐름을 구체적인 데이터 하나로 처음부터 끝까지 밀어붙인 사례입니다. 이번 편에 대응하는 Airflow, 모니터링, CI 편은 학습과 배포 DAG를 스케줄에 얹고, 입력과 예측 분포를 모니터링하며, CI로 코드 변경을 검증하는 과정을 실제 코드로 봅니다. 처음부터 읽으려면 설계 편에서 시작합니다.

정리

개념한 줄 요약
배포 후세상이 변해 학습 분포와 멀어지고, 모델은 에러 없이 조용히 낡는다
데이터 드리프트입력 P(X)가 변하고 관계는 그대로. 새 분포를 더해 재학습하면 대개 회복
개념 드리프트입력과 타깃의 관계 자체가 변한다. 최근 레이블로 다시 배워야 한다
입력과 예측 분포레이블 없이 즉시 계산하는 조기 경보. PSI, KS로 학습 분포와 비교
성능 지표레이블이 온 뒤의 RMSE, AUC. 가장 정확하지만 지연이 있다
운영 지표지연, 에러율, 처리량. 모델 품질과 별개로 서비스가 살아 있는가
스케줄 트리거정해진 주기로 재학습. 단순하고 예측 가능하지만 급변엔 늦다
성능과 드리프트 트리거임계 초과 시 재학습. 낭비 없고 빠르지만 임계 설정이 까다롭다
재학습 후자동 배포가 아니라 챔피언과 챌린저를 비교하고 승격 게이트를 거친다

배포가 시작인 이유가 여기 있습니다. 모델은 한 번 내보내고 끝나는 것이 아니라, 모니터링과 재학습으로 계속 살아 있게 유지하는 대상입니다. 이 시리즈가 정리한 열두 단계를 하나의 프로젝트로 처음부터 끝까지 구현한 사례를 이어서 읽어 보세요.

적용 사례: NYC 택시 수요 예측 파이프라인 (1) - 프로젝트 설계

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.