결정트리
스무고개와 같다고 생각하면 쉽다.
데이터를 잘 나눌 수 있는 질문을 찾는다고 했을 때 계속 질문을 추가해서 분류 정확도를 높일 수 있다. YES일때는 왼쪽, NO 일때는 오른쪽으로 이동하는 사다리라고 봐도 된다.
여기서 질문에 해당하는 부분이 노드이다. 노드는 훈련 데이터의 특성에 대한 테스트를 표현한다. 맨위의 노드를 루트 노드 맨 아래 끝에 달린 노드를 리프 노드라고 한다.
결정트리에서의 예측하는 방법은 매우 간단하다. 리프 노드에서 가장 많은 클래스가 에측 클래스가 된다. K-최근접 이웃과 매우 비슷하다.
결정트리는 표준화 전처리 과정이 필요 없으며 어떤 특성이 가장 유용한지 나타내는 특성 중요도를 계산해준다.
import pandas as pd
wine = pd.read_csv('https://bit.ly/wine_csv_data')
wine.head()
wine.info() # 데이터 프레임의 각 열의 데이터 타입과 누락된 데이터가 있는지 확인하는데 유용
wine.describe() # 열에 대한 간략한 통계를 출력
지니 불순도(Gini impurity)
gini 즉 지니 불순도는 DecicionTreeClassifier 클래스의 criterion매개변수의 기본값을 말한다. criterion매개변수의 용도는 노드에서 데이터를 분할할 기준을 정하는 것이다. 계산 방법은 다음과 같다.
\[ Gini = 1 - (p_{\text{negative}}^2 + p_{\text{positive}}^2) \]
정보 이득
부모와 자식 노드 사이의 불순도 차이를 정보 이득이라고 하는데 다음의 수식을 통해서 구할 수 있다.
\begin{equation}
\text{Information Gain} = \text{Impurity}_{\text{parent}} - \left( \frac{N_{\text{left}}}{N_{\text{parent}}} \times \text{Impurity}_{\text{left}} \right) - \left( \frac{N_{\text{right}}}{N_{\text{parent}}} \times \text{Impurity}_{\text{right}} \right)
\end{equation}
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)
print(train_input.shape, test_input.shape)
#결과 (5197, 3) (1300, 3)
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target)) #결과 0.7808350971714451
print(lr.score(test_scaled, test_target)) #결과 0.7776923076923077
print(lr.coef_, lr.intercept_) #결과 [[ 0.51270274 1.6733911 -0.68767781]] [1.81777902]
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(random_state=42)
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target)) #결과 0.996921300750433
print(dt.score(test_scaled, test_target)) #결과 0.996921300750433
import matplotlib.pyplot as plt
from sklearn.tree import plot_tree
#전체 트리 모양 출력
plt.figure(figsize=(10,7))
plot_tree(dt) #plot_Tree()는 사이킷런에서 지원하는 결정 트리를 이해하기 쉬운 그림으로 출력하게 해주는 메서드
plt.show()
#제한적인 부분 출력
plt.figure(figsize=(10,7))
plot_tree(dt, max_depth=1, filled=True, feature_names=['alcohol', 'sugar', 'pH'])
plt.show()
print(dt.feature_importances_) #결과 [0.12345626 0.86862934 0.0079144 ]
가지치기
결정트리의 성장을 제한하는 방법이다.
결정 트리에서 가지치기를 하는 가장 간단한 방법은 자라날 수 있는 트리의 최대 깊이를 지정하는 것이다.
dt = DecisionTreeClassifier(max_depth=3, random_state=42)
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target)) #결과 0.8454877814123533
print(dt.score(test_scaled, test_target)) #결과 0.8415384615384616
#1번
plt.figure(figsize=(20,15))
plot_tree(dt, filled=True, feature_names=['alcohol', 'sugar', 'pH'])
plt.show()
#2번 특성값을 표준점수로 바꾸지 않은 경우
dt = DecisionTreeClassifier(max_depth=3, random_state=42)
dt.fit(train_input, train_target)
print(dt.score(train_scaled, train_target)) #결과 0.8454877814123533
print(dt.score(test_scaled, test_target)) #결과 0.8415384615384616
plt.figure(figsize=(20,15))
plot_tree(dt, filled=True, feature_names=['alcohol', 'sugar', 'pH'])
plt.show()
검증세트
테스트 세트를 사용하지 않고 이를 측정하는 방법은 훈련 세트를 또 나누는 것이다. 이 데이터를 검증 세트 or 검증 DATA라고 한다. 일반적으로 훈련60% 검증 20% 테스트 10% 비율로 나눈다.
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)
검증 세트를 너무 조금 떼어 놓은 경우 Validation Score가 들쭉날쭉하고 불안정할 것이다.
이럴 때 안정적인 Validation Score를 얻고 훈련에 더 많은 데이터를 사용할 수 있다.
교차 검증은 검증 세트를 떼어 내어 평가하는 과정을 여러 번 반복한다. 그 다음 이 점수를 평균하여 최종 점수를 얻는다.
K- Fold Cross Validation
훈련 세트를 몇 부분으로 나누냐에 따라 다르게 부른다.
사이킷런에는 cross_valiate()라는 교차 검증 함수가 있다. 이 함수는 fit_time, score_time, test_score 키를 가진 딕셔너리를 반환한다.
cross_val_score()함수는 cross_validate()함수의 결과 중에서 test_score값만 반환한다.
교차 검증을 할때 훈련 세트를 섞으려면 분할기를 지정해야한다. 분할기는 교차 검증에서 폴드를 어떻게 나눌지 결정하는데 cross_validate()함수는 기본적으로 회귀 몯ㄹ일 경우 KFold 분할기를 사용하고 분류 모델일 경우 타깃 클래스를 골고루 나누기 위해 StrtifiedKFold를 사용한다.
from sklearn.model_selection import cross_validate
scores = cross_validate(dt, train_input, train_target)
print(scores)
#결과 {'fit_time': array([0.01211047, 0.01176715, 0.01241946, 0.01211166, 0.01173854]), 'score_time': array([0.00179029, 0.00211239, 0.00214624, 0.00200915, 0.00204825]), 'test_score': array([0.86923077, 0.84615385, 0.87680462, 0.84889317, 0.83541867])}
import numpy as np
print(np.mean(scores['test_score'])) #결과 0.855300214703487
from sklearn.model_selection import StratifiedKFold
scores = cross_validate(dt, train_input, train_target, cv=StratifiedKFold())
print(np.mean(scores['test_score'])) #결과 0.855300214703487
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
하이퍼파라미터 튜닝
모델이 학습할 수 없어서 사용자가 지정해야하만 하는 파라미터를 하이퍼파라미터라고 한다.
머신러닝 모델이 학습하는 파라미터는 모델 파라미터라고 한다.
ML 라이브러리를 사용할 때 이런 하이퍼파라미터는 모두 클래스나 메서드의 매개변수로 표현된다.
그리드 서치
하이퍼 파라미터 탐색과 교차 검증을 한번에 수행해준다.
사이킷런에서 제공하며 별도의 cross_validate() 함수를 호출할 필요가 없다.
cv 매개변수는 default = 5 -> 5-폴드 교차 검증 수행한다.
n_jobs은 병렬 실행에 사용할 CPU 코어 수를 지정, default = 1, -1로 지정하면 시스템에 있는 모든 코어를 사용한다.
best_estimator_은 검증 점수 가장 높은 모델의 매개변수 조합으로 전체 훈련 세트에서 자동으로 다시 모델 훈련한다.
정리하면 다음과 같다.
1. 먼저 탐색할 매개 변수를 지정한다.
2. 그 다음 훈련 세트에서 그리드 서치를 수행하여 최상의 평균 검증 점수가 나오는 매개변수 조합을 찾는다. 이 조합은 그리드 서치 객체에 저장된다.
3. 그리드 서치는 최상의 매개변수에서 (교차 검증에 사용한 훈련 세트가 아니라) 전체 훈련 세트ㅇ를 사용해 최종 모델을 훈련한다. 이 모델도 그리드 서치 객체에 저장된다.
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)
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]
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_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)
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
랜덤 서치
매개 변수의 값이 수치일 때 값의 범위나 간격을 미리 정하기 어려울 수 있다. 또한 너무 많은 매개 변수 조건이 있어 그리드 서치 수행시간이 오래 걸리 수 도 있다. 이럴 때 랜덤 서치를 사용하면 좋다.
랜덤서치에는 매개변수 값의 목록을 전달하는 것이 아니라 매개변수를 샘플링할 수 잇는 확률 분포 객체를 전달한다.
※ scipy란? 파이썬의 핵심 과학 라이브러리 중 하나이다. 적분, 보간, 선형대수, 확률 등을 포함한 수치 계산 전용 라이브러리이다. 싸이파이의 stats 서브 패키지에 있는 uniform과 randint클래스는 모두 주어진 범위에서 고르게 값을 뽑아준다. 즉 '균등 분포에서 샘플링한다'라고 한다. randint는 정숫값을 뽑고, uniform은 실숫값을 뽑는다.
RandomizedSearchCV는 교차 검증으로 랜덤한 하이퍼파라미터 탐색을 수행한다. 최상의 모델을 찾은 후 훈련 세트 전체를 사용해 최종 모델을 훈련한다.
from scipy.stats import uniform, randint
rgen = randint(0, 10)
rgen.rvs(10) #결과 array([2, 5, 4, 5, 1, 2, 3, 3, 1, 8])
np.unique(rgen.rvs(1000), return_counts=True)
#결과 (array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
# array([109, 100, 89, 104, 92, 102, 96, 94, 104, 110]))
ugen = uniform(0, 1)
ugen.rvs(10)
#결과 array([0.63683752, 0.50535298, 0.88464242, 0.71355432, 0.3611304 ,
# 0.17089516, 0.49779516, 0.7833665 , 0.38212916, 0.01930431])
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
정형 데이터 & 비정형 데이터
정형 데이터는 어떤 구조로 되어 있는 데이터를 말한다 CSV나 DB 엑셀에 저장하기 쉽다.
비정형 데이터는 DB나 엑셀로 표현하기 어려운 것들을 말한다. NoSQL DB는 엑셀이나 CSV에 담기 어려운 텍스트나 JSON 데이터를 저장하는데 용이하다.
앙상블 학습
결정 트리를 기반으로 만들어져 있으며 정형 데이터를 다루는데 가장 뛰어난 상과를 내는 알고리즘을 앙상블 학습이라고 한다.
랜덤 포레스트
랜덤 포레스트는 앙상블 학습의 대표 주자 중 하나로 안정적인 성능 덕분에 널리 사용되며 결정 트리를 랜덤하게 만들어 결정트르의 숲을 만들고 각 결정트리의 예측을 사용해 최종 예측을 만든다.
부트스트랩
데이터 세트에서 중복을 허용하여 데이터를 샘플리아하는 방식을 의미한다. 부트스트랩 샘플이란 부트스트랩 방식으로 샘플링하여 분류한 데이터를 의미한다. 랜덤포레스트의 RandomForestClassifier는 기본적으로 전체 특성 개수의 제곱근만큼의 특성을 선택하며(RamdomForestRegressor는 전체 특성 사용) DecisionTreeClassifer가 제공하는 중요한 매개변수를 모두 제공하며 특성 중요도를 계산해준다. return_train_score 매개변수를 True로 지정하면 Validation Score뿐만 아니라 훈련세트에 대한 점수도 같이 반환해준다.
부트스르랩 샘플에 포함되지 않고 남는 샘플을 OOB(Out Of Bag) 샘플이라고 하며 이를 이용해 부트스트랩 샘플로 훈련한 결정 트리를 평가할 수 있다. 이를 얻기 위해서는 RandomForestClassifier클래스의 oob_score 매개변수를 True로 저장해야한다.
import numpy as np
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']].to_numpy()
target = wine['class'].to_numpy()
train_input, test_input, train_target, test_target = train_test_split(data, target, test_size=0.2, random_state=42)
from sklearn.model_selection import cross_validate
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(n_jobs=-1, random_state=42)
scores = cross_validate(rf, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
#결과 0.9973541965122431 0.8905151032797809
rf.fit(train_input, train_target)
print(rf.feature_importances_) #결과 [0.23167441 0.50039841 0.26792718]
rf = RandomForestClassifier(oob_score=True, n_jobs=-1, random_state=42)
rf.fit(train_input, train_target)
print(rf.oob_score_) #결과 0.8934000384837406
엑스트라 트리
랜덤 포레스트와 매우 비슷하게 동작하며 기본적으로 100개의 결정 트리를 훈련한다. 랜덤 포렛트와 동일하게 결정트리가 제공하는 대두분의 매개변수를 지원하며 전체 특성 중에 일부 특성을 랜덤하게 선택하여 노드를 분할하는데 사용한다.
랜덤 포레스트와 엑스트라 트리의 차이점은 부트스트랩 샘플을 사용하지 않는다는 점이다. 각 결정 트리를 ㅁ나들 때 전체 훈련 세트를 사용하며 대신 노드를 분할 할 때 가장 좋은 분할을 찾는 것이 아닌 무작위로 분할한다.
from sklearn.ensemble import ExtraTreesClassifier
et = ExtraTreesClassifier(n_jobs=-1, random_state=42)
scores = cross_validate(et, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
#결과 0.9974503966084433 0.8887848893166506
et.fit(train_input, train_target)
print(et.feature_importances_)
#결과 [0.20183568 0.52242907 0.27573525]
그레이디언트 부스팅
깊이가 얕은 결정 트리를 사용하여 이전 트리의 오차를 보완하는 방식으로 앙상블하는 방버이다. 깊이가 얕은 결정트리를 사용하면 과대 적합에 강하고 일반적으로 높은 일반화 성능을 기대할 수 있다. 경사 하강법을 사용하여 트리를 앙상블에 추가하고 분류에서는 로지스틱 손실함수를 사용하고 회귀에서는 평균 제곱 오차 함수를 사용한다.
from sklearn.ensemble import GradientBoostingClassifier
gb = GradientBoostingClassifier(random_state=42)
scores = cross_validate(gb, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
#결과 0.8881086892152563 0.8720430147331015
gb = GradientBoostingClassifier(n_estimators=500, learning_rate=0.2, random_state=42)
scores = cross_validate(gb, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
#결과 0.9464595437171814 0.8780082549788999
gb.fit(train_input, train_target)
print(gb.feature_importances_)
#결과 [0.15872278 0.68010884 0.16116839]
히스토그램 기반 그레이디언트 부스팅
그레이디언트 부스팅의 속도와 성능을 더욱 개선한 것이다.
입력 특성을 256개의 구간으로 나눔으로써 노드를 분할 할 때 최적의 분할을 매우 빠르게 찾을 수 있다.
from sklearn.ensemble import HistGradientBoostingClassifier
hgb = HistGradientBoostingClassifier(random_state=42)
scores = cross_validate(hgb, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
#결과 0.9321723946453317 0.8801241948619236
from sklearn.inspection import permutation_importance
hgb.fit(train_input, train_target)
result = permutation_importance(hgb, train_input, train_target, n_repeats=10,
random_state=42, n_jobs=-1)
print(result.importances_mean)
#결과 [0.08876275 0.23438522 0.08027708]
result = permutation_importance(hgb, test_input, test_target, n_repeats=10,
random_state=42, n_jobs=-1)
print(result.importances_mean)
#결과 [0.05969231 0.20238462 0.049 ]
hgb.score(test_input, test_target)
#결과 0.8723076923076923
from xgboost import XGBClassifier
xgb = XGBClassifier(tree_method='hist', random_state=42)
scores = cross_validate(xgb, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
#결과 0.9558403027491312 0.8782000074035686
from lightgbm import LGBMClassifier
lgb = LGBMClassifier(random_state=42)
scores = cross_validate(lgb, train_input, train_target, return_train_score=True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
#결과 0.935828414851749 0.8801251203079884
나무(트리) 알고리즘에 대해서 야무지게 공부했다!
'혼공족 ML&DL' 카테고리의 다른 글
[혼공족 12기] 5주차(8/5-8/11) (0) | 2024.08.12 |
---|---|
[혼공족 12기] 3주차(7/15-7/21) (0) | 2024.07.22 |
[혼공족 12기] 2주차(7/8-7/14) (0) | 2024.07.17 |
[혼공족 12기] 1주차(7/1-7/7) (0) | 2024.07.07 |