HyperParameter Tuning 패키지, HyperOpt

HyperParameter Tuning 패키지, HyperOpt
Photo by Austin Distel / Unsplash

Motivation

  • 기존의 하이퍼파라미터 튜닝 방식은 주로 수동적이고 시간이 많이 소요되었습니다.
  • 그리드 서치(Grid Search)나 랜덤 서치(Random Search)는 단순하지만, 고차원의 하이퍼파라미터 공간에서는 비효율적입니다.
  • HyperOpt는 베이지안 최적화(Bayesian Optimization)와 같은 고급 기법을 통해 하이퍼파라미터 최적화를 보다 효율적으로 수행할 수 있게 합니다.

Pros & Cons

Pros

  • 효율적인 탐색: HyperOpt는 베이지안 최적화와 같은 고급 기법을 사용하여 하이퍼파라미터 공간을 효율적으로 탐색합니다.
  • 다양한 검색 알고리즘: TPE(Tree-structured Parzen Estimator), 랜덤 서치, 그리드 서치 등 다양한 검색 알고리즘을 지원합니다
  • 유연성: 하이퍼파라미터 공간을 유연하게 정의할 수 있으며, 사용자 정의 목표 함수도 쉽게 사용할 수 있습니다.

Cons

  • 설정 복잡성: 초기 설정이 다소 복잡할 수 있으며, 사용자 정의 목표 함수를 작성해야 합니다.
  • 성능 의존성: 최적화 성능은 설정된 검색 공간과 알고리즘에 크게 의존합니다.
  • 시간 소요: 고차원의 하이퍼파라미터 공간을 탐색할 때 시간이 많이 소요될 수 있습니다.

Alternatives

  • Optuna: HyperOpt와 유사한 기능을 제공하는 또 다른 하이퍼파라미터 최적화 라이브러리로, 직관적인 API와 강력한 기능을 제공합니다.
  • Scikit-Optimize: Scikit-Learn과 잘 통합되어 사용하기 쉽고, 다양한 최적화 알고리즘을 지원합니다.
  • Ray Tune: 분산 머신러닝을 지원하는 Ray 프레임워크의 하이퍼파라미터 튜닝 라이브러리로, 대규모 분산 환경에서 효율적으로 최적화를 수행할 수 있습니다.

샘플 코드

import numpy as np  
import pandas as pd  
  
np.random.seed(42)  
  
# 데이터 크기  
n_samples = 1000  
  
# 주문 시간 (아침: 0, 점심: 1, 저녁: 2, 밤: 3)  
order_time = np.random.choice([0, 1, 2, 3], size=n_samples)  
  
# 거리 (1km ~ 20km 사이의 랜덤 값)  
distance = np.random.uniform(1, 20, size=n_samples)  
  
# 주문량 (10 ~ 100 사이의 랜덤 값)  
order_volume = np.random.randint(10, 100, size=n_samples)  
  
# 날씨 (맑음: 0, 비: 1, 눈: 2)  
weather = np.random.choice([0, 1, 2], size=n_samples)  
  
# 라이더 배차 수락율 (0.5 ~ 1.0 사이의 랜덤 값)  
rider_acceptance_rate = np.random.uniform(0.5, 1.0, size=n_samples)  
  
# 라이더 규모 (작음: 0, 중간: 1, 큼: 2)  
rider_scale = np.random.choice([0, 1, 2], size=n_samples)  
  
# 배달 시간 생성 (기본: 10분 + 거리 * 2 + 주문량 * 0.1 + 날씨 * 5 + 주문 시간 효과 + 라이더 효과)  
delivery_time = 10 + distance * 2 + order_volume * 0.1 + weather * 5 + order_time * 5  
  
# 라이더 배차 수락율과 라이더 규모에 따른 분산 추가  
for i in range(n_samples):  
    if order_time[i] in [1, 2]:  # 점심과 저녁 시간  
        delivery_time[i] += np.random.normal(0, 3)  
    else:  # 다른 시간  
        delivery_time[i] += np.random.normal(0, 10)  
      
    if rider_scale[i] == 0:  # 라이더 규모가 작음  
        delivery_time[i] += np.random.normal(0, 5)  
    elif rider_scale[i] == 1:  # 라이더 규모가 중간  
        delivery_time[i] += np.random.normal(0, 2)  
    else:  # 라이더 규모가 큼  
        delivery_time[i] += np.random.normal(0, 1)  
  
# 데이터프레임 생성  
data = pd.DataFrame({  
    'order_time': order_time,  
    'distance': distance,  
    'order_volume': order_volume,  
    'weather': weather,  
    'rider_acceptance_rate': rider_acceptance_rate,  
    'rider_scale': rider_scale,  
    'delivery_time': delivery_time  
})  
  
data.head()

## 모델 생성
import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

# 데이터 전처리
X = data.drop('delivery_time', axis=1)
y = data['delivery_time']

scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2, random_state=42)

X_train = torch.tensor(X_train, dtype=torch.float32)
X_test = torch.tensor(X_test, dtype=torch.float32)
y_train = torch.tensor(y_train.values, dtype=torch.float32).view(-1, 1)
y_test = torch.tensor(y_test.values, dtype=torch.float32).view(-1, 1)

# 딥러닝 모델 정의
class DeliveryTimePredictor(nn.Module):
    def __init__(self, input_size, hidden_size1, hidden_size2):
        super(DeliveryTimePredictor, self).__init__()
        self.layer1 = nn.Linear(input_size, hidden_size1)
        self.layer2 = nn.Linear(hidden_size1, hidden_size2)
        self.layer3 = nn.Linear(hidden_size2, 1)

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

from hyperopt import fmin, tpe, hp, Trials

# 하이퍼파라미터 검색 공간 정의
space = {
    'hidden_size1': hp.choice('hidden_size1', [32, 64, 128]),
    'hidden_size2': hp.choice('hidden_size2', [16, 32, 64]),
    'learning_rate': hp.loguniform('learning_rate', np.log(1e-4), np.log(1e-2))
}

# 목표 함수 정의
def objective(params):
    hidden_size1 = params['hidden_size1']
    hidden_size2 = params['hidden_size2']
    learning_rate = params['learning_rate']

    model = DeliveryTimePredictor(input_size=X_train.shape[1], hidden_size1=hidden_size1, hidden_size2=hidden_size2)
    criterion = nn.MSELoss()
    optimizer = optim.Adam(model.parameters(), lr=learning_rate)

    # 모델 학습
    n_epochs = 50
    for epoch in range(n_epochs):
        model.train()
        optimizer.zero_grad()
        outputs = model(X_train)
        loss = criterion(outputs, y_train)
        loss.backward()
        optimizer.step()

    # 모델 평가
    model.eval()
    with torch.no_grad():
        predictions = model(X_test)
        val_loss = criterion(predictions, y_test).item()

    return val_loss

# Trials 객체 생성
trials = Trials()

# 최적화 수행
best = fmin(
    fn=objective,
    space=space,
    algo=tpe.suggest,
    max_evals=50,
    trials=trials
)

print("Best Hyperparameters:", best)