728x90
반응형
Pupbani와 신팀장의 보고를 받은 이사님은 다음과 같은 질문을 던졌다.
- max_depth를 3말고 다른 값으로 하면 성능이 달라지나요?"
- "네" 라고 Pupbani가 대답했다.
- "이런저런 값으로 모델을 많이 만들어서 테스트 세트로 평가하면 결국 테스트 세트에 잘 맞는 모델이 만들어지는게 아닌가요?"
Pupbani는 그 말을 듣고 기존에 했던 작업들을 돌아봤다.
- 훈련 세트에서 모델을 훈련하고 테스트 세트에서 모델을 평가했다.
- 이렇게 평가된 점수를 보고 일반화 성능을 가늠했다.
- 그런데 이렇게 테스트 세트를 자꾸 사용해서 성능을 확인하다보면 결국 모델을 테스트 세트에 맞추게 되는 셈입니다.
- 올바른 모델 개발을 위해 테스트 세트는 모델을 만들고 나서 마지막에 딱 한 번만 사용하는 것이 좋다.
- 그렇다면 어떻게 해야할까요??
검증 세트
- 검증 세트(Validation Set)는 테스트 세트를 사용하지 않고 모델이 과대적합인지 과소적합인지 확인할 수 있는 방법이다.
- 가장 간단한 방법은 훈련 세트를 또 나누는 방법이다.

이전에 사용했던 데이터를 가져와서 검증세트를 만들어 모델 훈련해보기
- 데이터를 가져와서 먼저 훈련 세트(80%)와 테스트 세트(20%)로 나눈다.
- train_test_split()의 test_size 매개변수를 통해 테스트 세트의 비율을 정할 수 있다.
- 훈련 세트를 훈련 세트(60%)와 검증 세트(20%)로 나눈다.
import pandas as pd
wine = pd.read_csv('https://bit.ly/wine-date')
data = wine[['alcohol', 'sugar', 'pH']].to_numpy()
target = wine['class'].to_numpy()
# 80% 20%
train_input, test_input, train_target, test_target = train_test_split(
data, target,test_size=0.2, random_state=42)
# 60% 20%
sub_input, val_input, sub_target, val_target = train_test_split(
train_input, train_target,test_size=0.2, random_state=42)
- 이렇게 나온 훈련 세트와 검증 세트로 모델을 학습하고 평가한다.
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(random_state=42)
dt.fit(sub_input, sub_target)
print(f"훈련 세트 정확도 : {dt.score(sub_input, sub_target)*100:.2f}%")
print(f"검증 세트 정확도 : {dt.score(val_input, val_target)*100:.2f}%")

- 이 모델은 훈련 세트에 대해 과대적합 되어 있다.
- 매개변수를 바꿔 더 좋은 모델을 찾아야한다.
교차 검증(Cross Validation)
- 검증 세트를 만드느라 훈련 세트가 줄었다.
- 보통 많은 데이터를 훈련에 사용할 수록 좋은 모델이 만들어 진다.
- 그렇다고 검증 세트를 너무 적게하면 모델의 성능이 들쭉날쭉하고 불안정할 것 이다.
- 이럴떄 사용하는 것이 교차검증(Cross Validation)이다.
- 검증 세트를 떼어 내어 평가하는 과정을 여러번 반복한다.
- K-폴드 교차 검증(K-fold cross validation)이라는 방법을 사용한다.
- 훈련 세트를 K부분으로 나누어 검증을 진행 하는 방법이다.

- 보통 5 또는 10-폴드 교차검증을 많이 사용한다.
- 이렇게 하면 데이터의 80~90%까지 훈련에 사용할 수 있다.
- 사이킷런에는 cross_validate() 라는 교차 검증 함수가 존재한다.
- 평가할 모델 객체를 첫번째 매개변수로 전달
- 직접 검증 세트를 떼어 내지 않고 훈련 세트 전체를 전달한다.
from sklearn.model_selection import cross_validate
scores = cross_validate(dt, train_input, train_target)
print(scores)
# 출력 결과
{'fit_time': array([0.00744486, 0.0057323 , 0.00587416, 0.00581193, 0.0055747 ]),
'score_time': array([0.0007453 , 0.00043416, 0.00042486, 0.00043082, 0.00043225]),
'test_score': array([0.86923077, 0.84615385, 0.87680462, 0.84889317, 0.83541867])}
- fit_time : 모델을 훈련한 시간
- score_time : 모델을 검증한 시간
- test_score : 검증 점수
- test_score의 값들을 평균을 내어 최종 점수를 얻을 수 있다.
import numpy as np
print(f"최종 정확도 : {np.mean(scores['test_score'])*100:.2f}%")

