하이퍼파라미터 튜닝

딥러닝이나 머신러닝 컴피티션을 나간다면 반드시 필요한 과정이 하이퍼파라미터 튜닝이다. 대부분의 머신러닝, 딥러닝 모델들은 별도의 하이퍼 파라미터 튜닝없이도 괜찮은 성능을 내긴하지만, 리더보드에서 상위권에 들기 위해서는 그것만으로는 부족하다. 리더보드 상위권의 경쟁은 매우 치열한데 결국 그 조금의 점수를 올리기 위해 하이퍼파라미터 튜닝을 시도하게 된다.
일반적으로 하이퍼파라미터를 튜닝하는 방법으로, scikit-learn의 그리드서치나 랜덤서치 객체를 사용하는 것이 유명하다. 하지만 이 방법들은 매우 고달픈 방법이다. 최적의 하이퍼파라미터를 찾는 과정이 효율적이지 않을 뿐더러 값을 계속 바꿔가면서 입력해줘야하는 것이 불편하다. 모델의 하이퍼파라미터를 튜닝하는 방법은 그리드서치, 랜덤서치 이외에도 더 좋은 방법이 있다. 그리고 이런 방법들을 쉽게 사용할 수 있는 라이브러리들이 존재한다. 이 포스팅에선 그 라이브러리들의 종류와 사용방법에 대해서 써보겠다.

Hyperopt

hyperopt는 베이지안 최적화 알고리즘을 사용하는 것을 도와주는 라이브러리다. 베이지안 최적화는 그리드서치나 랜덤서치보다 하이퍼파라미터를 튜닝하는 과정을 최적화시키는 알고리즘이다. hyperopt에서 베이지안 최적화는 TPE(Tree of parzen estimator)라는 것을 사용한다. TPE는 하이퍼파라미터 별로 모델을 실험하면서 손실함수의 값을 기록한다. 그리고 그 손실함수의 값이 일정한 threshold를 넘은 것과 넘지 못한 것을 분리한다. 그 후 threshold를 넘은 하이퍼파라미터의 확률분포를 구하여 최적 파라미터를 찾아가는 알고리즘이다.
hyperopt는 하이퍼파라미터가 들어갈 수 있는 space를 생성하고, 손실함수를 정의한 뒤, fmin 메소드를 통해 손실함수를 최소화하는 최적 하이퍼파라미터를 찾는 과정으로 이루어져 있다. 그리고 최적화 과정을 기록하기 위해서 따로 Trial 객체를 사용할 수도 있다. 예제 코드는 다음과 같다.

# 손실함수(목적함수) 정의
def objective(args):
    case, val = args
    if case == 'case 1':
        return val
    else:
        return val ** 2

# space 정의
from hyperopt import hp
space = hp.choice('a',
    [
        ('case 1', 1 + hp.lognormal('c1', 0, 1)),
        ('case 2', hp.uniform('c2', -10, 10))
    ])

# 손실함수를 minimize하는 값 찾기
from hyperopt import fmin, tpe
best = fmin(objective, space, algo=tpe.suggest, max_evals=100)

print best
# -> {'a': 1, 'c2': 0.01420615366247227}
print hyperopt.space_eval(space, best)
# -> ('case 2', 0.01420615366247227}

# 출처: 공식문서

hyperopt의 경우 lightgbm같은 앙상블 모델에 적용하기 적합하다. 그러나 공식문서를 참고해보니 최근에는 잘 쓰이지 않는다는 언급이 있어 이 점을 참고해야할 듯 하다.

Optuna

Optuna도 사용법은 hyperopt와 비슷하다. 그러나 hyperopt보다 관리가 잘 되고 있는 것으로 보이고 머신러닝 모델 뿐만 아니라, 텐서플로, 케라스, 파이토치 등으로 작성한 딥러닝 모델에도 적용할 수 있다고 공식문서에 쓰여 있다. 또한 최신 알고리즘과 병렬화를 지원한다. 가급적 hyperopt보다 optuna를 사용하는 것이 좋을 것으로 보인다. optuna도 hyperopt와 마찬가지로 목적함수와 space를 정의하고 optimize 메소드를 통해 최적 하이퍼파라미터를 탐색한다. 최적화 알고리즘은 공식문서에 따르면 hyperopt와 마찬가지로 TPE 알고리즘을 사용하고 있다. 공식문서의 예제코드는 다음과 같다.

import optuna

def objective(trial):
    x = trial.suggest_float('x', -10, 10)
    return (x - 2) ** 2

study = optuna.create_study()
study.optimize(objective, n_trials=100)

study.best_params  # E.g. {'x': 2.002108042}

Kerastunner

케라스튜너는 텐서플로와 케라스로 작성한 딥러닝 모델의 하이퍼파라미터 최적화 라이브러리다. 케라스튜너는 베이지안최적화와 더불어 hyperband 알고리즘을 지원한다. 케라스튜너는 케라스 자체에서 지원하는 라이브러리이므로, 관리도 잘 될 것이라고 예상할 수 있다. 다만 다른 머신러닝 모델이나 파이토치로 작성한 딥러닝 모델에는 사용하기 어렵다는 것이 단점이다.
케라스 튜너의 경우는 모델을 정의하는 model_builder 함수를 정의해줘야 한다. model_builder 함수는 input으로는 하이퍼파라미터를 받고 모델 객체를 리턴한다. 그리고 하이퍼밴드나 베이지안최적화와 같은 알고리즘을 선택하고 최적화를 진행한다. 예제코드는 다음과 같다.

def model_builder(hp):
  model = keras.Sequential()
  model.add(keras.layers.Flatten(input_shape=(28, 28)))

  # Tune the number of units in the first Dense layer
  # Choose an optimal value between 32-512
  hp_units = hp.Int('units', min_value = 32, max_value = 512, step = 32)
  model.add(keras.layers.Dense(units = hp_units, activation = 'relu'))
  model.add(keras.layers.Dense(10))

  # Tune the learning rate for the optimizer 
  # Choose an optimal value from 0.01, 0.001, or 0.0001
  hp_learning_rate = hp.Choice('learning_rate', values = [1e-2, 1e-3, 1e-4]) 

  model.compile(optimizer = keras.optimizers.Adam(learning_rate = hp_learning_rate),
                loss = keras.losses.SparseCategoricalCrossentropy(from_logits = True), 
                metrics = ['accuracy'])

  return model

  tuner = kt.Hyperband(model_builder,
                     objective = 'val_accuracy', 
                     max_epochs = 10,
                     factor = 3,
                     directory = 'my_dir',
                     project_name = 'intro_to_kt')

  tuner.search(img_train, label_train, epochs = 10, validation_data = (img_test, label_test), callbacks = [ClearTrainingOutput()])

# Get the optimal hyperparameters
best_hps = tuner.get_best_hyperparameters(num_trials = 1)[0]

print(f"""
The hyperparameter search is complete. The optimal number of units in the first densely-connected
layer is {best_hps.get('units')} and the optimal learning rate for the optimizer
is {best_hps.get('learning_rate')}.
""")

예제코드로 볼때, 케라스 튜너는 위 두 라이브러리보다 딥러닝 모델을 최적화할 때 더 용이한 특성을 가지고 있는 것으로 보인다. 또한 목적함수를 model_builder와 분리함으로써, 목적함수를 바꿔가며 하이퍼파라미터를 찾을 때도 더 간편할 것으로 예상된다. 또한 하이퍼밴드라는 강력한 알고리즘을 제공하는 것도 케라스 튜너의 장점이다.

+ Recent posts