
머신러닝 모델을 구축할 때 가장 먼저 마주하는 작업은 데이터를 훈련(Training) 세트와 테스트(Testing) 세트로 나누는 것입니다. "가진 데이터를 모두 학습에 사용하면 모델이 더 똑똑해지지 않을까?"라는 의문이 들 수 있지만, 이는 데이터 과학에서 가장 위험한 접근 방식 중 하나입니다. 본 포스팅에서는 데이터 분할의 본질적인 이유와 함께, 실무에서 흔히 발생하는 과적합(Overfitting) 문제를 해결하는 구체적인 전략을 심도 있게 다룹니다.
1. 훈련 데이터와 테스트 데이터를 반드시 나누어야 하는 3가지 결정적 이유
데이터 분할은 단순히 모델의 성적을 매기기 위한 절차가 아닙니다. 이는 모델이 '암기'를 하고 있는지 '학습'을 하고 있는지를 판별하는 유일한 장치입니다.
① 일반화(Generalization) 능력의 검증과 해결
머신러닝의 궁극적인 목표는 한 번도 보지 못한 미래의 데이터(Unseen Data)에 대해 정확한 예측을 내놓는 것입니다. 모든 데이터를 학습에 사용하면 모델은 데이터 내의 노이즈까지 암기하게 되어, 정작 실제 상황에서는 엉뚱한 결과를 도출합니다. 테스트 데이터는 이러한 '일반화 성능'을 평가하기 위한 최종 모의고사 역할을 합니다.
② 과적합(Overfitting) 탐지 및 방지
훈련 데이터에 대한 정확도는 매우 높지만 테스트 데이터에 대한 정확도가 현저히 낮은 현상을 과적합이라고 합니다. 데이터를 분리함으로써 우리는 모델이 데이터의 본질적인 패턴을 파악했는지, 아니면 단순히 훈련 데이터의 특이점(Outlier)에 매몰되었는지 실시간으로 확인할 수 있습니다.
③ 하이퍼파라미터 튜닝의 객관성 확보
모델의 설정값(Learning Rate, Tree Depth 등)을 조정할 때, 학습 데이터만 보고 판단하면 모델은 해당 데이터셋에만 최적화된 편향된 형태를 띠게 됩니다. 테스트 세트를 완전히 격리함으로써, 우리는 모델의 구조적 변경이 실제 성능 향상으로 이어지는지 객관적인 지표로 판단할 수 있습니다.
2. 데이터 분할 방식(Hold-out vs Cross-Validation) 차이 및 특징 비교
프로젝트의 규모와 데이터 양에 따라 적절한 분할 전략을 선택해야 합니다. 아래 표를 통해 각 방식의 차이점을 확인하세요.
| 구분 | 홀드아웃 (Hold-out) | K-폴드 교차 검증 (K-Fold CV) | 계층적 분할 (Stratified Split) |
|---|---|---|---|
| 핵심 개념 | 데이터를 일정 비율(예: 8:2)로 고정 분할 | 데이터를 K개로 나눠 순환 학습/검증 | 클래스 비율을 유지하며 분할 |
| 주요 장점 | 계산 비용이 적고 구현이 간단함 | 모든 데이터를 검증에 활용, 신뢰도 높음 | 불균형 데이터에서 편향 해결 |
| 주요 단점 | 데이터가 적을 경우 분할 운에 좌우됨 | 학습 시간이 K배로 늘어남 | 분류 문제에만 적용 가능 |
| 권장 상황 | 대용량 데이터셋(Big Data) | 소규모 데이터, 정밀한 성능 평가 필요 시 | 스팸 분류, 질병 진단 등 불균형 데이터 |
3. 실무 효율을 높이는 Python Example 7가지
개발자가 실무 파이프라인에서 데이터 누수(Data Leakage)를 방지하고 완벽한 모델 검증 환경을 구축할 수 있는 실전 예제입니다.
Example 1: Scikit-learn을 이용한 가장 기본적인 Hold-out 분할
import pandas as pd
from sklearn.model_selection import train_test_split
# 샘플 데이터 생성
df = pd.DataFrame({'feature': range(100), 'target': [0, 1] * 50})
# 8:2 비율로 분할 (random_state는 결과 재현을 위해 필수)
X_train, X_test, y_train, y_test = train_test_split(
df['feature'], df['target'], test_size=0.2, random_state=42
)
print(f"Train size: {len(X_train)}, Test size: {len(X_test)}")
Example 2: 클래스 불균형 해결을 위한 Stratified Split (계층적 분할)
# 타겟의 비율이 9:1인 경우, 훈련/테스트 세트에서도 이 비율을 유지해야 함
X_train, X_test, y_train, y_test = train_test_split(
df['feature'], df['target'], test_size=0.2, stratify=df['target'], random_state=42
)
print("Target ratio in Train:\n", pd.Series(y_train).value_counts(normalize=True))
Example 3: 시계열 데이터(Time Series) 분할 해결 방법
# 주식이나 날씨 데이터는 랜덤 분할 금지 (과거 데이터로 미래를 예측해야 함)
# shuffle=False 옵션을 통해 순서를 유지하며 분할
X_train, X_test = train_test_split(df['feature'], test_size=0.2, shuffle=False)
print("Train max index:", X_train.index.max())
print("Test min index:", X_test.index.min())
Example 4: 훈련, 검증, 테스트(3-way) 세트로 정밀 분할
# 1차로 Train(80%) : Test(20%) 분할
X_temp, X_test, y_temp, y_test = train_test_split(df['feature'], df['target'], test_size=0.2)
# 2차로 Train을 다시 Train(60%) : Val(20%)로 분할
X_train, X_val, y_train, y_val = train_test_split(X_temp, y_temp, test_size=0.25)
print(f"Train: {len(X_train)}, Val: {len(X_val)}, Test: {len(X_test)}")
Example 5: K-Fold Cross Validation을 통한 성능 해결
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier()
# 5겹 교차 검증 실시 (데이터를 5번 돌려가며 학습/검증)
scores = cross_val_score(model, df[['feature']], df['target'], cv=5)
print(f"평균 정확도: {scores.mean():.2f} (+/- {scores.std() * 2:.2f})")
Example 6: 그룹 기반 분할 (GroupKFold) - 동일 사용자 중복 방지
from sklearn.model_selection import GroupKFold
# 같은 사용자의 데이터가 Train과 Test에 섞여 들어가는 것을 방지
groups = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5] # 사용자 ID 등
gkf = GroupKFold(n_splits=5)
for train_idx, test_idx in gkf.split(df['feature'], df['target'], groups=groups):
print(f"Train Group IDs: {set([groups[i] for i in train_idx])}")
Example 7: 데이터 누수(Data Leakage) 방지를 위한 전처리 순서 해결
from sklearn.preprocessing import StandardScaler
# 핵심: 전체 데이터로 스케일링을 먼저 하면 안 됨!
# 1. 분할 먼저
X_train, X_test, y_train, y_test = train_test_split(df[['feature']], df['target'])
# 2. Train 데이터 기준으로만 스케일러 학습(fit)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
# 3. Test 데이터는 Train의 기준으로 변환(transform)만 수행
X_test_scaled = scaler.transform(X_test)
4. 결론 및 전문가의 조언
데이터를 나누는 행위는 단순히 모델의 점수를 확인하기 위함이 아니라, 모델의 신뢰성을 구축하는 과정입니다. 실무에서는 데이터가 충분하다면 Hold-out(8:2) 방식을 선호하지만, 하이퍼파라미터를 세밀하게 조정해야 하거나 데이터가 부족한 경우에는 반드시 Cross-Validation을 통해 모델의 변동성을 확인해야 합니다. 특히 시계열 데이터나 그룹 데이터에서 랜덤 분할을 사용하는 실수는 모델의 성능을 부풀리는 결과를 초래하므로, 오늘 소개한 예제 3번과 6번의 해결 방법을 반드시 숙지하시기 바랍니다.
5. 내용 출처 및 참고 문헌
- Scikit-learn Documentation: "Cross-validation: evaluating estimator performance" (scikit-learn.org)
- Machine Learning Mastery: "Why Train/Test Split is important"
- Deep Learning by Ian Goodfellow: "Capacity, Overfitting and Underfitting"
- The Elements of Statistical Learning by Trevor Hastie et al.
'Artificial Intelligence > 60. Python' 카테고리의 다른 글
| [PYTHON] 머신러닝의 필수 관문: 원-핫 인코딩(One-hot Encoding)이 필요한 3가지 이유와 해결 방법 (0) | 2026.04.07 |
|---|---|
| [PYTHON] 데이터 분석의 적, 이상치(Outlier) 판단 기준 3가지와 완벽 해결 방법 (0) | 2026.04.07 |
| [PYTHON] 데이터 불균형(Imbalance) 해결을 위한 3가지 샘플링 방법과 성능 최적화 전략 (0) | 2026.04.07 |
| [PYTHON] Monkey Patching의 위험성 3가지 해결 방법과 유닛 테스트 활용의 차이 (0) | 2026.04.07 |
| [PYTHON] 라이브러리 개발을 위한 pyproject.toml 표준 활용 방법 5가지와 해결 전략의 차이 (0) | 2026.04.07 |