2023. 8. 22. 10:34ㆍ2023-2 KHUDA/ML 기초세션
이 게시물은 한빛미디어의 <혼자 공부하는 머신러닝 + 딥러닝>를 정리한 글입니다.
혼자 공부하는 머신러닝+딥러닝
혼자 공부하는 머신러닝 딥러닝, 무료 동영상 강의, 머신러닝+딥러닝 용어집을 다운로드 하세요. 포기하지 마세요! 독학으로 충분히 하실 수 있습니다. ‘때론 혼자, 때론 같이’ 하며 힘이 되겠
hongong.hanbit.co.kr
이전까지는 문제를 간단히 하기 위해 테스트 세트를 사용했다. 하지만 테스트 세트로 일반화 성능을 올바르게 예측하려면 가능한 한 테스트 세트를 사용하지 말아야 한다. 모델을 다 만들고 나서 마지막에 딱 한 번만 사용하는 것이 좋다.
검증 세트
테스트 세트를 사용하지 않고 과대적합인지 과소적합인지를 측정하는 방법은 훈련 세트를 또 나눠 검증 세트(validation set)를 만들어낸다. 훈련 세트에서 모델을 훈련하고 검증 세트로 모델을 평가한다. 이런 식으로 테스트하고 싶은 매개변수를 바꿔가며 가장 좋은 모델을 고른다. 그다음 이 매개변수를 사용해 훈련 세트와 검증 세트를 합쳐 전체 훈련 데이터에서 모델을 다시 훈련한다. 그리고 마지막에 테스트 세트에서 최종 점수를 평가한다.
먼저 검증 세트를 만들어 보겠다.
import pandas as pd
wine = pd.read_csv('https://bit.ly/wine_csv_data')
data = wine[['alcohol', 'sugar', 'pH']].to_numpy()
target = wine['class'].to_numpy()
from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = train_test_split(
data, target, test_size=0.2, random_state=42)
sub_input, val_input, sub_target, val_target = train_test_split(
train_input, train_target, test_size=0.2, random_state=42)
print(sub_input.shape, val_input.shape)
# -> (4157, 3) (1040, 3)
이제 훈련 세트와 검증 세트를 사용해 모델을 만들고 평가할 것이다.
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(random_state=42)
dt.fit(sub_input, sub_target)
print(dt.score(sub_input, sub_target))
# -> 0.9971133028626413
print(dt.score(val_input, val_target))
# -> 0.864423076923077
이 모델은 확실히 훈련 세트에 과대적합되어 있다. 매개변수를 바꿔서 더 좋은 모델을 찾아야 한다.
교차 검증
검증 세트를 만드느라 훈련 세트가 줄었다. 보통 많은 데이터를 훈련에 사용할수록 좋은 모델이 만들어진다. 그렇다고 검증 세트를 조금만 만들면 검증 점수가 불안정할 것이다. 이럴 때 교차 검증(cross validation)을 이용하면 안정적인 검증 점수를 얻고 훈련에 더 많은 데이터를 사용할 수 있다. 교차 검증은 검증 세트를 떼어 내어 평가하는 과정을 여러 번 반복한다. 그다음 점수를 평균하여 최종 검증 점수를 얻는다. 이때 k 폴드 교차 검증을 사용한다. k 폴드 교차 검증은 훈련 세트를 k개의 부분으로 나눠 교차 검증을 수행하는 것을 뜻한다. 이렇게 하면 데이터의 80~90%까지 훈련에 사용할 수 있다. 검증 세트가 줄어들지만 각 폴드에서 계산한 검증 점수를 평균하기 때문에 안정된 점수로 생각할 수 있다.
사이킷런에는 cross_validate()라는 교차 검증 함수가 있다. 먼저 평가할 모델 객체를 첫 번째 매개변수로 전달하고, 앞에서처럼 직접 검증 세트를 떼어 내지 않고 훈련 세트 전체를 cross_validate() 함수에 전달한다.
from sklearn.model_selection import cross_validate
scores = cross_validate(dt, train_input, train_target)
print(scores)
# -> {'fit_time': array([0.00741601, 0.00696874, 0.0073185 , 0.00714064,
# 0.00696945]), 'score_time': array([0.00095677,
# 0.00093961, 0.00082994, 0.00091386, 0.00085044]),
# 'test_score': array([0.86923077, 0.84615385, 0.87680462,
# 0.84889317, 0.83541867])}
처음 두 개의 키는 각각 모델을 훈련하는 시간과 검증하는 시간을 의미한다. 각 키마다 5개의 숫자가 담겨있는데, cross_validate()함수는 기본적으로 5-폴드 교차 검증을 수행한다. cv 매개변수로 폴드 수를 바꿀 수도 있다. 교차 검증의 최종 점수는 test_score 키에 담긴 5개의 점수를 평균하여 얻을 수 있다. 이름은 test-score지만 검증 폴드의 점수이다.
import numpy as np
print(np.mean(scores['test_score']))
# -> 0.855300214703487
교차 검증을 수행하면 입력한 모델에서 얻을 수 있는 최상의 검증 점수를 가늠할 수 있다. 한 가지 주의할 점은 앞서 train_test_split()함수로 전체 데이터를 섞은 후 훈련 세트를 준비했기 때문에, cross_validate()는 훈련 세트를 섞어 폴드를 나누지 않는다. 하지만 만약 교차 검증을 할 때 훈련 세트를 섞으려면 분할기를 지정해야 한다. 사이킷런의 분할기는 교차 검증에서 폴드를 어떻게 나눌지 결정해준다. cross_validate() 함수는 기본적으로 회귀 모델일 경우 KFold 분할기를 사용하고 분류 모델일 경우 타깃 클래스를 골고루 나누기 위해 StratifiedKFold를 사용한다. 즉 앞서 수행한 교차 검증은 다음 코드와 동일하다.
from sklearn.model_selection import StratifiedKFold
scores = cross_validate(dt, train_input, train_target, cv=StratifiedKFold())
print(np.mean(scores['test_score']))
# -> 0.855300214703487
만약 훈련 세트를 섞은 후 10-폴드 교차 검증을 수행하려면 다음과 같이 작성한다.
splitter = StratifiedKFold(n_splits=10, shuffle=True, random_state=42)#n_splits는 폴더 갯수
scores = cross_validate(dt, train_input, train_target, cv=splitter)
print(np.mean(scores['test_score']))
# -> 0.8574181117533719
KFold 클래스도 동일한 방식으로 사용할 수 있다.
하이퍼파라미터 튜닝
하이퍼 파라미터 튜닝 작업은 모델의 매개변수를 동시에 바꿔가며 최적의 값을 찾아야 한다. 사이킷런에서 제공하는 그리드 서치(Grid Search)를 사용한다. 사이킷런의 GridSearchCV 클래스는 하이퍼파라미터 탐색과 교차 검증을 한 번에 수행한다. 별도로 cross_validate() 함수를 호출할 필요가 없다.
기본 매개변수를 사용한 결정 트리 모델에서 min_impurity_decrease 매개변수의 최적값을 찾아보겠다.
from sklearn.model_selection import GridSearchCV
params = {'min_impurity_decrease' : [0.0001, 0.0002, 0.0003, 0.0004, 0.0005]}
gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1)
cv 매개변수의 기본값은 5이다. 따라서 min_impurity_decrease 값마다 5-폴드 교차 검증을 수행한다. 결국 25개의 모델을 훈련한다! 많은 모델을 훈련하기 때문에 n_jobs 매개변수에서 -1로 지정해 시스템에 있는 모든 코어를 사용한다.
사이킷런의 그리드 서치는 훈련이 끝나면 25개의 모델 중에서 검증 점수가 가장 높은 모델의 매개변수 조합으로 전체 훈련 세트에서 자동으로 다시 모델을 훈련한다. 이 모델은 gs 객체의 best_estimator_ 속성에 저장되어 있다. 또 그리드 서치로 찾은 최적의 매개변수는 best_params_ 속성에 저장되어 있다.
gs.fit(train_input, train_target)
dt = gs.best_estimator_
print(dt.score(train_input, train_target))
# -> 0.9615162593804117
print(gs.best_params_)
# -> {'min_impurity_decrease': 0.0001}
print(gs.cv_results_['mean_test_score'])
# -> [0.86819297 0.86453617 0.86492226 0.86780891 0.86761605]
수동으로 고르는 것보다 넘파이 argmax() 함수를 사용하면 가장 큰 값의 인덱스를 추출할 수 있다. 그다음 이 인덱스를 사용해 params 키에 저장된 매개변수를 출력할 수 있다. 이 값이 최상의 검증 점수를 만든 매개변수 조합이다. 앞에서 출력한 gs.best_params_와 동일하다.
best_index = np.argmax(gs.cv_results_['mean_test_score'])
print(gs.cv_results_['params'][best_index])
# -> {'min_impurity_decrease': 0.0001}
조금 더 복잡한 매개변수 조합을 탐색해보자.
params = {'min_impurity_decreases' : np.arange(0.0001, 0.001, 0.0001),
'max_depth' : range(5, 20, 1),
'min_samples_split' : range(2, 100, 10)
}
gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1)
gs.fit(train_input, train_target)
print(gs.best_params)
# -> {'max_depth': 14, 'min_impurity_decrease': 0.0004, 'min_samples_split': 12}
print(np.max(gs.cv_results_['mean_test_score']))
# -> 0.8683865773302731
아직 조금 아쉬운 점이 있다. 앞에서 탐색할 매개변수의 간격을 0.0001 혹은 1로 설정했는데, 이렇게 간격을 둔 것에는 특별한 근거가 없다. 이보다 더 좁거나 넓은 간격으로 시도해 보자.
랜덤 서치
매개변수의 값이 수치일 때 값의 범위나 간격을 미리 정하기 어려울 수 있다. 또 너무 많은 매개변수 조건이 있어 그리드 서치 수행 시간이 오래 걸릴 수 있다. 이럴 때 랜덤 서치를 사용한다. 랜덤 서치에는 매개변수 값의 목록을 전달하는 것이 아니라 매개변수를 샘플링할 수 있는 확률 분포 객체를 전달한다.
from scipy.stats import uniform, randint
scipy의 stats 서브 패키지에 있는 uniform과 randint 클래스는 모두 주어진 범위에서 고르게 값을 뽑는다. 이를 '균등 분포에서 샘플링한다'라고 말한다. randint는 정숫값을 뽑고, uniform은 실숫값을 뽑는다.
params = {'min_impurity_decrease' : uniform(0.0001, 0.001),
'max_depth' : randint(20, 50),
'min_samples_split' : randint(2, 25),
'min_samples_leaf' : randint(1, 25),
}
from sklearn.model_selection import RandomizedSearchCV
gs = RandomizedSearchCV(DecisionTreeClassifier(random_state=42), params,
n_iter=100, n_jobs=-1, random_state=42)
gs.fit(train_input, train_target)
print(gs.best_params_)
# -> {'max_depth': 39, 'min_impurity_decrease': 0.00034102546602601173,
# 'min_samples_leaf': 7, 'min_samples_split': 13}
print(np.max(gs.cv_results_['mean_test_score']))
# -> 0.8695428296438884
dt = gs.best_estimator_
print(dt.score(test_input, test_target))
# -> 0.86
(여기서 n_iter=100은 params에 정의된 매개변수 범위에서 총 100번을 샘플링하여 교차 검증을 수행하고 최적의 매개변수 조합을 찾는다는 뜻이다.)
앞선 그리드 서치보다 훨씬 교차 검증 수를 줄이면서 넓은 영역을 효과적으로 탐색할 수 있다.
다음 절에서는 결정 트리를 확장하여 앙상블 모델에 대해 알아보겠다.
'2023-2 KHUDA > ML 기초세션' 카테고리의 다른 글
| [혼자 공부하는 머신러닝 + 딥러닝] Chapter 06-1 군집 알고리즘 (0) | 2023.08.29 |
|---|---|
| [혼자 공부하는 머신러닝 + 딥러닝] Chapter 05-3 트리의 앙상블 (0) | 2023.08.22 |
| [혼자 공부하는 머신러닝 + 딥러닝] Chapter 05-1 결정 트리 (1) | 2023.08.22 |
| [혼자 공부하는 머신러닝 + 딥러닝] Chapter 04-2 확률적 경사 하강법 (0) | 2023.08.15 |
| [혼자 공부하는 머신러닝 + 딥러닝] Chapter 04-1 로지스틱 회귀 (0) | 2023.08.15 |