본문 바로가기
Study/ML

8. 베이지안 최적화(Bayesian Optimization)

by 까망우동 2023. 5. 30.
반응형

Index

    ● 베이지안 최적화 개요

    • GridSearch 와 같은 방식의 파라미터 튜닝은, 모든 조합의 수를 다해보기 때문에 computational cost가 매우 크다. 
    • 따라서 데이터 사이즈가 크거나, XGB, LightGBM 과 같이 느린 모델에는 베이지안 최적화기법이 선호된다. 
    • 베이지안 최적화란 ? "목적함수의 식을 모를 때 최대/최소 y값(f(x))을 반환하는 x를 찾는 기법" 이다. 
    • 베이지안 최적화의 중요한 두가지 요소는 대체모델(Surrogate Model)과 획득함수(Acquisition Function)이다.  
    • 대체모델(Surrogate Model)은 미지의 목적함수를 확률적으로 추정하는 모델로, 획득함수가 계산한 입력값(하이퍼 파라미터)기반으로 모델(최적함수)를 개선해 나간다. 아래 그림에서 파랑색 음영이 줄어들며 실선이 점선(목적함수)에 가까워 져가도록 하는것이 대체 모델(실선)이다.
    • 획득함수(Acquisition Function)는 대체모델이 제시한 모델과, 이전 단계에서의 최적 관측값(가장큰값) 보다 더 큰 최대값을 가질것으로 기대되는 파라미터로 전달하고, 이를통해 대체모델은 모델을 개선해나간다. (파란음영이 좁아지고, 실선이 점선에 가까워진다) 

     

     HyperOpt 사용하기

    HyperOpt 는 베이지안 최적화를 구현한 파이썬 패키지이다. 아래 3개 단계를 따라 사용된다. 

     

    1. 입력변수와 검색공간 설정

    입력변수의 검색공간을 제공하는 함수는 quniform 외에도 정규분포, 로그정규 등 다양한 함수 사용가능

    from hyperopt import hp

    # 1. 입력변수와 공간 설정
    # -10 ~ 10까지 1간격을 가지는 입력 변수 x와 -15 ~ 15까지 1간격으로 입력 변수 y 설정.
    search_space = {'x': hp.quniform('x', -10, 10, 1), 'y': hp.quniform('y', -15, 15, 1) }

    2. 목적함수 설정(생성)

    # 2. 목적합수 설정
    # 목적 함수를 생성. 변숫값과 변수 검색 공간을 가지는 딕셔너리를 인자로 받고, 특정 값을 반환
    def objective_func(search_space):
        x = search_space['x']
        y = search_space['y']
        retval = x**2 - 20*y
       
        return retval

     

    3. 목적함수의 반환 최소값을 가지는 최적 입력값 유추 

    # 3. 목적함수의 반환 최솟값을 가지는 최적 입력값 유추
    from hyperopt import fmin, tpe, Trials

    # 입력 결괏값을 저장한 Trials 객체값 생성.
    trial_val = Trials()

    # 목적 함수의 최솟값을 반환하는 최적 입력 변숫값을 5번의 입력값 시도(max_evals=5)로 찾아냄. 랜덤시드 부분은 무시
    best_01 = fmin(fn=objective_func, space=search_space, algo=tpe.suggest, max_evals=5
                   , trials=trial_val, rstate=np.random.default_rng(seed=0))
     
    print('best:', best_01)  # 반환예시: best: {'x': 1.0, 'y': -3.0}

    # print(trial_val.results) trial 마다의 결과값 반환
    # print(trial_val.vals) trial 마다의 입력값 반환

    import pandas as pd

    losses = [loss_dict['loss'] for loss_dict in trial_val.results]
    result_df = pd.DataFrame({'x':trial_val.vals['x'], 'y':trial_val.vals['y'], 'losses':losses})
    result_df.head(3)

     

     HyperOpt 사용한 하이퍼 파라미터 최적화 예시 (XGBoost)

    from hyperopt import hp

    # 1. 입력변수와 공간설정
    # 정수형 파라미터는 정수형에 맞는 검색공간 설정 함수를 사용하고, 반환이후에도 int 함수로 변환해주는게 중요함
    # max_depth는 5에서 20까지 1간격으로, min_child_weight는 1에서 2까지 1간격으로
    # colsample_bytree는 0.5에서 1사이, learning_rate는 0.01에서 0.2 사이 정규 분포된 값으로 검색.
    xgb_search_space = {'max_depth': hp.quniform('max_depth', 5, 20, 1),
                        'min_child_weight': hp.quniform('min_child_weight', 1, 2, 1),
                        'learning_rate': hp.uniform('learning_rate', 0.01, 0.2),
                        'colsample_bytree': hp.uniform('colsample_bytree', 0.5, 1),
                       }

    # 2. 목적함수 설정 (정확도*-1 를 반환값으로 하는 함수)
    from sklearn.model_selection import cross_val_score
    from xgboost import XGBClassifier
    from hyperopt import STATUS_OK

    # search_space 값으로 입력된 모든 값은 실수형임.
    # XGBClassifier의 정수형 하이퍼 파라미터는 정수형 변환을 해줘야 함.
    # 정확도는 높을수록 더 좋은 수치임. -1 * 정확도를 곱해서 큰 정확도 값일수록 최소가 되도록 변환
    def objective_func(search_space):
        # 수행 시간 절약을 위해 n_estimators는 100으로 축소
        xgb_clf = XGBClassifier(n_estimators=100, max_depth=int(search_space['max_depth']),
                                min_child_weight=int(search_space['min_child_weight']),
                                learning_rate=search_space['learning_rate'],
                                colsample_bytree=search_space['colsample_bytree'],
                                eval_metric='logloss')
     
        accuracy = cross_val_score(xgb_clf, X_train, y_train, scoring='accuracy', cv=3)
        # cross_val_score API 를 XGBoost 나 LightGBM 에 사용할떄는 early stopping 기능 지원안함
       
        # accuracy는 cv=3 개수만큼 결과를 리스트로 가짐. 이를 평균해서 반환하되 -1을 곱함.
        return {'loss':-1 * np.mean(accuracy), 'status': STATUS_OK}

    # 최적의 파라미터 도출
    from hyperopt import fmin, tpe, Trials

    trial_val = Trials()
    best = fmin(fn=objective_func,
                space=xgb_search_space,
                algo=tpe.suggest,
                max_evals=50, # 최대 반복 횟수를 지정합니다.
                trials=trial_val, rstate=np.random.default_rng(seed=9))
    print('best:', best)

    xgb_wrapper = XGBClassifier(n_estimators=400,
                                learning_rate=round(best['learning_rate'], 5),
                                max_depth=int(best['max_depth']),
                                min_child_weight=int(best['min_child_weight']),
                                colsample_bytree=round(best['colsample_bytree'], 5)
                               )

    evals = [(X_tr, y_tr), (X_val, y_val)]
    xgb_wrapper.fit(X_tr, y_tr, early_stopping_rounds=50, eval_metric='logloss',
                    eval_set=evals, verbose=False)

    preds = xgb_wrapper.predict(X_test)
    반응형

    댓글