혼자 공부하는 머신러닝+딥러닝 Chapter 5
5. 트리 알고리즘
5-1. 결정 트리
로지스틱 회귀로 와인 분류하기
우선, 와인 데이터를 불러옵니다.
1
2
import pandas as pd
wine = pd.read_csv('https://bit.ly/wine-date')
와인 데이터셋을 제대로 읽어 들였는지 head() 메서드로 처음 5개의 샘플을 확인해봅니다.
1
wine.head()
처음 3개의 열은 알코올 도수, 당도, pH 값을 나타내고 네 번째 열(class)은 타깃값으로 0이면 레드 와인, 1이면 화이트 와인입니다.
즉, 레드 와인과 화이트 와인을 구분하는 이진 분류 문제이고, 화이트 와인이 양성 클래스입니다.
판다스 데이터프레임의 유용한 메서드 2개
판다스 데이터프레임을 넘파이 배열로 바꾸고 훈련 세트와 테스트 세트로 나눕니다.
1
2
3
4
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)
만들어진 훈련 세트와 테스트 세트의 크기를 확인해보죠.
1
print(train_input.shape, test_input.shape)
(5197,3) (1300, 3)
훈련 세트는 5197개이고, 테스트 세트는 1300개입니다. 이제 StandardScaler 클래스를 사용해 훈련 세트를 전처리해 보죠.
1
2
3
4
5
from sklearn.preprocessing import StandardScaler
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
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
훈련 세트와 테스트 세트의 점수가 모두 낮으니 모델이 다소 과소적합된 것 같습니다.
결정 트리(Decision Tree)
결정 트리 모델은 스무고개와 같고 사이킷런이 결정 트리 알고리즘을 제공합니다. 사이킷런의 DecisionTreeClassifier 클래스를 사용해 결정 트리 모델을 훈련해 보죠.
1
2
3
4
5
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))
0.996921300750433
0.8576923076923076
훈련 세트에 대한 점수가 엄청 높고 테스트 세트의 성능은 그에 비해 조금 낮으므로 과대적합된 모델이라고 볼 수 있습니다. 이 모델을 사이킷런에서는 plot_tree() 함수를 사용해 이해하기 쉬운 트리 그림으로 출력할 수 있습니다. 위에서 만든 결정 트리 모델 객체를 plot_tree() 함수에 전달해서 어떤 트리가 만들어졌는지 그려보죠.
결정 트리는 위에서부터 아래로 자라납니다. 맨 위의 노드를 루트 노드(root node)라 부르고 맨 아래 끝에 달린 노드를 리프 노드(leaf node)라고합니다.
노드가 뭔가요? 결정 트리를 구성하는 핵심 요소로, 훈련 데이터의 특성에 대한 테스트를 표현합니다.{: .prompt-tip }
너무 복잡하니 트리의 깊이를 제한해서 출력해 보죠. max_depth 매개변수를 1로 주면 루트노드를 제외하고 하나의 노드를 더 확장하여 그립니다. 또 filled 매개변수에서 클래스에 맞게 노드의 색을 칠할 수 있습니다. feature_names 매개변수에는 특성의 이름을 전달할 수 있습니다. 한번 이렇게 그려보죠.
1
2
3
plt.figure(figsize=(10, 7))
plot_tree(dt, max_depth=1, filled=True, feature_names=['alcohol', 'sugar', 'pH'])
plt.show()
결정 트리에서 예측하는 방법은 간단합니다. 리프 노드에서 가장 많은 클래스가 예측 클래스가 됩니다.
불순도
gini는 지니 불순도(Gini impurity)를 의미합니다. DecisionTreeClassifier 클래스의 노드에서 데이터를 분할할 기준을 정하는 criterion 매개변수의 기본값이 ‘gini’입니다. 지니 불순도는 클래스의 비율을 제곱해서 더한 다음 1에서 빼면 됩니다.
- 지니 불순도 = 1 - (음성 클래스 비율2 + 양성 클래스 비율2)
- 정보이득(부모와 자식 노드 사이의 불순도 차이) = 부모의 불순도-(왼쪽 노드 샘플 수/부모의 샘플 수)x왼쪽 노드 불순도-(오른쪽 노드 샘플 수/보무의 샘플 수)x오른쪽 노드 불순도
사이킷 런에는 또 다른 불순도 기준이 있습니다. DicisionTreeClassifier 클래스에서 criterion = ‘entropy’를 지정하여 엔트로피 불순도를 사용할 수 있습니다. 엔트로피 불순도는 제곱이 아니라 밑이 2인 로그를 사용하여 곱합니다.
- 엔트로피 불순도= -음성 클래스 비율 x log2(음성 클래스 비율) - 양성 클래스 비율x log2(양성 클래스 비율)
가지치기
과수원에서 가지치기를 하듯이 결정 트리도 가지치기를 해야합니다. 그렇지 않으면 무작정 끝까지 자라나는 트리가 만들어지기 때문이죠. 결정 트리에서 가지치기를 하는 가장 간단한 방법은 자라날 수 있는 트리의 최대 깊이를 지정하는 것입니다. DecisionTreeClassifier 클래스의 max_depth 매개변수를 3으로 지정하여 모델을 만들어 봅니다. 이렇게 하면 루트 노드 아래로 최대 3개의 노드까짐나 성장할 수 있습니다.
1
2
3
4
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
훈련 세트의 성능은 낮아졌지만 테스트 세트의 성능은 거의 그대로입니다. 트리 그래프를 그려보죠.
1
2
3
plt.figure(0, figsize=(20, 15))
plot_tree(dt, filled=True, feature_names=['alcohol', 'sugar', 'pH'])
plt.show()
루트 노드 다음에 있는 깊이 1의 노드는 당도를 기준으로 나눕니다. 깊이 2의 노드는 맨 왼쪽의 노드는 당도를 기준으로, 왼쪽에서 두 번째 노드는 알코올 도수(alcohol)를 기준으로, 오른쪽의 두 노드는 pH를 사용하네요. 깊이 3에 있는 노드가 최종 노드인 리프 노드입니다. 왼쪽에서 세 번째에 있는 노드만 음성 클래스가 더 많습니다. 즉, 이 노드에 도착해야만 레드 와인으로 예측합니다. 그럼 루트 노드부터 이 노드까지 도달하려면 당도는