"소수 클래스 샘플을 단순히 복제하기만 해도 모델이 더 잘 배울까?"
이전 글에서는 불균형 데이터 상황에서 다수 클래스를 줄이는 언더샘플링 방법을 봤습니다. 이번 글은 반대 방향, 즉 소수 클래스를 늘리는 오버샘플링(over-sampling)을 다룹니다. 우선 가장 단순한 형태인 무작위 복제부터 시작합니다.
발언 기회를 늘리기 위해 소수 의견을 여러 번 반복하기
10명 중 1명만 반대 의견을 내는 데, 반대 의견을 내는 사람에게 발언 기회를 3번 준다고 해봅시다. 회의록에는 반대 의견이 3번 등장하니 비중이 늘어난 것처럼 보입니다. 하지만 실제로 새로운 논거가 추가된 것은 아닙니다. 같은 말을 세 번 들었을 뿐입니다. Random Over-Sampling도 마찬가지로, 기존 소수 클래스 샘플을 그대로 복제해 개수만 늘릴 뿐 새로운 정보를 만들어내지는 않습니다.
Random Over-Sampling: 복제를 통한 균형 맞추기
RandomOverSampler는 소수 클래스에서 무작위로 샘플을 골라 복제하여, 다수 클래스와 비율이 같아질 때까지 반복합니다. 구현이 매우 단순하고 정보 손실이 전혀 없다는 점(다수 클래스를 건드리지 않으므로)이 언더샘플링과 대비되는 장점입니다.
데이터 증강과 단순 복제의 차이
여기서 중요한 개념 구분이 필요합니다.
- 단순 복제(Random Over-Sampling): 기존 샘플을 그대로 반복. 특징 공간상 완전히 동일한 점이 여러 개 생긴다.
- 데이터 증강(Data Augmentation): 원본을 약간 변형하여 "비슷하지만 완전히 같지는 않은" 새로운 샘플을 만든다. 이미지에서의 회전·자르기, 텍스트에서의 동의어 치환, 그리고 나중에 다룰 SMOTE가 여기에 해당한다.
단순 복제는 모델에게 "이 점 주변이 매우 중요하다"는 신호를 강하게 줄 수는 있지만, "이 점 주변이 어떻게 생겼는지"에 대한 새로운 정보는 주지 못합니다. 이 차이가 다음 실습에서 과적합 위험으로 나타납니다.

