포스트

머신러닝 실전 워크플로 (7) - 검증 전략과 누수 점검

오프라인 점수가 배포 후 성능을 정직하게 추정하도록 교차검증과 지표 선택, 베이스라인 대비, 누수 재점검, 시간 순 분할로 검증 절차를 설계하는 법을 정리한 7편입니다.

머신러닝 실전 워크플로 (7) - 검증 전략과 누수 점검

머신러닝 실전 워크플로 시리즈의 7편입니다. 6편의 “실험 추적: MLflow”에 이어집니다.

점수를 믿을 수 있는가

6편까지 오면서 실험을 돌리고 MLflow에 기록하는 체계를 갖췄습니다. 그런데 기록된 실험 중 어느 것이 가장 좋은지 고르는 일은, 각 실험에 붙은 점수가 애초에 믿을 만하다는 전제 위에서만 뜻이 있습니다. 검증 점수가 0.95라도 그 점수를 만든 절차가 잘못됐다면 배포 후 성능은 전혀 다를 수 있습니다.

검증 전략은 한 가지 질문에 답하는 설계입니다. 이 점수는 배포 후 성능을 정직하게 추정하는가. 이 편은 그 추정을 신뢰할 수 있게 만드는 절차를 정리합니다. 교차검증과 지표 선택, 베이스라인 대비, 누수 재점검, 시계열의 시간 순 분할이 그것입니다. 그리고 오프라인 점수가 좋아도 온라인이 나쁠 수 있는 이유로 마무리합니다.

오프라인 평가는 무엇을 추정하는가

오프라인 평가는 지금 손에 있는 데이터로 계산한 점수입니다. 이 점수의 목적은 가진 데이터를 요약하는 것이 아니라, 아직 오지 않은 데이터에서의 성능을 추정하는 것입니다. 그래서 좋은 검증 절차의 기준은 하나로 정리됩니다. 배포 환경을 얼마나 정확히 흉내 내는가.

배포 때 모델은 학습에 쓰지 않은 새 데이터를 만납니다. 검증도 그 상황을 그대로 재현해야 하고, 그러려면 검증에 쓸 데이터의 어떤 정보도 학습에 미리 반영되지 않아야 합니다. 이 원칙 하나가 이 편의 나머지를 관통합니다.

교차검증: 점수 하나가 아니라 분포를 본다

단일 홀드아웃은 데이터를 한 번 잘라 한 번 채점합니다. 문제는 어디서 잘랐느냐에 따라 점수가 흔들린다는 점입니다. 운 좋게 쉬운 검증셋을 뽑으면 점수가 높고 반대면 낮은데, 그 한 번의 점수만으로는 어느 쪽인지 알 수 없습니다. 교차검증은 데이터를 여러 폴드로 나눠 각 폴드를 한 번씩 검증셋으로 쓰고 점수를 여러 개 얻습니다. 평균은 성능의 추정치가 되고, 표준편차는 그 추정이 얼마나 불안정한지를 알려줍니다.

1
2
3
4
5
6
7
8
9
10
11
from sklearn.model_selection import cross_val_score
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression

pipe = make_pipeline(
    StandardScaler(),
    LogisticRegression(max_iter=5000, random_state=42))

scores = cross_val_score(pipe, X, y, cv=5, scoring="roc_auc")
scores.mean(), scores.std()   # 예: 0.981, 0.007. 평균만이 아니라 편차도 함께 본다

여기서 놓치기 쉬운 것이 편차입니다. 평균이 같은 두 모델이라도 편차가 크면 폴드에 따라 성능이 크게 달라진다는 뜻이고, 그런 모델은 배포 후 성능도 예측하기 어렵습니다. 평균이 조금 낮아도 편차가 작은 모델이 실전에서는 더 믿을 만한 경우가 많습니다. 교차검증의 방식과 하이퍼파라미터 튜닝은 머신러닝 기초 5편에서 다뤘습니다.

지표는 문제에서 나온다

