본문 바로가기
Learning-driven Methodology/DL (Deep Learning)

[Deep Learning] 베이지안 하이퍼파라미터 최적화 (Bayesian Hyperparameter Optimization)

by goatlab 2023. 5. 26.
728x90
반응형
SMALL

베이지안 하이퍼파라미터 최적화 (Bayesian Hyperparameter Optimization)

 

 

베이지안 하이퍼파라미터 최적화는 그리드 검색보다 더 효율적으로 하이퍼파라미터를 찾는 방법이다. 하이퍼파라미터의 각 후보 집합은 신경망을 다시 학습시켜야 하므로 후보 집합의 수를 최소한으로 유지하는 것이 가장 좋다. 베이지안 하이퍼파라미터 최적화는 좋은 하이퍼파라미터 후보 집합을 예측하는 모델을 학습시켜 이를 달성한다.

 

  • bayesian-optimization
  • hyperopt
  • spearmint
# Ignore useless W0819 warnings generated by TensorFlow 2.0.
# Hopefully can remove this ignore in the future.
# See https://github.com/tensorflow/tensorflow/issues/31308
import logging, os
logging.disable(logging.WARNING)
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
import pandas as pd
from scipy.stats import zscore

# Read the dataset
df = pd.read_csv(
    "https://data.heatonresearch.com/data/t81-558/jh-simple-dataset.csv",
    na_values=['NA', '?']
)

# Generate dummies for job
df = pd.concat([df, pd.get_dummies(df['job'], prefix="job")], axis=1)
df.drop('job', axis=1, inplace=True)

# Generate dummies for area
df = pd.concat([df, pd.get_dummies(df['area'], prefix="area")], axis=1)
df.drop('area', axis=1, inplace=True)

# Missing values for income
med = df['income'].median()
df['income'] = df['income'].fillna(med)

# Standardize ranges
df['income'] = zscore(df['income'])
df['aspect'] = zscore(df['aspect'])
df['save_rate'] = zscore(df['save_rate'])
df['age'] = zscore(df['age'])
df['subscriptions'] = zscore(df['subscriptions'])

# Convert to numpy - Classification
x_columns = df.columns.drop('product').drop('id')
x = df[x_columns].values
dummies = pd.get_dummies(df['product'])  # Classification
products = dummies.columns
y = dummies.values

 

이제, 데이터를 전처리했으므로 하이퍼파라미터 최적화를 시작할 수 있다. 먼저, 세 개의 매개변수만으로 모델을 생성하는 함수를 만드는 것으로 시작한다. 베이지안 최적화는 각 레이어에 몇 개의 레이어와 뉴런이 있는지와 같은 문제가 되는 개념이 아니라 숫자 벡터에서 작동한다. 이 복잡한 뉴런 구조를 벡터로 표현하기 위해 몇 가지 숫자를 사용하여 이 구조를 설명한다.

 

  • dropout : 각 레이어의 드롭아웃 비율
  • neuronPct : 고정된 최대 뉴런 수 5,000개 중 몇 퍼센트를 사용할 것인지 이 매개변수를 통해 전체 네트워크에서 뉴런의 총 개수를 지정
  • neuronShrink :신경망은 일반적으로 첫 번째 숨겨진 레이어에서 더 많은 뉴런으로 시작한 다음 추가 레이어에 대해 이 수를 줄인다. 이 백분율은 이전 레이어를 기준으로 후속 레이어를 얼마나 축소할지 지정한다. 뉴런이 부족해지면 더 이상의 레이어 추가를 중지한다 (' neuronPct'로 지정된 개수에 의해 지정됨).

 

이 세 가지 숫자는 신경망의 구조를 정의한다. 아래 코드의 쉼표는 프로그램이 네트워크를 구성하는 방법을 정확하게 보여준다.

 

import pandas as pd
import os
import numpy as np
import time
import tensorflow.keras.initializers
import statistics
import tensorflow.keras
from sklearn import metrics
from sklearn.model_selection import StratifiedKFold, StratifiedShuffleSplit, ShuffleSplit
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Activation, Dropout, InputLayer
from tensorflow.keras import regularizers
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras.layers import LeakyReLU, PReLU
from tensorflow.keras.optimizers import Adam

def generate_model(dropout, neuronPct, neuronShrink):
    neuronCount = int(neuronPct * 5000)
    model = Sequential()
    layer = 0
    while neuronCount > 25 and layer < 10:
        if layer == 0:
            model.add(Dense(neuronCount, input_dim=x.shape[1], activation=PReLU()))
        else:
            model.add(Dense(neuronCount, activation=PReLU()))
        layer += 1
        model.add(Dropout(dropout))
        neuronCount = neuronCount * neuronShrink
        
    model.add(Dense(y.shape[1], activation='softmax'))  # Output
    return model

 

이 코드를 테스트하여 이러한 세 가지 매개 변수를 기반으로 신경망을 생성하는 방법을 확인할 수 있다.

 

