99% 정확도가 쓸모없을 수 있다
사기 거래가 1%인 데이터에서 모든 거래를 정상이라고 예측해도 정확도는 99%다. 하지만 찾아야 할 사기를 하나도 찾지 못한다. 불균형 문제에서는 소수 클래스를 어떤 비용으로 얼마나 찾을지가 먼저다.
recall: 실제 소수 클래스 중 찾아낸 비율. 놓침 비용이 클 때 중요하다.
precision: 소수 클래스라고 경보한 것 중 맞은 비율. 조사 비용이 클 때 중요하다.
balanced accuracy: 클래스별 recall의 평균이라 다수 클래스만 맞히는 정확도 착시를 줄인다.
average precision과 PR 곡선: 임계값 전반의 precision-recall 관계를 본다. 양성 비율과 함께 해석한다.
분할이 샘플링보다 먼저다
무작위 분류 문제에서는 stratify=y로 train/test의 클래스 비율을 비슷하게 유지할 수 있다. 그러나 시간 순 예측, 환자·고객별 반복 행은 각각 시간 분할이나 그룹 분할이 더 우선이다. 동일 개체가 양쪽에 섞이면 계층화해도 누수다.
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=42
)
# test는 실제 운영 비율을 유지한다. 재표본화하지 않는다.
언더샘플링·오버샘플링·SMOTE
Random undersampling은 다수 클래스 행을 버려 빠르지만 유용한 경계·하위집단 정보도 잃을 수 있다.
Random oversampling은 소수 클래스 행을 복제한다. 정보가 새로 생기지는 않으며 과적합 가능성이 있다.
SMOTE는 소수 클래스 한 점과 이웃 사이를 보간해 합성점을 만든다. 단순 복제는 아니지만 현실의 새 관측을 보장하지 않는다.
Borderline-SMOTE는 경계 부근에 집중하고 ADASYN은 학습이 어려운 영역에 더 많이 만든다. 복잡한 변형이 항상 더 좋은 것은 아니다.
범주형 특성이 섞였다면 일반 SMOTE로 one-hot 공간을 보간하지 말고 SMOTENC 같은 데이터 타입에 맞는 방법을 검토한다.
SMOTE는 클래스 경계 밖, 잡음·이상치 주변에도 합성점을 만들 수 있다. 작은 소수 클래스에서는 k_neighbors보다 표본 수가 충분한지 먼저 확인한다.
재표본화는 CV 학습 폴드 안에서만
전체 데이터를 샘플링한 뒤 나누면 합성점이나 선택 규칙이 테스트 샘플 정보를 사용하고, 테스트 분포도 인위적으로 바뀐다. imbalanced-learn 공식 가이드는 이를 데이터 누수로 규정한다. sampler를 imblearn.pipeline.Pipeline 안에 넣으면 각 학습 폴드에만 적용된다.
from imblearn.over_sampling import SMOTE
from imblearn.pipeline import make_pipeline
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold, cross_validate
X, y = make_classification(
n_samples=600, n_features=8, n_informative=5,
weights=[0.9, 0.1], random_state=42
)
model = make_pipeline(
SMOTE(random_state=42),
LogisticRegression(max_iter=1000),
)
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_validate(
model, X, y, cv=cv,
scoring=["balanced_accuracy", "average_precision"],
)
print(round(scores["test_balanced_accuracy"].mean(), 3))
print(round(scores["test_average_precision"].mean(), 3))
정확한 점수는 설치된 scikit-learn·imbalanced-learn 버전에 따라 조금 달라질 수 있다. 검증 포인트는 두 점수가 0~1 범위이고 5개 폴드 점수가 생성되며, SMOTE가 검증 폴드를 보지 않는 파이프라인 구조라는 점이다.
샘플링 전에 강한 기준선부터
아무 샘플링도 하지 않은 모델과 class_weight를 쓴 모델을 먼저 비교한다.
동일한 CV 분할, 동일한 지표와 임계값 정책으로 샘플러 후보를 비교한다.
최종 holdout은 자연 클래스 비율 그대로 한 번만 평가한다.
확률이 필요하면 재표본화가 calibration에 미친 영향도 검증한다.
precision과 recall 비용을 반영해 검증 데이터에서 임계값을 정하고 test에 재조정하지 않는다.
도메인 질문을 계산식으로 바꾸기
좋은 특성은 열을 무작정 조합하는 데서 나오지 않는다. “예측 시점에 무엇을 알고 싶은가?”라는 질문을 먼저 쓰고, 그 답을 비율·차이·상호작용·날짜·집계로 표현한다.
비율: “체격 대비 체중은?” → BMI = weight_kg / height_m². 분모 0과 단위 오류를 검사한다.
차이: “배송에 며칠 걸렸나?” → delivery_date - order_date. 예측 시점에 배송일을 모른다면 이 특성은 누수다.
상호작용: “면적이 큰 고층인가?” → area × floor. 선형모델에 결합 효과를 직접 제공한다.
날짜: “가입 후 얼마나 지났나?” → as_of_date - join_date. 오늘 날짜가 아니라 각 행의 예측 기준일을 쓴다.
집계: “최근 구매 습관은?” → 고객별 과거 30일 주문 횟수·평균 금액. 현재 행과 미래 거래는 집계에서 제외한다.
종합 실습: 주문 데이터에서 시점 안전한 특성 만들기
import pandas as pd
orders = pd.DataFrame({
"customer": ["A", "A", "A", "B"],
"ordered_at": pd.to_datetime(["2026-01-01", "2026-01-10", "2026-02-01", "2026-01-05"]),
"amount": [100, 150, 120, 80],
"items": [2, 3, 1, 2],
})
orders = orders.sort_values(["customer", "ordered_at"])
orders["amount_per_item"] = orders["amount"] / orders["items"]
orders["days_since_previous"] = orders.groupby("customer")["ordered_at"].diff().dt.days
orders["past_order_count"] = orders.groupby("customer").cumcount()
orders["past_mean_amount"] = orders.groupby("customer")["amount"].transform(
lambda s: s.shift().expanding().mean()
)
print(orders[["customer", "amount_per_item", "days_since_previous", "past_order_count", "past_mean_amount"]].to_string(index=False))
customer amount_per_item days_since_previous past_order_count past_mean_amount
A 50.0 NaN 0 NaN
A 50.0 9.0 1 100.0
A 120.0 22.0 2 125.0
B 40.0 NaN 0 NaN
핵심은 shift()다. 현재 주문 금액이 자신의 과거 평균에 들어가지 않게 한 칸 밀었다. 실제 프로젝트에서는 CV 분할보다 먼저 전체 고객 이력을 집계하지 말고, 각 fold와 예측 기준 시점에서 사용할 수 있는 과거만 계산한다.
마지막 검증 질문
특성이 예측 시점에 실제로 존재하는가?
집계가 현재 행·검증 폴드·미래를 보지 않는가?
소수 클래스 성능을 정확도 외 지표로 보는가?
sampler와 전처리가 Pipeline 안에서 각 학습 폴드에만 fit되는가?
복잡한 특성이 단순 기준선보다 반복 CV와 holdout에서 일관되게 나은가?
참고 자료
imbalanced-learn 데이터 누수 가이드 — 전체 데이터 재표본화의 문제와 Pipeline 해법 (2026-08-05 확인)
imbalanced-learn Pipeline 공식 문서 — sampler를 학습 단계에 포함하는 방법 (2026-08-05 확인)
scikit-learn 모델 평가 공식 문서 — balanced accuracy, precision·recall과 average precision (2026-08-05 확인)
SMOTE 원 논문 — Chawla 등(2002)의 합성 소수 클래스 오버샘플링 방법 (2026-08-05 확인)
실전 Feature Engineering 강의 PDF 58~74쪽 — 불균형 처리와 질문 중심 파생 특성 전략 (2026-08-05 확인)
댓글 0
댓글을 불러오는 중…