Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Archives
Today
Total
관리 메뉴

cb

[혼공머신] Chapter 05 - 트리 알고리즘 본문

ai - study

[혼공머신] Chapter 05 - 트리 알고리즘

10011001101 2024. 2. 17. 22:10

본 게시물은 <혼자서 공부하는 머신러닝+딥러닝>의 Chapter 05 - 트리 알고리즘을 보고 정리한 글입니다. 원본 코드는 책의 저자인 박해선님의 깃허브 코드를 참고하시길 바랍니다.

 

GitHub - rickiepark/hg-mldl: <혼자 공부하는 머신러닝+딥러닝>의 코드 저장소입니다.

<혼자 공부하는 머신러닝+딥러닝>의 코드 저장소입니다. Contribute to rickiepark/hg-mldl development by creating an account on GitHub.

github.com


결정 트리

결정 트리(Decision Tree) 모델은 아래의 예시처럼 순서도를 통해 분류를 진행하는 모델이다. 그렇기 때문에 사람이 직관적으로 이해하기도 쉽다.  데이터를 잘 나눌 수 있는 질문을 찾는다면 계속 질문을 추가해서 분류 정확도를 높일 수 있다.

 

 

트리 알고리즘은 특정 기준을 통해 샘플들을 분류하기 때문에, 데이터 스케일의 영향을 받지 않는다. 훈련 이전에 전처리를 해 주지 않아도 된다는 장점이 있다.

 

아래는 데이터를 불러오고, 사이킷런의 결정 트리 라이브러리를 통해 모델을 훈련하는 과정이다.

# 데이터 불러오기
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)
# 라이브러리 임포트
from sklearn.tree import DecisionTreeClassifier

# 클래스 객체 생성
dt = DecisionTreeClassifier(random_state=42)
dt.fit(train_scaled, train_target) # 모델 훈련

print(dt.score(train_scaled, train_target)) # train score 
print(dt.score(test_scaled, test_target)) # test score

 

위의 코드를 실행시키고 나면, 약 0.99의 train score과 0.86의 test score를 확인할 수 있을 것이다.

앞서 결정 트리는 직관적으로 이해하기 쉽다고 설명했으므로, plot_tree() 함수를 이용해 모델을 그림으로 표현해 보자.

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

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

 

맨 위의 노드(node)부모 노드(parent node), 맨 아래 끝에 달린 노드를 리프 노드(leaf node)라고 한다.

위의 사진처럼 트리가 나타난다면 너무 복잡하기 떄문에, 아래의 코드를 활용하여 트리의 깊이(depth)를 제한해 줄 수도 있다. 또, filled 매개변수를 활용하여 노드의 색을 칠할 수도 있고, feature_name를 통해 특성의 이름을 전달할 수도 있다.

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

 

위의 그림을 이해하는 방법은 다음과 같다.

 

루프 노드는 당도(sugar)가 -0.239 이하인지 질문을 한다. 만약 당도가 -0.239보다 낮다면 샘플은 왼쪽 가지로 가고, 아니라면 오른쪽 가지로 이동한다.

 

두 번째 줄의 gini는 지니 불순도(gini impurity)를 의미하는데, 이는 DesicionTreeClassifier 클래스에서 데이터를 분할할 기준을 정하는 매개변수 중 하나에 해당한다. 지니 불순도는 아래의 식을 통해 계산 가능하다.

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

결정 트리 모델은 부모 노드와 자식 노드의 불순도 차이가 가능한 크도록 트리를 성장시킨다. 이때 부모와 자식 노드 사이의 불순도 차이를 정보 이득(information gain)이라고 한다. 

 

이때 노드에 하나의 클래스만 있다면 지니 불순도는 0이 되어 가장 작은 값을 가지게 된다. 이런 노드를 순수 노드라고 하고, 노드를 순수하게 나눌수록 정보 이득이 커지기 때문에, 결정 트리 알고리즘은 정보 이득이 최대가 되도록 노드를 분할한다.

 

이외에도, 아래의 식을 사용하느 엔트로피 불순도(entropy impurity)가 존재한다.

$$-음성 클래스 비율 * log2(음성 클래스 비율) - 양성 클래스 비율 * log2(양성 클래스 비율)$$

 

 

세 번째 줄의 samples 매개변수는 해당 노드에 있는 총 샘플 수를 나타내고, value 매개변수는 각각 왼쪽 노드로 가는 샘플의 수, 오른쪽 노드로 가는 샘플의 수를 나타낸다.


검증 세트

지금까지 우리는 훈련 세트에서 모델을 훈련하고 테스트 세트에서 모델을 평가했다. 그런데 여기서 자꾸 테스트 세트를 사용해 성능을 확인하다 보면, 점점 테스트 세트에 모델을 맞추게 된다는 문제가 생긴다.

 

