손실 함수
학습 목표
손실 함수(Loss Function)의 역할과 학습 과정에서의 위치를 이해한다
회귀와 분류 태스크에 적합한 손실 함수를 선택할 수 있다
Cross-Entropy Loss의 수학적 원리를 이해한다
Focal Loss 등 특수 목적 손실 함수의 동작 방식을 이해한다
왜 중요한가
손실 함수는 모델의 예측이 정답과 얼마나 다른지를 수치화 하는 함수입니다. 역전파(Backpropagation)는 이 손실 값의 기울기를 계산하여 가중치를 업데이트합니다. 올바른 손실 함수를 선택하지 않으면 모델은 잘못된 방향으로 학습합니다.
회귀용 손실 함수
MSE (Mean Squared Error)
L MSE = 1 n ∑ i = 1 n ( y i − y ^ i ) 2 \mathcal{L}_{\text{MSE}} = \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2 L MSE = n 1 i = 1 ∑ n ( y i − y ^ i ) 2
예측값과 정답의 차이를 제곱하여 평균합니다. 큰 오차에 높은 페널티를 부여합니다.
import torch
import torch.nn as nn
criterion = nn.MSELoss()
y_pred = torch.tensor([ 2.5 , 0.0 , 2.1 , 7.8 ])
y_true = torch.tensor([ 3.0 , - 0.5 , 2.0 , 7.5 ])
loss = criterion(y_pred, y_true)
print ( f "MSE Loss: { loss.item() :.4f} " )
MAE (Mean Absolute Error, L1 Loss)
L MAE = 1 n ∑ i = 1 n ∣ y i − y ^ i ∣ \mathcal{L}_{\text{MAE}} = \frac{1}{n}\sum_{i=1}^{n}|y_i - \hat{y}_i| L MAE = n 1 i = 1 ∑ n ∣ y i − y ^ i ∣
절대값을 사용하므로 이상치(Outlier)에 MSE보다 강건합니다.
criterion = nn.L1Loss()
loss = criterion(y_pred, y_true)
print ( f "MAE Loss: { loss.item() :.4f} " )
Huber Loss (Smooth L1)
L δ ( a ) = { 1 2 a 2 if ∣ a ∣ ≤ δ δ ( ∣ a ∣ − 1 2 δ ) otherwise \mathcal{L}_{\delta}(a) = \begin{cases}
\frac{1}{2}a^2 & \text{if } |a| \leq \delta \\
\delta(|a| - \frac{1}{2}\delta) & \text{otherwise}
\end{cases} L δ ( a ) = { 2 1 a 2 δ ( ∣ a ∣ − 2 1 δ ) if ∣ a ∣ ≤ δ otherwise
MSE와 MAE의 장점을 결합합니다. 작은 오차에는 MSE처럼, 큰 오차에는 MAE처럼 동작합니다.
criterion = nn.SmoothL1Loss() # delta=1.0 기본값
loss = criterion(y_pred, y_true)
print ( f "Huber Loss: { loss.item() :.4f} " )
손실 함수 이상치 민감도 기울기 특성 사용 사례 MSE 높음 오차에 비례 일반 회귀 MAE 낮음 상수 (±1) 이상치가 많은 경우 Huber 중간 혼합 균형 잡힌 회귀
분류용 손실 함수
Binary Cross-Entropy (BCE)
이진 분류에 사용됩니다. 모델 출력에 Sigmoid를 적용한 확률값과 정답 레이블의 차이를 측정합니다.
L BCE = − 1 n ∑ i = 1 n [ y i log ( y ^ i ) + ( 1 − y i ) log ( 1 − y ^ i ) ] \mathcal{L}_{\text{BCE}} = -\frac{1}{n}\sum_{i=1}^{n}\left[y_i \log(\hat{y}_i) + (1-y_i)\log(1-\hat{y}_i)\right] L BCE = − n 1 i = 1 ∑ n [ y i log ( y ^ i ) + ( 1 − y i ) log ( 1 − y ^ i ) ]
# 방법 1: Sigmoid를 직접 적용한 후 BCELoss 사용
criterion = nn.BCELoss()
y_pred = torch.sigmoid(torch.tensor([ 0.8 , - 0.5 , 1.2 ])) # Sigmoid 적용
y_true = torch.tensor([ 1.0 , 0.0 , 1.0 ])
loss = criterion(y_pred, y_true)
# 방법 2: BCEWithLogitsLoss (Sigmoid 내장, 수치적으로 더 안정)
criterion = nn.BCEWithLogitsLoss()
logits = torch.tensor([ 0.8 , - 0.5 , 1.2 ]) # Sigmoid 적용 전 (raw logits)
loss = criterion(logits, y_true)
print ( f "BCE Loss: { loss.item() :.4f} " )
nn.BCEWithLogitsLoss는 Sigmoid와 BCE를 하나로 합쳐 수치적 안정성이 높습니다. 실무에서는 이 방법을 권장합니다.
Cross-Entropy Loss (다중 분류)
다중 클래스 분류의 표준 손실 함수입니다. Softmax + Negative Log-Likelihood를 결합한 것입니다.
L CE = − ∑ c = 1 C y c log ( y ^ c ) = − log ( y ^ t r u e ) \mathcal{L}_{\text{CE}} = -\sum_{c=1}^{C} y_c \log(\hat{y}_c) = -\log(\hat{y}_{true}) L CE = − c = 1 ∑ C y c log ( y ^ c ) = − log ( y ^ t r u e )
원-핫 인코딩에서는 정답 클래스의 확률에 대한 음의 로그값만 남습니다.
criterion = nn.CrossEntropyLoss()
# logits: Softmax 적용 전의 raw 값 (배치 크기 2, 클래스 5)
logits = torch.tensor([[ 2.0 , 1.0 , 0.1 , - 1.0 , 0.5 ],
[ 0.5 , 2.5 , 0.3 , 0.1 , - 0.2 ]])
# 정답 레이블 (클래스 인덱스)
labels = torch.tensor([ 0 , 1 ])
loss = criterion(logits, labels)
print ( f "Cross-Entropy Loss: { loss.item() :.4f} " )
# Softmax 확률 확인
probs = torch.softmax(logits, dim = 1 )
print ( f "예측 확률: \n { probs } " )
PyTorch의 nn.CrossEntropyLoss는 내부적으로 Softmax를 포함합니다. 모델 출력에 Softmax를 적용한 후 이 함수를 사용하면 이중 적용이 되어 잘못된 학습이 됩니다. 반드시 raw logits 을 입력하세요.
Focal Loss
클래스 불균형(Class Imbalance) 문제를 해결하기 위해 쉬운 샘플의 가중치를 줄이고, 어려운 샘플에 집중합니다.
L FL = − α t ( 1 − p t ) γ log ( p t ) \mathcal{L}_{\text{FL}} = -\alpha_t (1 - p_t)^{\gamma} \log(p_t) L FL = − α t ( 1 − p t ) γ log ( p t )
γ \gamma γ (focusing parameter): 쉬운 샘플의 손실을 얼마나 줄일지 조절 (보통 2)
α t \alpha_t α t : 클래스별 가중치
class FocalLoss ( nn . Module ):
"""클래스 불균형 문제를 위한 Focal Loss"""
def __init__ ( self , alpha = 1.0 , gamma = 2.0 ):
super (). __init__ ()
self .alpha = alpha
self .gamma = gamma
def forward ( self , logits , targets ):
ce_loss = nn.functional.cross_entropy(logits, targets, reduction = 'none' )
pt = torch.exp( - ce_loss) # 정답 클래스의 예측 확률
focal_loss = self .alpha * ( 1 - pt) ** self .gamma * ce_loss
return focal_loss.mean()
# 사용 예시
criterion = FocalLoss( gamma = 2.0 )
loss = criterion(logits, labels)
print ( f "Focal Loss: { loss.item() :.4f} " )
태스크별 손실 함수 선택
태스크 손실 함수 PyTorch 클래스 회귀 MSE nn.MSELoss()회귀 (이상치 강건) Huber nn.SmoothL1Loss()이진 분류 BCE nn.BCEWithLogitsLoss()다중 분류 Cross-Entropy nn.CrossEntropyLoss()다중 레이블 분류 BCE (각 레이블) nn.BCEWithLogitsLoss()불균형 분류 Focal Loss 커스텀 구현 객체 탐지 Focal + Smooth L1 복합 손실
Cross-Entropy와 KL Divergence의 관계는?
Cross-Entropy는 두 확률 분포 사이의 차이를 측정합니다. H ( p , q ) = H ( p ) + D K L ( p ∥ q ) H(p, q) = H(p) + D_{KL}(p \| q) H ( p , q ) = H ( p ) + D K L ( p ∥ q ) 에서 정답 분포 p p p 의 엔트로피 H ( p ) H(p) H ( p ) 는 상수이므로, Cross-Entropy를 최소화하는 것은 KL Divergence를 최소화하는 것과 동일합니다. VAE에서 KL Divergence가 별도로 사용되는 이유는 VAE 문서에서 다룹니다.
원-핫 레이블 [0, 0, 1, 0]을 [0.025, 0.025, 0.925, 0.025]로 부드럽게 만드는 기법입니다. 모델이 지나치게 확신하는 것을 방지하여 일반화 성능을 개선합니다. PyTorch에서는 nn.CrossEntropyLoss(label_smoothing=0.1)로 간단히 적용할 수 있습니다.
체크리스트
다음 문서