본문 바로가기
파이썬 라이브러리를 활용한 머신러닝

Grid Search

by 일일과제중 2019. 12. 13.
반응형

 

그리드 서치 (Grid Search)

 

하이퍼파라미터를 튜닝하여 일반화 성능을 개선할 수 있습니다.

 

여러 알고리즘의 하이퍼파라미터 설정에 대해 얘기했습니다.

 

하이퍼파라미터를 조정하기 전에 하이퍼파라미터의 의미를 이해하는 것이 중요합니다. 

 

모델에서 중요한 하이퍼파라미터의 (일반화 성능을 최대로 높여주는) 값을 찾는 일은 어려운 작업이지만, 모든 모델과 데이터셋에서 해야 하는 필수적인 일입니다.

 

가장 널리 사용하는 방법은 그리드 서치로서 관심 있는 하이퍼파라미터들을 대상으로 가능한 모든 조합을 시도해보는 것입니다.

 

 

SVC 파이썬 클래스에 구현된 RBF 커널 SVM을 사용해보겠습니다.

 

커널의 폭에 해당하는 gamma와 규제 매개변수 C가 중요합니다.

 

매개변수 C와 gamma에 0.001, 0.01, 0.1, 1, 10, 100 값을 적용해보겠습니다.

 

C와 gamma의 설정값이 각각 6개씩이니 조합의 수는 총 36개입니다.

 

그림1

 

 

아이리스 데이터셋 예시

 

from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score

iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, random_state=0)
scaler = StandardScaler()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)
best_score = 0 

for gamma in [0.001, 0.01, 0.1, 1, 10, 100]:
    for C in [0.001, 0.01, 0.1, 1, 10, 100]:
        # 매개변수의 각 조합에 대해 SVC를 훈련시킵니다
        clf = SVC(gamma=gamma, C=C)
        clf.fit(X_train_scaled, y_train)
        # 테스트 세트로 SVC를 평가합니다
        y_test_hat = clf.predict(X_test_scaled)
        score = accuracy_score(y_test, y_test_hat)
        # 점수가 더 높으면 매개변수와 함께 기록합니다
        if score > best_score:
            best_score = score
            best_parameters = {'C': C, 'gamma': gamma}
            
print("최고 점수: {:.2f}".format(best_score))
print("최적 파라미터:", best_parameters)

최고 점수: 0.97

최적 파라미터: {'C': 100, 'gamma': 0.001}

 

간단한 그리드 서치를 for 문을 사용해 만들 수 있습니다. 

 

 

매개변수 과대적합과 검증 세트

 

앞의 결과를 보면 이 데이터셋에서 모델 정확도가 97%라고 보고할 수 있습니다. 

 

하지만 이런 주장은 다음과 같은 이유로 매우 낙관적인 (혹은 잘못된) 것일 수 있습니다.

 

여러 가지 하이퍼파라미터 값으로 많이 시도해보고 테스트 세트 정확도가 가장 높은 조합을 선택했습니다.

 

하지만 이 정확도는 새로운 데이터에까지 이어지지 않을 수 있습니다.

 

하이퍼파라미터를 조정하기 위해 테스트 세트를 이미 사용했기 때문에 모델이 얼마나 좋은지 평가하는 데는 더 이상 사용할 수 없습니다.

 

평가를 위해서는 모델을 만들 때 사용하지 않은 독립된 데이터셋이 필요합니다.

 

 

검증 세트

 

데이터를 다시 나눠서 세 개의 세트로 만들어 이 문제를 해결할 수 있습니다.

 

훈련 세트로는 모델을 만듭니다. 

 

검증 세트로는 모델의 하이퍼파라미터를 선택합니다.

 

테스트 세트로는 선택된 하이퍼파라미터의 성능을 평가합니다.

 

그림2

 

 

아이리스 데이터셋 예시

 

from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score

iris = load_iris()
X_trainval, X_test, y_trainval, y_test = train_test_split(
iris.data, iris.target, test_size=0.25, random_state=0)
X_train, X_valid, y_train, y_valid = train_test_split(
X_trainval, y_trainval, test_size=0.25, random_state=1)

print("훈련 세트의 크기: {}   검증 세트의 크기: {}   테스트 세트의 크기:"
      " {}\n".format(X_train.shape[0], X_valid.shape[0], X_test.shape[0]))

