Post

[머신러닝] 5-1. 결정 트리

[머신러닝] 5-1. 결정 트리

결정트리 알고리즘을 사용해 새로운 분류 문제를 다루어보자.
결정트리가 머신러닝 문제를 어떤헤 해결할까?

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

1
2
3
4
5
import pandas as pd
wine=pd.read_csv('https://bit.ly/wine-date')

wine.head()//제대로 읽었는지 확인하기 위해 처음 5 샘플 확인

판다스 데이터 프레임에 유용한 메서드 2개를 먼저 알아보자
info()메서드로 데이터 프레임의 각 열의 데이터 타입과 누락된 데이터가 있는지 확인하는데 유용하다

1
wine.info()

출력결과 None-Null Count가 6497로 누락된 값은 없다

describe()메서드로 열에 대한 간략한 통계를 출력해준다
최소, 최대, 평균값 등을 볼 수 있다

1
wine.describe()

alt

알코올 도수와 당도, pH 값의 스케일이 다르다. 판다스 데이터 프레임을 넘파일 배열로 바꾸고 훈련 세트와 테스트 세트로 나누자! 그리고 사이킷런의 StandardScaler 클래스를 사용해 특성을 표준화하자

1
2
data=wind[['alcohol','sugar','pH']].to_numpy()
target=wine['class'].to_numpy()

처음 3개 열을 넘파이 배열로 바꿔서 data 배열에 저장하고 마지막 class열을 넘파이 배열로 바꿔서 target에 저장했다

1
2
from sklearn.model_selectcion import train_test_split
train_input, test_input, train_target, test_target=train_test_split(data, target, test_size=0.2, random_state=42)

train_test_split()함수는 설정값을 지정하지 않으면 25%를 테스트 세트로 지정한다
여기서는 샘플 개수가 충분히 많아서 test_size=0.2로 20%만 테스트 세트로 나눴다

1
2
print(train_input.shape, test_input.shape)
#(5197, 3) (1300, 3)

훈련 세트는 5197개이고 테스트 세트는 1300개이다

우와우 이제 전처리다

1
2
3
4
5
from sklearn.perprocessing import StandeardScaler
ss=StandardScaler()
ss.fit(train_input)
train_scaled=ss.transform(train_input)
test_scaled=ss.transform(test_input)

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

1
2
3
4
5
6
7
from sklearn.linear_model import LogisticRegression
lr=LogisticRegression()
lr.fir(train_scaled, train_target)
print(lr.score(train_scaled, train_target))
print(lr.score(test_scaled, test_target))
#0.7808
#0.7776

점수가 별로 맘에 들지 않는다. 훈련 세트 테스트 세트 점수 둘 다 낮은게 과소적합이다.

해결하기 위해 규제 매개변수 C를 바꿔보자
뭐 slover매개변수에서 다른 알고리즘을 선택할 수도 있고 다항 특성을 만들어서 추가할 수도 있겠다

결정트리

결정 트리 모델이 이유를 설명하기 쉽다

데이터를 잘 나눌 수 있는 질문을 찾으면 계속 질문을 추가해서 분류 정확도를 높일 수 있다

사이킷런의 DecisionTreeClassifier 클래스를 사용해 결정트리 모델을 훈련해보자

fit()메서드로 훈련하고 score()로 정확도를 평가하면 된다

alt

1
2
3
4
5
6
7
8
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.9969
#0.8592

훈련세트 점수는 높고 테스트 세트 점수는 낮은 과대적합임을 볼 수 있다

plot_tree()함수를 사용해서 트리 그림을 보여준다

1
2
3
4
5
import matplotlib.pyplot as plt
from sklearn.tree import plot_tree
plt.figure(figsize=(10,7))
plot_tree(dt)
plt.show()

alt

위에서 아래로 자라는 나무로 맨 위의 노드를 루프 노드라고 하고 맨 아래 노드를 리프 노드라고 한다

깊이를 제한해서 그려보자 max_depth 매개변수를 1을 주면 루트 노드를 제외하고 하나의 노드를 더 그려준다

filled 매개변수를 통해 색도 줄 수 있다

featuer_names 매개변수에는 특성 이름을 전달 할 수 있다

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

alt

alt

alt

루트 노드의 총 샘플 수는 5197개이다. 이 중에서 음성 클래스(레드 와인)은 1258개, 양성 클래스(화이트 와인)은 3939개이다. value를 통해 알 수 있다

<- 레드와인
-> 화이트와인

alt

음성 클래스가 81개 양성 클래스가 2194개로 대부분의 화이트 와인이 이 노드로 이동했다

노드 바탕 색도 의미가 있다. filled=True로 지정하면 클래스마다 색깔을 부여하고 어떤 클래스의 비율이 높아지면 점점 진한 색으로 표시한다

alt

루트 노드보다 화이트와인 비율이 크게 줄었다.

결정트리에서 리프 노드가 가장 많은 클래스가 예측 클래스가 된다. k-최근접 이웃과 비슷하다. 결정트리를 여기까지 한다고 하면 왼쪽 노드에 도달한 샘플과 오른쪽 노드에 도달한 샘플은 모두 양성 클래스로 예측된다. 두 노드 모두 양성 클래스의 개수가 많기 때문이다

??

불순도

불순도란 다양한 범주들의 개체들이 얼마나 포함되어 있는가를 의미한다. 여러가지 클래스가 섞여있는 정도를 말한다. 트리는 불순도가 낮아지게끔 분할 기준을 선택한다.

gini는 지니 불순도를 의미한다. DecisionTreeClassifier 클래스의 criterion 매개변수의 기본값이 ‘gini’이다. criterion 매개변수는 노드에서 데이터를 분할할 기준을 정한다.

