혼공머신

[혼공머신] 4주차 : ch5) 트리 알고리즘

영쥬주 2024. 7. 28. 03:45

Chapter 5 : 트리 알고리즘 - 화이트 와인을 찾아라!

5-1 결정 트리

<결정 트리>

결정 트리(Decision Tree) ? 스무고개처럼 질문을 이어가며 학습하는 모델

    - 사이킷런이 결정 트리 알고리즘 제공 (DecisionTreeClassifier)

        - plot_tree() : 결정 트리를 이해하기 쉬운 트리 그림으로 출력해주는 함수

    - 표준화 전처리가 필요 없음!!!!

 

↓ 결정 트리 모델 코드

더보기
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier()
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target))
print(dt.score(test_scaled, test_target))
# 결정 트리 출력
import matplotlib.pyplot as plt
from sklearn.tree import plot_tree
plt.figure(figsize=(10, 7))
plot_tree(dt)
plt.show()
# 결정 트리의 depth 지정

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

- 루트 노드 : suger가 0.239 이하인지 확인

    - Yes -> 왼쪽 가지 / No -> 오른쪽 가지

    - 총 샘플 수 : 5197개 == value

        - 음성 클래스(레드) : 1258개 / 양성 클래스(화이트) : 3939개

- filled=True : 클래스마다 색깔을 부여하고, 어떤 클래스의 비율이 높아지면 점점 진한 색으로 표시함

    - 오른쪽 노드에서 양성 클래스의 비율이 높기 때문에 가장 진함

 

 

<불순도>

- gini : 지니 불순도(Gini impurity) : criterion 매개변수의 기본값

    - criterion : 노드에서 데이터를 분할할 기준을 정함

    - 지니 불순도 = 1 - (음성 클래스 비율^2 + 양성 클래스 비율^2)

    - 노드에 하나의 클래스만 있을 때, 지니 불순도 == 0 <- 순수 노드

    - 클래스의 비율이 동일할 때, 지니 불순도 == 0.5 <- 최악

 

- 정보 이득(information gain) ? 부모와 자식 노드 사이의 불순도 차이

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

    - 정보 이득이 최대가 되도록 데이터 분할함. 이때, 지니 불순도를 기준으로 사용함

 

- 엔트로피 불순도 ? criterion='entropy', 불순도의 기준을 결정

    - 음성 클래스 비율 * ㏒_2(음성 클래스 비율) - 양성 클래스 비율 * ㏒_2(양성 클래스 비율)

 

- 결정 트리에서 예측하는 방법 : 리프 노드에서 가장 많은 클래스 == 예측 클래스!!

    - 위 그림에서는 왼쪽, 오른쪽 노드 둘 다 양성 클래스의 개수가 많으므로 양성 클래스가 예측 클래스

 

 

<가지치기>

- 방법 : 트리의 최대 깊이를 지정

 

↓ 가지치기 코드

더보기
# 가지치기 

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))
plt.figure(figsize=(20, 15))
plot_tree(dt, filled=True, feature_names=['alcohol', 'sugar', 'pH'])
plt.show()

- 음성 클래스 : 노란색 노드

- max_depth=3 : 루트 노드 아래로 최대 3개의 노드까지만 성장할 수 있음

더보기
print(dt.feature_importances_) # 특성 중요도 출력
# [0.12345626 0.86862934 0.0079144 ]
# [alcohol, sugar, pH]

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

<검증 세트>

검증 세트(validation set) ? 훈련 세트를 또 다시 나눈 세트

    - 훈련 세트에서 모델 훈련, 검증 세트로 모델 평가

 

↓ 검증 세트로 분리하는 코드

더보기
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와 검증 세트 val로 나눔
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 validation) ? 검증 세트를 떼어 내어 평가하는 과정을 여러 번 반복하는 방법

    - 안정적인 검증 점수를 얻고 훈련에 더 많은 데이터를 사용할 수 있음

    - 3-폴트 교차 검증 ? 훈련 세트를 세 부분으로 나눠서 교차 검증을 수행하는 방법

        - 사이킷런에 cross_validate()라는 교차 검증 함수 존재

 

기본 숙제(필수): 교차 검증을 그림으로 설명하기



 

↓ 교차 검증하는 코드

더보기
from sklearn.model_selection import cross_validate

# 평가할 모델 객체를 첫 번째 파라미터에 넣음
# 훈련 세트 전체를 cross_validate() 함수에 전달
scores = cross_validate(dt, train_input, train_target)
print(scores)

'''
{'fit_time': array([0.0150857 , 0.0118525 , 0.01249075, 0.01304889, 0.0188992 ]), 
'score_time': array([0.00233412, 0.00231385, 0.00222421, 0.00210118, 0.00281906]), 
'test_score': array([0.86923077, 0.84615385, 0.87680462, 0.84889317, 0.83541867])}'
'''

 

