콘텐츠로 이동

교차 검증 (Cross-Validation)

난이도: 초급~중급
선수 지식: 혼동 행렬
관련 문서: 데이터 누수 | 하이퍼파라미터 최적화 | 통계적 검정

핵심 요약: 교차 검증(Cross-Validation)은 데이터를 K등분해서, 각 조각을 돌아가며 테스트용으로 사용하는 방법이다. 한 번 나누는 것보다 훨씬 안정적인 성능 추정을 제공한다. 시험 문제를 한 세트만 풀면 운이 좌우하지만, 10세트를 풀면 실력이 드러나는 것과 같은 원리다.

  • 홀드아웃(Hold-out): 데이터를 훈련용/검증용으로 한 번만 잘라 쓰는 방식. 간단하지만 어떻게 잘랐느냐에 따라 결과가 출렁인다.
  • 폴드(Fold): 데이터를 나눈 조각. kk개로 나누면 kk-Fold.
  • kk-Fold 교차 검증: 조각을 돌아가며 한 번씩 검증용으로 쓰고, kk번의 성능을 평균낸다. 운에 덜 좌우된다.
  • 층화(Stratified): 각 조각에 클래스 비율을 원본과 똑같이 맞추는 것. 불균형 데이터에서 필수다.
  • 중첩 교차 검증(Nested CV): 하이퍼파라미터를 고르는 과정까지 교차 검증 안에 넣는 것. 이걸 안 하면 성능이 낙관적으로 부풀려진다.

모델의 성능을 추정할 때, 단일 훈련/테스트 분할(데이터를 한 번만 나누는 것)은 분할 방법에 따라 결과가 크게 달라지는 노이즈가 많은 추정이다. 교차 검증(Cross-Validation, CV)은 데이터를 여러 번 다르게 분할하여 모든 데이터를 훈련과 검증에 모두 사용하면서도 일반화 성능의 안정적인 추정치를 얻는 방법이다.

교차 검증은 단순한 평가 기법이 아니라, 하이퍼파라미터 튜닝과 모델 선택의 기반이 된다.

비유: 홀드아웃은 모의고사를 딱 한 번 보는 것이다. 그날 컨디션이나 문제 운에 따라 점수가 출렁인다. 교차 검증은 같은 범위를 다섯 번 나눠 보고 평균을 내는 것이다. 한 번의 운에 좌우되지 않으니 실력을 더 정확히 알 수 있다. 대신 시험을 다섯 번 치러야 하므로 시간이 다섯 배 든다.


교차 검증의 아이디어 자체는 1930년대 통계학까지 거슬러 올라가지만, 오늘날의 “k=10이 표준”이라는 실무 관행은 Ron Kohavi의 1995년 논문 “A Study of Cross-Validation and Bootstrap for Accuracy Estimation and Model Selection”에 크게 기인한다. Kohavi는 다양한 데이터셋과 분류기에 대해 Leave-One-Out CV, 2-fold, 5-fold, 10-fold, 20-fold 등을 체계적으로 비교하는 대규모 경험적 연구(empirical study)를 수행했다. 핵심 발견은 다음과 같았다: LOOCV는 이론적으로 편향이 낮지만 분산이 매우 높아 불안정했고, 작은 k(예: 2-fold)는 편향이 높아 성능을 과소 추정했다. 10-fold 층화 교차 검증(Stratified 10-fold CV)이 편향과 분산 사이에서 가장 좋은 균형을 보였으며, 대부분의 데이터셋에서 안정적인 성능 추정을 제공했다. 이 연구는 이후 수천 편의 논문에서 인용되었고, scikit-learn 등 주요 ML 라이브러리의 기본 설정(default k=5 또는 10)에도 직접적인 영향을 미쳤다.


데이터를 kk개의 동일한 크기의 부분집합(fold, 접힌 조각이라는 뜻)으로 나누고, 각 반복에서 1개 fold를 검증에, 나머지 k1k-1개를 훈련에 사용한다. kk번 반복하면 모든 데이터가 정확히 한 번씩 검증에 사용된다.

k-Fold 교차 검증 다이어그램

100개의 데이터로 5-Fold CV를 수행한다고 하자:

Fold훈련 데이터테스트 데이터정확도
1데이터 21~100 (80개)데이터 1~20 (20개)85%
2데이터 120 + 41100 (80개)데이터 21~40 (20개)82%
3데이터 140 + 61100 (80개)데이터 41~60 (20개)88%
4데이터 160 + 81100 (80개)데이터 61~80 (20개)84%
5데이터 1~80 (80개)데이터 81~100 (20개)86%

CV Score = (85 + 82 + 88 + 84 + 86) / 5 = 85.0% +- 2.0% (모집단 표준편차 기준)

만약 Fold 3만 사용했다면 “정확도 88%“라고 낙관적으로 보고했을 것이다. 5-Fold CV를 통해 더 현실적인 85%를 얻었다.

보고: 평균과 표준편차를 함께 보고한다.

