Hyperparameter
머신 러닝에서 Hyperparameter는 모델이나 알고리즘을 제어하는 변수이다.
이러한 변수는 모델의 학습 과정을 제어하며, 모델의 성능에 큰 영향을 미친다.
예를 들어, neural network에서 하이퍼파라미터에는 다음과 같은 것들이 있다.
- 학습률 (learning rate)
- 배치 크기 (batch size)
- 에포크 수 (number of epochs)
- 은닉층의 수 (number of hidden layers)
- 은닉층의 뉴런 수 (number of neurons in each hidden layer)
- 드롭아웃 비율 (dropout rate)
이러한 하이퍼파라미터는 모델의 성능을 개선하기 위해 잘 조정(fine-tuned)되어야 한다.
이번 포스팅에서는 하이퍼파라미터를 튜닝하여 모델이 최고의 성능을 갖도록 하는 방법을 직접 PyTorch 코드를 통해 알아보자.
Hyperparameter Optimization의 의미와 방법
Hyperparameter optimization은 최고의 성능을 달성하기 위해 머신 러닝 모델에서 최적의 하이퍼 파라미터를 선택하는 과정이다.
인트로에서 살펴본 것처럼 하이퍼파라미터는 모델을 트레이닝하기 전에 설정하는 매개 변수이다.
Weight, 혹은 Bias와 같은 모델 내부의 매개 변수와는 달리 하이퍼파라미터는 데이터에서 학습할 수 없지만 사용자가 직접 설정할 수 있다.
하이퍼 파라미터의 선택은 모델의 성능에 상당한 영향을 미친다.
예를 들어, 학습 속도(learning rate)을 너무 크게 설정하면 모델이 최적의 솔루션을 지나쳐 오버슈팅할 수 있는 반면, 너무 낮게 설정하면 수렴 속도가 느려지거나 로컬 미니마에 갇힐 수 있다.
또는 정규화 파라미터(regularization parameter)를 너무 낮게 설정하면 오버피팅이 발생할 수 있고 너무 높게 설정하면 언더피팅이 발생할 수도 있다.
이제 하이퍼파라미터 튜닝에 사용되는 몇 가지 일반적인 방법이 있다.
- Grid Search
- Random Search
- Bayesian Optimization
- Gradient-based Optimization
- Ensemble-based Methods
이제 각 방법을 알아보고 어떻게 PyTorch 코드로 구현할 수 있는지 살펴보자.
1. Grid Search
그리드 서치는 하이퍼파라미터의 범위를 지정하는 brute-force 테크닉으로 이름처럼 모델을 가능한 모든 하이퍼파라미터 조합에 대해 트레이닝하고 평가한다.
그리드서치는 하이퍼파라미터의 범위가 클 때 계산 비용이 많이 들 수 있다는 단점이 있지만 지정된 범위 내에서 최적의 파라미터를 찾는 것을 보장하는 무식하지만 강력한 방법이다.
코드도 무식할 거 같지만 sklearn
라이브러리의 GridSearchCV
클래스를 사용해서 그리드 서치를 할 수 있다.
from sklearn.model_selection import GridSearchCV
import torch
import torch.nn as nn
import torchvision.datasets as dsets
import torchvision.transforms as transforms
# Define the neural network
class NeuralNet(nn.Module):
def __init__(self, input_size, hidden_size, num_classes):
super(NeuralNet, self).__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(hidden_size, num_classes)
def forward(self, x):
out = self.fc1(x)
out = self.relu(out)
out = self.fc2(out)
return out
# Define the hyperparameters
input_size = 784 # 28x28
hidden_size = [128, 256, 512]
num_classes = 10
batch_size = [64, 128, 256]
learning_rate = [0.01, 0.001, 0.0001]
# Define the dataset and data loader
train_dataset = dsets.MNIST(root='./data', train=True, transform=transforms.ToTensor(), download=True)
test_dataset = dsets.MNIST(root='./data', train=False, transform=transforms.ToTensor())
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False)
# Define the model and criterion
model = NeuralNet(input_size, hidden_size[0], num_classes)
criterion = nn.CrossEntropyLoss()
# Define the grid of hyperparameters
param_grid = {'hidden_size': hidden_size, 'batch_size': batch_size, 'lr': learning_rate}
# Define the grid search
grid_search = GridSearchCV(estimator=model, param_grid=param_grid, scoring='accuracy', cv=5)
# Fit the grid search on the data
grid_search.fit(train_loader, criterion)
# Print the best hyperparameters and the corresponding accuracy
print("Best hyperparameters: ", grid_search.best_params_)
print("Best accuracy: ", grid_search.best_score_)
먼저 모델을 히든 레이어가 하나인 간단한 뉴럴 네트워크로 정의하였다.
이제 hidden_size, batch_size 및 learning rate라는 세 하이퍼파라미터에 대해 그리드 검색을 할 것이다.
이제 param_grid라는 dictionary을 사용해서 그리드를 정의할 것인데, 키는 하이퍼파라미터의 이름이고 값는 각 하이퍼파라미터에 대해 가능한 값의 리스트이다.
그런 다음 모델, 하이퍼파라미터의 그리드, 스코어링 메트릭(이 경우엔 accuracy)을 사용하여 GridSearchCV을 정의한다.
이제 모든 하이퍼매개변수 조합에서 모델을 평가한다음 최상의 하이퍼파라미터와 정확도를 리턴한다.
2. Random Search
랜덤 서치는 하이퍼파라미터가 지정된 범위에서 무작위로 샘플링되는 방식이다.
랜덤 서치는 그리드 서치보다 보통 빠르지만 최적의 파라미터를 찾는 것은 보장하지 않을 수 있다.
PyTorch에서는 sklearn 라이브러리의 RandomizedSearchCV 클래스를 사용해서 랜덤 서치를 할 수 있다.
코드를 통해 살펴보자.
from sklearn.model_selection import RandomizedSearchCV
import torch
import torch.nn as nn
import torchvision.datasets as dsets
import torchvision.transforms as transforms
import numpy as np
# Define the neural network
class NeuralNet(nn.Module):
def __init__(self, input_size, hidden_size, num_classes):
super(NeuralNet, self).__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(hidden_size, num_classes)
def forward(self, x):
out = self.fc1(x)
out = self.relu(out)
out = self.fc2(out)
return out
# Define the hyperparameters and the search space
input_size = 784 # 28x28
search_space = {'hidden_size': np.arange(128, 513, 128), 'batch_size': [64, 128, 256], 'lr': np.logspace(-4, -1, num=10)}
# Define the dataset and data loader
train_dataset = dsets.MNIST(root='./data', train=True, transform=transforms.ToTensor(), download=True)
test_dataset = dsets.MNIST(root='./data', train=False, transform=transforms.ToTensor())
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=128, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=128, shuffle=False)
# Define the model and criterion
model = NeuralNet(input_size, 128, 10)
criterion = nn.CrossEntropyLoss()
# Define the random search
random_search = RandomizedSearchCV(estimator=model, param_distributions=search_space, n_iter=50, scoring='accuracy', cv=5, random_state=42)
# Fit the random search on the data
random_search.fit(train_loader, criterion)
# Print the best hyperparameters and the corresponding accuracy
print("Best hyperparameters: ", random_search.best_params_)
print("Best accuracy: ", random_search.best_score_)
그리드 서치처럼 설정하는데 다른 점은 search_space을 설정하는 방식이다.
이렇게 하여 search space에서 무작위로 하이퍼파라미터를 샘플링하고 각 조합에 대해 모델을 평가하여 최상의 하이퍼파라미터와 정확도를 리턴하게 된다.
3. Bayesian Optimization
베이지안 최적화는 모델 성능을 하이퍼파라미터의 함수로 모델링하고 베이지안 추론을 사용해서 최적의 하이퍼파라미터를 검색하는 확률론적 기법이다.
베이지안 최적화는 계산을 효율적이며 그리드나 랜덤 서치보다 적은 시행으로 최적의 하이퍼 파라미터를 찾을 수 있는 강력한 방법이다.
코드는 다음과 같다.
from bayes_opt import BayesianOptimization
import torch
import torch.nn as nn
import torchvision.datasets as dsets
import torchvision.transforms as transforms
# Define the neural network
class NeuralNet(nn.Module):
def __init__(self, input_size, hidden_size, num_classes):
super(NeuralNet, self).__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(hidden_size, num_classes)
def forward(self, x):
out = self.fc1(x)
out = self.relu(out)
out = self.fc2(out)
return out
# Define the objective function to optimize
def objective(hidden_size, batch_size, lr):
input_size = 784 # 28x28
# Define the dataset and data loader
train_dataset = dsets.MNIST(root='./data', train=True, transform=transforms.ToTensor(), download=True)
test_dataset = dsets.MNIST(root='./data', train=False, transform=transforms.ToTensor())
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False)
# Define the model and criterion
model = NeuralNet(input_size, int(hidden_size), 10)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
# Train the model
num_epochs = 5
total_step = len(train_loader)
for epoch in range(num_epochs):
for i, (images, labels) in enumerate(train_loader):
# Move tensors to the configured device
images = images.reshape(-1, 28*28).to(device)
labels = labels.to(device)
# Forward pass
outputs = model(images)
loss = criterion(outputs, labels)
# Backward and optimize
optimizer.zero_grad()
loss.backward()
optimizer.step()
# Test the model
with torch.no_grad():
correct = 0
total = 0
for images, labels in test_loader:
images = images.reshape(-1, 28*28).to(device)
labels = labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
accuracy = 100 * correct / total
# Return the negative accuracy (to maximize)
return -accuracy
# Define the search space for the hyperparameters
pbounds = {'hidden_size': (128, 512), 'batch_size': (64, 256), 'lr': (0.0001, 0.1)}
# Define the Bayesian optimization object and run the optimization
optimizer = BayesianOptimization(f=objective, pbounds=pbounds)
optimizer.maximize(init_points=2, n_iter=10)
# Print the best hyperparameters and the corresponding accuracy
print("Best hyperparameters: ", optimizer.max['params'])
print("Best accuracy: ", -optimizer.max['target'])
먼저 bayes_opt은 pip install bayesian-optimization으로 설치할 수 있다.
4. Gradient-based optimization
그래디언트 기반 최적화는 하이퍼파라미터를 변수로 처리하고 gradient descent을 통해 최적화하는 기술이다.
그래디언트 기반 최적화는 계산 비용이 많이 들어 하이퍼파라미터 수가 적고 하이퍼파라미터가 연속적일 때 사용할 수 있다.
5. Ensemble-based Methods
앙상블 기반 방법은 서로 다른 하이퍼파라미터를 사용하여 여러 모델을 훈련한 다음 이들을 결합하여 전체 성능을 향상시키는 방법을 말한다.
앙상블 기반 방법은 계산 비용이 많이 들 수 있지만 종종 더 높은 정확도와 더 나은 일반화를 보여줄 수 있다.
결론적으로, 하이퍼파라미터 튜닝은 특정 작업 또는 데이터 세트에서 잘 돌아가는 모델을 트레이닝하기 위한 중요한 단계이다.
여러 기술들을 살펴보고 적용해서 최상의 성능을 가진 모델을 트레이닝 시켜보자.
'머신 러닝 > Theory' 카테고리의 다른 글
[Machine Learning] PyTorch로 Transfer Learning (전이 학습) 구현하기 (0) | 2023.03.08 |
---|---|
[Machine Learning] 오버피팅(Overfitting), 언더피팅(Underfitting)의 개념 및 해결 방법 (0) | 2023.03.04 |
[Machine Learning] 옵티마이저(Optimizer) 이해 및 종류별 특징 (1) | 2023.03.03 |