네이버 부스트캠프/LEVEL-1

[부스트캠프][P-stage][WK04 / Day4] Image Classification 4

1. 금일 목표

  • 체크포인트 모델 저장
  • F1-score Metric 을 구현 or 코드
  • Gradient Accumulation을 적용
  • 다양한 스케쥴러 적용 및 비교
  • Label smoothing, Focal Loss, F1 Loss 등 활용
  • 위 사항별 성능 비교

2. 진행사항

1) 체크포인트 모델 저장

if epoch_val_loss < best_val_loss:
    print("New best model for val loss! saving the model..")
    torch.save(model.state_dict(), os.path.join(f'{path}/{epoch:03}_loss_{epoch_val_loss:4.2}.pt'))
    best_val_loss = epoch_val_loss


if epoch_val_f1 > best_val_f1:
    print("New best model for val f1! saving the model..")
    torch.save(model.state_dict(), os.path.join(f'{path}/{epoch:03}_f1_{epoch_val_f1:4.2}.pt'))
    best_val_f1 = epoch_val_f1
    # 가장 성능이 좋은 모델을 따로 저장해 나중에 불러옴
    best_model = f'{path}/{epoch:03}_f1_{epoch_val_f1:4.2}.pt'

2) F1-score Metric 을 구현 or 코드

from sklearn.metrics import f1_score

epoch_f1 += f1_score(output.argmax(dim=1).cpu(), label.cpu(), average='macro') / len(train_loader)

3) Gradient Accumulation을 적용, 다양한 스케쥴러 적용 및 비교

  • Scheduler
    • StepLR: 특정 Step 마다 LR 감소
    • CosineAnnealingLR: Cosine 함수 형태처럼 LR을 급격히 변경
    • ReduceLROnPlateau: 더 이상 성능 향상이 없을 때 LR 감소
# batch size가 32인 경우 NUM_ACCUM을 4로 설정하면 128batch만큼 진행된 후 optimizer 파라미터를 업데이트 합니다.
NUM_ACCUM = 4
gamma = 0.7 # step_size만큼의 epoch가 진행되면 기존의 lr에 gamma값을 곱해 새로운 lr 생성
scheduler = StepLR(optimizer, step_size=5, gamma=gamma)
lrs = []

for epoch in range(epochs):
    for i, data in enumerate(tqdm(train_loader,leave=True)):
        model.train()
        inputs, label = data
        inputs = inputs['image'].to(device)
        label = label.to(device)

        output = model(inputs)
        loss = criterion(output, label)

        loss.backward()

        if i % NUM_ACCUM == 0:
            optimizer.step()
            optimizer.zero_grad()
    lrs.append(optimizer.param_groups[0]["lr"]) # lr 확인을 위해 담아줌
    scheduler.step()

4) Label smoothing, Focal Loss, F1 Loss 등 활용

  • Focal Loss
    Focal Loss는 one-stage detector에서 클래스 불균형 문제를 해결하기 위해 제안된 loss function으로 cross-entropy loss에 인자를 하나 추가한 것입니다. 해당 인자를 modulating factor이라 하며, easy example의 영향을 감소시키고 hard example에 집중하도록 합니다. $\gamma$는 하이퍼 파라미터로 easy/hard example의 가중치를 조절할 수 있습니다. example이 잘못 분류됐으면 $p_t$는 낮은 값을 가지게 되어 modulating factor($1-p_t$)은 1에 가까운 값을 갖게 되고 loss는 가중치에 영향을 거의 받지 않게 됩니다.

  • Label smoothing
    잘못된 loss 의 영향을 줄이기 위하여 label 을 0또는 1이 아니라 smooth 하게 부여하는 아이디어로, hard target을 soft target으로 바꾸어 모델의 over confidence 문제를 해결하는데 사용됩니다.

아래 크로스 엔트로피 loss의 $y(k)$에 위의 $y(k)'$를 대입해줍니다.

그 결과 아래와 같은 결과가 나오게 됩니다.

class LabelSmoothingLoss(nn.Module):
    def __init__(self, classes, smoothing=0.0, dim=-1):
        super(LabelSmoothingLoss, self).__init__()
        self.confidence = 1.0 - smoothing
        self.smoothing = smoothing
        self.cls = classes
        self.dim = dim

    def forward(self, pred, target):
        pred = pred.log_softmax(dim=self.dim)
        with torch.no_grad():
            # true_dist = pred.data.clone()
            true_dist = torch.zeros_like(pred)
            true_dist.fill_(self.smoothing / (self.cls - 1))
            true_dist.scatter_(1, target.data.unsqueeze(1), self.confidence)
        return torch.mean(torch.sum(-true_dist * pred, dim=self.dim))
  • F1 Loss
    Metric으로 사용되는 f1을 활용해 loss function으로 사용합니다. 성능이 진짜로 잘나오는지 실험예정입니다.
class F1_Loss(nn.Module):
    def __init__(self, epsilon=1e-7):
        super().__init__()
        self.epsilon = epsilon

    def forward(self, y_pred, y_true,):
        assert y_pred.ndim == 2
        assert y_true.ndim == 1
        y_true = F.one_hot(y_true, 2).to(torch.float32)
        y_pred = F.softmax(y_pred, dim=1)

        tp = (y_true * y_pred).sum(dim=0).to(torch.float32)
        tn = ((1 - y_true) * (1 - y_pred)).sum(dim=0).to(torch.float32)
        fp = ((1 - y_true) * y_pred).sum(dim=0).to(torch.float32)
        fn = (y_true * (1 - y_pred)).sum(dim=0).to(torch.float32)

        precision = tp / (tp + fp + self.epsilon)
        recall = tp / (tp + fn + self.epsilon)

        f1 = 2* (precision*recall) / (precision + recall + self.epsilon)
        f1 = f1.clamp(min=self.epsilon, max=1-self.epsilon)
        return 1 - f1.mean()

5) 성능 비교

  • EfficientNet-b3
  • EfficientNet-b3 + scheduler
  • 표기오류. lr=0.0001에서 시작.
  • EfficientNet-b3 + scheduler + Gradient Accumulation
  • 표기오류. lr=0.0001에서 시작.
  • EfficientNet-b3 + Centercrop
  • ResNet18 best loss
  • ResNet18 best f1 score

3. 피어세션 정리

https://hackmd.io/C9fuGkS6RlSAT1wKbhHO5g


4. 학습 회고

centercrop을 적용해 보았지만 성능이 오히려 떨어졌습니다. 또한 Gradient Accumulation, 스케쥴러 모두 적용해 보았지만 파라미터를 잘못 설정했는지 성능은 나아지지 않았습니다. 데이터 불균형 문제를 해결하기 위해 다른 방법을 더 찾아보고 다른 모델을 사용하여 실험해봐야 할 것 같습니다.