- fit_time : 모델을 훈련하는 시간

- score_time : 모델을 검증하는 시간

- test_score : 검증 폴드의 점수

    - 교차 검증의 최종 점수를 test_score 키에 담긴 5개의 점수를 평균하여 얻을 수 있음

    - print(np.mean(scores['test_score']))

 

 cross_validate()는 훈련 세트를 섞어 폴드를 나누지 않음 

    -> 훈련 세트를 섞기 위해 분할기(splitter) 지정 필요

    - 사이킷런에서 회귀 모델일 경우 KFold 분할기를, 분류 모델일 경우 StratifiedKFold 분할기를 사용

 

↓ 분할기 사용한 코드

더보기
# 분할기 사용

from sklearn.model_selection import StratifiedKFold
scores = cross_validate(dt, train_input, train_target, cv=StratifiedKFold())
print(np.mean(scores['test_score']))

# n_splits 매개변수 : 몇 폴드 교차 검증을 할 지 정함
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']))

<하이퍼파라미터 튜닝>

- 하이퍼파라미터 ? 모델이 학습할 수 없어서 사용자가 지정해야만 하는 파라미터

    - 하이퍼파라미터 튜닝 진행 방식

        1. 먼저 라이브러리가 제공하는 기본값을 그대로 사용해 모델을 훈련한다.

        2. 검증 세트의 점수나 교차 검증을 통해서 매개변수를 조금씩 바꿔 본다.

    - 교차 검증에서 최적의 하이퍼파라미터를 찾으면 전체 훈련 세트로 모델을 다시 만들어야 함

 

<그리드 서치>

- 그리드 서치(Grid Search) ? 하이퍼파라미터 탐색과 교차 검증을 한 번에 수행

    - 사이킷런에서 GridSearchCV로 제공

    - 별도로 cross_validate() 부를 필요 X

    - 그리드 서치 진행 방식

        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))
print(gs.best_params_) # 그리드 서치로 찾은 최적의 매개변수
print(gs.cv_results_['mean_test_score']) # 각 매개변수에서 수행한 교차 검증의 평균 점수

best_index = np.argmax(gs.cv_results_['mean_test_score']) # 가장 큰 값의 인덱스 출력 
print(gs.cv_results_['params'][best_index])

# 0.9615162593804117
# {'min_impurity_decrease': 0.0001}
# [0.86819297 0.86453617 0.86492226 0.86780891 0.86761605]
# {'min_impurity_decrease': 0.0001}

-> 평균점수가 약 0.868로 가장 높은 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_)
print(np.max(gs.cv_results_['mean_test_score']))

# {'max_depth': 14, 'min_impurity_decrease': 0.0004, 'min_samples_split': 12}
# 0.8683865773302731

 

<랜덤 서치>

- 매개변수를 샘플링할 수 있는 확률 분포 객체를 전달함

 

↓ 랜덤 서치 코드

더보기
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_)
print(np.max(gs.cv_results_['mean_test_score']))

df = gs.best_estimator_
print(df.score(test_input, test_target))

# {'max_depth': 39, 'min_impurity_decrease': 0.00034102546602601173, 
#   'min_samples_leaf': 7, 'min_samples_split': 13}

# 0.8695428296438884

# 0.86

 


5-3 트리의 앙상블

<정형 데이터와 비정형 데이터>

정형 데이터(structured data) ? 구조화된 데이터

    ex) CSV, 데이터베이스, 엑셀, ...

비정형 데이터(unstructured data) ? 비구조화된 데이터

    ex) 텍스트 데이터, 사진, 음악, ...

 

앙상블 학습(ensemble learning) ? 여러 개의 분류기를 생성하고 각 예측들을 결합함으로써 보다 정확한 예측을 도출하는 기법

    - 정형 데이터를 다루는 데 가장 뛰어난 성과를 내는 알고리즘

    - 대부분 결정 트리를 기반으로 만들어져 있음


<랜덤 포레스트>

