StandardScaler에서 fit_transform을 학습·시험 데이터에 각각 쓰면 안 되는 이유
결론부터 말하면, fit_transform은 평균과 표준편차를 배우는 fit과 그 통계로 값을 바꾸는 transform을 함께 수행한다. 시험 데이터에 다시 호출하면 평가 전에 시험 데이터의 분포를 배운다.
따라서 먼저 학습·시험 데이터를 나누고, 학습 데이터에는 fit_transform, 시험 데이터에는 같은 scaler의 transform만 사용한다. 교차검증에서는 Pipeline 안에 scaler를 넣어 각 학습 fold에서만 통계를 배우게 하는 편이 안전하다.
2026-08-22에 격리된 Python 3.12 컨테이너와 scikit-learn 1.9.0으로 직접 비교했다. 학습값 10·12·14·16의 평균은 13이지만 시험값 100·102에 다시 fit하면 평균 101을 사용해 시험값이 -1과 1로 바뀐다. 이는 학습 시점에 알 수 없던 평가 데이터의 중심을 사용한 결과다.
이 글로 해결할 수 있는 것
fit과transform의 역할을 구분한다.- 시험 데이터에 다시 fit할 때 무엇이 새어 들어오는지 숫자로 확인한다.
- Pipeline이 교차검증 누수를 줄이는 이유를 이해한다.
실행 환경
- 일회성
python:3.12-slim컨테이너 - Python 3.12
- scikit-learn 1.9.0
- 증거 파일:
content/evidence/scaler_leakage_sklearn_20260822.json
올바른 순서
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)mean_과 scale_은 학습 데이터에서 계산돼 scaler에 저장된다. 시험 데이터는 저장된 값을 사용해 변환만 한다.
실제 비교 결과
| 방법 | 배운 평균 | 배운 scale | 시험값 변환 결과 |
|---|---|---|---|
| 학습에 fit, 시험에 transform | 13 | 2.236068 | 38.907583, 39.802010 |
| 시험에 다시 fit_transform | 101 | 1 | -1, 1 |
| 전체에 미리 fit | 42.333333 | 41.527768 | 1.388629, 1.436790 |
첫 행의 큰 숫자는 오류가 아니다. 시험 데이터가 학습 범위에서 매우 멀다는 사실을 그대로 보여준다. 두 번째 행은 시험 데이터 자체의 평균을 빼서 보기 좋은 -1과 1로 만들지만, 바로 그 과정에서 평가 데이터 정보를 사용했다.
교차검증에서는 Pipeline을 쓴다
pipeline = make_pipeline(StandardScaler(), model)
scores = cross_val_score(pipeline, X, y, cv=5)Pipeline은 각 fold의 학습 부분에서 scaler와 모델을 fit하고 검증 부분에는 transform/predict만 적용한다. 전처리를 전체 데이터에 먼저 적용한 뒤 교차검증하는 순서를 피할 수 있다.
한계
극단적으로 떨어진 작은 숫자 배열을 사용해 차이를 눈에 보이게 만든 교육용 예다. 누수가 항상 정확도를 높이는 것은 아니지만, 평가가 실제 배포 조건을 대표하지 못하게 만든다는 점이 핵심이다. 시계열은 무작위 분할 자체가 미래정보를 섞을 수 있어 시간 순서도 별도로 확인해야 한다.
출처와 변경 이력
- scikit-learn 공식 문서: Data leakage
- scikit-learn 공식 StandardScaler API
- 최종 검토: 2026-08-22
- 2026-08-22: scikit-learn 1.9.0 격리 실행 결과 추가