훈련 세트의 크기: 84 검증 세트의 크기: 28 테스트 세트의 크기: 38

 

데이터를 훈련 세트, 검증 세트, 테스트 세트로 나누었습니다.

 

 

scaler = StandardScaler()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
X_valid_scaled = scaler.transform(X_valid)
X_test_scaled = scaler.transform(X_test)

 

훈련 세트의 데이터를 스케일링 했습니다. 

 

 

best_score = 0

for gamma in [0.001, 0.01, 0.1, 1, 10, 100]:
    for C in [0.001, 0.01, 0.1, 1, 10, 100]:
        # 매개변수의 각 조합에 대해 SVC를 훈련시킵니다
        clf = SVC(gamma=gamma, C=C)
        clf.fit(X_train_scaled, y_train)
        # 검증 세트로 SVC를 평가합니다
        y_valid_hat = clf.predict(X_valid_scaled)
        score = accuracy_score(y_valid, y_valid_hat)
        # 점수가 더 높으면 매개변수와 함께 기록합니다
        if score > best_score:
            best_score = score
            best_hyperparameters = {'C': C, 'gamma': gamma}

print("검증 세트에서 최고 점수: {:.2f}".format(best_score))
print("최적 파라미터: ", best_hyperparameters)

검증 세트에서 최고 점수: 0.93

최적 파라미터: {'C': 100, 'gamma': 0.001}

 

검증 세트에서 최고 점수는 93%입니다. 

 

 

scaler.fit(X_trainval)
X_trainval_scaled = scaler.transform(X_trainval)

clf= SVC(**best_hyperparameters)
clf.fit(X_trainval_scaled, y_trainval)

y_test_hat = clf.predict(X_test_scaled)
test_score = accuracy_score(y_test, y_test_hat)
print("최적 파라미터에서 테스트 세트 점수: {:.2f}".format(test_score))

최적 파라미터에서 테스트 세트 점수: 0.97

 

테스트 세트 점수는 97%입니다. 

 

 

훈련 세트, 검증 세트, 테스트 세트의 구분은 실제 머신러닝 알고리즘을 적용하는 데 아주 중요합니다.

 

테스트 세트 정확도에 기초에 어떤 선택을 했다면 테스트 세트의 정보를 모델에 누설한 것입니다.

 

그렇기 때문에 최종 평가에만 사용하도록 테스트 세트를 분리해 유지하는 것이 중요합니다. 

 

 

교차 검증을 사용한 그리드 서치

 

데이터를 훈련 세트, 검증 세트, 테스트 세트로 나누는 방법은 잘 작동하고 널리 사용됩니다.

 

그러나 데이터를 나누는 방법에 매우 민감합니다. 특히 데이터 사이즈가 작을 때 그렇습니다.

 

일반화 성능을 더 잘 평가하려면 훈련 세트와 검증 세트를 한 번만 나누지 않고, 교차 검증을 사용해서 각 하이퍼파라미터 조합의 성능을 평가할 수 있습니다. 

 

교차 검증의 단점은 이 모델들을 모두 학습시키는 데 걸리는 시간입니다. 

 

 

아이리스 데이터셋 예시

 

scaler = StandardScaler()
scaler.fit(X_trainval)
X_trainval_scaled = scaler.transform(X_trainval)
X_test_scaled = scaler.transform(X_test)

 

X_trainval에 대하여 데이터 스케일링을 합니다.

 

 

from sklearn.model_selection import StratifiedKFold, GridSearchCV

kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=2)
hyperparam_grid = {'C' : [0.001, 0.01, 0.1, 1, 10, 100],
                  'gamma' : [0.001, 0.01, 0.1, 1, 10, 100]}

grid_search = GridSearchCV(SVC(), hyperparam_grid, scoring='accuracy', refit=True, 
                          cv=kfold)
grid_search.fit(X_trainval_scaled, y_trainval)

print("검증 세트에서 최고 점수: {:.2f}".format(grid_search.best_score_))
print("최적 파라미터: {}".format(grid_search.best_params_))

검증 세트에서 최고 점수: 0.97

최적 파라미터: {'C': 100, 'gamma': 0.01}

 

GridSearchCV를 사용하려면 먼저 딕셔너리 형태로 검색 대상 매개변수를 지정해야 합니다. 

 

