HalvingGridSearchCV

경고: 이 노트북은 사이킷런 0.24 이상에서 실행할 수 있습니다.

In [1]:
import pandas as pd

df = pd.read_csv('https://archive.ics.uci.edu/ml/'
                 'machine-learning-databases'
                 '/breast-cancer-wisconsin/wdbc.data', header=None)
In [2]:
from sklearn.preprocessing import LabelEncoder

X = df.loc[:, 2:].values
y = df.loc[:, 1].values
le = LabelEncoder()
y = le.fit_transform(y)
In [3]:
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = \
    train_test_split(X, y, 
                     test_size=0.20,
                     stratify=y,
                     random_state=1)

비교를 위해 GridSearchCV 실행 결과를 출력합니다.

In [4]:
from sklearn.model_selection import GridSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.pipeline import make_pipeline
import numpy as np

pipe_svc = make_pipeline(StandardScaler(),
                         SVC(random_state=1))

param_range = [0.0001, 0.001, 0.01, 0.1, 1.0, 10.0, 100.0, 1000.0]

param_grid = [{'svc__C': param_range, 
               'svc__kernel': ['linear']},
              {'svc__C': param_range, 
               'svc__gamma': param_range, 
               'svc__kernel': ['rbf']}]

gs = GridSearchCV(estimator=pipe_svc, 
                  param_grid=param_grid,
                  cv=10,
                  n_jobs=-1)
gs = gs.fit(X_train, y_train)
print(gs.best_score_)
print(gs.best_params_)
print(np.sum(gs.cv_results_['mean_fit_time']))
0.9846859903381642
{'svc__C': 100.0, 'svc__gamma': 0.001, 'svc__kernel': 'rbf'}
0.6009267091751098

사이킷런 0.24 버전에서 추가된 HalvingGridsearchCV는 모든 파라미터 조합에 대해 제한된 자원으로 실행한 다음 가장 좋은 후보를 골라서 더 많은 자원을 투여하는 식으로 반복적으로 탐색을 수행합니다. 이런 방식을 SH(Successive Halving)이라고 부릅니다. HalvingGridsearchCVresource 매개변수는 반복마다 늘려갈 자원을 정의합니다. 기본값은 'n_samples'로 샘플 개수입니다. 이 외에도 탐색 대상 모델에서 양의 정수 값을 가진 매개변수를 지정할 수 있습니다. 예를 들면 랜덤 포레스트의 n_estimators가 가능합니다.

factor 매개변수는 반복마다 선택할 후보의 비율을 지정합니다. 기본값은 3으로 후보 중에서 성능이 높은 1/3만 다음 반복으로 전달합니다. max_resources 매개변수는 각 후보가 사용할 최대 자원을 지정합니다. 기본값은 'auto'resources='n_samples'일 때 샘플 개수가 됩니다.

min_resources는 첫 번째 반복에서 각 후보가 사용할 최소 자원을 지정합니다. resources='n_samples'이고 min_resources='smallest'이면 회귀일 때 cv $\times$ 2가 되고 분류일 때는 cv $\times$ 클래스개수 $\times$ 2가 됩니다. 그외에는 1입니다. min_resources='exhaust'이면 앞에서 계산한 값과 max_resourcesfactor**n_required_iterations으로 나눈 몫 중 큰 값입니다. 기본값은 'exhaust'입니다(n_required_iterations는 $ \text{log}_{factor}(전체 후보 갯수) + 1$ 입니다).

마지막으로 aggressive_elimination 매개변수를 True로 지정하면 마지막 반복에서 factor만큼 후보가 남을 수 있도록 자원을 늘리지 않고 초기에 반복을 여러 번 진행합니다. 기본값은 False입니다.

HalvingGridsearchCV 아직 실험적이기 때문에 sklearn.experimental 패키지 아래에 있는 enable_halving_search_cv을 임포트해야 사용할 수 있습니다. verbose=1로 지정하면 각 반복 과정을 자세히 살펴 볼 수 있습니다.

In [5]:
from sklearn.experimental import enable_halving_search_cv
from sklearn.model_selection import HalvingGridSearchCV

hgs = HalvingGridSearchCV(estimator=pipe_svc,
                          param_grid=param_grid,
                          cv=10,
                          n_jobs=-1, verbose=1)
hgs = hgs.fit(X_train, y_train)
print(hgs.best_score_)
print(hgs.best_params_)
n_iterations: 3
n_required_iterations: 4
n_possible_iterations: 3
min_resources_: 40
max_resources_: 455
aggressive_elimination: False
factor: 3
----------
iter: 0
n_candidates: 72
n_resources: 40
Fitting 10 folds for each of 72 candidates, totalling 720 fits
----------
iter: 1
n_candidates: 24
n_resources: 120
Fitting 10 folds for each of 24 candidates, totalling 240 fits
----------
iter: 2
n_candidates: 8
n_resources: 360
Fitting 10 folds for each of 8 candidates, totalling 80 fits
0.9887301587301588
{'svc__C': 10.0, 'svc__gamma': 0.01, 'svc__kernel': 'rbf'}

출력 결과를 보면 첫 번째 반복(iter: 0)에서 72개의 후보를 40개의 샘플로 교차 검증을 수행합니다. 여기에서 72/3 = 24개의 후보를 뽑아 두 번째 반복(iter: 1)을 수행합니다. 두 번째 반복에서는 40 * 3 = 120개의 샘플을 사용합니다. 같은 방식으로 세 번째 반복(iter: 2)에서는 8개의 후보가 360개의 샘플로 평가됩니다. 최종 결과는 98.3%로 GridSearchCV 보다 조금 낮습니다. 찾은 매개변수 조합도 달라진 것을 볼 수 있습니다.

3번의 반복 동안 HalvingGridSearchCV가 수행한 교차 검증 횟수는 모두 104번입니다. 각 교차 검증에 걸린 시간은 cv_results_ 속성의 mean_fit_time에 저장되어 있습니다. 이를 GridSearchCV와 비교해 보면 5배 이상 빠른 것을 볼 수 있습니다.

In [6]:
print(np.sum(hgs.cv_results_['mean_fit_time']))
0.14410743713378907

각 반복 단계에서 사용한 샘플 개수와 후보 개수는 각각 n_resources_ 속성과 n_candidates_ 속성에 저장되어 있습니다.

In [7]:
print('자원 리스트:', hgs.n_resources_)
print('후보 리스트:', hgs.n_candidates_)
자원 리스트: [40, 120, 360]
후보 리스트: [72, 24, 8]