logo

[딥러닝] 데이터셋 분할

과적합(Overfitting)은 머신러닝 및 딥러닝 모델 훈련 과정에서 자주 발생하는 문제입니다. 이 상태에서는 모델이 훈련 데이터에 지나치게 잘 맞춰져 있어, 새로운 데이터나 테스트 데이터에 대해서는 예측 성능이 오히려 떨어지는 현상을 의미합니다.

 

과적합의 정의와 식별 방법

과적합은 훈련 데이터의 잡음이나 불필요한 세부사항까지 학습하여, 모델이 훈련 데이터에는 높은 성능을 보이지만, 테스트 데이터나 실제 상황에서는 그 성능이 급격히 떨어지는 경우를 말합니다. 이를 식별하는 방법 중 하나는 훈련 데이터와 검증 데이터(또는 테스트 데이터)에 대한 모델의 성능을 비교하는 것입니다. 과적합 상태에서는 모델이 훈련 데이터에 대해서는 매우 높은 정확도를 보이지만, 검증 데이터에 대해서는 상대적으로 낮은 정확도를 보입니다.

 

과적합의 주요 원인

과적합의 주요 원인은 다음과 같습니다:

  1. 모델의 복잡도: 너무 많은 수의 파라미터를 가지는 복잡한 모델은 훈련 데이터의 노이즈까지 학습할 위험이 있습니다.
  2. 훈련 데이터의 양: 데이터셋이 작을 경우, 모델이 데이터의 잡음이나 비현실적인 패턴을 학습하기 쉽습니다.
  3. 데이터의 노이즈: 데이터 자체에 오류가 많거나 노이즈가 심한 경우, 모델이 이를 유의미한 신호로 잘못 학습할 수 있습니다.
 

식별 방법과 예시 코드

PyTorch를 이용하여 과적합을 감지하는 간단한 방법을 예로 들 수 있습니다. 여기서는 훈련 데이터와 검증 데이터에 대해 모델의 성능을 비교하여 과적합을 식별합니다.

import torch
import torch.nn as nn
import torch.optim as optim

# 예시 모델 정의
class ExampleModel(nn.Module):
    def __init__(self):
        super(ExampleModel, self).__init__()
        self.layer1 = nn.Linear(10, 20)
        self.relu = nn.ReLU()
        self.layer2 = nn.Linear(20, 1)

    def forward(self, x):
        x = self.layer1(x)
        x = self.relu(x)
        x = self.layer2(x)
        return x

# 모델, 손실 함수, 옵티마이저 초기화
model = ExampleModel()
loss_function = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 데이터 로더(dummy data)
batch_size = 64
train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
val_loader = DataLoader(dataset=val_dataset, batch_size=batch_size, shuffle=False)

# 훈련 및 검증 과정의 예
for epoch in range(num_epochs):
    model.train()
    train_loss = 0.0
    for data, target in train_loader:
        optimizer.zero_grad()
        output = model(data)
        loss = loss_function(output, target)
        loss.backward()
        optimizer.step()
        train_loss += loss.item()

    train_loss = train_loss / len(train_loader)

    model.eval()
    val_loss = 0.0
    with torch.no_grad():
        for data, target in val_loader:
            output = model(data)
            loss = loss_function(output, target)
            val_loss += loss.item()

    val_loss = val_loss / len(val_loader)

    print(f'Epoch {epoch}, Train Loss: {train_loss:.4f}, Validation Loss: {val_loss:.4f}')

    # 과적합 식별
    if train_loss < val_loss:
        print('과적합이 발생하였습니다.')

위의 코드에서는 간단한 선형 회귀 모델을 정의하고, 훈련 및 검증 데이터셋에 대한 손실을 계산하여 출력합니다. 이 예시에서는 손실 함수로 MSE를 사용하였으며, 만약 훈련 손실이 검증 손실보다 지속적으로 낮게 나타난다면 이는 과적합 신호일 수 있음을 나타냅니다. 실제 사용 시에는 다양한 방법을 통해 과적합을 더 정밀하게 감지할 수 있습니다.


 