그러므로, 우리는 훈련 세트를 한 번 더 나눠 테스트 세트를 사용하지 않고 모델이 과대적합인지 과소적합인지 판단해 볼 수 있다. 이렇게 나눈 데이터 세트를 검증 세트(validation set)라고 한다.

이는 기존 방법과 같이 데이터 세트를 train, test로 분할한 뒤 train 데이터셋을 sub, val로 나누어 주면 된다. 코드는 기존 데이터 분할 코드에서 아래의 코드를 추가하기만 하면 되기 때문에 쉽게 구현 가능하다.

sub_input, val_input, sub_target, val_target = train_test_split(
    train_input, train_target, test_size=0.2, random_state=42)

교차 검증

검증 세트를 만드느라 훈련 세트가 줄어들었기 때문에, 모델 훈련에 사용되는 데이터의 양이 적을 수 있다. 그렇다고 검증 세트를 너무 조금만 사용하면 검증 점수가 들쭉날쭉 불안정할 것이기 떄문에 교차 검증(cross validation)을 사용하여 안정적인 검증 점수를 얻을 수 있다.

 

교차 검증은 검증 세트를 떼어 내어 평가하는 과정을 여러 번 반복한다. 그 후, 이 점수를 평균 내어 최종 점수를 검증한다. 아래와 같이 훈련 세트를 세 부분으로 나누어 교차 검증을 수행하는 것을 3-폴드 교차 검증이라 한다. 폴드의 개수는 나누기 나름이기 때문에, k-폴드 교차 검증(k-fold cross validation)이라고도 한다.

 

코드는 아래와 같다. 앞에서처럼 직접 검증 세트를 떼어 내지 않고, 훈련 세트 전체를 cross_validate() 함수에 전달하기만 하면 된다.

from sklearn.model_selection import cross_validate

scores = cross_validate(dt, train_input, train_target)
print(scores)

 

위의 함수는 fit_score, score_time, test_score의 키를 가진 딕셔너리를 반환한다. 각각 모델 훈련 시간, 검증 시간, 테스트 스코어를 의미한다.

 

import numpy as np

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

 

교차 검증의 최종 점수는 test_score 키에 담긴 키의 점수를 평균하여 얻을 수 있다. 키의 이름은 test_score이지만 이는 val set을 사용하여 얻은 점수이기 때문에 최종 점수가 아닌, 검증 점수라는 점을 유의하자.

 

또, 한 가지 주의할 점은 cross_validate() 함수가 훈련 세트를 안 섞고 폴드를 나눈다는 점이다. 그렇기 때문에 cross_validate() 함수의 경우 회귀 모델일 때 KFold 분할기를 사용하고, 분류 모델일 때 StratifiedKFold()를 사용한다. 코드는 아래와 같다.

from sklearn.model_selection import StratifiedKFold

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

 

훈련 세트를 섞은 후 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']))

하이퍼파라미터 튜닝

하이퍼 파라미터는 사용자가 지정하는 파라미터를 의미한다. 그 예로 결정 트리 모델의 max_depth 값, 모델의 epoch 반복 횟수 등이 있다.

 

사이킷런에서는 그리드 서치(Grid Search) 기능을 제공한다. 이는 하이퍼파라미터 탐색과 교차 검증을 한 벙네 수행해 주는 도구이다.

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)

# 훈련, (검증 점수가 가장 높은 모델의 매개변수 조합으로 best_estimator_ 속성에 저장)
gs.fit(train_input, train_target)

# 일반 결정 트리처럼 best_estimator_ 속성을 통해 score 확인
dt = gs.best_estimator_
print(dt.score(train_input, train_target))

# 최적의 매개변수는 best_params_ 속성에 저장
print(gs.best_params_)

# 각 매개변수에서 수행한 교차 검증의 평균 점수
print(gs.cv_results_['mean_test_score'])

# argmax()를 사용하여 가장 큰 값의 인덱스를 추출
best_index = np.argmax(gs.cv_results_['mean_test_score'])
print(gs.cv_results_['params'][best_index])

랜덤 서치

랜덤 서치(Random Search)는 매개변수 값의 목록을 전달하는 것이 아니라, 매개변수를 샘플링할 수 있는 확률 분포 객체를 전달하는 방법이다. 쉽게 말해, 매개 변수의 값이 수치일 때 값의 범위나 간격을 정하여 매개변수를 탐색하는 방법이다.

 

아래의 코드와 같이 매개변수를 랜덤으로 샘플링할 수 있다.

from scipy.stats import uniform, randint

# 0에서부터 9까지 랜덤한 숫자를 샘플링
rgen = randint(0, 10)
rgen.rvs(10)

# 숫자 1000개를 샘플링
np.unique(rgen.rvs(1000), return_counts=True)

# uniform 함수를 사용하여 10개의 실수를 추출
ugen = uniform(0, 1)
ugen.rvs(10)

 

 

사이킷런에서 랜덤 서치는 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),
          }
          
          
