혼공머신 - 7

Apic·2025년 8월 4일
0

검증 세트

테스트 세트를 사용하지 않으면 일반화 성능을 올바르게 예측할 수 없다.
이것을 해결하기 위해 훈련 세트를 80 : 20으로 훈련, 테스트로 나누고 그 80을 다시 80 : 20으로 훈련, 검증 세트로 나눈다.

그러면 최종적으로 훈련, 검증, 테스트가 60 : 20 : 20으로 나뉘게 된다.

import pandas as pd
from sklearn.model_selection import train_test_split

wine = pd.read_csv('https://bit.ly/wine_csv_data')

data = wine[['alcohol', 'sugar', 'pH']]
target = wine['class']

# train 80 : test 20
train_input, test_input, train_target, test_target = train_test_split(data, target, test_size=0.2, random_state=42)

# train 80 : val 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(dt.score(sub_input, sub_target))
print(dt.score(val_input, val_target))

이렇게 원래는 학습 데이터를 학습시키고 훈련 점수, 테스트 점수를 확인하지만 훈련 점수, 검증 점수를 보게 된다.

교차 검증

훈련 세트를 검증 세트로 나눠서 데이터가 줄어들었다.
데이터가 적기 때문에 학습을 잘 못하기 때문에 이때 교차 검증을 사용한다.

교차 검증은 검증 세트에서 일부분 때서 평가하고, 이 과정을 여러번 반복한다.

이 교차 검증을 원하는 수 만큼 할 수 있고 이것을 K-폴드 교차 검증이라고 한다.

from sklearn.model_selection import cross_validate

scores = cross_validate(dt, train_input, train_target)
print(scores)
# {'fit_time': array([0.0040555 , 0.00372982, 0.00385141, 0.00396609, 0.00490022]), 'score_time': array([0.0008688 , 0.00079441, 0.00080442, 0.00135827, 0.00124073]), 'test_score': array([0.86923077, 0.84615385, 0.87680462, 0.84889317, 0.83541867])}
  • fit_time: 훈련 시간
  • score_time: 검증 시간
  • test_score: 검증 점수
import numpy as np

print(np.mean(scores['test_score']))
# 0.855300214703487

이렇게 하면 검증 점수의 평균을 얻을 수 있다.

from sklearn.model_selection import StratifiedKFold

splitter = StratifiedKFold(
    n_splits=10,    # K개
    shuffle=True,   # 섞음
    random_state=42
)
scores = cross_validate(dt, train_input, train_target, cv=splitter)
print(np.mean(scores['test_score']))

하이퍼파라미터 튜닝

모델을 학습시킬 때 모델이 학습할 수 없어서 사용자가 지정해야만 하는 파라미터를 하이퍼파라미터라고 한다.
머신러닝 라이브러리를 사용할 때의 하이퍼파라미터는 모두 클래스나 메서드의 매개변수로 표현된다.

하지만 만약 결정 트리 모델에서 max_depthmin_samples_split을 변경할 수 있는데, 만약 모델을 여러번 학습을 시도해서 최적의 max_depth를 찾았을 때 그 값을 고정하고 min_samples_split의 최적의 값을 찾으려고 한다면, min_samples_split을 변경하는 순간 최적의 max_depth도 변경하게 된다.

그래서 여러 매개변수의 최적의 값을 쉽게 찾기 우해서 그리드 서치를 사용한다.

from sklearn.model_selection import GridSearchCV

# min_inpurity_decrease의 최적값을 찾아보기 위해 여러 값 시도
params = {
    'min_impurity_decrease': [0.0001, 0.0002, 0.0003, 0.0004, 0.0005]
}
gs = GridSearchCV(
    DecisionTreeClassifier(random_state=42), 
    params, 
    n_jobs=-1   # CPU 코어 수
)
gs.fit(train_input, train_target)

dt = gs.best_estimator_
print(dt.score(train_input, train_target))      # 0.9615162593804117
print(gs.best_estimator_)                       # min_impurity_decrease=0.0001
print(gs.cv_results_['mean_test_score'])        # [0.86819297 0.86453617 0.86492226 0.86780891 0.86761605]
print(gs.cv_results_['params'][gs.best_index_]) # {'min_impurity_decrease': 0.0001}

이렇게 수동으로 고르지 않고 자동으로 고를 수 있다.

  1. 탐색할 매개변수를 지정한다. 여기서는 min_impurity_decrease
  2. 그리드 서치를 통해 최고의 평균 점수가 나오는 매개변수 조합을 찾고 그리드 서치 객체에 저장한다.
  3. 최상의 매개변수에서 전체 훈련 세트를 사용해 모델을 훈련한다. 이 모델도 그리드 서치 객체에 저장된다.
from sklearn.model_selection import GridSearchCV
import numpy as np

params = {
    'min_impurity_decrease': np.arange(0.0001, 0.001, 0.0001),  # 9개
    'max_depth': range(5, 20, 1),                               # 15개
    'min_samples_split': range(2, 100, 10)                      # 10개
}   # 총 9 * 15 * 10 = 1350개 * 5(K-Fold 기본 값) = 6750
gs = GridSearchCV(
    DecisionTreeClassifier(random_state=42), 
    params, 
    n_jobs=-1   # CPU 코어 수
)
gs.fit(train_input, train_target)

dt = gs.best_estimator_
print(dt.score(train_input, train_target))       # 0.892053107562055
print(gs.best_params_)                           # {'max_depth': 14, 'min_impurity_decrease': np.float64(0.0004), 'min_samples_split': 12}
print(np.max(gs.cv_results_['mean_test_score'])) # 0.8683865773302731

랜덤 서치

매개변수의 범위를 너무 크게 잡으면 학습이 굉장히 오래 걸린다. 이럴때 랜덤 서치를 사용한다.

랜덤 서치에서는 매개변수 값의 목록을 전달하는 것이 아닌, 확률 분포 객체를 전달한다.

from scipy.stats import uniform, randint

rgen = randint(0, 10)
print(rgen.rvs(10)) # [1 8 9 3 1 1 3 3 7 2]
print(np.unique(rgen.rvs(1000), return_counts=True))
# (array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
# array([105,  86, 103, 109,  92,  91,  98, 118, 109,  89]))

ugen = uniform(0, 1)
print(ugen.rvs(10))
# [0.99885329 0.38056558 0.47262844 0.55087183 0.03469407 0.51688101 0.45908212 0.54994372 0.73262376 0.89946438]
from sklearn.model_selection import RandomizedSearchCV

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)
}

rs = RandomizedSearchCV(
    DecisionTreeClassifier(random_state=42),
    params,
    n_iter=100,
    n_jobs=-1,
    random_state=42
)
rs.fit(train_input, train_target)
print(rs.best_params_)  # {'max_depth': 39, 'min_impurity_decrease': np.float64(0.00034102546602601173), 'min_samples_leaf': 7, 'min_samples_split': 13}
print(np.max(rs.cv_results_['mean_test_score']))    # 0.8695428296438884

dt = rs.best_estimator_
print(dt.score(test_input, test_target)) # 0.86
profile
코딩 공부하는 사람

0개의 댓글