- 여기서 주의할 점은 cross_validate()는 훈련 세트를 섞어 폴드를 나누지 않는다.
- 이전에는 train_test_split으로 데이터를 섞어서 전달했기 때문에 따로 섞을 필요가 없었지만 만약 교차검증 시 훈련 세트를 섞으려면 분할기(Splitter)를 이용해야 한다.
- 분할기는 교차 검증에서 폴드를 어떻게 나눌지 결정한다.
- cross_validate()는 기본적으로 회귀 모델일 때는 KFold 분할기, 분류 모델일 때는 StratifiedKFold를 사용한다.
from sklearn.model_selection import StratifiedKFold
scores = cross_validate(dt, train_input, train_target, cv=StratifiedKFold())
# or
# n_splits는 K의 값, 지금은 10-폴드
# splitter = StratifiedKFold(n_splits=10, shuffle=True, random_state=42)
# scores = cross_validate(dt, train_input, train_target, cv=splitter)
print(f"최종 정확도 : {np.mean(scores['test_score'])*100:.2f}%")

하이퍼파라미터 튜닝
- 머신러닝 모델이 학습하는 파라미터 : 모델 파라미터
- 모델이 학습할 수 없어서 사용자가 지정해야하는 파라미터 : 하이퍼파라미터
- 하이퍼파라미터는 모두 클래스나 메서드의 매개변수로 표현된다.
- 사람의 개입 없이 하이퍼파라미터 튜닝을 자동으로 수행하는 기술을 "AutoML"이라고 한다.
- 하이퍼파라미터 튜닝 과정
- 라이브러리가 제공하는 기본값을 그대로 사용해 모델을 훈련한다.
- 검증 세트의 점수나 교차 검증을 통해 매개변수를 조금씩 바꿔본다.
- 결정 트리에서 하이퍼파라미터 튜닝
- max_depth를 최적의 값으로 고정하고 min_samples_split을 바꿔가며 최적의 값을 찾는다.
- 불행히도 max_depth의 최적값은 min_samples_split 매개변수의 값이 바뀌면 함께 달라진다.
- 두 개의 매개변수를 동시에 바꿔가며 최적의 값을 찾아야 한다.
- 이 경우 매개변수의 수가 많아질 경우 문제가 더 복잡해 진다.
- 위의 문제를 해결하기 위해선 사이킷런에서 제공하는 그리드 서치(Grid Search)를 사용하면 된다.
- GridSearchCV 클래스를 사용하면 하이퍼파라미터 탐색과 교차 검증을 한 번에 수행할 수 있다.
- cross_validate() 함수를 따로 호출할 필요 없다.
- 사용방법
- 먼저 탐색할 매개변수와 탐색할 값의 리스트를 딕셔너리로 만든다.
- GridSearchCV에 매개변수로 탐색대상 모델과 위에서 만든 딕셔너리를 전달한다.
- n_jobs 매개변수는 병렬 실행에 사용할 CPU 코어 수를 지정할 수 있다.
- 기본값은 1, -1로 지정하면 시스템에 있는 모든 코어를 사용한다.
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 클래스로 만들어진 객체의 best_estimator_ 속성에 저장되어 있다.
- 최적의 매개변수는 best_params_ 속성에 저장되어 있다.
dt = gs.best_estimator_
print(f"그리드 서치를 통해 찾은 최적의 모델의 정확도 : {dt.score(train_input, train_target)*100:.2f}%")

print(f"최적의 매개변수 : {gs.best_params_}")

- 0.0001이 가장 좋은 값으로 선택되었다.
- 각 매개변수에서 수행한 교차 검증의 평균 점수는 cv_results_ 속성의 'mean_test_score'키에 저장되어 있다.
- 5번의 교차 검증으로 얻은 점수를 출력해보자.
print(gs.cv_results_['mean_test_score'])

- 이렇게 찾은 값들 중 가장큰 값의 인덱스를 찾는다.
- Numpy의 argmax() 함수를 사용하면 쉽게 찾을 수 있다.
- 이 인덱스를 사용해 params 키에 저장된 매개변수를 출력할 수 있다.
- 이렇게 나온 매개변수가 최상의 검증 점수를 만든 매개변수 조합이다.
- 앞에서 출력한 gs.best_params_와 동일한지 비교해보자.
best_index = np.argmax(gs.cv_results_['mean_test_score'])
print(f"최적의 매개변수 : {gs.cv_results_['params'][best_index]}")
print(f"최적의 매개변수(best_params_) : {gs.best_params_}")

