Baseline 코드의 loss.py 파일에 들어가면 기본적으로 3가지 Loss가 정의되어 있고, nn.CrossEntropy가 있다.
CrossEntropyLoss
FocalLoss
LabelSmoothingLoss
F1Loss
이 네 가지를 사용하는 경우도 알고 쓰고, 이를 알면서 또 내 모델에 어떤 Loss function을 쓰는게 좋을 지 생각해보자.
Cross Entropy Loss
N가지 종류의 클래스에서 데이터의 라벨이 one - hot encoding으로 정답이 있을 때, 활성화 함수 Softmax를 통과한 클래스의 예측값이 다음과 같이 나왔다고 하자. 이 식은
다음의 식으로 변경이 가능하며 2번 클래스로 예측한 확률이 가장 높기 때문에 실제 확률 분포의 확률 분포가 [0, 1, 0] 으로 one-hot encoding 된 상태라면 정답이다.
이때 Cross - Entropy를 사용해 \(P(x)\) 와 \(Q(x)\)의 차이를 다음과 같이 계산한다.
이 값 0.357을 Cross-Entropy의 값이라고 하며 이 값을 손실 함수로 사용할 수 있기 때문에 Cross-entropy를 통해 구한 값이 CrossEntroypyLoss라고 할 수 있다. 예측 클래스의 2번 확률이 0.8, 0.9 등 더 늘어난다면 이 Cross-entropy 값이 줄어들기 때문이다.
Focal Loss
Cross entropy는 class의 unbalance를 고려하지 않는다는 단점이 있다. 예를 들어 1 stage detector인 Object Detection의 예시를 들면 한 장의 이미지 내에서 소수의 객체에 대한 예측과 달리 수천배의 background에 대한 예측이 존재하기 때문에 오답 데이터가 지나치게 많다. 따라서 정답이 아닌 데이터의 Loss가 누적되어 정답에 대한 Loss를 압도하고, 모델이 background에 대한 학습에 집중해버린다. 이를 개선하기 위해 'Focal Loss for Dense Object Detection' 에서 등장한 개념이 focal loss이다.
감마값을 조정하지 않은 파란색 선이 일반적인 Cross Entropy Loss이며 이에 비해 x축인 예측 확률값이 낮을 때 보다 확률값이 높은 경우 loss를 낮게 준다. 즉 Cross entropy 식에서 \(-(1-p_{t})\gamma\) 텀이 추가된 식이다.
ex) prob = 0.98 (easy) 와 prob = 0.2 (hard) 인 경우 Focal Loss는
prob = 0.98인 경우 -(1 - 0.98)^2 * log(0.98) = 0.00003508, prob = 0.2인 경우 -(1 - 0.2)^2 * log(0.2) = 0.44734로 easy와 hard인 경우 loss의 격차를 크게 만든다.
따라서 최종 식
를 통해 클래스 간 불균형이 있을 때 loss function의 성능을 높일 수 있다.
이 코드를 파이썬을 통해 구현하면 다음과 같다.
# https://discuss.pytorch.org/t/is-this-a-correct-implementation-for-focal-loss-in-pytorch/43327/8
class FocalLoss(nn.Module):
def __init__(self, weight=None,
gamma=2., reduction='mean'):
nn.Module.__init__(self)
self.weight = weight
self.gamma = gamma
self.reduction = reduction
def forward(self, input_tensor, target_tensor):
log_prob = F.log_softmax(input_tensor, dim=-1)
prob = torch.exp(log_prob)
return F.nll_loss(
((1 - prob) ** self.gamma) * log_prob,
target_tensor,
weight=self.weight,
reduction=self.reduction
)
__init__ 부분에서는 위에서 설명한 gamma 값을 2로 둔다. (논문에서도 gamma = 2 인 경우가 가장 좋다고 했다) 이후 reduction = "mean"으로 설정하며, weight = None으로 둔다.
이후 forward에서 log_softmax를 통해 log_prob, torch.exp()를 통해 prob를 구한다. 이후 위의 식 (1-prob)^2 * log_prob를 f.nll_loss를 통해 계산하게 된다.
F.nll_loss는 negative log likelihood loss로, cross-entropy 내에서 사용된다. cross-entropy는 LogSoftmax + NLLLoss를 동시에 수행하지만 focal loss에서는 LogSoftmax 대신 위의 gamma값이 적용된 식을 사용하기 때문에 cross-entropy 내에서 사용되는 최종 처리 함수를 쪼개서 위 코드에서 사용했다고 보면 된다.
LabelSmoothingLoss
정규화에서 가장 많이 쓰이는 hard-label을 soft-label로 바꾸는 Label Smoothing이며 모델의 일반화 성능을 높여준다.
Dropout 다음으로 많이 사용되는 기법이다. 위의 Cross-Entropy Loss에서 나온 P(x)처럼 [0, 1, 0]이 hard-label이라면 Q(x)의 [0.2, 0.7, 0.1]이 soft-label이다.
Label Smoothing의 식은 one-hot encoding이 된 label에 Uniform-distribution을 결합한다. 이 스무딩 파라미터를 \(\alpha\)라고 할 때,
의 식으로 k번째 식에 대한 스무딩을 한다.
이 변환을 거친 이후 클래스의 분포가
이처럼 균일 분포를 따르기 때문에 Uniform distribution을 결합했다고 볼 수 있다. 이후 Cross-entropy에서 loss를 최소화 할 때 정답 타겟을 라벨 스무딩이 적용된 타겟으로 적용하면 Label Smoothing Loss이다.
이를 코드로 구현하면
class LabelSmoothingLoss(nn.Module):
def __init__(self, classes=3, 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 = 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))
와 같다. class 3개는 대회에서 사용되는 마스크 이미지의 mask, age, gender 3 class이기 때문에 초기값으로는 classes = 3, smoothing = 0.0을 적용한다.
이후 forward에서 pred = log_softmax를 적용한 결과이고 torch.no_grad()를 통해 자동 미분 계산을 하지 않고 pred의 크기만큼 0을 채운다. 이후 self.smoothing / (self.cls - 1)로 0으로 채워진 true_dist tensor를 채우고, .scatter_를 통해 주어진 data의 크기에 맞게 dim1 차원에 대해 라벨이 smoothing 된다. 이를 통해 마지막에 torch.mean을 수행하면 softmax를 거쳤던 Cross-Entropy 결과들에 대해 LabelSmoothing이 이뤄져 LabelSmoothingLoss가 된다.
F1Loss
F1 score는 Confusion matrix에서 계산되는 확률이다.
혼동 행렬 내에서 Recall(재현률)은 TP/TP + FN 으로 계산하며, Precision(정확도)는 TP/TP + FP로 계산한다.
그리고 이 Recall과 Precision에 대해 F1 score는
다음과 같이 계산한다. 이 F1 score를 통한 loss function은
Is it possible to make F1_Score differentiable and use it directly as a Loss function?
One of the metrics that is widely used in binary classification is the F1 score: $F_1 = 2\cdot \frac{recall \cdot precision}{recall+precision}$ The problem of the F1-score is that it is not
datascience.stackexchange.com
다음 글에서 찾을 수 있는데
확률적 관점으로 접근해 \(Dice(X,Y)\)에 대해 계산하고 이를 1에서 뺀 \(L_{Dice}\)를 Loss function으로 사용할 수 있다.
이를 코드로 구현하면
# https://gist.github.com/SuperShinyEyes/dcc68a08ff8b615442e3bc6a9b55a354
class F1Loss(nn.Module):
def __init__(self, classes=3, epsilon=1e-7):
super().__init__()
self.classes = classes
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, self.classes).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()
y_true에 one-hot encodding된 vector를 넣고, pred에는 Softmax를 통한 코드를 구한다. 이후 코드 내 TP, TN, FP, FN, precision, recall, f1을 구하는 식을 통해 각 항목을 구한다. 이 f1 score에 대해 torch.clamp() 함수를 사용해 min = epsilon 1e-7, max = 1 - 1e7 사이에서 값을 구한다. clamp 함수는 min보다 작은 값을 min으로, max보다 큰 값을 max로 min, max 의 범주에 속하게끔 tensor의 값을 변경한다. 이후 1 - f1.mean()을 통해 F1Loss를 계산한다.
어떤 Loss를 사용할까?
loss.py에 구현된 모든 loss에 대해 조금이나마 알아봤다. 일단 내가 생각하는 바로는 F1 score 자체가 binary classification에서 나오는 개념이기 때문에 이 f1 score를 사용하는 F1 loss는 우선 탈락시키는게 좋을 것 같다.
그리고 Cross-Entropy Loss는 분류 문제에서 너무 Valnilla loss의 느낌이 있다. 괜히 이를 개선하려고 사람들이 노력한 것이 아니지 않을까.
그렇다면 Label smoothing과 focal loss가 있는데 focal loss가 데이터 불균형의 문제에서 효율이 좋다고 했다. 기존의 Cross-entropy에 비해 큰 차이점을 두지 않으면서도 해당 모델에서는 60대의 데이터가 다른 데이터에 비해 적고, 특히 50대의 데이터는 다른 나이구간에 비해 많은 모습을 보인다. 즉 클래스간의 불균형이 심하기 때문에 Focal loss가 좋지 않을까 싶다. label smoothing은 좋을지 안 좋을지 감이 안온다. 이는 직접 실험을 통해 알아보는 것이 좋을 것 같다.
Reference
https://velog.io/@cha-suyeon/%EC%86%90%EC%8B%A4%ED%95%A8%EC%88%98loss-function-Cross-Entropy-Loss
손실함수(loss function), Cross Entropy Loss
손실함수(loss function)은 머신러닝을 공부해보았다면 들어보았을 것입니다. 처음에 미니 데이터셋으로 모델을 돌리고 loss와 accuracy가 어떻게 나올까 두근두근☺하며 기다렸던 기억이 있네요.(저
velog.io
https://visionhong.tistory.com/40
Focal Loss
Focal Loss는 2017년 말에 Fair(현 Meta AI Research)에서 발표한 논문 'Focal Loss for Dense Object Detection'에서 소개되었으며 현재 Object Detection 모델중 1 stage detector(YOLO, SSD)와 같이 anchor box를 활용해 dense prediction
visionhong.tistory.com
[ML | TIL] Label Smoothing에 대해 알아보기 (feat. When Does Label Smoothing Help? 논문)
competition 대회를 진행하면서 loss의 설정에 대한 부분을 신경을 많이 쓰게 되었는데 label smoothing과 focal loss를 많이 다루게 되었다.그냥 좋다니까 가져다 쓰는 것 보다는 이해가 선행되는 것이 좋
velog.io
'네이버 부스트캠프 학습 정리 > 6주차' 카테고리의 다른 글
[P-stage 1] EfficientNet (1) | 2023.04.16 |
---|---|
[P-stage 1] 실험 환경 세팅 (0) | 2023.04.15 |
P stage 돌입과 학습 정리 방식 변경 (0) | 2023.04.15 |