모델. 검색 대상 하이퍼파라미터 그리드, 원하는 교차 검증으로 GridSearchCV의 객체를 생성합니다.

 

 

y_test_hat = grid_search.predict(X_test_scaled)
test_score = accuracy_score(y_test, y_test_hat)
print("Test set score with best hyperparameters: {:.2f}".format(test_score))

Test set score with best hyperparameters: 0.97

 

교차 검증으로 하이퍼파라미터를 선택하고 테스트 세트 정확도를 본 결과 97%가 나왔습니다. 

 

매개 변수를 선택하는 데 테스트 세트를 사용하지 않았다는 점이 중요합니다. 

 

그림3

 

 

아이리스 데이터셋 예시 - GridSearchCV를 직업 구현

 

from sklearn.model_selection import StratifiedKFold, cross_validate


kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=2)

best_score = 0

for gamma in [0.001, 0.01, 0.1, 1, 10, 100]:
    for C in [0.001, 0.01, 0.1, 1, 10, 100]:
        
        clf = SVC(gamma=gamma, C=C)
        scores = cross_validate(clf, X_trainval_scaled, y_trainval, 
                               scoring='accuracy', cv=kfold, 
                               return_train_score=True, return_estimator=True)
        score = scores['test_score'].mean()
    
        if score > best_score:
            best_score = score
            best_models = scores['estimator']
            best_hyperparameters = {'C': C, 'gamma': gamma}

print("검증 세트에서 최고 점수: {:.2f}".format(best_score))
print("최적 파라미터: ", best_hyperparameters)

검증 세트에서 최고 점수: 0.97

최적 파라미터: {'C': 100, 'gamma': 0.01}

 

5-겹 교차 검증으로 C와 gamma 설정에 대한 SVM의 정확도를 평가하려면 36 * 5 =180개의 모델이 필요합니다.

 

 

clf = SVC(**best_hyperparameters)
clf.fit(X_trainval_scaled, y_trainval)

y_test_hat = clf.predict(X_test_scaled)
test_score = accuracy_score(y_test, y_test_hat)
print("Test set score with best hyperparameters: {:.2f}".format(test_score))

Test set score with best hyperparameters: 0.97

 

모델은 훈련 세트와 검증 세트를 합쳐서 만듭니다. 

 

 

교차 검증 결과 분석

 

그리드 서치는 연산 비용이 매우 크므로 비교적 간격을 넓게 하여 적은 수의 그리드로 시작하는 것이 좋습니다.

 

그런 다음 교차 검증된 그리드 서치의 결과를 분석하여 검색을 확장해나갈 수 있습니다. 

 

교차 검증의 결과를 시각화하면 검색 대상 매개변수가 모델의 일반화에 영향을 얼마나 주는지 이해하는 데 도움이 됩니다. 

 

 

검색 대상 하이퍼파라미터 그리드가 2차원이므로, 히트맵으로 시각화하기가 좋습니다.

 

히트맵의 각 포인트는 특정 매개변수 설정에 대한 교차 검증 실행을 나타냅니다.

 

교차 검증의 정확도가 높으면 밝은 색으로, 낮으면 어두운 색으로 나타냈습니다. 

 

 

그림4

 

히트맵을 보면 SVC는 하이퍼파라미터 설정에 매우 민감합니다.

 

많은 매개변수 설정에서 40% 부근의 낮은 정확도를 내고 있습니다.

 

어떤 설정에서는 96% 이상을 만들었습니다.

 

두 하이퍼파라미터 (C와 gamma)가 매우 중요하며 어떻게 조정하는지에 따라 정확도가 40%에서 96%까지 차이가 납니다.

 

 

그림5

 

첫 번째 그래프는 점수변화가 없어서 전체 하이퍼파라미터 그리드가 같은 색입니다.

 

이런 결과는 매개변수 C와 gamma의 스케일과 범위가 부적절할 때 발생합니다.

 

그러나 매개변수 설정이 바뀌어도 정확도에 아무런 변화가 없다면, 그 매개변수가 전혀 중요하지 않은 것일 수도 있습니다. 

 

 

두 번째 그래프는 세로 띠 형태를 보입니다.

 

이는 gamma 하이퍼파라미터만 정확도에 영향을 준다는 뜻입니다. 

 

즉, gamma 매개변수는 적절한 범위를 탐색하고 있지만, C 매개변수는 그렇지 못하든지, 아니면 중요한 매개변수가 아닐 수도 있습니다.

 

 

