본문 바로가기
책 이야기/스터디 기록

[혼공머신] 트리 알고리즘

by meticulousdev 2022. 7. 30.
반응형

들어가며

    이 글은 한빛미디어에서 진행하는 혼공학습단 8기에 참여하면서 공부한 것들에 대한 글입니다. 책은 제 돈으로 구매하였으며, 글의 주된 내용은 책을 기반으로 하여 작성되었습니다.

 

구성

1. 내용 정리

2. 기본 미션 - 교차 검증을 그림으로 설명하기

3. 선택 미션 - Ch.05(05-3) 앙상블 모델 손코딩 코랩 화면 인증샷 

 

1. 내용 정리

1.1 pandas를 활용한 exploratory data analysis

    pandas를 이용해서 데이터를 다룰 때 사용할만한 몇 가지 기능들이 있습니다. 흔히들 많이 사용하는 것이 head, info, 그리고 describe 입니다. Colab을 사용하신 다면 데이터를 확인할 때 한 번쯤은 사용해보고 출력 결과를 보신 적이 있으실 겁니다. 그렇다면 각각이 반환하는 것은 무엇일까요? 책에 나온 예제인 와인 데이터를 가지고 확인해보겠습니다.

 

# 코드
import pandas as pd
wine = pd.read_csv('https://bit.ly/wine_csv_data')

print(f"type(wine.head()): {type(wine.head())} \n")
print(f"type(wine.info()): {type(wine.info())} \n")
print(f"type(wine.describe()): {type(wine.describe())} \n")
# 출력 결과
type(wine.head()): <class 'pandas.core.frame.DataFrame'> 

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6497 entries, 0 to 6496
Data columns (total 4 columns):
 #   Column   Non-Null Count  Dtype  
---  ------   --------------  -----  
 0   alcohol  6497 non-null   float64
 1   sugar    6497 non-null   float64
 2   pH       6497 non-null   float64
 3   class    6497 non-null   float64
dtypes: float64(4)
memory usage: 203.2 KB
type(wine.info()): <class 'NoneType'> 

type(wine.describe()): <class 'pandas.core.frame.DataFrame'>

 

결과를 보시면 3가지 함수에서 반환하는 것들이 전부 DataFame인 것을 확인할 수 있습니다. 즉, 필요에 따라서 반환된 결과를 활용할 수 있다는 것을 의미합니다. describe를 사용한 예시를 하나 만들어보겠습니다.

 

wine.describe()

 

dataframe이기 때문에 특정 칼럼을 추출하는 것이 가능하며 합니다. 

wine.describe()[['alcohol']]

 

또한, 데이터 분석에서 자주 쓰이는 값인 자주 쓰이는 값인 1 사분위수와 3 사분위수의 값도 추출이 가능합니다. describe를 사용해서 데이터의 통계량 값들을 한 번에 봤다면, 굳이 이후에 함수를 따로 호출해서 계산할 필요가 없습니다.

 

# 코드
Q1_salary = wine.describe()['alcohol'].loc['25%']
Q3_salary = wine.describe()['alcohol'].loc['75%']

print(f"25% : {Q1_salary}")
print(f"75% : {Q3_salary}")

 

# 출력 결과
25% : 9.5
75% : 11.3

 

추가적으로 quantile을 이용하여도 위와 동일한 값의 출력이 가능합니다.

 

# 코드
Q1_salary = wine['alcohol'].quantile(q=0.25)
Q3_salary = wine['alcohol'].quantile(q=0.75)

print(f"25% : {Q1_salary}")
print(f"75% : {Q3_salary}")

 

# 출력 결과
25% : 9.5
75% : 11.3

 