랜덤 포레스트(Random Forest) ? 앙상블 학습의 대표 중 하나

    - 결정 트리를 랜덤하게 만들어 결정 트리의 숲을 만듦

    - 각 결정 트리의 예측을 사용해 최종 예측을 만듦

    - 훈련 세트에 과대적합되는 것을 막아주며, 검증 세트와 테스트 세트에서 안정적인 성능 얻을 수 있음

    - 랜덤 포레스트 형성 단계

        1. 훈련 데이터에서 랜덤하게 샘플을 추출하여 각 트리를 훈련하기 위한 데이터를 만든다.

            - 이때 샘플은 중복될 수 있으며, 이렇게 만들어진 샘플을 부트스트랩 샘플이라고 부름 

            - OOB(out of bag) 샘플 ? 부트스트랩 샘플에 포함되지 않는 샘플

            - OOB 샘플을 사용하여 부트스트랩 샘플로 훈련한 결정 트리를 평가할 수 있음

        2. 각 노드를 분할할 때, 일부 특성을 랜덤하게 고르고, 이 중 최선의 분할을 찾는다.

            - 이때 전체 특성 개수의 제곱근만큼의 특성을 선택함

        3. 회귀 / 분류

            - 회귀 : 각 트리의 예측을 평균함

            - 분류 : 각 트리의 클래스별 확률을 평균하여 가장 높은 확률을 가진 클래스를 예측을 삼는다.

 

 

↓ 랜덤 포레스트 코드

더보기
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']))

rf.fit(train_input, train_target)
print(rf.feature_importances_)

# 0.9973541965122431 0.8905151032797809
# [0.23167441 0.50039841 0.26792718]

- 결정 트리보다 과대적합을 줄이고 일반화 성능을 높일 수 있는 모델을 만들 수 있음

# OOB 점수
rf = RandomForestClassifier(oob_score=True, n_jobs=-1, random_state=42)
rf.fit(train_input, train_target)
print(rf.oob_score_)

# 0.8934000384837406

return_train_score=True : 검증 점수뿐만 아니라 훈련 세트에 대한 점수도 같이 반환


<엑스트라 트리>

엑스트라 트리(Extra Trees) : 랜덤 포레스트와 매우 비슷하게 동작함

    - 부트스트랩 샘플을 사용하지 않음 -> 전체 훈련 세트 사용

    - 노드 분할할 때, 무작위로 분할함

    - 성능은 낮아질 수 있으나, 과대적합을 막고 검증 세트의 점수를 높일 수 있음

    - 사이킷런에서 ExtraTreesClassifier 제공

 

↓ 엑스트라 트리 코드

더보기
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']))

et.fit(train_input, train_target)
print(et.feature_importances_)

# 0.9974503966084433 0.8887848893166506
# [0.20183568 0.52242907 0.27573525]

<그레이디언트 부스팅>

그레이디언트 부스팅(Gradient Boosting) ?
깊이가 얕은 결정 트리를 사용하여 이전 트리의 오차를 보완하는 방식으로 앙상블 하는 방법

    - 과대적합에 강하고, 일반적으로 높은 일반화 성능 기대 가능

    - 사이킷런에서는 GradientBoostingClassifier 제공

    - 경사 하강법을 사용하여 트리를 앙상블에 추가함

    - 분류 : 로지스틱 손실 함수 사용 / 회귀 : 평균 제곱 오차 함수 사용

    - 성능 : 그레이디언트 부스팅 > 랜덤 포레스트 (일반적임)

 

↓ 그레이디언트 부스팅 코드

더보기
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']))

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']))

gb.fit(train_input, train_target)
print(gb.feature_importances_)

# 0.8881086892152563 0.8720430147331015
# 0.9464595437171814 0.8780082549788999
# [0.15872278 0.68010884 0.16116839]

 


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

히스토그램 기반 그레이디언트 부스팅(Histogram-based Gradient Boosting) ?

그레이디언트 부스팅의 속도와 성능을 개선한 앙상블 기법

    - 입력 특성을 256개의 구간으로 먼저 나눔

        -> 노드 분할 시, 최적의 분할을 매우 빠르게 찾을 수 있음

    - 사이킷런에서는 HistGradientBoostingClassifier 제공

        - 트리의 개수 지정 시, 부스팅 반복 횟수를 지정하는 max_iter 사용

 

 

↓ 히스토그램 기반 그레이디언트 부스팅 코드 - HistGradientBoostingClassifier

더보기
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)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))

hgb.fit(train_input, train_target)
print(rf.feature_importances_)

# 0.9321723946453317 0.8801241948619236
# [0.23167441 0.50039841 0.26792718]

=> 그레이디언트 부스팅보다 더 높은 성능 제공

↓ 히스토그램 기반 그레이디언트 부스팅 코드 - 다양한 라이브러리 

더보기
XGBoost
from xgboost import XGBClassifier
xgb = XGBClassifier(tree_method='hist', random_state=42)
scores = cross_validate(xgb, train_input, train_target, return_train_score=True)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))

# 0.9558403027491312 0.8782000074035686

 

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

 

 

추가 숙제(선택): Ch.05(05-3) 앙상블 모델 손코딩 코랩 화면 인증하기


.

.

.

지금까지 혼공머신의 절반을 공부했는데 여름방학이라니!

지금까지 공부했던 걸 복습할 수 있는 기간이 있는 거자나?

완전 럭키영쥬자나🍀