데이터 누수 (Data Leakage)¶
개요¶
데이터 누수(Data Leakage)는 학습 과정에서 실제 예측 시점에는 사용할 수 없는 정보가 유입되는 현상이다. 데이터 누수가 있는 모델은 검증 시 뛰어난 성능을 보이지만, 실제 배포 후 성능이 급락한다. ML에서 가장 흔하면서도 발견하기 어려운 문제 중 하나이며, "too good to be true" 성능은 거의 항상 데이터 누수를 의심해야 한다.
핵심 개념¶
1. 데이터 누수 유형¶
Train-Test Contamination (학습-테스트 오염)¶
테스트 데이터의 정보가 학습 과정에 유입되는 경우이다.
대표적 사례: - 전체 데이터에 스케일링을 적용한 후 학습/테스트 분할 - 교차 검증 바깥에서 전처리 (target encoding, SMOTE 등) - 동일 원본의 증강 데이터가 학습/테스트 세트에 분산
Target Leakage (타겟 누수)¶
예측 시점에 사용할 수 없는 정보가 특성에 포함되는 경우이다.
예시: - 환자 퇴원일로 입원 중 사망 예측 (퇴원 = 생존 의미) - 주문 배송완료 여부로 주문 취소 예측 - 고객의 해지 사유로 해지 예측
Temporal Leakage (시간적 누수)¶
시계열 데이터에서 미래 데이터가 학습에 사용되는 경우이다.
원인: random split을 사용하면 미래 데이터가 학습 세트에 포함 해결: 반드시 time-based split 사용
Feature Leakage (특성 누수)¶
타겟의 다른 표현이 특성으로 포함되는 경우이다.
예시: 타겟이 "매출"인데 "수익"이 특성에 포함 (매출 = 수익 + 비용)
2. 누수 탐지 (Detection)¶
| 신호 | 설명 |
|---|---|
| 비정상적으로 높은 성능 | "too good to be true" — 현실적이지 않은 AUC > 0.99 |
| 의심스러운 특성 중요도 | 한 특성이 압도적 중요도 → 타겟과 직접 관련 가능성 |
| 학습-테스트 성능 괴리 없음 | 과적합이 전혀 없다면 누수 의심 |
| 배포 후 성능 급락 | 개발 환경에서만 좋은 성능 |
Adversarial Validation:
학습/테스트 데이터를 구분하는 이진 분류기를 학습한다. AUC가 0.5보다 유의하게 높으면 두 데이터의 분포가 다르다는 신호이며, 누수 또는 분포 차이를 의미한다.
- \(\text{AUC}_{\text{adv}} \approx 0.5\): 학습/테스트 분포가 유사하다 (누수 없음)
- \(\text{AUC}_{\text{adv}} \gg 0.5\): 분포 차이가 존재한다 (누수 의심)
분포 차이 정량화 — PSI (Population Stability Index):
두 분포 간 차이를 정량적으로 측정하는 지표이다. 각 구간(bin)별 비율 차이를 합산한다.
여기서 \(p_i\)는 기준 분포(reference)에서 구간 \(i\)의 비율, \(q_i\)는 현재 분포에서 구간 \(i\)의 비율이다.
| PSI 범위 | 해석 |
|---|---|
| < 0.1 | 유의한 변화 없음 |
| 0.1 – 0.25 | 주의 필요 |
| > 0.25 | 유의한 분포 변화 (누수 또는 드리프트 의심) |
KL 발산 (Kullback-Leibler Divergence):
학습 데이터와 서빙 데이터 간 분포 불일치를 측정하는 또 다른 방법이다.
\(D_{KL} = 0\)이면 두 분포가 동일하며, 값이 클수록 분포 차이가 크다. KL 발산은 비대칭이므로 \(D_{KL}(P \| Q) \neq D_{KL}(Q \| P)\)임에 유의한다.
3. 누수 예방 (Prevention)¶
flowchart TD
A["데이터 누수 예방"] --> B["1. 시간 순서 존중<br>시계열은 time-based split"]
A --> C["2. 파이프라인 일체화<br>sklearn Pipeline 사용"]
A --> D["3. 특성 감사<br>각 특성의 사용 가능 시점 확인"]
A --> E["4. 전처리 순서<br>분할 → fit(train) → transform"]
A --> F["5. 도메인 전문가 검토<br>특성의 인과관계 확인"] 파이프라인 사용:
# 잘못된 방법
scaler.fit(X) # 전체 데이터에 fit
X_scaled = scaler.transform(X)
X_train, X_test = split(X_scaled)
# 올바른 방법
X_train, X_test = split(X)
scaler.fit(X_train) # 학습 데이터에만 fit
X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 가장 좋은 방법: Pipeline
from sklearn.pipeline import Pipeline
pipe = Pipeline([
('scaler', StandardScaler()),
('model', LogisticRegression())
])
pipe.fit(X_train, y_train) # 내부적으로 올바르게 처리
교차 검증에서의 올바른 전처리 수식:
잘못된 방법 — 전체 데이터에 전처리를 적용한 후 교차 검증을 수행하면 누수가 발생한다:
올바른 방법 — 각 fold 내부에서 학습 데이터에만 fit한 전처리를 적용한다:
여기서 \(\text{transform}_k\)는 \(D_{\text{train}}^{(k)}\)에만 fit된 변환이며, \(D_{\text{val}}^{(k)}\)는 \(k\)번째 fold의 검증 데이터, \(K\)는 fold 수이다.
상세 내용¶
데이터 누수 체크리스트¶
| 점검 항목 | 확인 방법 |
|---|---|
| 전처리가 학습 데이터에만 fit되었는가? | 코드 리뷰 |
| 시간 순서가 유지되었는가? | 학습/테스트의 시간 범위 확인 |
| 각 특성이 예측 시점에 사용 가능한가? | 도메인 전문가와 논의 |
| 동일 엔티티가 학습/테스트에 분산되지 않았는가? | Group split 확인 |
| Target encoding/SMOTE가 fold 내부에서 적용되었는가? | 파이프라인 검토 |
| 성능이 현실적인 수준인가? | 도메인 기대치 비교 |
시간 기반 분할의 중요성¶
graph LR
subgraph "❌ Random Split (시계열에서 금지)"
R1["2023-01 학습"] --> R2["2023-03 테스트"]
R2 --> R3["2023-02 학습"]
R3 --> R4["2023-04 학습"]
end
subgraph "✅ Time-Based Split"
T1["2023-01"] --> T2["2023-02"]
T2 --> T3["2023-03"]
T3 --> T4["| 2023-04 테스트"]
end Kaggle에서의 교훈¶
Kaggle 대회에서 leakage 특성에 의존하여 높은 순위를 달성하는 경우가 있다. 그러나 이런 모델은 실제 환경에서는 작동하지 않는다. 대회 목적과 실제 배포 목적을 구분해야 한다.
언제 사용하는가¶
데이터 누수 검사는 모든 ML 프로젝트에서 수행해야 한다. 특히:
| 상황 | 누수 위험 |
|---|---|
| 시계열 데이터 | 매우 높음 (temporal leakage) |
| 그룹 데이터 (환자, 사용자) | 높음 (그룹 내 정보 공유) |
| 외부 데이터 결합 | 높음 (시점 불일치) |
| 복잡한 전처리 파이프라인 | 중간 |
| 성능이 비현실적으로 높을 때 | 누수 확인 필수 |
흔한 오해와 함정¶
-
"스케일링은 누수가 아니다": 전체 데이터의 평균/분산을 사용하면 테스트 데이터의 정보가 학습에 유입된다. 영향이 작을 수 있지만, 원칙적으로는 누수이다.
-
"교차 검증이면 누수가 없다": 교차 검증 바깥에서 전처리를 하면 여전히 누수이다. 모든 전처리는 각 fold 내부에서 수행해야 한다.
-
"특성 중요도가 높으면 좋은 특성": 특성 중요도가 비정상적으로 높으면 누수 특성일 가능성을 먼저 의심해야 한다.
-
"데이터 누수는 큰 영향이 없다": 사소해 보이는 누수도 모델의 실제 배포 성능을 크게 과대 추정하게 만들어, 잘못된 비즈니스 의사결정으로 이어질 수 있다.
-
이미지 증강 시 누수: 같은 원본 이미지의 증강 결과가 학습과 검증 세트에 동시에 포함되면, 모델이 증강 패턴을 학습하여 성능이 과대 추정된다.