세 번째 그래프는 C와 gamma 둘 모두에 따라 값이 변했습니다.

 

하지만 그래프의 왼쪽 아래 영역에서는 아무런 변화가 없습니다. 아마도 다음 번 검색 때는 하이퍼파라미터 그리드에서 매우 작은 단위는 제외될 것입니다.

 

최적치가 그래프의 경계에 있으니 이 경계 너머에 더 나은 값이 있다고 생각할 수 있씁니다. 그러니 이 영역이 포함되도록 하이퍼파라미터 검색 범위를 바꿔줘야 합니다. 

 

 

교차 검증 점수를 토대로 하이퍼파라미터 그리드를 튜닝하는 것은 아주 안전한 방법이며, 하이퍼파라미터의 중요도를 확인하는 데도 좋습니다.

 

최종 테스트 세트 대상으로 여러 하이퍼파라미터 범위를 테스트해서는 안됩니다.

 

테스트 세트 평가는 사용할 모델이 정확히 결정되고 나서 딱 한 번만 해야 합니다. 

 

 

중첩 교차 검증

 

앞선 예에서 데이터를 한 번에 훈련, 검증, 테스트 세트로 나눈 방식에서, 훈련 세트와 테스트 세트로 나눈 다음 훈련 세트로 교차 검증을 수행하는 방식으로 바꿨습니다. 

 

하지만 GridsearchCV를 사용할 때 여전히 데이터를 훈련 세트와 테스트 세트로 한 번만 나누기 때문에, 결과가 불안정하고 테스트 데이터의 분할에 크게 의존합니다.

 

원본 데이터를 훈련 세트와 테스트 세트로 한 번만 나누는 방식 대신 더 나아가 교차 검증 분할 방식을 사용할 수 있습니다. 이를 중첩 교차 검증이라고 합니다.

 

중첩 교차 검증에서는 바깥쪽 루프에서 데이터를  훈련 세트와 테스트 세트로 나눕니다.

 

그리고 각 훈련 세트에 대해 그리드 서치를 실행합니다(아마도 바깥쪽 루프에서 분할된 훈련 세트마다 최적의 매개변수가 다를 것입니다).

 

그런 다음 바깥쪽에서 분할된 테스트 세트의 점수를 최적의 매개변수 설정을 사용해 각각 측정합니다. 

 

그림6

 

이 방법은 모델이나 하이퍼파라미터 설정이 아닌 테스트 점수의 목록을 만들어줍니다.

 

이 점수들은 그리드 서치를 통해 찾은 최적 하이퍼파라미터가 모델을 얼마나 잘 일반화시키는지 알려줍니다.

 

특정 데이터셋에서 주어진 모델이 얼마나 잘 일반화되는지 평가하는 데 유용한 방법입니다.

 

scikit-learn을 활용하면 중첩 교차 검증을 직관적으로 구형할 수 있습니다.

 

GridSearch CV의 객체를 모델로 삼아 cross_val_score (or cross_validate) 함수를 호출하면 됩니다. 

 

새로운 데이터에 적용할 모델을 만드는 것이 아니니, 중첩 교차 검증은 미래의 데이터에 적용하기 위한 예측 모델을 찾는 데는 거의 사용하지 않습니다. 

 

 

아이리스 데이터셋 예시

 

hyperparam_grid = [{'kernel' : ['rbf'],
                   'C': [0.001, 0.01, 0.1, 1, 10, 100],
                  'gamma' : [0.001, 0.01, 0.1, 1, 10, 100]},
                  {'kernel' : ['linear'],
                  'C' : [0.001, 0.01, 0.1, 1, 10, 100]}]

inner_kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=2)
outer_kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=2)
grid_search = GridSearchCV(SVC(), hyperparam_grid, scoring='accuracy', refit=True,
                          cv=inner_kfold)
scores = cross_validate(grid_search, iris.data, iris.target, scoring='accuracy',
                       cv=outer_kfold, return_train_score=True,
                       return_estimator=True)

print("Outer cross-validation score: ", scores['test_score'].mean())

Outer cross-validation score: 0.9666666666666666

 

중첩 교차 검증의 결과를 요약하면 'SVC는 iris 데이터셋에서 평균 교차 검증 정확도가 97%다'라고 할 수 있습니다. 

 

 