CV Score=1ki=1kScorei±std(Score1,,Scorek)\text{CV Score} = \frac{1}{k}\sum_{i=1}^{k} \text{Score}_i \quad \pm \quad \text{std}(\text{Score}_1, \ldots, \text{Score}_k)

k 선택의 편향-분산 트레이드오프:

k 값편향분산계산 비용
작은 k (예: 2)높음 (적은 훈련 데이터)낮음낮음
큰 k (예: 20)낮음높음 (fold 간 유사)높음
k = 5 또는 10적절한 균형적절한 균형보통

Kohavi (1995)의 연구에 따르면, k = 10이 일반적으로 좋은 절충점이다. 층화(stratified) 10-fold가 실무 기본값으로 많이 사용된다.


각 fold에서 클래스 분포를 원본과 동일하게 유지한다. 불균형 분류에서 필수적이다.

  • 분류: 각 fold의 양성/음성 비율이 전체 데이터와 동일
  • 회귀: 목표 변수를 구간(bin)으로 나누어 층화 가능

층화하지 않으면, 특정 fold에 소수 클래스가 매우 적거나 아예 없을 수 있어 성능 추정이 불안정해진다.


k=nk = n (각 fold에 샘플 하나). 즉, 매번 1개 샘플만 빼고 나머지 전부로 훈련한다.

  • 장점: 거의 비편향적 일반화 오차 추정 (훈련 데이터 n1n-1개 사용)
  • 단점: 분산이 매우 높고, 계산 비용이 큼 (nn번 훈련)
  • 특수한 경우: 선형 모델에서는 Hat matrix hiih_{ii}를 이용해 한 번의 학습으로 LOOCV를 정확히 계산할 수 있다
  • 사용 시기: 데이터가 매우 적을 때 (수십 개 수준)

k-fold를 서로 다른 무작위 분할로 여러 번 반복한다.

  • 효과: 성능 추정의 분산을 줄인다
  • 일반적 설정: 5x2 CV (2-fold를 5회 반복), 10x10 CV (10-fold를 10회 반복)
  • 비용: 반복 횟수 ×\times kk만큼 훈련이 필요

시계열 교차 검증 (Time-Series Split)

섹션 제목: “시계열 교차 검증 (Time-Series Split)”

시계열 데이터에서 표준 k-fold는 미래 데이터로 과거를 예측하는 데이터 누수(Data Leakage, 테스트 정보가 훈련에 새어 들어가는 것)를 발생시킨다.

시계열 교차 검증 (Time-Series Split) 다이어그램

방법설명
확장 윈도우 (Expanding Window)훈련 기간이 점차 늘어남: [1..t][1..t]로 훈련, [t+1..t+h][t+1..t+h]로 테스트
슬라이딩 윈도우 (Sliding Window)고정 길이 훈련: [tw..t][t-w..t]로 훈련, [t+1..t+h][t+1..t+h]로 테스트
Gap 추가자기상관(autocorrelation) 누수 방지를 위해 훈련과 테스트 사이에 간격
Walk-Forward실제 운영 환경 모사: 시간이 지남에 따라 모델을 재학습

그룹 내 샘플들이 상관될 때 (예: 한 환자의 여러 검사 결과, 한 사용자의 여러 클릭), 같은 그룹의 모든 샘플이 같은 fold에 있어야 한다.

  • 그룹 간에 데이터가 나뉘어야만 진정한 일반화 성능을 측정할 수 있다
  • 그룹을 무시하면 같은 환자의 데이터가 훈련과 테스트에 동시에 들어가 누수가 발생한다

중첩 교차 검증 (Nested Cross-Validation)

섹션 제목: “중첩 교차 검증 (Nested Cross-Validation)”

중첩 교차 검증 (Nested Cross-Validation) 다이어그램

  • 외부 루프: 일반화 성능 추정
  • 내부 루프: 하이퍼파라미터 튜닝 / 모델 선택
  • 필요한 이유: 같은 CV를 튜닝과 평가에 모두 사용하면 낙관적 편향 발생
  • 계산 비용: kouter×kinnerk_{\text{outer}} \times k_{\text{inner}}번 학습
  • 일반적 설정: 5x5 또는 10x5

전략장점단점추천 상황
k-Fold (k=10)좋은 편향-분산 균형i.i.d. 가정범용 기본값
Stratified k-Fold불균형에 강건i.i.d. 가정불균형 분류
LOOCV거의 비편향높은 분산, 높은 비용매우 작은 데이터
Repeated k-Fold낮은 분산높은 비용안정적 추정 필요 시
Time-Series Split시간 순서 보존초기 fold 훈련 데이터 적음시계열 데이터
Group k-Fold그룹 상관 처리그룹 정보 필요그룹 구조 데이터
Nested CV비편향 성능 + 튜닝매우 높은 비용공정한 모델 비교

  • 모델의 일반화 성능을 추정할 때 → k-Fold CV (기본값: 층화 10-fold)
  • 하이퍼파라미터 튜닝 + 성능 추정을 동시에 할 때 → 중첩 CV
  • 시계열 데이터를 다룰 때 → 시계열 분할 (절대 표준 k-fold 사용 금지)
  • 환자/사용자 등 그룹 구조가 있을 때 → 그룹 k-Fold
  • 논문에서 모델 비교를 보고할 때 → 반복 k-Fold + 통계적 검정

