혼공 ML+DL

4주차 Chapter 05 (트리 알고리즘)

채영sw 2024. 2. 20. 06:44

Chapter 05

5-1. 결정 트리

  • 결정 트리(Decision Tree) : 질문을 하나씩 던져 정답을 맞춰가며 학습하는 알고리즘으로 비교적 예측 과정을 이해하기 쉬움.

 

- 로지스틱 회귀로 와인 분류하기

#데이터 불러오기
import pandas as pd

wine = pd.read_csv('https://bit.ly/wine_csv_data')
#데이터를 잘 불러들였는지 확인
wine.head()
# 데이터프레임의 요약된 정보 출력. 데이터프레임의 각 열의 데이터 타입과 누락된 데이터가 있는지 확인하는 데 유용
wine.info()
# 열에 대한 간략한 통계를 출력해줌(최대, 평균값 등)
wine.describe()

# 판다스 데이터프레임을 넘파이 배열로 바꾸고 훈련 세트와 테스트 세트로 나누기
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) # 테스트 세트 20%로 설정
# 훈련 세트와 테스트 세트의 크기 확인
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)

 

표준점수로 변환된 train_scaled 와 test_scaled를 사용해 로지스틱 회귀 모델 훈련

from sklearn.linear_model import LogisticRegression

lr = LogisticRegression()
lr.fit(train_scaled, train_target)

print(lr.score(train_scaled, train_target))
print(lr.score(test_scaled, test_target))
# 과소적합
0.7808350971714451
0.7776923076923077
# 로지스틱 회귀가 학습한 계수와 절편 출력
print(lr.coef_, lr.intercept_)

 


- 결정 트리

from sklearn.tree import DecisionTreeClassifier

dt = DecisionTreeClassifier(random_state=42)
dt.fit(train_scaled, train_target)

print(dt.score(train_scaled, train_target))
print(dt.score(test_scaled, test_target))
# 과대적합
0.996921300750433
0.8592307692307692

 

plot_tree() 함수를 사용해 결정 트리를 이해하기 쉬운 트리 그림으로 출력할 수 있음.

import matplotlib.pyplot as plt
from sklearn.tree import plot_tree

plt.figure(figsize=(10,7))
plot_tree(dt)
plt.show()

 

트리의 깊이를 제한해서 출력

max_depth 매개변수를 1로 주면 루트 노드를 제외하고 하나의 노드를 더 확장하여 그림.

filled 매개변수 : 노드의 색 칠하기

feature_names 매개변수 : 특성의 이름 전달

plt.figure(figsize=(10,7))
plot_tree(dt, max_depth=1, filled=True, feature_names=['alcohol', 'sugar', 'pH'])
plt.show()


 

- 불순도

지니 불순도(Gini impurity)

 

순수 노드: 지니 불순도가 0인 노드

결정 트리 모델은 부모 노드와 자식 노드의 불순도 차이(정보 이득)가 가능한 크도록 트리를 성장시킴

 

엔트로피(entropy)

 


- 가지치기

자라날 수 있는 트리의 최대 깊이 지정

dt = DecisionTreeClassifier(max_depth=3, random_state=42)
dt.fit(train_scaled, train_target)

print(dt.score(train_scaled, train_target))
print(dt.score(test_scaled, test_target))
0.8454877814123533
0.8415384615384616

 

#그래프
plt.figure(figsize=(20,15))
plot_tree(dt, filled=True, feature_names=['alcohol', 'sugar', 'pH'])
plt.show()

결정 트리는 표준화 전처리를 할 필요가 없음!

# 전처리 전 데이터로 다시 훈련
dt = DecisionTreeClassifier(max_depth=3, random_state=42)
dt.fit(train_input, train_target)

print(dt.score(train_input, train_target))
print(dt.score(test_input, test_target))
#결과 같음
0.8454877814123533
0.8415384615384616
plt.figure(figsize=(20,15))
plot_tree(dt, filled=True, feature_names=['alcohol', 'sugar', 'pH'])
plt.show()

 

#어떤 특성이 가장 유용한지 나타내는 특성 중요도를 계산해줌
print(dt.feature_importances_)
[0.12345626 0.86862934 0.0079144 ]

 


5-2. 교차 검증과 그리드 서치

  • 검증 세트(validation set) : 하이퍼파라미터 튜닝을 위해 모델을 평가할 때, 테스트 세트를 사용하지 않기 위해 훈련 세트에서 다시 떼어 낸 데이터 세트.
  • 교차 검증(cross validation) : 훈련 세트를 여러 폴드로 나눈 다음 한 폴드가 검증 세트의 역할을 하고 나머지 폴드에서는 모델을 훈련함. 나눈 모든 폴드에 대해 검증 점수를 얻어 평균하는 방법으로 교차 검증을 이용하면 검증 점수가 안정적이며, 훈련에 더 많은 데이터를 사용할 수 있음. 
  • 그리드 서치(Grid Search) : 하이퍼파라미터 탐색을 자동화해 주는 도구
  • 랜덤 서치(Random Search) : 연속적인 매개변수 값을 탐색할 때 유용

- 검증 세트

import pandas as pd

wine = pd.read_csv('https://bit.ly/wine_csv_data')
#class 열을 타깃으로 사용하고 나머지 열은 특성 배열에 저장
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))
print(dt.score(val_input, val_target))
#과대적합
0.9971133028626413
0.864423076923077

 


- 교차 검증

사이킷런의 cross_validate() 교차 검증 함수

rom sklearn.model_selection import cross_validate

