본문 바로가기
C.W.K.
Stream
Lesson 06 of 07 · published

불균형한 데이터: 표본 추출기, 클래스 가중치, 포컬 손실

~12 min · imbalanced, sampler, class-weight, focal-loss

Level 0텐서 탐구자
0 XP0/62 lessons0/13 achievements
0/120 XP to next level120 XP to go0% complete

서로 다른 세 전략을 필요에 따라 조합해

현실의 분류 문제는 클래스 분포가 치우친 경우가 많아. 예를 들어 정상 거래가 99%, 사기가 1%라면 단순히 학습한 모델은 모든 거래를 정상이라고 예측하는 법만 배울 수 있어. 정확도는 높아도 쓸모는 없지. 다음 세 도구는 갈수록 더 적극적으로 불균형을 보정해:

  1. 가중 표본 추출: DataLoader가 샘플을 뽑는 확률을 바꿔 표본이 적은 클래스를 더 자주 보여 줘.
  2. 손실의 클래스 가중치: 원래 표본 비율은 유지하되 표본이 적은 클래스를 틀렸을 때 더 큰 벌점을 줘. nn.CrossEntropyLoss(weight=...)로 설정해.
  3. 포컬 손실: 1:1000을 넘는 극단적인 불균형에서 이미 맞히기 쉬운 샘플의 비중을 낮춰. RetinaNet에서 처음 제안했어.

필요하면 조합할 수 있지만 먼저 클래스 가중치부터 시작해. 가장 간단하고 조정할 하이퍼파라미터도 적어. 부족하면 가중 표본 추출을 추가하고, 정말 극단적인 경우에 포컬 손실을 고려해.

피해야 할 함정

표본이 적은 클래스를 과표본 추출하면서 클래스 가중치까지 주면 같은 효과가 두 번 적용돼. 둘 중 하나를 고르거나 함께 쓸 때 전체 가중치를 신중히 조정해. 포컬 손실의 α 매개변수 자체도 클래스 가중치 역할을 한다는 점을 기억해.

Code

WeightedRandomSampler: 희소한 클래스 과표본 추출·python
import torch
from torch.utils.data import DataLoader, WeightedRandomSampler

# Suppose 1000 class-A samples, 100 class-B samples, 50 class-C samples
labels = torch.cat([
    torch.zeros(1000, dtype=torch.long),
    torch.ones(100, dtype=torch.long),
    torch.full((50,), 2, dtype=torch.long),
])

class_counts = torch.tensor([1000., 100., 50.])
weights_per_class = 1.0 / class_counts
sample_weights = weights_per_class[labels]

sampler = WeightedRandomSampler(
    weights=sample_weights,
    num_samples=len(sample_weights),
    replacement=True,                  # required for over-sampling rare classes
)

loader = DataLoader(dataset, batch_size=32, sampler=sampler)
# Each batch now sees roughly equal counts of A, B, C
클래스 가중치를 적용한 CrossEntropyLoss·python
import torch
import torch.nn as nn

# Inverse-frequency weighting: rare classes get bigger weight
class_counts = torch.tensor([1000., 100., 50.])
weights = 1.0 / class_counts
weights = weights / weights.sum() * len(class_counts)  # normalize to mean 1

criterion = nn.CrossEntropyLoss(weight=weights.to(device))

# Same training loop as usual; the loss value is now class-aware
포컬 손실: 극단적인 불균형·python
import torch
import torch.nn as nn
import torch.nn.functional as F

class FocalLoss(nn.Module):
    """Down-weights well-classified (easy) examples."""
    def __init__(self, alpha=1.0, gamma=2.0, reduction='mean'):
        super().__init__()
        self.alpha = alpha
        self.gamma = gamma
        self.reduction = reduction

    def forward(self, logits, targets):
        ce = F.cross_entropy(logits, targets, reduction='none')
        pt = torch.exp(-ce)            # prob of correct class
        focal = self.alpha * (1 - pt) ** self.gamma * ce
        if self.reduction == 'mean':
            return focal.mean()
        if self.reduction == 'sum':
            return focal.sum()
        return focal

# Use just like CrossEntropyLoss
criterion = FocalLoss(alpha=0.25, gamma=2.0)

External links

Exercise

클래스별 샘플 수가 1000개, 100개, 10개인 3클래스 불균형 데이터셋을 만들어 봐. 분류기를 (a) 재균형 없이, (b) 클래스 가중치로, (c) WeightedRandomSampler로, (d) 포컬 손실로 각각 학습하고 혼동 행렬을 출력해. 아무 보정도 하지 않은 모델은 가장 작은 클래스의 재현율이 거의 0%일 수 있어. 다른 방법들이 그 클래스를 얼마나 살리는지 비교해.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.