데이터셋 분할의 필요성

데이터셋 분할은 딥러닝 모델 학습 과정에서 중요한 단계입니다. 모델이 학습 데이터에만 지나치게 최적화되는 과적합(Overfitting)을 방지하고, 실제 세계에서의 성능을 평가하기 위해 필요합니다. 또한, 모델의 범용성을 검증하고, 학습 과정에서 모델의 하이퍼파라미터를 조정하여 성능을 개선하는 데 도움이 됩니다.

 

트레이닝셋, 검증셋, 테스트셋의 역할

  • 트레이닝셋(Training Set): 모델 학습에 사용되는 데이터셋입니다. 모델은 이 데이터를 사용하여 가중치와 편향을 조정하면서 학습합니다.
  • 검증셋(Validation Set): 모델이 학습 과정 중에 성능을 평가하기 위해 사용되는 데이터셋입니다. 검증셋을 통해 하이퍼파라미터 튜닝이나 모델의 구조를 결정할 때 활용됩니다.
  • 테스트셋(Test Set): 모델 학습이 완료된 후, 모델의 최종 성능을 평가하기 위해 사용되는 데이터셋입니다. 테스트셋은 전혀 새로운 데이터로, 모델이 이전에 본 적 없는 데이터입니다. 이를 통해 모델의 범용성과 실제 세계에서의 성능을 평가할 수 있습니다.
 

데이터의 분할 비율

일반적으로 데이터셋을 분할할 때는 다음과 같은 비율 가이드라인이 제안됩니다:

  • 트레이닝셋: 70-80%
  • 검증셋: 10-15%
  • 테스트셋: 10-15%

이 비율은 프로젝트의 특성과 사용 가능한 데이터의 양에 따라 조정될 수 있습니다. 데이터가 매우 많은 경우, 트레이닝셋의 비율을 늘릴 수 있으며, 반대로 데이터가 부족한 경우에는 트레이닝셋의 비율을 줄이고 데이터 증강(Data Augmentation) 등의 기법을 고려할 수 있습니다.

 

예시 코드: PyTorch를 이용한 데이터셋 분할

PyTorch를 사용하여 간단한 데이터셋 분할 방법을 소개합니다. 여기서는 torch.utils.data.random_split 함수를 활용하여 데이터셋을 분할합니다.

import torch
from torchvision import datasets, transforms
from torch.utils.data import DataLoader, random_split

# MNIST 데이터셋을 예로 들어보겠습니다.
transform = transforms.Compose([transforms.ToTensor()])
dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)

# 데이터셋 크기 계산
dataset_size = len(dataset)
train_size = int(dataset_size * 0.7)
validation_size = int(dataset_size * 0.15)
test_size = dataset_size - train_size - validation_size

# 데이터셋 분할
train_dataset, validation_dataset, test_dataset = random_split(dataset, [train_size, validation_size, test_size])

# DataLoader를 사용하여 배치 사이즈 지정 및 로드
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
validation_loader = DataLoader(validation_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=True)

print(f"Training set size: {len(train_loader.dataset)}")
print(f"Validation set size: {len(validation_loader.dataset)}")
print(f"Test set size: {len(test_loader.dataset)}")

위 코드는 MNIST 데이터셋을 예로 들어 트레이닝셋, 검증셋, 테스트셋으로 분할하는 방법을 보여줍니다. 비율은 앞에서 제시한 가이드라인에 따라 설정했습니다. DataLoader는 데이터를 배치로 처리할 때 사용되며, shuffle=True 옵션을 통해 데이터셋을 무작위로 섞어 과적합을 방지할 수 있습니다.


검증셋은 모델의 성능을 평가하고, 하이퍼파라미터를 조정하는 데 매우 중요한 역할을 합니다. 이 세션에서는 검증셋을 사용하여 하이퍼파라미터 최적화, 조기 종료를 통한 과적합 방지, 그리고 모델의 안정성과 일반화 능력을 평가하는 교차 검증 기법에 대해 배워보겠습니다.

 

