[혼자공부하는 머신러닝 + 딥러닝] 10_결정트리
- 결정트리
- 이번에는 생선에 이어 신상품으로 캔 와인을 판매하려 한다. 주류는 온라인 판매가 불가능해서 예약 후 오프라인 매장에서 구매를 유도할 계획이다.
- 급하게 입고되다 보니 와인이 화이트와인과 레드와인이 섞여버렸다. 각각의 와인에는 표기가 되어있어야 하지만 표기가 되어있지 않다. 해당 캔에는 알코올, 도수, 당도, pH값만 기재되어있다.
- 먼저, 위의 값들을 이용해 로지스틱 회귀 모델을 활용해보자
1. 들어가며
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.tree import plot_tree
wine = pd.read_csv('https://bit.ly/wine_csv_data')
ss = StandardScaler()
lr = LogisticRegression()
dt = DecisionTreeClassifier(random_state=42)
wine.head()
alcohol | sugar | pH | class | |
---|---|---|---|---|
0 | 9.4 | 1.9 | 3.51 | 0.0 |
1 | 9.8 | 2.6 | 3.20 | 0.0 |
2 | 9.8 | 2.3 | 3.26 | 0.0 |
3 | 9.8 | 1.9 | 3.16 | 0.0 |
4 | 9.4 | 1.9 | 3.51 | 0.0 |
- 처음 3개의 열은 각각 도수, 당도, pH값을 나타낸다. 네 번째 열은 타깃값으로 0이면 레드와인 1이면 화이트와인이라고 한다.
- 레드와인과 화이트와인을 분류하는 이진분류 문제이고, 화이트와인이 양성클래스이다. 즉, 전체 와인에서 화이트와인을 골라내는 문제라고 볼 수 있다.
wine.info() # 누락된 값이 존재하는지 확인하기 위해 사용
<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
wine.describe() # 기초통계량을 알아보고 이를 활용하기 위해 사용
alcohol | sugar | pH | class | |
---|---|---|---|---|
count | 6497.000000 | 6497.000000 | 6497.000000 | 6497.000000 |
mean | 10.491801 | 5.443235 | 3.218501 | 0.753886 |
std | 1.192712 | 4.757804 | 0.160787 | 0.430779 |
min | 8.000000 | 0.600000 | 2.720000 | 0.000000 |
25% | 9.500000 | 1.800000 | 3.110000 | 1.000000 |
50% | 10.300000 | 3.000000 | 3.210000 | 1.000000 |
75% | 11.300000 | 8.100000 | 3.320000 | 1.000000 |
max | 14.900000 | 65.800000 | 4.010000 | 1.000000 |
wine.columns
Index(['alcohol', 'sugar', 'pH', 'class'], dtype='object')
data = wine[['alcohol', 'sugar', 'pH']].to_numpy()
target = wine['class'].to_numpy()
- 위의 통계량을 통해 알 수 있는 것은 도수, 당도 pH의 스케일이 다르다는 것이다. 이를 먼저 표준화해주어야 한다.
- 데이터를 나누고 표준화를 진행한다.
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%로 조정했다.
print(train_input.shape, test_input.shape)
(5197, 3) (1300, 3)
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)
- 스케일링 된 데이터를 사용해 로지스틱 회귀 모델의 훈련을 준비한다.
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 매개변수에서 다른 알고리즘을 선택할 수도 있다. 또는 다항 특성을 만들어 추가 할 수도 있다.
- 로지스틱 회귀가 학습한 계수와 절편을 출력하면 아래와 같다.
print(lr.coef_, lr.intercept_)
[[ 0.51270274 1.6733911 -0.68767781]] [1.81777902]
2. 결정트리
- 위의 로지스틱 회귀의 결과 등이나 대부분의 머신러닝 모델은 학습의 결과를 설명하기가 어렵다는 문제가 있다. 이를 쉽게 하기 위해 결정트리 모델을 사용한다.
- 사이킷런에서도 이런 결정트리 모델을 사용할 수 있다. 사이킷럿의 결정트리 알고리즘은 노드에서 최적의 분할을 찾기 전에 특성의 순서를 섞는다. 즉, 무작위성이 주입된다. 지금은 이해를 위해 random_state를 이용해 일정하게 만들어주지만 실전에서는 쓰이지 않는다.
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()를 이용해 그릴수있다.
plt.figure(figsize=(10,7))
plot_tree(dt)
plt.show()
- 해당 그림의 맨위 노드는 루트 노드라 부르고 맨 아래 끝에 달린 노드를 리프노드라고 한다.
- 여기서 노드란 결정 트리를 구성하는 핵심 요소를 말한다. 노드는 훈련 데이터의 특성에 대한 테스트를 표현한다. 일반적으로 하나의 노드는 두개의 가지를 가진다.
- 너무 복잡하기 때문에 깊이를 제한해서 그려볼 수도 있다. max_depth매개변수를 1로 주면 루트노드를 제외하고 하나의 노드를 더 확장해 그린다. feature_names 매개변수에는 특성의 이름을 전달할 수 있다.
plt.figure(figsize=(10,7))
plot_tree(dt, max_depth = 1, filled=True, feature_names=['alchol', 'sugar', 'pH'])
plt.show()
- 위의 그림을 통해 노드를 더 잘 이해할 수 있다. 각각의 노드의 설명을 확인해보도록 하자.
- 각각 당도가 얼마 이하인지 질문하고 샘플이 몇개인지, 그리고 불순도, 양성 음성 클래스가 몇개인지를 나타내준다.
- 결정 트리에서 예측하는 방법은 간단하다. 리프 노드에서 가장 많은 클래스가 예측 클래스가 된다. 그렇다면 위의 불순도가 무엇인지 살펴보자.
3. 불순도
- 여기서 gini는 지니 불순도를 의미한다. 의사결정나무 클래스의 criterion 매개변수의 기본값이 ‘gini’이다. criterion 매개변수의 용도는 노드에서 데이터를 분할할 기준을 정하는 것을 말한다.
- 지니 불순도의 계산은 클래스의 비율을 제곱해서 더한 다음 1에서 빼면 된다.
- 결정 트리 모델은 부모 노드와 자식 노드의 불순도 차이가 가능한 크도록 트리를 성장시킨다. 부모 노드와 자식 노드의 불순도 차이를 계산하는 방법은 자식 노드의 불순도를 샘플 개수에 비례해 모두 더한 뒤 부모 노드의 불순도에서 뺀다. 이런 부모와 자식 노드 사이의 불순도 차이를 정보 이득이라고 한다.
- 분순도 기준은 지니 이외에도 엔트로피 불순도도 존재한다. 이를 사용하기 위해서 criterion 매개변수를 ‘entropy’로 바꾼다.
- 엔트로피 불순도도 노드의 클래스 비율을 사용하지만 지니 불순도처럼 제곱이 아니라 밑이 2인 로그를 사용하여 곱한다.
- 일반적으로 지니 불순도와 엔트로피 불순도가 만든 결과의 차이는 크지 않다.
- 결정트리 알고리즘에서는 불순도 기준을 사용해 정보 이득이 최대가 되도록 노드를 분할한다. 노드는 순수하게 나눌수록 정보이득이 커진다. 그리고 새로운 샘플에 대해 예측할 때에는 노드의 질문에 따라 트리를 이동한다. 마지막에 도달한 노드의 클래스 비율을 보고 예측을 만든다.
- 위의 트리는 제한 없이 자라났기 때문에 훈련 세트보다 테스트 세트에서 점수가 크게 낮았던 것이다. 이를 수정해보자
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
plt.figure(figsize=(20,15))
plot_tree(dt, filled=True, feature_names=['alchol', 'sugar', 'pH'])
plt.show()
- 가지치기를 통해 본 트리는 보다 읽기 쉽다. 하지만 이는 실습용 데이터의 트리 깊이가 비교적 얼마 되지 않아서 해석이 쉬운것도 있다.
- 하지만 이를 설명하는데 있어서 당도가 음수로나오는 문제가 있다. 앞서 스케일링 작업을 거쳐서 그런 것인데, 의사결정 나무 모델을 사용하면 표준화 전처리 작업을 하지 않아도 된다는 장점이 있다.
- 아래에서 기존의 input데이터를 이용해 다시 작성해보자
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=['alchol', 'sugar', 'pH'])
plt.show()
- 결론적으로는 같은 트리이지만 특성값을 표준점수로 바꾸지 않아 이해하기가 더 쉽다.
- 결정트리는 어떤 특성이 가장 유용한지를 나타내는 특성 중요도를 계산해준다. 이러한 특성 중요도는 결정 트리 모델의 feature_importances_속성에 저장되어 있다.
print(dt.feature_importances_)
[0.12345626 0.86862934 0.0079144 ]
- 결과를 확인해보면 역시 당도가 가장 높은 중요도를 가지고 있다는 것을 알 수 있다.
5. 마치며
- 이번시간에는 의사결정 나무에 대해 배워보았다. 예/아니오에 대한 질문을 이어나가면서 정답을 찾아 학습하는 알고리즘을 말한다.
- 결정트리에는 불순도라는 기준이 존재한다. 불순도 기준을 이용해 정보이득이 최대가 되도록 분할한다. 이런 불순도 기준에는 엔트로피와 지니 불순도가 있다.
- 위의 정보이득은 부모 노드와 자식 노드의 불순도 차이를 말한다.
- 결정 트리는 제한 없이 자라면 과대적합된다. 따라서 가지치기를 통해 제한을 해주어 과대적합을 막아야 한다.
- 결정 트리는 특성 중요도를 가지고있다. 이는 결정 트리에 사용된 특성이 불순도를 감소하는데 기여한 정도를 나타낸느 값을 말한다.
- 자주 접하는 모델이다보니 가장 친숙했던것 같다. 배우면 배울수록 흥미가 생기는것 같다.
Leave a comment