hyperparam_grid = [{'kernel' : ['rbf'],
                   'C': [0.001, 0.01, 0.1, 1, 10, 100],
                  'gamma' : [0.001, 0.01, 0.1, 1, 10, 100]},
                  {'kernel' : ['linear'],
                  'C' : [0.001, 0.01, 0.1, 1, 10, 100]}]

inner_kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=2)
outer_kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=2)

score_test = []
for train_idx, test_idx in outer_kfold.split(iris.data, iris.target):
    
    X_train = iris.data[train_idx]
    y_train = iris.target[train_idx]
    X_test = iris.data[test_idx]
    y_test = iris.target[test_idx]
    
    grid_search = GridSearchCV(SVC(), hyperparam_grid, scoring='accuracy', refit=True,
                          cv=inner_kfold)
    grid_search.fit(X_train, y_train)
    
    y_test_hat = grid_search.predict(X_test)
    score_test.append(accuracy_score(y_test, y_test_hat))
    
import numpy as np
print("Outer cross-validation score: ", np.mean(score_test))

Outer cross-validation score: 0.9666666666666666

 

데이터 스케일링을 적용하기 위해서 split 메소드를 다시 사용했습니다.

 

 

hyperparam_grid = [{'kernel' : ['rbf'],
                   'C': [0.001, 0.01, 0.1, 1, 10, 100],
                  'gamma' : [0.001, 0.01, 0.1, 1, 10, 100]},
                  {'kernel' : ['linear'],
                  'C' : [0.001, 0.01, 0.1, 1, 10, 100]}]

inner_kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=2)
outer_kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=2)


scaler = StandardScaler()
score_test = []
for train_idx, test_idx in outer_kfold.split(iris.data, iris.target):
    
    X_train = iris.data[train_idx]
    y_train = iris.target[train_idx]
    X_test = iris.data[test_idx]
    y_test = iris.target[test_idx]
    
    
    scaler.fit(X_train)
    X_train_scaled = scaler.transform(X_train)
    X_test_scaled = scaler.transform(X_test)
    
    grid_search = GridSearchCV(SVC(), hyperparam_grid, scoring='accuracy', refit=True,
                          cv=inner_kfold)
    grid_search.fit(X_train_scaled, y_train)
    
    y_test_hat = grid_search.predict(X_test_scaled)
    score_test.append(accuracy_score(y_test, y_test_hat))
    
import numpy as np
print("Outer cross-validation score: ", np.mean(score_test))

Outer cross-validation score: 0.9466666666666667

 

데이터 스케일링을 적용한 후의 결과입니다.

 

 

결론

 

훈련 세트, 검증 세트, 테스트 세트의 구분은 실제 머신러닝 알고리즘을 적용하는 데 아주 중요합니다.

 

가장 널리 사용되는 방법은 평가를 위한 훈련/테스트 분할과 훈련 세트에서 모델 선택을 위한 교차 검증을 사용하는 방법입니다.

 

본래 데이터셋이 매우 작을 때는, 중첩 교차 검증을 사용합니다.

 

테스트 세트 정확도에 기초해 어떤 선택을 했다면 테스트 세트의 정보를 모델에 누설한 것입니다.

 

그리드 서치와 교차 검증을 병렬화할 수 있습니다.

 

하나의 교차 검증 분할에서 특정 매개변수 설정을 사용해 모델을 만드는 일은 다른 매개변수 설정이나 모델과 전혀 상관없이 진행할 수 있기 때문입니다. 

 

만약 멀티 코어 CPU를 사용한다면, n_jobs 파라미터를 사용하여 CPU 코어 수를 지정합니다. 

 

 

Advanced Hyperparameter Selection Methods

 

메타 휴리스틱 (Meta-heuristics)

 

가까운 최적 해를 찾기 위해서 효율적인 하이퍼파라미터 공간을 탐색합니다. (e.g. Genetic alogrithm)

 

그림7

 

 

모델 기반 최적화 

 

하이퍼파라미터와 검증 성능 사이의 함수적 관계를 찾습니다. (e.g. Bayesian optimization)

 

그림8

 

 

파이썬 라이브러리를 활용한 머신러닝 책과 성균관대학교 강석호 교수님 수업 내용을 바탕으로 요약 작성되었습니다.

 

반응형

댓글