🤖 AI/ML

교차 검증

Cross-Validation

데이터를 나눠 모델 성능 평가. K-fold가 대표적.

📖 상세 설명

교차 검증(Cross-Validation)은 한정된 데이터에서 모델의 일반화 성능을 더 신뢰성 있게 평가하기 위한 기법입니다. 단순히 데이터를 학습/테스트로 한 번 나누는 것과 달리, 여러 번의 분할과 평가를 수행하여 성능의 평균과 분산을 측정합니다. 이를 통해 특정 데이터 분할에 의한 우연한 결과를 방지하고 모델 성능을 보다 정확하게 추정할 수 있습니다.

가장 널리 사용되는 방법은 K-Fold 교차 검증입니다. 전체 데이터를 K개의 동일한 크기의 폴드(fold)로 나누고, 각 반복에서 1개 폴드를 검증용으로, 나머지 K-1개를 학습용으로 사용합니다. K번의 학습과 평가를 반복하여 K개의 성능 점수를 얻고, 이들의 평균을 최종 성능으로 사용합니다. 일반적으로 K=5 또는 K=10이 많이 사용됩니다.

교차 검증의 변형으로 Stratified K-Fold, Leave-One-Out(LOO), Time Series Split 등이 있습니다. Stratified K-Fold는 분류 문제에서 각 폴드에 클래스 비율을 동일하게 유지합니다. LOO는 K를 데이터 개수와 같게 설정하여 하나의 샘플만 검증에 사용하는 극단적인 방식입니다. Time Series Split은 시계열 데이터에서 미래 데이터가 과거를 학습하는 데이터 누수를 방지합니다.

교차 검증은 하이퍼파라미터 튜닝에도 활용됩니다. GridSearchCV나 RandomizedSearchCV는 여러 하이퍼파라미터 조합에 대해 교차 검증을 수행하여 최적 조합을 찾습니다. 다만 교차 검증은 모델을 K번 학습해야 하므로 계산 비용이 증가합니다. 데이터가 충분히 크다면 단순 홀드아웃(train/valid/test split)도 유효한 대안입니다.

💻 코드 예제

Scikit-learn의 다양한 교차 검증 기법과 하이퍼파라미터 튜닝 예제입니다.

# pip install scikit-learn from sklearn.datasets import load_iris from sklearn.model_selection import ( cross_val_score, KFold, StratifiedKFold, GridSearchCV, cross_validate ) from sklearn.ensemble import RandomForestClassifier from sklearn.svm import SVC import numpy as np # 데이터 로드 X, y = load_iris(return_X_y=True) # 1. 기본 K-Fold 교차 검증 model = RandomForestClassifier(n_estimators=100, random_state=42) scores = cross_val_score(model, X, y, cv=5, scoring='accuracy') print("=== K-Fold 교차 검증 (K=5) ===") print(f"각 폴드 점수: {scores}") print(f"평균 정확도: {scores.mean():.4f} (+/- {scores.std() * 2:.4f})") # 2. Stratified K-Fold (분류 문제에 권장) skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) scores_stratified = cross_val_score(model, X, y, cv=skf, scoring='accuracy') print("\n=== Stratified K-Fold 교차 검증 ===") print(f"평균 정확도: {scores_stratified.mean():.4f} (+/- {scores_stratified.std() * 2:.4f})") # 3. 다중 메트릭 평가 scoring = ['accuracy', 'precision_macro', 'recall_macro', 'f1_macro'] results = cross_validate(model, X, y, cv=5, scoring=scoring, return_train_score=True) print("\n=== 다중 메트릭 교차 검증 ===") for metric in scoring: test_key = f'test_{metric}' print(f"{metric}: {results[test_key].mean():.4f} (+/- {results[test_key].std() * 2:.4f})") # 4. GridSearchCV로 하이퍼파라미터 튜닝 param_grid = { 'C': [0.1, 1, 10], 'kernel': ['rbf', 'linear'], 'gamma': ['scale', 'auto'] } svc = SVC(random_state=42) grid_search = GridSearchCV(svc, param_grid, cv=5, scoring='accuracy', n_jobs=-1) grid_search.fit(X, y) print("\n=== GridSearchCV 결과 ===") print(f"최적 하이퍼파라미터: {grid_search.best_params_}") print(f"최고 교차 검증 점수: {grid_search.best_score_:.4f}") # 5. 각 폴드별 상세 결과 print("\n=== 폴드별 상세 결과 ===") cv_results = grid_search.cv_results_ best_idx = grid_search.best_index_ for i in range(5): print(f"Fold {i+1}: {cv_results[f'split{i}_test_score'][best_idx]:.4f}") # 결과 예시: # === K-Fold 교차 검증 (K=5) === # 각 폴드 점수: [0.967 0.967 0.933 0.967 1.0] # 평균 정확도: 0.9667 (+/- 0.0422)

🗣️ 실무에서 이렇게 말하세요

PM:
"모델 정확도가 95%라고 했는데, 테스트 데이터 하나로만 평가한 거 아닌가요? 신뢰할 수 있나요?"
ML 엔지니어:
"5-Fold Stratified CV로 평가했습니다. 각 폴드 정확도가 93%~96% 범위이고 표준편차 1.5%라서 꽤 안정적이에요. 클래스 불균형이 있어서 Stratified로 했고, 최종 테스트 세트는 CV에 전혀 사용 안 했으니 데이터 누수 걱정은 없습니다."

⚠️ 흔한 실수 & 주의사항

전체 데이터로 전처리(스케일링, 피처 선택) 후 교차 검증 수행
각 폴드 내에서 학습 데이터만으로 전처리 파이프라인 구축 (Pipeline 사용)
시계열 데이터에 일반 K-Fold 사용 (미래 데이터 누수)
TimeSeriesSplit 또는 시간 순서를 고려한 분할 사용
교차 검증 점수로 모델을 선택한 후 같은 데이터로 최종 성능 보고
별도로 분리해둔 테스트 세트로 최종 성능 평가

🔗 관련 용어

📚 더 배우기