교차검증이 점수를 안정적으로 만들어 준다면, 어떤 점수를 잴지는 별개의 결정입니다. 지표 선택은 통계 문제가 아니라 비즈니스 문제입니다. 같은 분류 모델이라도 정확도를 볼지, 정밀도와 재현율을 볼지는 오류의 비용이 어느 쪽에 쏠리는지에 달려 있습니다. 놓친 사기 거래가 비싼지 정상 거래를 막는 것이 비싼지에 따라 최적화할 지표가 달라집니다.

그 기준은 이미 1편에서 성공 지표로 정해 둔 값입니다. 검증에서 최적화하는 지표는 1편에서 정한 성공 기준과 같아야 하고, cross_val_scorescoring 인자로 그것을 명시합니다. 분류 지표의 선택 기준은 머신러닝 기초 7편, 회귀 지표는 8편에서 정리했습니다.

모델을 고를 때 여러 지표를 동시에 보고 그때그때 유리한 것을 고르면 사실상 검증셋에 맞춰 튜닝하는 셈이 된다. 모델 선택의 기준이 되는 단일 지표를 미리 하나 정하고, 나머지 지표는 참고로만 본다.

베이스라인 대비로 읽는다

절대 점수는 그 자체로 의미를 갖지 못합니다. MAE 6분이 좋은지 나쁜지는 비교 대상이 있어야 판단됩니다. 그 기준이 베이스라인입니다. 베이스라인은 아주 단순한 규칙이면 됩니다. 회귀에서는 항상 train 평균을 예측하기, 수요 예측에서는 지난주 같은 시간대 값을 그대로 쓰기 같은 것입니다.

1
2
3
4
5
from sklearn.dummy import DummyRegressor
from sklearn.metrics import mean_absolute_error

baseline = DummyRegressor(strategy="mean").fit(X_tr, y_tr)   # 항상 train 평균을 예측
mean_absolute_error(y_test, baseline.predict(X_test))        # 예: 8.4. 모델은 이 값을 넘어야 한다

모델이 베이스라인을 못 넘으면 복잡한 모델을 쓸 이유가 없고, 데이터나 특성을 다시 봐야 한다는 신호입니다. 베이스라인 대비로 읽으면 점수의 절대값에 휘둘리지 않고 개선의 크기를 직접 볼 수 있습니다. NYC 택시 파이프라인에서도 LightGBM과 PyTorch 모델을 먼저 단순 베이스라인과 나란히 놓고 비교합니다(모델 비교).

누수 재점검: 점수가 너무 좋으면 의심한다

검증 점수가 유난히 높으면 축하하기 전에 누수를 의심하는 편이 안전합니다. 누수는 예측 시점에 알 수 없는 정보가 학습에 새어 든 것이고, 그때 점수는 실제보다 높게 나옵니다. 유형과 방지법은 머신러닝 기초 13편에서 자세히 다뤘습니다. 워크플로 관점에서 매 실험마다 재점검할 것은 두 가지입니다.

첫째, 전처리가 교차검증 안에서 폴드마다 다시 fit되는가. 5편에서 만든 피처 파이프라인을 하나의 파이프라인 객체로 묶어 cross_val_score에 통째로 넘기면 이 규칙이 저절로 지켜집니다. 전처리를 교차검증 밖에서 전체 데이터에 미리 해두면 모든 폴드가 서로의 통계를 엿본 상태가 되어 점수가 부풀려집니다.

둘째, 특성 중에 타깃에서 파생된 값이 섞여 있지 않은가. “이 특성의 값을 예측 시점에 실제로 알 수 있는가”를 특성마다 묻는 것이 유일한 점검법입니다. 이 질문에 아니오가 나오는 특성은 배포 환경에서는 존재하지 않으므로 빼야 합니다.

시계열이면 시간 순으로 자른다

무작위 분할은 각 행이 서로 독립이라는 가정에 기댑니다. 시간이 흐르는 데이터에서는 이 가정이 깨집니다. 무작위로 나누면 미래의 행이 학습에 들어가고 과거의 행이 검증에 들어가, 모델이 미래를 본 채로 과거를 맞히게 됩니다. 점수는 좋아지지만 실제 예측은 언제나 과거로 미래를 향하므로, 그 점수는 배포 후에 재현되지 않습니다.