왜 과적합 위험이 커지는가?
유연한(복잡한) 모델일수록 복제된 샘플 각각을 정확히 암기해버릴 수 있습니다. 결정 트리 계열 모델은 특히 이 위험이 큽니다. 같은 점이 여러 번 등장하면, 트리는 그 점을 정확히 분리하는 매우 좁은 규칙을 만들어낼 유인이 커지기 때문입니다.
작은 숫자 예제
소수 클래스가 50개, 다수 클래스가 950개인 데이터에서 Random Over-Sampling을 적용하면, 소수 클래스의 각 샘플이 평균 19번씩 복제되어 950개가 됩니다(950/50 = 19). 특징 공간에는 여전히 서로 다른 소수 클래스 "위치"가 50곳뿐입니다. 겉보기 샘플 수는 950개로 늘었지만, 실제 정보의 다양성은 원본 50개에서 전혀 늘어나지 않았다는 점이 이 예제의 핵심입니다.
Python 실습: 오버샘플링의 효과와 과적합 위험 확인
# 설치가 필요한 환경에서 한 번만 실행
# !pip install -q scikit-learn imbalanced-learn numpy
import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split, StratifiedKFold, cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import f1_score
from imblearn.over_sampling import RandomOverSampler
from imblearn.pipeline import Pipeline as ImbPipeline
RANDOM_STATE = 42
np.random.seed(RANDOM_STATE)
X, y = make_classification(
n_samples=3000,
n_features=10,
n_informative=5,
n_redundant=2,
weights=[0.95, 0.05],
class_sep=1.0,
random_state=RANDOM_STATE,
)
print("원본 클래스 분포:", np.bincount(y))
X_res, y_res = RandomOverSampler(random_state=RANDOM_STATE).fit_resample(X, y)
print("오버샘플링 후 분포:", np.bincount(y_res))
print("-" * 60)
# 1) 교차검증: 학습 세트에만 오버샘플링 적용, 검증은 원본 분포 그대로 (7장 원칙 준수)
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=RANDOM_STATE)
baseline_scores = cross_val_score(
LogisticRegression(max_iter=2000, random_state=RANDOM_STATE),
X, y, cv=cv, scoring="f1",
)
print(f"[베이스라인, 샘플링 없음] F1 평균: {baseline_scores.mean():.4f}")
ros_pipeline = ImbPipeline([
("ros", RandomOverSampler(random_state=RANDOM_STATE)),
("model", LogisticRegression(max_iter=2000, random_state=RANDOM_STATE)),
])
ros_scores = cross_val_score(ros_pipeline, X, y, cv=cv, scoring="f1")
print(f"[RandomOverSampler] F1 평균: {ros_scores.mean():.4f}")
print("-" * 60)
# 2) 과적합 위험 시연: 유연한 모델(RandomForest)에서 학습 F1 vs 테스트 F1
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=RANDOM_STATE,
)
X_train_res, y_train_res = RandomOverSampler(random_state=RANDOM_STATE).fit_resample(X_train, y_train)
rf = RandomForestClassifier(random_state=RANDOM_STATE)
rf.fit(X_train_res, y_train_res)
train_f1 = f1_score(y_train_res, rf.predict(X_train_res))
test_f1 = f1_score(y_test, rf.predict(X_test)) # 테스트 세트는 원본 분포 그대로 평가
print(f"[RandomForest] 오버샘플링된 학습 세트 F1: {train_f1:.4f}")
print(f"[RandomForest] 원본 테스트 세트 F1 : {test_f1:.4f}")
실제 실행 결과:
원본 클래스 분포: [2835 165]
오버샘플링 후 분포: [2835 2835]
------------------------------------------------------------
[베이스라인, 샘플링 없음] F1 평균: 0.3403
[RandomOverSampler] F1 평균: 0.2214
------------------------------------------------------------
[RandomForest] 오버샘플링된 학습 세트 F1: 1.0000
[RandomForest] 원본 테스트 세트 F1 : 0.5714
두 가지 결과가 눈에 띕니다.
첫째, Logistic Regression에서는 오버샘플링을 적용해도 F1이 베이스라인보다 오히려 낮아졌습니다(0.3403 → 0.2214). 선형 모델은 애초에 각 클래스에 속한 점들의 "무게 중심"과 분포 형태로 경계를 긋기 때문에, 같은 점을 단순히 여러 번 복제하는 것만으로는 결정경계 자체가 크게 개선되지 않고, 오히려 클래스 균형이 맞춰지면서 모델이 더 공격적으로 양성을 예측하게 되어 Precision이 떨어지는 부작용이 나타날 수 있습니다.
둘째, RandomForest에서는 학습 세트 F1이 정확히 1.0000입니다. 이는 모델이 복제된 소수 클래스 샘플들을 완벽하게 암기했다는 뜻입니다. 그러나 원본 분포를 유지한 테스트 세트에서는 F1이 0.5714로 뚝 떨어집니다. 이 큰 격차(1.0 vs 0.57)가 바로 오버샘플링이 유발하는 과적합의 전형적인 신호입니다.

흔한 실수
- 학습 F1만 보고 "잘 학습되었다"고 판단하는 것. 오버샘플링 후 학습 점수는 거의 항상 비정상적으로 높게 나오므로, 반드시 별도의 원본 분포 테스트 세트로 평가해야 합니다.
- 검증 데이터 분포까지 오버샘플링하는 것. 앞의 글에서 다룬 원칙과 동일하게, 검증/테스트 데이터는 항상 원본 클래스 분포를 유지해야 합니다.
- 선형 모델에 오버샘플링을 적용하면서 결정경계가 크게 개선될 것이라 기대하는 것. 단순 복제는 새로운 형태의 정보를 주지 못하므로, 선형 모델에서는 추후 다룰 클래스 가중치 조정이 더 효율적인 대안이 될 수 있습니다.
한 걸음 더: 그렇다면 언제 오버샘플링을 선택해야 하는가
Random Over-Sampling은 대체로 "다른 방법을 시도하기 전의 빠른 베이스라인" 또는 "다수 클래스의 정보를 절대 잃고 싶지 않을 때"의 선택지로 활용하는 것이 합리적입니다. 진짜로 원하는 것이 "새로운 정보가 담긴 그럴듯한 소수 클래스 샘플"이라면, 다음 글에서 다룰 SMOTE(합성 소수 샘플링 기법)가 훨씬 정교한 대안이 됩니다. SMOTE는 기존 소수 샘플들 사이를 보간하여 실제로 새로운 좌표에 샘플을 만들어내기 때문에, 이 장에서 확인한 "정보량이 늘지 않는다"는 한계를 부분적으로 극복합니다.
'머신러닝' 카테고리의 다른 글
| 불균형 데이터 다루기 11 - 잡음과 경계를 다듬는 하이브리드 샘플링 (0) | 2026.09.13 |
|---|---|
| 불균형 데이터 다루기 10 - SMOTE와 합성 샘플링의 원리 (0) | 2026.09.13 |
| 불균형 데이터 다루기 8 - 다수 데이터를 줄이는 언더샘플링 (0) | 2026.09.12 |
| 불균형 데이터 다루기 7 - 분할·교차검증·데이터 누수 방지 (0) | 2026.09.12 |
| 불균형 데이터 다루기 6 - 업무 비용과 목표에 맞는 평가 기준 (0) | 2026.09.12 |