한 데이터 과학 팀이 고객 이탈 예측(Customer Churn Prediction) 모델을 개발하면서, 전체 데이터에 대해 먼저 StandardScaler로 스케일링(Scaling)과 PCA(주성분 분석)를 적용한 후 10-fold 교차 검증을 수행했다. 결과는 AUC 0.92로 매우 우수했다. 그러나 완전히 새로운 테스트 데이터(hold-out set)에 배포하자 AUC가 0.78로 급락했다. 원인은 전형적인 전처리 누수(Preprocessing Leakage)였다 — 스케일링과 PCA를 CV 루프 밖에서 수행했기 때문에, 검증 fold의 통계 정보(평균, 분산, 주성분 방향)가 훈련에 이미 반영되어 있었다. 이 팀이 sklearn의 Pipeline을 사용하여 전처리를 CV 루프 안으로 옮기자, CV 점수는 0.81로 정직하게 내려왔고, 새 데이터에서의 실제 성능(0.79)과 거의 일치했다. 0.92에서 0.78로의 14%p 격차가 순전히 전처리 누수에서 발생한 것이다.


import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score, StratifiedKFold, train_test_split
X, y = load_breast_cancer(return_X_y=True)
clf = LogisticRegression(max_iter=5000)
# 홀드아웃을 시드만 바꿔 5번 -> 점수가 출렁인다
holdout = []
for seed in range(5):
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=.2, random_state=seed, stratify=y)
holdout.append(clf.fit(Xtr, ytr).score(Xte, yte))
print(f"홀드아웃 5회 : {np.round(holdout, 3)} 범위 {max(holdout)-min(holdout):.3f}")
cv = cross_val_score(clf, X, y, cv=StratifiedKFold(5, shuffle=True, random_state=0))
print(f"5-Fold CV : {np.round(cv, 3)} 평균 {cv.mean():.3f} +- {cv.std():.3f}")
# 실행 결과:
# 홀드아웃 5회 : [0.947 0.956 0.965 0.956 0.965] 범위 0.018
# 5-Fold CV : [0.93 0.982 0.947 0.93 0.956] 평균 0.949 +- 0.020

시드만 바꿨을 뿐인데 홀드아웃 점수가 0.947~0.965로 흔들린다. 논문이나 보고서에 이 중 하나만 적으면 운을 실력으로 보고하는 셈이다.


  1. “전처리를 CV 밖에서 해도 된다” — 스케일링, 특성 선택 등 전처리는 반드시 CV 루프 안에서 수행해야 한다. 전체 데이터로 전처리하면 검증 세트 정보가 훈련에 누수된다. 이것이 가장 흔한 실수 중 하나다.

  2. “표준 k-fold는 모든 데이터에 적용된다” — k-fold는 i.i.d. (독립 동일 분포) 가정을 한다. 시계열, 공간 데이터, 그룹 구조 데이터에서는 이 가정이 깨진다.

  3. “LOOCV가 항상 최선이다” — LOOCV는 편향은 낮지만 분산이 매우 높다. fold 간 훈련 세트가 거의 동일하기 때문이다. 대부분의 경우 10-fold가 더 좋은 성능 추정을 준다.

  4. “CV 점수의 평균만 보면 된다” — 표준편차도 반드시 보고해야 한다. 평균은 같지만 분산이 다른 모델은 안정성이 다르다.

  5. “CV로 성능을 추정하면서 동시에 하이퍼파라미터를 튜닝할 수 있다” — 같은 CV를 두 목적에 사용하면 추정이 낙관적으로 편향된다. 중첩 CV를 사용해야 한다.

  6. “데이터가 많으면 CV가 필요 없다” — 데이터가 충분하면 단일 hold-out도 괜찮을 수 있지만, CV는 성능 추정의 분산을 줄여주므로 여전히 가치가 있다.


1. 시계열 데이터에 일반적인 kk-Fold를 쓰면 안 되는 이유는?

정답 보기

셔플이 미래 데이터로 학습해 과거를 예측하게 만들기 때문이다. 실제로는 불가능한 조건이라 성능이 부풀려진다. TimeSeriesSplit처럼 시간 순서를 지키는 분할을 써야 한다.

2. 중첩 교차 검증(Nested CV)은 왜 필요한가?

정답 보기

하이퍼파라미터를 고르는 데 쓴 검증셋으로 최종 성능까지 보고하면 낙관적으로 편향된다. 바깥 루프는 성능 추정, 안쪽 루프는 하이퍼파라미터 선택으로 분리해야 한다.

3. kk를 크게 하면(예: LOOCV) 어떤 장단점이 있는가?

정답 보기

학습 데이터를 최대한 쓰므로 편향은 줄지만, 폴드끼리 거의 같은 데이터로 학습해 분산이 커지고 계산 비용도 kk배 든다. 보통 5 또는 10이 절충점이다.