시계열에서는 시간 순서로 자릅니다. 과거로 학습하고 바로 뒤 구간으로 검증합니다. scikit-learn의 TimeSeriesSplit이 이 방식을 폴드로 구현합니다.

1
2
3
4
5
6
from sklearn.model_selection import TimeSeriesSplit, cross_val_score

# 시간 순으로 정렬된 X, y 를 전제로 한다
tscv = TimeSeriesSplit(n_splits=5)   # 매 폴드에서 과거로 학습하고 바로 뒤 구간으로 검증
scores = cross_val_score(pipe, X, y, cv=tscv, scoring="neg_mean_absolute_error")
-scores.mean()   # 폴드별 MAE의 평균. neg_ 지표라 부호를 뒤집는다

NYC 택시 수요 예측이 바로 이 경우입니다. 시간당 수요는 시계열이라 무작위 분할이 곧 시간 누수가 됩니다. 데이터와 EDA에서 시간 구조를 확인하고 분할도 시간 순으로 둡니다. 이동평균 같은 파생 특성도 그 시점 이후의 값을 포함하지 않도록 과거 값만으로 계산해야 하며, 이 원칙은 5편의 피처 파이프라인에서 이미 지켜 두는 것이 안전합니다.

오프라인이 좋아도 온라인은 다를 수 있다

여기까지가 오프라인 평가입니다. 오프라인 점수를 아무리 정직하게 만들어도, 그것이 온라인 성능을 보장하지는 않습니다. 이유는 몇 가지로 나뉩니다.

  • 학습-서빙 편차: 학습 때와 서빙 때 특성을 계산하는 코드가 다르면 같은 입력에도 다른 값이 나와, 오프라인에서 잰 성능이 온라인에서 재현되지 않습니다. 9편 서빙에서 다룹니다.
  • 분포 변화: 배포 후 들어오는 데이터의 분포가 학습 데이터와 달라지면 성능이 서서히 떨어집니다. 12편 모니터링에서 드리프트로 다룹니다.
  • 피드백 루프: 모델의 예측이 사용자 행동을 바꾸면, 오프라인 데이터에서 관찰한 관계가 온라인에서 그대로 유지되지 않습니다.

그래서 오프라인 평가는 배포 후보를 거르는 1차 관문이지 최종 판정이 아닙니다. 최종 판정은 온라인에서 실제 트래픽으로 확인합니다. 서빙 구조와 학습-서빙 편차는 9편, 배포 후 성능 추적과 드리프트는 12편에서 이어집니다. 이 편에서 만든 오프라인 점수는 그 온라인 검증의 출발점이 됩니다.

정리

개념한 줄 요약
오프라인 평가의 목적가진 데이터의 요약이 아니라 안 온 데이터의 성능 추정. 기준은 배포 환경을 흉내 내는가
교차검증점수 하나가 아니라 여러 폴드의 평균과 편차를 본다. 편차가 크면 불안정한 모델
지표 선택통계가 아니라 비즈니스 결정. 1편의 성공 지표를 scoring으로 고정한다
베이스라인 대비절대 점수는 무의미. 단순 규칙을 못 넘으면 복잡한 모델을 쓸 이유가 없다
누수 재점검점수가 너무 좋으면 의심. 파이프라인을 CV에 통째로, 타깃 파생 특성 제거
시간 순 분할시계열은 무작위 분할이 곧 시간 누수. TimeSeriesSplit으로 과거 학습, 미래 검증
온라인과의 간극오프라인이 좋아도 편차, 드리프트, 피드백으로 온라인은 다를 수 있다

오프라인 검증까지가 모델을 고르는 단계입니다. 다음 편은 그렇게 고른 모델과 그 산출물을 어떻게 버전으로 관리하고 배포로 넘기는지를 다룹니다.

다음 글: 머신러닝 실전 워크플로 (8) - 모델 레지스트리와 아티팩트

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