인공지능 관련

결정 트리

PGNV 2021. 5. 12. 16:31

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

 

 

 

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

cs데이터 다운로드하기

 

wine.head()

알코올 도수, 당도, PH값을 차례대로 나타냅니다. 

class는 타깃값이 0이면 레드와인, 1이면 화이트 와인입니다.

 

wine.info()

데이터 info()메서드 입니다. 데이터프레임의 각 열의 데이터 타입과 누락된 데이터가 있는지 확인하는데 유용합니다

 

 

 

 

wine.describe()

describe() 메서드는 열에 대한 간략한 통계를 출력해줍니다. 최소, 최대 ,평균값을 볼수 있습니다

평균(mean), 표준편차(std), 최소(min), 최대(max) 값을 볼 수 있습니다

중간값(50%), 1사분위수(25%), 3사분위수(75%)를 알려줍니다.

 

 

 

 

 

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

wine 데이터 프레임에서 처음 3개의 열을 넘파이 배열로 바꿔서 data 배열에 저장하고,

마지막 class열을 넘파이 배열로 바꿔서 target 배열에 저장했습니다

 

 

 

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)

훈련 세트와 테스트 세트로 나눈 다음 train_test_split() 함수는 설정값을 지정하지 않으면 25%를 테스트 세트로 지정합니다

샘플 개수가 많아서 20% 정도만 테스트 세트로 나눴습니다 (test_size=0.2 이런 의미입니다)

 

 

print(train_input.shape, test_input.shape)
#출력: (5197, 3) (1300, 3)

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

 

 

 

 

StandardScaler 클래스를 사용해 훈련세트를 전처리 합니다. 그다음 같은 객체를 그대로 사용해 테스트 세트를 변환합니다.

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

점수가 높지 않습니다. 훈련 세트와 테스트 세트의 점수가 모두 낮으니 모델이 과소적합이 된거 같스빈다

이문제를 해결하기 위해 규제 매개변수인 C의 값을 바꿔봅니다. 아니면 solver 매개변수에서 다른 알고리즘을 선택할수도 있습니다 또는 다항 특성을 만들어 추가 할 수도 있습니다

 

 

 

 

 

결정 트리(Decision Tree) 모델을 사용하면 이러한 문제를 해결할 수 있습니다.

결정 트리 모델은 스무고개와 같습니다 질문을 하나씩 던져서 정답과 맞춰나갑니다.

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

사이킷런이 결정 트리 알고리즘을 제공합니다 DecisionTreeClassifier 클래스를 사용해 결정 트리 모델을 훈련해 보죠.

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()함수를 사용해 결정 트리를 이해하기 쉬운 트리 그림으로 출력합니다.

결정 트리 맨위노드를 루트 노드(root node)라고 부르고 맨아래 끝을 리프 노드(leaf node)라고 합니다

 

 

 

 

 

위에 그림은 너무 복잡해서 plot_tree()함수에서 트리의 깊이를 제한해서 출력해봅니다.

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

위에 그림을 해석하면

테스트 조건(sugar)

불순도(gini)

총 샘플 수 (samples)

클래스별 샘플 수 (value)

좌측:yes  우측:no

입니다.

루트 노드가 당도(sugar) -0.239보다 작거나 같으면 왼쪽 아니면 오른쪽으로 갑니다.

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

 

 

gini는 지니 불순도(Gini impurity)를 의미합니다. DecisionTreeClassifier 클래스의 criterion 매개변수의 기본값이 'gini'입니다.

criterion 매개변수의 용도는 노드에서 데이터를 분할할 기준을 정하는 것입니다.

어떻게 당도(sugar) -0.239 기준으로 왼쪽과 오른쪽 노드로 나눌까요?

바로 criterion매개변수에 지정한 지니 불순도를 사용합니다.

지니 불순도는 어떻게 계산할까요?

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

이 데이터에서는 총 5197개 샘플이 있고 음성클래스:1258개 양성클래스:3939개

지니 불순도 = 1-((1258/5197)^2+(3939/5197)^2 = 0.367

다른 예로 100개의 샘플중 두노드의 두클래스 비율이 정확히 1/2이면 지니 불순도는 0.5 최악입니다.

지니 불순도 = 1-((50/100)^2 + (50/100)^2) = 0.5

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

지니 불순도 = 1-((0/100)^2+(100/100)^2) = 0

 

 

결정트리모델은 부모노드(parent node)자식노드(child node) 불순도 차이가 가능한 크도록 트리를 성장 시킵니다.

부모의 불순도-(왼쪽 노드 샘플수/부모의 샘플 수) X 왼쪽 노드 불순도 - (오른쪽 노드 샘플 수/부모의 샘플 수) X 오른쪽 노드 불순도 

= 0.367 - (2922/5197) X 0.481 - (2275/5197) X0.069=0.066

이런 부모와 자식 노드 사이의 불순도 차이를 정보 이득(information gain)이라고 부릅니다.

결정 트리 알고리즘은 정보 이득이 최대가 되도록 데이터를 나눕니다. 이때 지니 불순도를 기준으로 사용합니다.

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

 

 

DecisionTreeClassifier클래스에서 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개의 노드까지만 성장할 수 있습니다.

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


 

 

훈련 세트의 성능은 낮아졌지만 테스트 세트의  성능은 거의 그대로 입니다.

이런 모델은 트리 그래프로 그리면 이해하기 쉽습니다.

plot_tree()함수로 그려봅니다.

 

-0.802라는 음수로 된 당도는 어떻게 설명 해야할까요?

앞서 불순도를 기준으로 샘플을 나눈다고 했습니다. 불순도는 클래스별 비율을 가지고 계산했죠

샘플을 어떤 클래스 비율 나누는지 계산할 때 특성값의 스케일이 계산에 영향을 미칠까요?

전혀 영향을 주지않습니다. 따라서 결정트리 알고리즘은 표준화 전처리를 할필요가 없습니다.

이게 결정트리 알고리즘의 또 다른 장점입니다.

 

-0.802라는 음수로 된 당도를 전처리하기전에 훈련 세트(train_input)와 테스트 세트(test_input)로 결정트리 모델로 다시 훈련 해보겠습니다

 

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

같은 트리지만 특성값은 표준점수로 바꾸지 않아서 이해하기가 훨씬 쉽습니다.

 

 

마지막으로 어떤 특성이 가장 유용한지 나타내는 중요도를 계산해 줍니다.

트리의 루트 노드와 깊이 1에서 당도(sugar)를 사용했기 때문에 아마도 당도(sugar)가 가장 유용한 특성 중 하나 일것입니다.

특성 중요도는 결정트리 모델 feature_importances_ 속성에 저장 되어 있습니다.

print(dt.feature_importances_)
#출력 : [0.12345626 0.86862934 0.0079144 ]
#       alcohol,     sugar,        ph

두번째 특성인 0.86862934 당도가 특성 중요도가 가장 높습니다.

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

특성 중요도를 활용하면 결정 트리모델을 특성 선택에 활용 할 수 있습니다.

이것이 결정트리 알고리즘의 또 다른 장점입니다.

 

 

참고 : 혼자 공부하는 머신러닝+딥러닝