- 동일한 것을 확인할 수 있다.
- 앞에서 했던 과정을 정리해보자
- 먼저 탐색할 매개변수를 지정한다.
- 훈련 세트에서 그리드 서치를 수행한다.
- 그리드 서치를 통해 나오는 최상의 평균 검증 점수가 나오는 매개변수 조합을 찾는다.(이 조합은 그리드 서치 객체에 저장)
- 그리드 서치는 최상의 매개변수에서 (교차 검증에 사용한 훈련 세트 아님) 전체 훈련 세트를 사용해 최종 모델을 훈련한다.(이 모델도 그리드 서치 객체에 저장된다.)
- GridSearchCV를 사용하니까 매개변수를 일일이 바꿔가며 교차 검증을 수행하지 않고 원하는 매개변수 값을 나열하면 자동으로 교차 검증을 통해 최상의 매개변수를 찾아준다.
- 하지만 탐색할 매개변수의 간격을 미리 알아서 정해야하는 불편함이 있다.
랜덤서치(Random Search)
- 매개변수의 값이 수치일 때 값의 범위나 간격을 미리 정하기 어려울 수 있다.
- 너무 많은 변수 조건이 있어 그리드 서치 수행 시간이 오래 걸릴 수 있다.
- 이러한 문제는 랜덤 서치(Random Search)를 사용하여 해결하면 된다.
- 매개변수를 샘플링할 수 있는 확률 분포 객체를 전달한다.
확률 분포 객체 만들기
- Scipy의 stats 서브 패키지에 있는 uniform과 randint 클래스는 모두 주어진 범위에서 고르게 값을 뽑는다.
- 이를 "균등 분포에서 샘플링 한다."라고 말한다.
- randint : 정숫값을 뽑는다.
- uniform : 실수값을 뽑는다.
from scipy.stats import uniform, randint
rgen = randint(0, 10)
rg = np.unique(rgen.rvs(1000), return_counts=True)
df = pd.DataFrame(rg[1].reshape(-1,10),columns = rg[0])
print(df)

- 0 ~ 9까지의 숫자가 고르게 추출 되었다.
ugen = uniform(0, 1)
print(ugen.rvs(10))

- 이제 이 두함수를 가지고 탐색할 매개변수의 딕셔너리를 만들어 보곘다.
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),
}
- min_impurity_decresase : 0.0001 ~ 0.001 사이의 실수
- max_depth : 20 ~ 50 사이의 정수
- min_samples_split : 2 ~ 25 사이의 정수
- min_samples_leaf : 1~ 25 사이의 정수
랜덤서치 하기
- RandomizedSearchCV 클래스로 랜덤 서치를 진행한다.
- n_iter 매개변수로 샘플링 횟수를 설정한다.
- 교차 검증할 모델, 매개변수 딕셔너리, 사용할 CPU 코어 개수, 데이터를 섞을 때 사용할 시드값을 매개변수로 전달한다.
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(f"최적의 매개변수 조합 : {gs.best_params_}")

- 최고의 교차 검증 점수를 출력한다.
print(f"최고의 교차검증 점수 : {np.max(gs.cv_results_['mean_test_score']) * 100:.2f}%")

- 최적의 모델은 best_estimator_에 저장되어 있다.
- 이 모델로 테스트 세트의 성능을 확인해 본다.
dt = gs.best_estimator_
print(f"최종 테스트 세트에 대한 점수 : {dt.score(test_input, test_target) * 100 : .2f}%")

- 테스트 세트의 점수는 검증 세트에 대한 점수보다 조금 작은 것이 일반적이다.
테스트 세트의 점수가 만족스럽지는 않지만 충분히 다양한 매개변수를 테스트해서 얻은 결과임을 말할 수 있을 것 같다.
Pupbani는 이제 앞으로 수동으로 매개변수를 바꾸는 것이 아니라 그리드 서치나 랜덤 서치를 사용하기로 했다.
728x90
반응형
'AI > 기계학습(Machine Learning)' 카테고리의 다른 글
[기계학습/ML]13. 비지도학습 - 군집(Clustering) (0) | 2022.12.05 |
---|---|
[기계학습/ML]12. 앙상블 학습 - 랜덤 포레스트, 엑스트라 트리, 그레이디언트 부스팅 (0) | 2022.10.24 |
[기계학습/ML]10. 트리알고리즘 - 결정트리 (0) | 2022.10.23 |
[기계학습/ML]9. 확률적 경사 하강법 - 손실 함수, 에포크 (0) | 2022.10.23 |
[기계학습/ML]8. 회귀 알고리즘(3) - 로지스틱 회귀 (0) | 2022.10.23 |