지니 불순도 개산 방법

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

루트 노드 총 5197개, 음성 클래스 1258개, 양성 클래스 3939개
1-((1258/5197)2+(3939/5197)2)=0.367

만약 100개의 샘플이 있는 어떤 노드의 두 클래스의 비율이 정확히 1/2씩이라면 지니 불순도는 0.5가 되어 최악이다!

노드에 하나의 클래스만 있다면 지니 불순도는 0이 되어 가장 작다. 이를 순수 노드라고 한다.

결정 크리 모델은 부모 노드와 자식 노드의 불순도 차이가 크도록 트리를 성장시킨다. 부모 노드와 자식 노드의 불순도 차이를 계산하는 방법을 알아보자

정보이득=불순도 차이=부모의 불순도-(왼쪽 노드 샘플 수/부모의 샘플 수)x왼쪽 노드 불순도-(오른쪽 노드 샘플 수/보무의 샘플 수)x오른쪽 노드 불순도

왼쪽 노드로는 2922개, 오른쪽 노드로는 2275개

0.367 -(2922/5197) x 0.481 - (2275/5197) x 0.069 = 0.066

정보 이득이 최대가 되도록 데이터를 나누자

사이킷런에는 또 다른 불순도 기준이 있다.

DicisionTreeClassifier 클래스에서 criterion=’entropy’를 지정하여 엔트로피 불순도를 사용할 수 있다. 엔트로피 불순도는 제곱이 아니라 밑이 2인 로그를 사용하여 곱한다.

엔트로피 불순도= -음성 클래스 비율 x log2(음성 클래스 비율) - 양성 클래스 비율x log2(양성 클래스 비율)

-(1258/5197) x log2(1258/5197) - (3939/5197) x log2(3939/5197) = 0.798

불순도 기준을 사용해 정보 이득이 최대가 되도록 노드를 분할한다. 노드를 순수하게 나눌수록 정보 이득이 커진다.

마지막에 도달한 노드의 비율을 보고 예측을 만든다.

가지치기

트리가 무작정 끝까지 자라지 않게 하기 위해 가지치기를 해줘야 한다. 일반화, 훈련 세트에는 잘 맞지만 테스트 세트에서는 점수가 못 미칠 것이기 때문이다.

가지치기를 어떻게 하냐? 최대 깊이를 정하면 된다. DecisionTreeClassifier 클래스의 max_depth 매개변수를 3으로 지정하여 모델을 만들어보자. 이러면 루트 노드 아래로 3개까지만 자라나게 된다

1
2
3
4
5
6
dt=DecisionTreeClassifier(max_depth=3, random_state=42)
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target))
print(dt.score(dt.score(test_scale, test_target)))
#0.8454
#0.8415
1
2
3
plt.figure(figsize(20,15))#그래프 크기
plot_tree(dt, filled=True, feature_names=['alcohol','sugar','pH'])
plt.show()

alt

루트 노드 다음에 깊이 1노드는 당도(sugar)을 기준으로 훈련 세트를 나눈다. 깊이 2의 노드는 맨 왼쪽 노드만 당도를 기준으로 나누고 왼쪽에서 두 번째 노드는 알코올 도수(alcohol)를 기준으로 나눈다. 오른쪽 두 노드는 pH를 기준으로 한다.

이제 3이 최종 노드, 리프 노드이다.

왼쪽에서 세번째 노드만 음성 클래스가 더 많다. 이 부분에 도달한 것들만 레드 와인으로 예측되는 것이다. 여기로 도달하려면 당도는 -0.239보다 작고 -0.872보다 작아야 한다. 알코올 도수는 0.454보다 작아야 한다.

하지만 이상한 게 하나 있다. 음수로 된 당도는 어떻게 된 것인가? 불순도는 클래스별 비율을 가지고 계산했다. 샘플을 클래스로 나눌 때 특성값 스케일이 계산에 영향을 미치지 않는다. 따라서! 표준화 전처리가 필요없다. 결정 트리 알고리즘의 장점 중 하나다.

그러면 전처리 하지 않고 훈련세트와 테스트 세트로 훈련시켜보자

1
2
3
4
5
6
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.8454
#0.8415
1
2
3
plt.figure(figsize=(20,15))
plot_tree(dt, filled=True, feature_names=['alcohol','sugat','pH'])
plt.show()

alt

결과적으로는 같은 트리지만 특성값을 표준점수로 바꾸지 않아서 이해하기 더 쉽다. 당도가 1.625와 같거나 작은 와인 중에 알코올 도수가 11.025와 같거나 작은 것이 레드 와인이다. 나머지는 다 화이트 와인!

어떤 특성이 가장 유용한지 특성 중요도를 계산해보자. 틀의 루트 노드와 깊이 1에서 당도를 사용했기 때문에 당도(sugar)이 가장 유용한 특성 중에 하나같다. 특성 중요도는 결정트리 모델의 feature_importances_ 속성에 있다

1
2
3
print(dt.feature_importances_)

#[0.12345626 0.86862934 0.0079144]

두 번째 특성인 당도가 0.87로 특성 중요도가 가장 높다. 그다음 알코올 도수, pH이다. 이 값을 모두 더하면 1이 된다.

특성 중요도는 (각 노드의 정보 이득 x 전체 샘플에 대한 비율)한 후 특성 별로 더하여 계산한다.

이를 활용하면 결정 트리 모델의 특성 선택에 활용할 수 있다. 이것도 장점!


마무리

키워드

결정트리 불순도 정보 이득 가지치기 특성 중요도

핵심 패키지와 함수

pandas

info() describe()

scikit-learn

DecisionTreeClassifier plot_tree()


참고자료

This post is licensed under CC BY 4.0 by the author.