히비스서커스의 블로그

[error] ValueError: CategoricalDistribution does not support dynamic value space. (feat. 파이썬 내장함수 getattr() 활용) 본문

Programming/Python

[error] ValueError: CategoricalDistribution does not support dynamic value space. (feat. 파이썬 내장함수 getattr() 활용)

HibisCircus 2022. 5. 23. 22:39
728x90

optuna를 통해 최적의 하이퍼 파라미터를 찾는 코드를 작성 후 실행하다가 에러를 마주하였다. 먼저, 실행하였던 코드들을 간략하게 정리해보겠다.

 

이미지 출처 : (http://cloudrain21.com/python-show-builtin-function)

 

실행하고 있던 코드가 나와있는 원문

https://towardsdatascience.com/https-medium-com-perlitz-hyper-parameter-optimization-with-optuna-1c32785e7df

 

A 5 min guide to hyper-parameter optimization with Optuna

Finding the best hyper-parameters for your model is now a breeze.

towardsdatascience.com

 

 

라이브러리를 불러오고 dataloader 함수를 작성한다.

from __future__ import print_function
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms

def get_mnist_loaders(train_batch_size, test_batch_size):
    """Get MNIST data loaders"""
    train_loader = torch.utils.data.DataLoader(
        datasets.MNIST('../data', train=True, download=True,
                       transform=transforms.Compose([
                           transforms.ToTensor(),
                           transforms.Normalize((0.1307,), (0.3081,))
                       ])),
        batch_size=train_batch_size, shuffle=True)
    
    test_loader = torch.utils.data.DataLoader(
        datasets.MNIST('../data', train=False, transform=transforms.Compose([
                           transforms.ToTensor(),
                           transforms.Normalize((0.1307,), (0.3081,))
                       ])),
        batch_size=test_batch_size, shuffle=True)

    return train_loader, test_loader

 

다음으로 실행시킬 network 함수를 작성한다.

class Net(nn.Module):
    def __init__(self, activation):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 32, 3, 1)
        self.conv2 = nn.Conv2d(32, 64, 3, 1)
        self.dropout1 = nn.Dropout2d(0.25)
        self.dropout2 = nn.Dropout2d(0.5)
        self.fc1 = nn.Linear(9216, 128)
        self.fc2 = nn.Linear(128, 10)
        self.activation = activation 

    def forward(self, x):
        x = self.activation(self.conv1(x))
        x = self.conv2(x)
        x = F.max_pool2d(x, 2)
        x = self.dropout1(x)
        x = torch.flatten(x, 1)
        x = self.activation(self.fc1(x))
        x = self.dropout2(x)
        x = self.fc2(x)
        output = F.log_softmax(x, dim=1)
        return output

 

다음으로 network를 통해 train하는 함수와 test하는 함수를 작성한다.

def train(log_interval, model, train_loader, optimizer, epoch):
    model.train()
    for batch_idx, (data, target) in enumerate(train_loader):
        optimizer.zero_grad()
        output = model(data.to(device)
        loss = F.nll_loss(output, target.to(device)
        loss.backward()
        optimizer.step()
        if batch_idx % log_interval == 0:
            print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                epoch, batch_idx * len(data), len(train_loader.dataset),
                100. * batch_idx / len(train_loader), loss.item()))           

def test(model, test_loader):
    model.eval()
    test_loss = 0
    correct = 0
    with torch.no_grad():
        for data, target in test_loader:
            output = model(data.to(device)
            test_loss += F.nll_loss(output, target.to(device), reduction='sum').item()  # sum up batch loss
            pred = output.argmax(dim=1, keepdim=True)  # get the index of the max log-probability
            correct += pred.eq(target.to(device).view_as(pred)).sum().item()

    test_loss /= len(test_loader.dataset)
    test_accuracy = 100. * correct / len(test_loader.dataset))
    
    print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
        test_loss, correct, len(test_loader.dataset),
        100. * correct / len(test_loader.dataset)))
    
    return test_accuracy
    
   return

 

이제 optuna를 통해 'lr', 'momentum', 'optimizer'에서 최적의 값을 찾아보기 위해 아래와 같은 코드를 작성 후 실행한다.

def train_mnist(trial):
  
  cfg = { 'device' : "cuda" if torch.cuda.is_available() else "cpu",
          'train_batch_size' : 64,
          'test_batch_size' : 1000,
          'n_epochs' : 1,
          'seed' : 0,
          'log_interval' : 100,
          'save_model' : False,
          'lr'       : trial.suggest_loguniform('lr', 1e-3, 1e-2),          
          'momentum' : trial.suggest_uniform('momentum', 0.4, 0.99),
          'optimizer': trial.suggest_categorical('optimizer',[optim.SGD, optim.RMSprop]),
          'activation': F.relu}

  torch.manual_seed(cfg['seed'])
  train_loader, test_loader = get_mnist_loaders(cfg['train_batch_size'], cfg['test_batch_size'])
  model = Net(cfg['activation']).to(device)
  optimizer = cfg['optimizer'](model.parameters(), lr=cfg['lr'])
  for epoch in range(1, cfg['n_epochs'] + 1):
      train(cfg['log_interval'], model, train_loader, optimizer, epoch)
      test_accuracy = test(model, test_loader)

  if cfg['save_model']:
      torch.save(model.state_dict(), "mnist_cnn.pt")
      
  return test_accuracy

if __name__ == '__main__':
    study = optuna.create_study(sampler=optuna.samplers.TPESampler(), direction='maximize')
    study.optimize(train_mnist, n_trials=20, direction='maximize')
    joblib.dump(study, '/content/gdrive/My Drive/Colab_Data/studies/mnist_optuna.pkl')

 

이때, 다음과 같은 에러가 발생하였다.

 

ValueError: CategoricalDistribution does not support dynamic value space.

 

확실하지는 않지만 cfg에 들어가는 값들은 특정 값 (int, str 등) 이어야지 함수나 메서드 같은 것들로 설정하면 안되는 듯 하다. 따라서, 위 cfg의

 

	'optimizer': trial.suggest_categorical('optimizer',[optim.SGD, optim.RMSprop]),

 

부분에서 optim,SGD, optim,RMSprop 부분이 특정 값으로 들어가야 한다. 이때, 활용할 수 있는 파이썬 내장함수로 getattr()이 있다.

 

getattr()란?

이름에 해당하는 객체 속성의 값을 가져오는 Build-in 함수이다.

getattr(object, name[, default])

위에 적용해보자면 optim.SGD와 같이 불러왔던 것을 getattr(optim, 'SGD')와 같이 활용할 수 있다는 것이다. 

 

 

이제 getattr()을 적용하여 train_mnist에서 두 곳을 바꿔주면 코드가 잘 돌아갈 수 있게 된다.

# 바꾸기 전
          'optimizer': trial.suggest_categorical('optimizer',[optim.SGD, optim.RMSprop]),

  optimizer = cfg['optimizer'](model.parameters(), lr=cfg['lr'])
  
# 바꾼 후
          'optimizer': trial.suggest_categorical('optimizer',['SGD', 'RMSprop']),

  optimizer = getattr(optim, cfg['optimizer'])(model.parameters(), lr=cfg['lr'])

 

getattr()이란 내장함수가 존재하는지도 몰랐으나 optuna를 사용하는 상황과 같이 특정 상황일 때 잘 활용하면 유용하게 쓸 수 있을 듯 하다.

 

 

 

-히비스서커스-

728x90