# 랜덤 서치 라이브러리 임포트
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)

 

이후 앞에서 했던 것처럼 best_params_, mean_test_score를 통해 최적의 파라미터와 검증 점수를 확인할 수도 있다.


 

앙상블 학습

앙상블 학습(ensemble learning)은 정형 데이터를 다루는 데에 가장 뛰어난 성과를 내는 알고리즘이다. 비정형 데이터(unstructured data)는 쉽게 말해 정해진 구조가 없는 데이터이고, 정형 데이터(structured data)는 그 반대에 속한다.

 

랜덤 포레스트(Random Forest)는 대표적인 앙상블 학습의 방법이다. 이는 결정 트리를 랜덤하게 만들어 결정 트리의 숲을 만들고, 결정트리의 예측을 사용해 최종 예측을 만든다.

 

부트스트랩 샘플(bootstrap sample)은 샘플을 랜덤하게 뽑을 때, 이미 뽑았던 샘플을 중복되게 추출하여 만들어진 샘플을 의미한다.

사이킷런의 랜덤 포레스트는 아래와 같은 코드로 동작한다.

기본적인 함수의 이름은 다른 클래스들과 동일하기 때문에, 아래 코드를 보면 쉽게 어떤 동작을 하는지 알 수 있을 것이다.

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

 

 

이 훈련 과정에서, 부트스트랩 샘플에 포함되지 않고 남는 샘플이 존재한다. 이는 OOB(out of bag) 샘플이라고 하고, 우리는 남는 샘플을 사용하여 결정 트리를 평가할 수도 있다.

 

그 과정은 아래 코드와 같다.

rf = RandomForestClassifier(oob_score=True, n_jobs=-1, random_state=42)

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

 


엑스트라 트리

엑스트라 트리(extra tree)는 부트스트랩 샘플을 사용하지 않는 랜덤 포레스트 모델이다. 결정 트리를 만들 떄 전체 훈련 세트를 사용한다고 보면 된다. 대신, 노드를 분할할 때 가장 좋은 분할을 찾는 것이 아니라 무작위로 분할한다.

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

# extra tree를 활용하여 훈련
et.fit(train_input, train_target)
print(et.feature_importances_) # 특성 중요도 출력

그레이디언트 부스팅

그레이디언트 부스팅(gradient boosting)은 깊이가 얕은 결정 트리를 사용하여 이전 트리의 오차를 보완하는 방식으로 앙상블 하는 방법이다. 사이킷런의 GradientBoostingClassifier는 기본적으로 깊이가 3인 결정 트리 100개를 사용한다. 깊이가 얕기 때문에 과대적합이 일어날 일이 거의 없고, 일반적으로 높은 일반화 성능을 기대할 수 있다.

 

from sklearn.ensemble import GradientBoostingClassifier

# 클래스 객체 생성
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)

# train, val 점수 출력
print(np.mean(scores['train_score']), np.mean(scores['test_score']))

# 훈련
gb.fit(train_input, train_target)
print(gb.feature_importances_) # 중요 특성 출력

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

히스토그램 기반 그레이디언트 부스팅(Histogram-based Gradient Boosting)은 정형 데이터를 다루는 머신러닝 알고리즘 중 가장 인기가 높다. 이는 먼저 입력 특성을 256개의 구간으로 나눈다. 그 후, 나눠진 구간 중 하나를 떼어 놓고 누락된 값을 위해서 사용한다. 따라서 입력 특성에 누락된 특성이 있더라도 이를 따로 전처리할 필요가 없다.

 

히스토그램 기반 그레이디언트 부스팅의 회귀 버전은 HistGradientBoostingRegressor 클래스에 구현되어 있다. 실행 코드는 아래와 같다,

# 사이킷런 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)

# train, val 점수 확인
print(np.mean(scores['train_score']), np.mean(scores['test_score']))

# 훈련
hgb.fit(train_input, train_target)
print(et.feature_importances_) # 중요 특성 추출

 

 

사이킷런 말고도 히스토그램 기반 그레이디언트 부스팅을 구현한 대표적인 라이브러리는 XGBoost이다. 이는 사이킷런의 cross_validate() 함수와 함께 사용할 수도 있다. 여기서, 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']))

 

 

널리 사용하는 또 다른 히스토그램 기반 그레이디언트 부스팅 라이브러리는 마이크로소프트에서 만든 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']))

마무리

이번 챕터에서는 결정 트리 모델, 최적의 모델을 위한 하이퍼파라미터 탐색, 앙상블 학습을 통한 성능 향상에 대해 알아보았다. 결정 트리의 지니 계수, 하이퍼 파라미터, 앙상블 모델은 본 게시글에서의 내용뿐만 아니라 더 다양한 지식까지 담고 있기 때문에 궁금한 사람은 추가 자료를 활용하여 찾아보는 것이 좋겠다.