여태까지 배웠던 최근접 이웃, 로지스틱 회귀, SGD, 트리 등 다양한 머신러닝 모델들이 있었지만,
모든 모델들은 결국 실전에 투입시키기 위해 사용한다. 따라서 성능을 높이기 위해 반복적으로 테스트 세트를 활용하여 점수를 높여가야하는데, 이는 목적과 다르게 모델이 테스트 세트에 더 적합해지는 불상사가 생길 수 있다.
이를 해결하기 위해 검증 세트를 활용해보자.
검증 세트 (validation set)
테스트 세트를 마지막에 한 번 사용하기위해 훈련 세트를 또 나누어서 검증세트를 만들고 이 세트를 테스트 세트와 똑같이 사용할 수 있다.
import pandas as pd
wine = pd.read_csv("https://bit.ly/wine_csv_data")
# class 열을 타겟으로 설정하고 나머지 열은 특성으로 저장 ( .to_numpy() )
data = wine[["alcohol","sugar","pH"]].to_numpy()
target = wine["class"].to_numpy()
# 이제 훈련 테스트 세트 나누기
from sklearn.model_selection import test_train_split
train_input , test_input, train_target, test_target = test_train_split(data,target,test_size=0.2, random_state=42)
# 검증 세트 만들기
sub_input,val_input, sub_target,val_target = test_train_split(train_input,train_target,test_size=0.2, random_state=42)
print(sub_input.shape, val_input.shape)
(4157, 3) (1040, 3) # <최종 훈련세트수 4157개, 검증세트 수 1040개>
이제 평가해보자.
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier( random_state=42 )
df.fit(sub_input, sub_target)
print(dt.score(sub_input,sub_target))
print(dt.score(val_input,val_target))
0.9971133028626413
0.864423076923077
교차 검증
검증세트를 배워 보았는데, 확실히 성능 향상에 좋은 모델을 만들 수 있을것 같다!
하지만 훈련에 많은 데이터를 활용할수록 좋은데, 검증세트로 인해 훈련세트가 적어지는 단점이 있다.
그렇다고 검증세트를 적게하면 그만큼 점수가 불안정하다.
이럴 때 교차검증을 이용하면 안정적인 검증 점수를 얻고 훈련에 더 많은 데이터를 사용할 수 있다.
교차검증은 검증 세트를 떼어 내어 평가하는 과정을 여러 번 반복한다. 그 다음 이 점수를 평균하여 최종 검증 점수를 얻는다. 위 그림과 같이 3개로 나누는것을 3-폴드 교차 검증이라 한다.
보통 실전에서는 5-폴드나 10-폴드 교차검증을 주로 사용한다. 이렇게 하면 데이터의 8~90%까지 훈련에 사용할 수 있다.
사이킷런의 cross_validate() 교차 검증 함수를 사용하여 코드를 실행해보자.
from sklearn.model_selection import cross_validate
scores = cross_validate(dt, train_input, train_target)
print(scores)
{'fit_time': array([0.01978898, 0.01216435, 0.01753545, 0.01066256, 0.03117847]), 'score_time': array([0.00559211, 0.00121975, 0.00127912, 0.00102139, 0.00114012]), 'test_score': array([0.86923077, 0.84615385, 0.87680462, 0.84889317, 0.83541867])}
cross_validate() 는 fit_time , score_time, test_score 각각의 키를 가진 딕셔너리를 반환한다.
fit_time: 모델을 훈련하는 시간
score_time: 모델을 검증하는 시간
test_score: 교차 검증 폴드의 점수들
교차 검증의 최종점수는 교차 검증 폴드의 점수들 (test_score) 의 평균이다.
print(np.mean(scores['test_score']))
0.855300214703487
※ 교차 검증은 데이터를 섞을 필요가 없다. ※
※ 교차 검증을 수행하면 모델에서 얻을 수 있는 최상의 검증 점수를 얻을 수 있다. ※
훈련 세트를 섞으려면 분할기를 지정해야 한다.
cross_validate()에서 사용하는 분할기- 회귀일 때는 KFold / 분류일 때는 StrarifiedKFold
여기에서는 분류이므로
from sklearn.model_selection import StratifiedKFold
scores = cross_validate(dt, train_input, train_target, cv=StratifiedKFold())
print(np.mean(scores['test_score']))
0.855300214703487
위 두개는 결과가 같다 == cross_validate 할 때는 따로 섞지 않아도 된다!
만약 훈련 세트를 섞고 10-폴드 교차 검증을 하고 싶으면
from sklearn.model_selection import StratifiedKFold
scores = cross_validate(dt, train_input, train_target, cv=splitter)
print(np.mean(scores['test_score']))
0.8574181117533719
하이퍼파라미터 튜닝
모델파라미터 : 모델이 학습하는 파라미터
하이퍼파라미터 : 모델이 학습할 수 없어서 사용자가 지정해야 하는 파라미터
사이킷런과 같은 머신러닝 라이브러리를 사용할 때 하이퍼파라미터는 모두 클래스/메소드의 매개변수로 표현됨
< 하이퍼파라미터 : 머신러닝 클래스/함수의 파라미터(변수)>
머신러닝 모델에서 최적의 파라미터를 찾는다고 가정해보자. DecisionTreeClassifier을 예를들면,
최적의 파라미터들을 찾는다고 할 때, 먼저 최적의 max_depth를 찾고, 고정한 후 최적의 min_samples_split를 찾고.. 이런 방법으로 하이퍼파라미터 튜닝을 할 수 있을까?
불행하게도 이런식으로 찾을 수 없다. max_depth의 최적값은 min_samples_split 변수의 값이 바뀌면 따라 바뀐다.즉, 변수들을 동시에 바꿔가며 최적의 값을 찾아야 한다. ( 변수의 값이 많을 수록 매우 복잡해진다.)
이렇게 동시에 변수들의 값을 바꿔가며 최적의 값을 찾아주는 기능이 있다.
그리드 서치 (Grid Search) (하이퍼파라미터 튜닝)
그리드 서치는 위에 설명한 최적값을 교차 검증을 통해 찾아내 준다!!
따라서 cross_validate()를 따로 호출할 필요가 없다.
from sklearn.model_selection import GridSearchCV
params = {'min_impurity_decrease' : [0.0001, 0.0002, 0.0003, 0.0004, 0.0005]}
#그리드서치는 클래스에 탐색 대상 모델과 params 변수(딕셔너리로)를 전달하여 실행
gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params , n_jobs= -1)
# n_jobs = 사용할 CPU 코어 수 { -1은 제한없이 최대로 }
gs.fit(train_input,train_target) (그리드 서치를 통해 25개의 모델을 훈련시킨다)
dt = gs.best_estimator_ # 24개 모델 중 최고의 모델! 이 모델로 학습시키면된다 (dt)
print( dt.score(train_input, train_target))
0.9615162593804117
위 코드를 실행하면 그리드 서치는
prarams 변수 (5개)를 바꿔가며 5번(default=5) 실행한다. 따라서 총 25가지의 경우의 수가 나온다.
그 중 최적의 하이퍼파라미터를 찾으면 best_estimator_를 통해서 그 모델로 학습시키면 될 것 같다.
또한 최적의 매개변수는 best_params_에 저장되어 있다.
# best_ estimator_
# best_params_
print(dt.best_params_)
{'min_impurity_decrease': 0.0001}
여기서는 0.0001 ( 첫 번째 변수) 변수가 최적이다.
각 매개변수에서 수행한 교차 검증의 평균 점수또한 cv_results_속성의 mean_test_score에 저장되어 있다.
a = gs.cv_results_['mean_test_score']
print(a)
[0.86819297 0.86453617 0.86492226 0.86780891 0.86761605]
# gs.cv_results_['mean_test_score'] : 평균 점수 나열
변수 개수가 많거나 찾기 힘들때는
np.argmax() 메소드를 통해 가장 큰 값의 인덱스를 추출할 수 있다.
best_index = np.index[np.argmax(gs.cv_results_['mean_test_score']] # 인덱스 번호 반환
print(gs.cv_results_['params'][best_index]) # 키 값을 통해 params value 출력
{'min_impurity_decrease': 0.0001}
정리하자면
- 탐색할 매개변수를 지정 ex) max_depth, min_impurity_decrease
- 훈련 세트에서 그리드 서치를 하여 최상의 평균 검증 점수가 나오는 매개변수 조합을 찾음.
- 최상의 매개변수에서 전체 훈련 세트를 사용해 최종 모델을 훈련
더 복잡하게 해보자. <<min_impurity_decrease = 노드를 분할하기 위한 불순도 감소 최소량을 지정>>
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)
}
# 총 경우의 수 (?) = 9 x 15 x 10 x 5(default =5) = 6,750 개
최적의 매개변수 조합을 확인해보자.
gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_hobs = -1)
gs.fit(train_input, train_target)
print(gs.best_estimator_)
DecisionTreeClassifier(max_depth=14, min_impurity_decrease=0.0004,
min_samples_split=12, random_state=42)
max_depth= 14, min_impurity=0.0004, min_samples_split= 12 일 때가 최적이다!
print(np.max(gs.cv_results_['mean_test_score']))
0.8683865773302731
랜덤 서치
위 코딩처럼 매개 변수처럼 범위나 간격을 일일히 설정해주기 힘들 수 있다. 변수가 많거나 할 수 없는 환경일 때 !
따라서 랜덤 서치를 이용할 수 있다. by using 싸이파이
랜덤 서치에는 매개변수 값의 목록이 아닌 매개변수를 샘플링할 수 있는 확률 분포 객체를 전달한다.
싸이파이에서 2개의 확률 분포 클래스를 임포트해보자.
from scipy.stats import uniform, randint
randint = 정수 / uniform = 실수
scipy.stats 에 있는 uniform과 randint 클래스는 모두 주어진 범위에서 고르게 값을 뽑는데, 이를
"균등 분포에서 샘플링한다"라고 한다.
rgen = randint(0,10)
rgen.rvs(10)
array([6, 9, 4, 9, 5, 1, 0, 3, 6, 1])
데이터 10개 중에서 10개를 뽑아서 균일하게 뽑혔는지 잘 모른다.
rgen = randint(0,10)
a = rgen.rvs(1000)
np.unique(a, return_counts = True)
(array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
array([ 98, 107, 107, 93, 88, 95, 98, 102, 106, 106]))
대략적으로 골고루 뽑히는것을 확인할 수 있다.
일종의 난수 발생기와 유사하다고 볼 수 있다.
이제 균등 분포에서 샘플링을 해보자!
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 = 에 지정 가능
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}
위 params에서 정의된 매개변수 범위에서 총 100번을 샘플링하여 교차 검증을 수행하고 최적의 매개변수 조합을 찾는다.
앞서 그리드 서치보다 훨씬 교차 검증 수를 줄이면서 넓은 영역을 효과적으로 탐색할 수 있다.
최고의 교차 검증 점수도 확인해보자.
print(np.max(gs.cv_results_['mean_test_score']))
0.8695428296438884
최적의 모델은 이미 전체 훈련세트로 훈련되어 best_estimator_ 속성에 저장되어 있다.
이 모델을 최종 모델로 결정하고 이제 테스트 세트로 성능을 확인해보자!!!!
dt = gs.best_estimator_
print(dt.score(test_input,test_target))
0.86
'머신러닝 & 딥러닝 기초 > 5장 - 트리 알고리즘' 카테고리의 다른 글
5 - 3 : 트리의 앙상블 (0) | 2023.01.17 |
---|---|
5 - 1 결정 트리 (1) | 2023.01.09 |