메이플의 개발 스토리
[ML] 5-2 교차 검증과 그리드 서치 본문
교차 검증과 그리드 서치¶
검증 세트¶
- validation set, 개발 세트(dev set)
- 하이퍼파라미터 튜닝을 위해 모델을 평가할 때, 테스트 세트를 사용하기 않기 위해 훈련 세트에서 다시 떼어 낸 데이터 세트
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)
교차 검증¶
- cross validation
- 훈련 세트를 여러 폴드로 나눈 다음 한 폴드가 검증 세트의 역할을 하고 나머지 폴드에서는 모델을 훈련
- 모든 폴드에 대해 검증 점수를 얻어 평균하는 방법
- 3-폴드 교차 검증 : 훈련 세트를 세 부분으로 나눠서 교차 검증을 수행하는 것
- 보통 5-폴드 교차 검증이나 10-폴드 교차 검증을 많이 사용함
scores = cross_validate(dt, train_input, train_target)
그리드 서치¶
- Grid Search
- 하이퍼파라미터 탐색을 자동화해 주는 도구
- 탐색할 매개변수를 나열하면 교차 검증을 수행하여 가장 좋은 검증 점수의 매개변수 조합을 선택, 마지막으로 이 매개변수 조합으로 최종 모델을 훈련
- 그리드 서치를 하는 이유
params = {'min_impurity_decrease': [0.0001, 0.0002, 0.0003, 0.0004, 0.0005]} gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1)
랜덤 서치¶
- Random Search
- 연속된 매개변수 값을 탐색할 때 유용
- 탐색할 값을 직접 나영하는 것이 아니라 탐색 값을 샘플링할 수 있는 확률 분포 객체를 전할
- 지정된 횟수만큼 샘플링하여 교차 검증을 수행하기 때문에 시스템 자원이 허락하는 만큼 탐색량을 조절할 수 있음
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), } gs = RandomizedSearchCV(DecisionTreeClassifier(random_state=42), params, n_iter=100, n_jobs=-1, random_state=42) gs.fit(train_input, train_target)
검증 세트¶
1) 데이터 불러오기¶
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()
2) 훈련 세트, 테스트 세트, 검증 세트로 분리¶
- 60 : 20 : 20 비율
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)
3) 훈련 세트로 결정 트리 모델 훈련, 검증 세트로 성능 확인¶
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(random_state=42)
dt.fit(sub_input, sub_target)
print(dt.score(sub_input, sub_target))
print(dt.score(val_input, val_target))
0.9971133028626413 0.864423076923077
훈련 세트에 과대적합되어 있음
교차 검증¶
1) 교차 검증 함수¶
cross_validation()
- 먼저 평가할 모델 객체를 첫 번째 매개변수로 전달, 그 다음 직접 검증 세트를 떼어 내지 않고 훈련 세트 전체를 전달
- fit_time, score_time, test_score 키를 가진 딕셔너리를 반환
- cv 매개변수에서 폴드 수를 바꿀 수 있음 (기본값은 5-폴드 교차 검증)
from sklearn.model_selection import cross_validate
scores = cross_validate(dt, train_input, train_target)
print(scores)
{'fit_time': array([0.00769353, 0.00704193, 0.01228404, 0.01399946, 0.01027465]), 'score_time': array([0.00086141, 0.0007205 , 0.00139689, 0.00118089, 0.00079584]), 'test_score': array([0.86923077, 0.84615385, 0.87680462, 0.84889317, 0.83541867])}
2) 교차 검증의 최종 점수¶
test_score 키에 담긴 5개의 점수를 평균하여 얻을 수 있음
import numpy as np
print(np.mean(scores['test_score']))
0.855300214703487
3) 분할기 지정¶
train_test_split() 함수는 전체 데이터를 섞은 후 훈련 세트를 준비했기 때문에 따로 섞을 필요가 없음
cross_validate()는 훈련 세트를 섞어 폴드를 나누지 않기 때문에 교차 검증을 할 때 훈련 세트를 섞으려면 분할기(splitter)를 지정해야 함
- 회귀 모델일 경우 - KFold 분할기를 사용
- 분류 모델일 경우 - StratifiedFold를 사용
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)
scores = cross_validate(dt, train_input, train_target, cv=splitter)
print(np.mean(scores['test_score']))
0.8574181117533719
하이퍼파라미터 튜닝¶
- 모델 파라미터 : 머신러닝 모델이 학습하는 파라미터, 모델 내부에서 결정되는 변수
(예시 - 한 클래스에 속해 있는 학생들의 키에 대한 정규분포를 그린다고 할 때 나오는 평균과 표준편차, 선형 회귀의 계수) - 하이퍼 파라미터 : 모델이 학습할 수 없어서 사용자가 지정해야만 하는 파라미터, 사용자 지정 파라미터
좀 더 자세한 설명
그리드 서치¶
1) 탐색할 매개변수 지정 (한 개)¶
- GridSearchCV 클래스를 통해 하이퍼파라미터 탐색과 교차 검증을 한 번에 수행함
- n_jobs = -1 : CPU 모든 코어 사용
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)
gs.fit(train_input, train_target)
GridSearchCV(estimator=DecisionTreeClassifier(random_state=42), n_jobs=-1, param_grid={'min_impurity_decrease': [0.0001, 0.0002, 0.0003, 0.0004, 0.0005]})
2) 최상의 평균 검증 점수가 나오는 매개변수 조합 출력¶
- bestestimator 속성 : 검증 점수가 가장 높은 모델의 매개변수 조합이 저장됨
사이킷런의 그리드 서치는 훈련이 끝나면 25개의 모델 중에서 검증 점수가 가장 높은 모델의 매개변수 조합으로 전체 훈련 세트에서 자동으로 다시 모델을 훈련함
dt = gs.best_estimator_
print(dt.score(train_input, train_target))
0.9615162593804117
- bestparams 속성 : 그치드 서치로 찾은 최적의 매개변수는 bestparams 속성에 저장
print(gs.best_params_)
{'min_impurity_decrease': 0.0001}
- cvresults 속성의 'mean_test_score' : 각 매개변수에서 수행한 교차 검증의 평균 점수
print(gs.cv_results_['mean_test_score'])
[0.86819297 0.86453617 0.86492226 0.86780891 0.86761605]
넘파이 argmax() 함수를 사용해서 가장 큰 값의 인덱스를 추출 → 해당 인덱스를 사용해 params 키에 저장된 매개변수를 출력
=> 최상의 검증 점수를 만든 매개변수 조합 (= bestparamas)
best_index = np.argmax(gs.cv_results_['mean_test_score'])
print(gs.cv_results_['params'][best_index])
{'min_impurity_decrease': 0.0001}
1) 탐색할 매개변수 지정 (여러 개)¶
params = {'min_impurity_decrease': 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)
GridSearchCV(estimator=DecisionTreeClassifier(random_state=42), n_jobs=-1, param_grid={'max_depth': range(5, 20), 'min_impurity_decrease': array([0.0001, 0.0002, 0.0003, 0.0004, 0.0005, 0.0006, 0.0007, 0.0008, 0.0009]), 'min_samples_split': range(2, 100, 10)})
2) 최상의 평균 검증 점수가 나오는 매개변수 조합 출력¶
최상의 매개변수 조합
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
랜덤 서치¶
매개변수의 값이 수치일 때 값의 범위나 간격을 미리 정하기 어렵거나
너무 많은 매개변수 조건이 있어 그리드 서치 수행 시간이 오래 걸릴 경우 사용하면 좋음
cf) 싸이파이 예시¶
- uniform : 정수 값을 균등 분포에서 샘플링
- randint : 실수 값을 균등 분포에서 샘플링
샘플링 횟수는 시스템 자원이 허락하는 범위 내에서 최대한 크게 하기
from scipy.stats import uniform, randint
0에서 10 사이의 범위를 갖는 randint 객체를 만들고 10개의 숫자를 샘플링 => 고르게 샘플링되지 않음
rgen = randint(0, 10)
rgen.rvs(10)
array([1, 0, 0, 8, 2, 9, 8, 8, 4, 9])
1000개의 숫자를 샘플링 => 샘플링 숫자를 늘렸더니 고르게 샘플림됨
np.unique(rgen.rvs(1000), return_counts=True)
(array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), array([ 99, 95, 99, 115, 95, 97, 86, 99, 114, 101]))
0에서 1 사이의 범위를 갖는 uniform 객체를 만들고 10개의 숫자를 샘플링
ugen = uniform(0, 1)
ugen.rvs(10)
array([0.69686071, 0.73286821, 0.75811145, 0.03463952, 0.18767496, 0.47607295, 0.44609313, 0.00511455, 0.08438922, 0.57094176])
1) 싸이파이 샘플링 함수를 통해 랜덤서치¶
- min_samples_split 매개변수 : 노드를 분할하기 위한 최소한의 샘플 데이터수
- min_samples_leaf 매개변수 : 리프 노드가 되기 위한 최소 샘플의 개수
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),
}
RandomizedSearchCV 클래스
- 사이킷런의 랜덤 서치 클래스
- n_iter 매개변수 : 샘플링 횟수 지정
100 번을 샘플링하여 교차 검증을 수행하고 최적의 매개변수 조합을 찾는다.
그리드 서치보다 훨씬 교차 검증 수를 줄이면서 넓은 영역을 효과적으로 탐색할 수 있다
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)
RandomizedSearchCV(estimator=DecisionTreeClassifier(random_state=42), n_iter=100, n_jobs=-1, param_distributions={'max_depth': <scipy.stats._distn_infrastructure.rv_frozen object at 0x7f08b4807690>, 'min_impurity_decrease': <scipy.stats._distn_infrastructure.rv_frozen object at 0x7f08b4af19d0>, 'min_samples_leaf': <scipy.stats._distn_infrastructure.rv_frozen object at 0x7f08b4807710>, 'min_samples_split': <scipy.stats._distn_infrastructure.rv_frozen object at 0x7f08b4807390>}, random_state=42)
2) 최상의 평균 검증 점수가 나오는 매개변수 조합 출력¶
최상의 매개변수 조합
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
확인문제¶
마지막 RandomizedSearchCV 예제에서 DecisionTreeClassifier 클래스에 splitter='random' 매개변수를 추가하고 다시 훈련해 보세요.
splitter 매개변수의 기본값은 'best'로 각 노드에서 최선의 분할을 찾습니다.
'random'이면 무작위로 분할한 다음 가장 좋은 것을 고릅니다.
왜 이런 옵션이 필요한지는 다음 절에서 알 수 있습니다.
테스트 세트에서 성능이 올라갔나요? 내려갔나요?
gs = RandomizedSearchCV(DecisionTreeClassifier(splitter='random', random_state=42), params,
n_iter=100, n_jobs=-1, random_state=42)
gs.fit(train_input, train_target)
RandomizedSearchCV(estimator=DecisionTreeClassifier(random_state=42, splitter='random'), n_iter=100, n_jobs=-1, param_distributions={'max_depth': <scipy.stats._distn_infrastructure.rv_frozen object at 0x7f08b4807690>, 'min_impurity_decrease': <scipy.stats._distn_infrastructure.rv_frozen object at 0x7f08b4af19d0>, 'min_samples_leaf': <scipy.stats._distn_infrastructure.rv_frozen object at 0x7f08b4807710>, 'min_samples_split': <scipy.stats._distn_infrastructure.rv_frozen object at 0x7f08b4807390>}, random_state=42)
print(gs.best_params_)
print(np.max(gs.cv_results_['mean_test_score']))
dt = gs.best_estimator_
print(dt.score(test_input, test_target))
{'max_depth': 43, 'min_impurity_decrease': 0.00011407982271508446, 'min_samples_leaf': 19, 'min_samples_split': 18} 0.8458726956392981 0.786923076923077
'ML DL' 카테고리의 다른 글
[ML] 5-3 트리의 앙상블 (0) | 2022.01.08 |
---|---|
[ML] 5-1 결정 트리 (0) | 2022.01.08 |
[ML] 4-2 확률적 경사 하강법 (0) | 2022.01.08 |
[ML] 4-1 로지스틱 회귀 (0) | 2022.01.08 |
[ML] 3-3 특성 공학과 규제 (0) | 2022.01.08 |