하이퍼파라미터 최적화

하이퍼파라미터 최적화는 모델의 성능에 직접적인 영향을 주는 인자들을 최적화하는 과정입니다. 이는 학습률, 배치 크기, epoch 수와 같은 학습 설정 뿐만 아니라, 신경망의 깊이나 뉴런 수 같은 구조적 인자도 포함될 수 있습니다.

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset, random_split
from sklearn.model_selection import ParameterGrid
import numpy as np

# 하이퍼파라미터 후보 생성
param_grid = {
    'learning_rate': [0.01, 0.001],
    'batch_size': [16, 32],
}

# 데이터 준비
# X와 y를 실제 데이터로 대체해야 함
X = torch.rand(100, 10)
y = torch.rand(100, 1)

# 데이터셋 분할
dataset = TensorDataset(X, y)
train_size = int(0.8 * len(dataset))
val_size = len(dataset) - train_size
train_dataset, val_dataset = random_split(dataset, [train_size, val_size])

# 모델 정의
class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.fc = nn.Linear(10, 1)

    def forward(self, x):
        return self.fc(x)

# 하이퍼파라미터 검색
best_val_loss = float('inf')
best_params = {}

for params in ParameterGrid(param_grid):
    model = Model()
    criterion = nn.MSELoss()
    optimizer = optim.SGD(model.parameters(), lr=params['learning_rate'])
    train_loader = DataLoader(train_dataset, batch_size=params['batch_size'], shuffle=True)
    val_loader = DataLoader(val_dataset, batch_size=params['batch_size'])

    # 학습 및 검증 과정 생략

    # 검증 손실 계산
    val_loss = ...  # 실제 계산 과정 추가

    if val_loss < best_val_loss:
        best_val_loss = val_loss
        best_params = params

print(f"Best Params: {best_params}")
 

조기 종료(Early Stopping)를 통한 과적합 방지 방법

조기 종료는 검증 데이터셋에서의 성능이 더 이상 개선되지 않을 때 학습을 멈추는 방법입니다. 이를 통해 과적합을 방지하면서 계산 자원을 효율적으로 사용할 수 있습니다.

early_stop_counter = 0
max_patience = 10  # 성능이 개선되지 않는 epoch 허용 횟수

for epoch in range(max_epochs):
    # 학습 과정 생략

    # 검증 손실 계산
    val_loss = ...  # 실제 계산 과정 추가

    if val_loss < best_val_loss:
        best_val_loss = val_loss
        early_stop_counter = 0
    else:
        early_stop_counter += 1
        if early_stop_counter >= max_patience:
            print("Early stopping triggered.")
            break
 

교차 검증(K-Fold Cross Validation) 기법

모델의 일반화 능력을 더 정확하게 평가하기 위해 사용되는 방법입니다. 데이터셋을 K개의 폴드(fold)로 나누고, 그 중 하나를 검증셋으로 나머지를 학습셋으로 사용하는 과정을 K번 반복하여 모델을 평가합니다.

from sklearn.model_selection import KFold

kf = KFold(n_splits=5)

for train_index, val_index in kf.split(X):
    X_train, X_val = X[train_index], X[val_index]
    y_train, y_val = y[train_index], y[val_index]

    # DataLoader, 모델, 손실 함수 및 최적화 설정 생략

    # 학습 및 검증 과정 생략
    # 각 Fold에서의 성능 측정 및 저장

# 폴드별 성능 평균내어 최종 모델 성능 평가

위 예시들을 통해 검증셋을 활용하여 하이퍼파라미터 최적화, 과적합 방지, 그리고 모델의 일반화 능력을 평가하는 방법을 배웠습니다. 검증셋 활용은 모델 학습 과정에서 매우 중요한 부분임을 기억하세요.

Previous
역전파 알고리즘