# 모델 생성 및 결과 구조 확인
model = generate_model(dropout=0.2, neuronPct=0.1, neuronShrink=0.25)
model.summary()

 

이제, 세 가지 파라미터를 사용하여 신경망을 평가하는 함수를 만들어 보겠다. 부트스트랩을 사용하는 이유는 한 번의 훈련 실행에 무작위 가중치가 할당되는 '불운'이 있을 수 있기 때문이다. 이 함수를 사용하여 신경망을 훈련한 다음 평가한다.

 

SPLITS = 2
EPOCHS = 500
PATIENCE = 10

def evaluate_network(dropout, learning_rate, neuronPct, neuronShrink):
    # Bootstrap
    # for Classification
    boot = StratifiedShuffleSplit(n_splits=SPLITS, test_size=0.1)
    # for Regression
    # boot = ShuffleSplit(n_splits=SPLITS, test_size=0.1)
    
    # Track progress
    mean_benchmark = []
    epochs_needed = []
    num = 0
    
    # Loop through samples
    for train, test in boot.split(x, df['product']):
        start_time = time.time()
        num += 1
        
        # Split train and test
        x_train = x[train]
        y_train = y[train]
        x_test = x[test]
        y_test = y[test]
        
        model = generate_model(dropout, neuronPct, neuronShrink)
        model.compile(loss='categorical_crossentropy', optimizer=Adam(learning_rate=learning_rate))
        
        monitor = EarlyStopping(monitor='val_loss', min_delta=1e-3, patience=PATIENCE, verbose=0, mode='auto', restore_best_weights=True)
        
        # Train on the bootstrap sample
        model.fit(x_train, y_train, validation_data=(x_test, y_test), callbacks=[monitor], verbose=0, epochs=EPOCHS)
        epochs = monitor.stopped_epoch
        epochs_needed.append(epochs)
        
        # Predict on the out-of-bootstrap (validation)
        pred = model.predict(x_test)
        
        # Measure this bootstrap's log loss
        y_compare = np.argmax(y_test, axis=1)  # For log loss calculation
        score = metrics.log_loss(y_compare, pred)
        mean_benchmark.append(score)
        
        m1 = statistics.mean(mean_benchmark)
        m2 = statistics.mean(epochs_needed)
        mdev = statistics.pstdev(mean_benchmark)
        
        # Record this iteration
        time_took = time.time() - start_time
        tensorflow.keras.backend.clear_session()
        
    return (-m1)

 

세 가지 하이퍼파라미터와 학습률을 조합하여 이 네 가지 수치가 얼마나 효과적인지 확인할 수 있다. 물론, 이 네 가지 하이퍼파라미터의 다양한 조합을 수동으로 선택하는 것이 아니라 자동화를 추구하는 것이 목표이다.

 

print(evaluate_network(dropout=0.2, learning_rate=1e-3, neuronPct=0.2, neuronShrink=0.2))

 

먼저, Colab을 사용하는 경우 베이지안 최적화 패키지를 설치해야 한다.

 

!pip install bayesian-optimization

 

이제, 이 프로세스를 자동화하겠다. 이 네 가지 하이퍼파라미터 각각에 대한 경계를 정의하고 베이지안 최적화를 시작한다. 프로그램이 완료되면 발견된 최적의 하이퍼파라미터 조합이 표시된다. 최적화 함수는 프로세스를 완료하는 데 걸리는 시간에 큰 영향을 미치는 두 가지 매개변수를 허용한다

 

  • n_iter : 수행하려는 베이지안 최적화 단계의 수이다. 단계가 많을수록 합리적인 최대값을 찾을 가능성이 높아진다.
  • init_points : 수행하려는 무작위 탐색의 단계 수이다. 무작위 탐색은 탐색 공간을 다양화함으로써 도움이 될 수 있다.
from bayes_opt import BayesianOptimization
import time
# Suppress NaN warnings
import warnings
warnings.filterwarnings("ignore", category=RuntimeWarning)

# Bounded region of parameters space
pbounds = {'dropout': (0.0, 0.499),
            'learning_rate': (0.0, 0.1),
            'neuronPct': (0.01, 1),
            'neuronShrink': (0.01, 1)
}

optimizer = BayesianOptimization(
    f=evaluate_network,
    pbounds=pbounds,
    verbose=2,  # verbose = 1 prints only when a maximum
    # is observed, verbose = 0 is silent
    random_state=1,
)

start_time = time.time()
optimizer.maximize(init_points=10, n_iter=20,)
time_took = time.time() - start_time
print(f"Total runtime: {hms_string(time_took)}")
print(optimizer.max)
Total runtime: 1:36:11.56
{'target': -0.6955536706512794, 'params': {'dropout': 0.2504561773412203, 'learning_rate': 0.00762323467091429, 'neuronPct': 0.01264879152181182, 'neuronShrink': 0.5229748831552032}}

 

보시다시피 알고리즘은 총 30회의 반복을 수행했다. 이 총 반복 횟수에는 10번의 무작위 반복과 20번의 최적화 반복이 포함된다.

728x90
반응형
LIST