과적합
Overfitting
모델이 학습 데이터에 지나치게 맞춰져 새로운 데이터에 대한 일반화 성능이 떨어지는 현상.
Overfitting
모델이 학습 데이터에 지나치게 맞춰져 새로운 데이터에 대한 일반화 성능이 떨어지는 현상.
과적합(Overfitting)은 머신러닝 모델이 학습 데이터의 노이즈나 세부 패턴까지 외워버려서 새로운 데이터에 대한 예측 성능이 떨어지는 현상입니다. 학습 데이터에서는 거의 완벽한 성능을 보이지만, 테스트 데이터나 실제 서비스에서는 성능이 크게 하락합니다. 이는 모델이 데이터의 일반적인 패턴이 아닌 학습 데이터의 특수한 특성을 학습했기 때문입니다.
과적합이 발생하는 주요 원인은 모델 복잡도가 데이터 양에 비해 높거나, 학습을 너무 오래 진행하거나, 학습 데이터가 편향되어 있는 경우입니다. 예를 들어 100개의 데이터 포인트에 수백만 개의 파라미터를 가진 딥러닝 모델을 학습하면 모델이 모든 데이터를 암기하게 됩니다. 또한 검증 없이 학습 에포크를 계속 증가시키면 학습 손실은 줄어들지만 검증 손실은 오히려 증가하는 시점이 옵니다.
과적합을 방지하는 대표적인 기법으로 정규화(Regularization), 드롭아웃(Dropout), 조기 종료(Early Stopping), 데이터 증강(Data Augmentation)이 있습니다. L1/L2 정규화는 가중치 크기에 페널티를 부과하여 모델 복잡도를 제한합니다. 드롭아웃은 학습 시 무작위로 뉴런을 비활성화하여 특정 뉴런에 대한 의존도를 낮춥니다. 조기 종료는 검증 손실이 더 이상 개선되지 않을 때 학습을 멈춥니다. 데이터 증강은 이미지 회전, 자르기 등으로 학습 데이터를 인위적으로 늘립니다.
과적합과 반대되는 개념으로 과소적합(Underfitting)이 있습니다. 과소적합은 모델이 너무 단순하거나 학습이 부족하여 학습 데이터의 패턴조차 포착하지 못하는 상태입니다. 좋은 모델은 과적합과 과소적합 사이의 최적 지점을 찾아 편향-분산 트레이드오프(Bias-Variance Tradeoff)를 균형있게 맞춥니다. 이를 위해 교차 검증, 하이퍼파라미터 튜닝, 앙상블 기법 등을 활용합니다.
PyTorch에서 드롭아웃과 조기 종료를 활용하여 과적합을 방지하는 예제입니다.
# pip install torch scikit-learn matplotlib
import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
# 데이터 생성 (일부러 작은 데이터셋)
X, y = make_classification(n_samples=200, n_features=20, n_informative=10,
n_redundant=5, random_state=42)
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.3, random_state=42)
# 텐서 변환
X_train = torch.FloatTensor(X_train)
y_train = torch.FloatTensor(y_train).unsqueeze(1)
X_val = torch.FloatTensor(X_val)
y_val = torch.FloatTensor(y_val).unsqueeze(1)
# 과적합 방지를 위한 모델 (드롭아웃 적용)
class RegularizedModel(nn.Module):
def __init__(self, dropout_rate=0.5):
super().__init__()
self.fc1 = nn.Linear(20, 128)
self.dropout1 = nn.Dropout(dropout_rate)
self.fc2 = nn.Linear(128, 64)
self.dropout2 = nn.Dropout(dropout_rate)
self.fc3 = nn.Linear(64, 1)
self.relu = nn.ReLU()
self.sigmoid = nn.Sigmoid()
def forward(self, x):
x = self.relu(self.fc1(x))
x = self.dropout1(x)
x = self.relu(self.fc2(x))
x = self.dropout2(x)
x = self.sigmoid(self.fc3(x))
return x
# 조기 종료 클래스
class EarlyStopping:
def __init__(self, patience=10, min_delta=0.001):
self.patience = patience
self.min_delta = min_delta
self.counter = 0
self.best_loss = float('inf')
self.early_stop = False
def __call__(self, val_loss):
if val_loss < self.best_loss - self.min_delta:
self.best_loss = val_loss
self.counter = 0
else:
self.counter += 1
if self.counter >= self.patience:
self.early_stop = True
# 모델 학습
model = RegularizedModel(dropout_rate=0.5)
criterion = nn.BCELoss()
optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4) # L2 정규화
early_stopping = EarlyStopping(patience=15)
train_losses, val_losses = [], []
for epoch in range(500):
# 학습
model.train()
optimizer.zero_grad()
outputs = model(X_train)
train_loss = criterion(outputs, y_train)
train_loss.backward()
optimizer.step()
# 검증
model.eval()
with torch.no_grad():
val_outputs = model(X_val)
val_loss = criterion(val_outputs, y_val)
train_losses.append(train_loss.item())
val_losses.append(val_loss.item())
# 조기 종료 체크
early_stopping(val_loss.item())
if early_stopping.early_stop:
print(f"조기 종료! Epoch: {epoch+1}")
break
if (epoch + 1) % 50 == 0:
print(f"Epoch {epoch+1}: Train Loss={train_loss:.4f}, Val Loss={val_loss:.4f}")
# 학습 곡선 시각화
plt.figure(figsize=(10, 5))
plt.plot(train_losses, label='Train Loss')
plt.plot(val_losses, label='Validation Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.title('Training vs Validation Loss (Overfitting Prevention)')
plt.savefig('learning_curve.png')
print("학습 곡선 저장: learning_curve.png")