들어가며
손실 함수는 모델이 얼마나 틀렸는지를 하나의 숫자로 표현하는 함수다. 학습의 목표가 이 값을 최소화하는 것이기 때문에, 어떤 손실 함수를 쓰느냐가 모델의 학습 방향 자체를 결정한다. 문제 유형에 따라 적절한 손실 함수를 선택하는 게 중요하다.
손실 함수란?
예측값(ŷ)과 실제값(y)의 차이를 수치화한 함수
좋은 예측 → 손실 작음
나쁜 예측 → 손실 큼
학습 목표: 손실을 최소화하는 가중치 W 찾기
θ* = argmin L(y, ŷ)
웹 개발로 비유하면 손실 함수는 테스트 실패율 같은 것이다. 테스트가 많이 실패할수록 코드를 더 많이 고쳐야 하듯, 손실이 클수록 가중치를 더 많이 업데이트해야 한다.
회귀 손실 함수
연속적인 값을 예측하는 문제에서 사용한다.
MSE (Mean Squared Error)
MSE = (1/n) * Σ (yᵢ - ŷᵢ)²
import numpy as np
import torch
import torch.nn as nn
def mse_loss(y_true, y_pred):
return np.mean((y_true - y_pred) ** 2)
y_true = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
y_pred_good = np.array([1.1, 2.0, 2.9, 4.1, 5.0])
y_pred_bad = np.array([2.0, 3.0, 4.0, 5.0, 6.0])
print(f"좋은 예측 MSE: {mse_loss(y_true, y_pred_good):.4f}") # 0.006
print(f"나쁜 예측 MSE: {mse_loss(y_true, y_pred_bad):.4f}") # 1.0
# PyTorch
criterion = nn.MSELoss()
y_t = torch.tensor([1.0, 2.0, 3.0, 4.0, 5.0])
y_p = torch.tensor([1.1, 2.0, 2.9, 4.1, 5.0])
print(f"PyTorch MSE: {criterion(y_p, y_t):.4f}")
장점:
- 큰 오차에 더 큰 패널티 (제곱이라서)
- 미분이 쉬움 → 경사하강법 적용 간단
단점:
- 이상치(outlier)에 민감
예: 하나의 예측이 10 차이나면 MSE에 100 기여
사용처: 주가 예측, 집값 예측, 온도 예측 등 일반적인 회귀
MAE (Mean Absolute Error)
MAE = (1/n) * Σ |yᵢ - ŷᵢ|
def mae_loss(y_true, y_pred):
return np.mean(np.abs(y_true - y_pred))
y_true = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
y_outlier = np.array([1.1, 2.0, 2.9, 4.1, 15.0]) # 마지막값이 이상치
print(f"이상치 MSE: {mse_loss(y_true, y_outlier):.4f}") # 20.006 → 크게 영향받음
print(f"이상치 MAE: {mae_loss(y_true, y_outlier):.4f}") # 2.06 → 덜 영향받음
# PyTorch
criterion_mae = nn.L1Loss()
이상치가 있고 그게 중요하지 않을 때 → MAE
이상치도 중요하게 다뤄야 할 때 → MSE
이상치가 없는 깔끔한 데이터 → MSE (기울기 계산 안정적)
Huber Loss (Smooth L1)
MSE와 MAE의 장점을 결합한 손실 함수다. 작은 오차에는 MSE, 큰 오차에는 MAE처럼 동작한다.
def huber_loss(y_true, y_pred, delta=1.0):
error = y_true - y_pred
is_small = np.abs(error) <= delta
squared_loss = 0.5 * error ** 2
linear_loss = delta * (np.abs(error) - 0.5 * delta)
return np.mean(np.where(is_small, squared_loss, linear_loss))
# PyTorch
criterion_huber = nn.SmoothL1Loss()
분류 손실 함수
Binary Cross-Entropy (BCE)
이진 분류 (0 또는 1)에서 사용한다.
BCE = -(1/n) * Σ [yᵢ * log(ŷᵢ) + (1-yᵢ) * log(1-ŷᵢ)]
def bce_loss(y_true, y_pred):
epsilon = 1e-10
y_pred = np.clip(y_pred, epsilon, 1 - epsilon)
return -np.mean(
y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred)
)
y_true = np.array([1, 0, 1, 1, 0])
y_pred_good = np.array([0.9, 0.1, 0.8, 0.95, 0.05])
y_pred_bad = np.array([0.1, 0.9, 0.2, 0.3, 0.8])
print(f"좋은 예측 BCE: {bce_loss(y_true, y_pred_good):.4f}") # ≈ 0.113
print(f"나쁜 예측 BCE: {bce_loss(y_true, y_pred_bad):.4f}") # ≈ 2.036
# PyTorch (수치 안정적인 버전 권장)
criterion_bce = nn.BCEWithLogitsLoss() # sigmoid 포함
정답이 1인데:
0.99 예측 → 손실 ≈ 0.01 (거의 없음)
0.50 예측 → 손실 ≈ 0.69 (중간)
0.01 예측 → 손실 ≈ 4.61 (매우 큼)
모델이 확신을 가지고 틀렸을수록 더 큰 패널티를 받는다.
Categorical Cross-Entropy
다중 분류 (여러 클래스 중 하나)에서 사용한다.
CCE = -(1/n) * Σᵢ Σⱼ yᵢⱼ * log(ŷᵢⱼ)
# PyTorch CrossEntropyLoss = LogSoftmax + NLLLoss
# 입력: 로짓(logit) - softmax 통과 전 값
# 레이블: 클래스 인덱스 (one-hot 아님)
criterion_ce = nn.CrossEntropyLoss()
logits = torch.tensor([[2.0, 1.0, 0.1],
[0.5, 2.5, 0.3]]) # (batch, num_classes)
labels = torch.tensor([0, 1]) # 클래스 인덱스
loss = criterion_ce(logits, labels)
print(f"CrossEntropyLoss: {loss:.4f}")
Focal Loss
클래스 불균형 문제를 해결하는 손실 함수다. 쉬운 샘플에는 낮은 가중치, 어려운 샘플에는 높은 가중치를 준다.
def focal_loss(y_true, y_pred, gamma=2.0, alpha=0.25):
epsilon = 1e-10
y_pred = np.clip(y_pred, epsilon, 1 - epsilon)
ce = -(y_true * np.log(y_pred) + (1 - y_true) * np.log(1 - y_pred))
p_t = y_true * y_pred + (1 - y_true) * (1 - y_pred)
focal_weight = alpha * (1 - p_t) ** gamma
return np.mean(focal_weight * ce)
# 클래스 불균형 예시: 정상 90%, 불량 10%
y_true = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 1])
y_pred = np.array([0.1]*9 + [0.6])
print(f"BCE: {bce_loss(y_true, y_pred):.4f}")
print(f"Focal Loss: {focal_loss(y_true, y_pred):.4f}")
# Focal Loss가 어려운 샘플(불량)에 더 집중
손실 함수 선택 가이드
이진 분류 → BCEWithLogitsLoss
다중 분류 → CrossEntropyLoss
다중 레이블 분류 → BCEWithLogitsLoss (각 클래스 독립적으로)
회귀 (이상치 없음) → MSELoss
회귀 (이상치 있음) → SmoothL1Loss
클래스 불균형 → FocalLoss 또는 weight 파라미터 활용
언어 모델 → CrossEntropyLoss
이미지 생성 (GAN) → BCEWithLogitsLoss + 추가 손실
클래스 불균형 처리
# CrossEntropyLoss에 클래스 가중치 적용
# 데이터: 클래스 0이 900개, 클래스 1이 100개
class_counts = np.array([900, 100])
class_weights = 1.0 / class_counts
class_weights = class_weights / class_weights.sum()
weights = torch.tensor(class_weights, dtype=torch.float)
criterion = nn.CrossEntropyLoss(weight=weights)
print(f"클래스 0 가중치: {class_weights[0]:.4f}") # 0.1 (적게)
print(f"클래스 1 가중치: {class_weights[1]:.4f}") # 0.9 (많이)
커스텀 손실 함수
# RMSE (Root MSE)
class RMSELoss(nn.Module):
def __init__(self, epsilon=1e-8):
super().__init__()
self.epsilon = epsilon
self.mse = nn.MSELoss()
def forward(self, y_pred, y_true):
return torch.sqrt(self.mse(y_pred, y_true) + self.epsilon)
# MSE + MAE 결합
class CombinedLoss(nn.Module):
def __init__(self, alpha=0.5):
super().__init__()
self.alpha = alpha
self.mse = nn.MSELoss()
self.mae = nn.L1Loss()
def forward(self, y_pred, y_true):
return self.alpha * self.mse(y_pred, y_true) + (1 - self.alpha) * self.mae(y_pred, y_true)
criterion = RMSELoss()
y_pred = torch.tensor([1.0, 2.0, 3.0])
y_true = torch.tensor([1.1, 2.1, 3.1])
print(f"RMSE Loss: {criterion(y_pred, y_true):.4f}")
정리
| 손실 함수 | 수식 | 사용처 | 특징 |
|---|---|---|---|
| MSE | (y-ŷ)² 평균 | 회귀 | 이상치에 민감 |
| MAE | |y-ŷ| 평균 | 회귀 | 이상치에 강건 |
| Huber | MSE+MAE 결합 | 회귀 | 이상치 영향 완화 |
| BCE | -[y·log(ŷ)+(1-y)·log(1-ŷ)] | 이진 분류 | 확신 오류 패널티 큼 |
| CCE | -Σy·log(ŷ) | 다중 분류 | Softmax와 함께 |
| Focal | BCE × (1-pₜ)^γ | 불균형 분류 | 어려운 샘플 집중 |
'딥러닝 > 딥러닝 기초 개념' 카테고리의 다른 글
| 과적합과 정규화 (Overfitting, Dropout, BatchNorm) (0) | 2026.05.15 |
|---|---|
| 경사하강법과 최적화 (Gradient Descent, Adam, SGD) (0) | 2026.05.15 |
| 순전파와 역전파 (Feedforward & Backpropagation) (0) | 2026.05.15 |
| 신경망이란? (퍼셉트론, 활성화 함수) (0) | 2026.05.13 |