scores = cross_validate(dt, train_input, train_target)
print(scores)
#모델을 훈련하는 시간, 검증하는 시간 
{'fit_time': array([0.00931716, 0.00749564, 0.00773239, 0.00731683, 0.00710797]), 'score_time': array([0.00109315, 0.00111032, 0.00101209, 0.00106931, 0.00115085]), '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

 

 

# 사이킷런의 분할기 -> 교차 검증에서 폴드를 어떻게 나눌지 결정. StratifiedKFold 사용
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

 


- 하이퍼파라미터 튜닝

사이킷런의 GridSearchCV 클래스로 하이퍼파라미터 탐색과 교차 검증을 한 번에 수행 가능

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)

 

검증 점수가 가장 높은 모델의 매개변수 조합으로 전체 훈련 세트에서 자동으로 다시 모델을 훈련함 

-> 이 모델은 gs 객체의 best_estimator_ 속성에 저장됨 -> 일반 결정 트리처럼 똑같이 사용 가능

dt = gs.best_estimator_
print(dt.score(train_input, train_target))
0.9615162593804117

 

그리드 서치로 찾은 최적의 매개변수는 best_params_ 속성에 저장되어 있음

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]

 

argmax() 함수로 가장 큰 값의 인덱스 추출

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) : 적분, 보간, 선형 대수, 확률 등을 포함한 수치 계산 전용 라이브러리

#확률 분포 클래스 임포트 - 주어진 범위에서 고르게 값을 뽑음(균등 분포에서 샘플링)
from scipy.stats import uniform, randint

 

 

#리프 노드가 되기 위한 최소 샘플의 개수
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

 


5-3. 트리의 앙상블

  • 정형 데이터(structured date) : 특정 구조로 이루어진 데이터(csv / 데이터베이스)
  • 비정형 데이터(unstructured data) : 정형화되기 어려운 사진이나 음악 등의 데이터
  • 앙상블 학습(ensemble learning) : 여러 알고리즘을 합쳐서 성능을 높이는 머신러닝 기법
  • 랜덤 포레스트(Random Forest) : 대표적인 결정 트리 기반의 앙상블 학습 방법. 안정적인 성능 덕분에 널리 사용됨. 부트스트랩 샘플을 사용하고 랜덤하게 일부 특성을 선택하여 트리를 만드는 것이 특징.
  • 부트스트랩 샘플(bootstrap sample) : 데이터 세트에서 중복을 허용하여 데이터를 샘플링하는 방식

 

- 랜덤포레스트

각 노드를 분할할 때 전체 특성 중에서 일부 특성을 무작위로 고른 다음 그 중에서 최선의 분할을 찾음.

기본적으로 전체 특성 개수의 제곱근만큼의 특성을 선택.

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) #n_jobs = -1 : 모든 CPU 코어 사용
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]

 

-> 하나의 특성에 과도하게 집중하지 않고 좀 더 많은 특성이 훈련에 기여할 기회를 얻음.

 

OOB(out of bag) 샘플 : 부트스트랩 샘플에 포함되지 않고 남는 샘플. 이것을 사용해 부트스트랩 샘플로 훈련한 결정 트리 평가 가능(검증 세트 역할)

#각 결정 트리의 OOB 점수 평균하여 출력
rf = RandomForestClassifier(oob_score=True, n_jobs=-1, random_state=42)

rf.fit(train_input, train_target)
print(rf.oob_score_)
0.8934000384837406

 


- 엑스트라 트리

  • 엑스트라 트리(extra trees) : 랜덤 포레스트와 비슷하게 동작하며 결정 트리를 사용하여 앙상블 모델을 만들지만 브트스트랩 샘플을 사용하지 않는 대신 랜덤하게 노드를 분할하여 과대적합을 감소시킴.
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]

 


- 그레이디언트 부스팅

  • 그레이디언트 부스팅(gradient boosting) : 깊이가 얕은 결정트리를 사용하여 이전 트리의 오차를 보완하는 방식으로 앙상블하는 방법. 깊이가 얕은 결정 트리를 사용하기 때문에 과대적합에 강하고 일반적으로 높은 일반화 성능을 기대할 수 있음.
  • 분류에서는 로지스틱 손실 함수, 회귀에서는 평균 제곱 오차 함수를 사용
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]

-> 랜덤 포레스트보다 일부 특성에 집중

-> 랜덤 포레스트보다 더 높은 성능, but 속도  느림

 


- 히스토그램 기반 그레이디언트 부스팅

  • 히스토그램 기반 그레이디언트 부스팅(Histogram-baesd Gradient Boosting) : 그레이디언트 부스팅의 속도를 개선한 것으로 과대적합을 잘 억제하며 그레이디언트 부스팅보다 조금 더 높은 성능을 제공. 안정적인 결과와 높은 성능으로 매우 인기가 높음.
  • 먼저 256개의 구간으로 입력 특성을 나눔 -> 최적의 분할을 매우 빠르게 찾을 수 있음
# 사이킷런 1.0 버전 아래에서는 다음 라인의 주석을 해제하고 실행하세요.
# from sklearn.experimental import enable_hist_gradient_boosting
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)


# hgb.fit(train_input, train_target)
# print(rf.feature_importances_)
[0.08876275 0.23438522 0.08027708]
hgb.score(test_input, test_target)

 

0.8723076923076923

 

 

 

사이킷런 외  히스토그램 기반 그레이디언트 부스팅 알고리즘 구현한 라이브러리

- XGBoost

tree_method 매개변수 'hist' 로 지정해서 사용 가능.

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.9555033709953124 0.8799326275264677

 

- LightGBM

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