1.2 random_state는 왜 42인가?

    머신러닝 책을 공부하거나 인터넷에 올라온 예제를 보다 보면 한 가지 공통점이 있습니다. 모두 random_state으로 42라는 값을 사용한다는 것입니다. random_state를 고정하여 실행 결과를 같게 한다는 것을 알겠는데 많고 많은 수 중에서 왜 42일까요? 행운을 바란다면 7이라는 숫자도 있고 777을 사용해도 될 텐데 왜 42라는 수를 사용할까요? 뭔가 엄청난 과학적인 의미가 있는 것일까요? 이에대한 답은 42가 많이 쓰이는 이유를 물어본 것에 대한 stackoverflow 답변에서 확인할 수 있었습니다. 42라는 숫자가 은하수를 여행하는 히치하이커를 위한 안내서 (원제: The Hitchhiker's Guide to the Galaxy)에서 궁극의 수라고 나오기 때문입니다. 하하...

 

"Answer to the Ultimate Question of Life, the Universe, and Everything"

The Hitchhiker's Guide to the Galaxy by Douglas Adams

 

1.3 결정 트리에서 Gini impurity와 Entropy

    분류 알고리즘 중에서 결정 트리는 분류 결과에 대한 결과를 설명하기에 매우 쉬운 알고리즘입니다. 우리가 성격 검사 등을 할 때 흔하게 접할 수 있는 YES or NO 질문지와 비슷하며 대답의 결과 성격이 분류되는 것과 매우 유사합니다. 성격 검사에서는 행동에 대한 기준을 가지고 질문지를 나눴습니다. 그렇다면 결정 트리에서 무엇을 기준으로 하여서 데이터를 분할할지 정할까요? scikit-learn에 있는 DecisionTreeClassifier의 경우 gini, entropy, 그리고 log_loss를 사용합니다. 이 글에서는 gini 및 entropy에 대해서 알아보겠습니다.

 

    지니 불순도(Gini impurity, $G_i$)의 값은 아래와 같이 정의됩니다(출처: Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow; Second Edition; O’Reilly (2019); Aurélien Géron). 이때, $p_{i, k}$는 i번째 노드의 k 클래스의 비율입니다. (책에서는 이진 분류였기 때문에 음성 클래스의 비율과 양성 클래스의 비율에 대해서만 고려되었습니다.)

$$G_i=1-\sum_{i=1} ^{k} p_{i,k}^2$$

 

    다음은 엔트로피(Entropy, $H_i$)에 대한 정의입니다(출처:Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow; Second Edition; O’Reilly (2019); Aurélien Géron).

$$H_i=-\sum_{k=1} ^{n} p_{i,k} log_2 (p_{i,k}), where\,\, p_{i,k} \neq0$$

 

    이렇게 2개의 서로 다른 정의가 있을 때 무엇을 써야 할까요? 어떤 것을 쓰던지 크게 상관없습니다. 둘의 결과는 비슷하지만 Gini impuriti를 사용할 경우 자주 등장하는 클래스를 분류하는 개별 가지를 만들고, Entropy를 사용할 경우 전반적으로 균형 잡힌 트리를 만듭니다.

 

The truth is, most of the time it does not make a big difference: they lead to similar trees. Gini impurity is slightly faster to compute, so it is a good default. However, when they differ, Gini impurity tends to isolate the most frequent class in its own branch of the tree, while entropy tends to produce slightly more balanced trees.

Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow; Second Edition; O’Reilly (2019); Aurélien Géron

 

예제를 통해서 확인해보면 다음과 같다.

 

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.preprocessing import StandardScaler

ss = StandardScaler()
ss.fit(train_input)

train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)

 

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

 

- Gini impurity

# Gini impurity
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()

- Entropy

# Entropy
dt = DecisionTreeClassifier(criterion='entropy', 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.8443332691937656
# 0.8415384615384616

 

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

간단한 테스트를 통해서 확인할 수 있는 결과는 다음과 같습니다.

1) score값에서는 큰 차이를 보이지 않습니다.

2) plot_tree를 해보면 기준이 gini와 entropy로 출력된 것을 확인할 수 있습니다.

3) 분류의 기준 값이라던가 샘플들의 수가 조금씩 다른 것을 확인할 수 있습니다.

4) 위의 설명처럼 entropy가 더 균형을 맞춘 결정 트리를 만드는지는 다른 데이터로 확인이 필요합니다.

 

이렇게 만들어진 트리의 장점 중 하나는 어떤 인자가 결정 트리를 만드는데 중요하게 작용했는지 확인할 수 있다는 점입니다. 이때 활용하는 속성은 feature_importances_입니다.

 

1.4 Ensemble Leaning

    결정 트리 다음으로 관심을 가져야 할 것은 앙상블 학습입니다. 책에 나와 있던 것처럼 나무가 아닌 숲을 보는 것입니다.

- 랜덤 포레스트(RandomForest): 부트스트랩 샘플을 사용하여 랜덤 하게 선택된 특성들로 트리를 만듭니다.

- 엑스트라 트리(ExtraTree): 부트스트랩을 사용하지 않고 랜덤 하게 노드를 분할합니다. 이를 통해서 과대 적합을 감소시킵니다.

- 그레디언트 부스팅(GradientBoosting): 결정 트리를 연속적으로 추가하여 손실 함수를 최소화합니다. 그레디언트 부스팅의 속도를 개선한 것으로는 히스토그램 기반 그레디언트 부스팅(Histogram-based Gradient Boosting)이 있습니다.

 

2. 기본 미션 - 교차 검증을 그림으로 설명하기

    교차 검증을 진행하는 이유는 크게 2가지입니다. 1) 데이터를 훈련 데이터와 검증 데이터로 나눴을 때 안정적인 검증 점수를 얻을 수 있고, 2) 하이퍼 파라미터 튜닝 과정에서 사용하여 적합한 하이퍼 파라미터를 찾는 데 사용할 수 있습니다. train_test_split을 활용하여 데이터를 어떻게 나누느냐에 따라서 훈련과 검증 데이터에서의 성능이 다르게 나올 수 있기 때문에 cross_validate을 활용할 경우 안정적인 검증 점수를 얻을 수 있습니다.

    아래 그림은 5-fold cross validation에 대한 설명입니다. 전체 데이터를 훈련/검증 데이터(70%)와 테스트 데이터(30%)로 나눕니다. 이후에 훈련/검증 데이터를 5세트로 나누고 cross validation을 진행합니다. 5세트 중 4세트는 훈련에 사용하고 나머지 1세트를 검증으로 사용합니다. 이렇게 해서 5세트 모두 한 번씩 검증 데이터로 사용될 때까지 작업을 진행합니다. 5번의 작업을 진행하면 5개의 검증 성능 값이 얻어질 겁니다. 그러면 이를 평균 내어서 훈련/검증 데이터에 대한 성능 지표로 사용합니다. 끝으로 테스트 데이터를 대상으로 모델의 성능을 평가하면 됩니다.

 

 

    GridSearchCV는 이름에서 알 수 있듯이 CV(cross validation)을 사용합니다. 전달해주는 파라미터 중 cv는 교차 검증을 몇 개로 나눌지에 대한 값입니다. RandomizedSearchCV 역시 동일한 방식으로 교차 검증을 진행할 수 있습니다.

 

cv : int, cross-validation generator or an iterable, default=None
Determines the cross-validation splitting strategy. Possible inputs for cv are:

출처: https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html; https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.RandomizedSearchCV.html

 

3. 선택 미션 - Ch.05(05-3) 앙상블 모델 손코딩 코랩 화면 인증샷 

- RandomForestClassifier, ExtraTreeClassifier, GradientBoostingClassifier: feature_importance_ 사용

- GradientBoostingClassifier, HistGradientBoostingClassifier: importance_mean 사용

- XGBClassifier, LGBMClassifier: feature_importance_ 사용, LGBMClassifier 값 확인 필요

 

긴 글 읽어주셔서 감사합니다. 
글과 관련된 의견은 언제든지 환영입니다.

 

 

반응형

댓글