[혼자 공부하는 머신러닝+딮러닝] 트리 알고리즘 - 결정 트리
로지스틱 회귀로 와인 분류하기
알코올, 도수, 당도, pH 값으로 와인의 종류를 구별해보자
와인은 레드와인과 화이트와인이 있다
import pandas as pd
wine = pd.read_csv("https://bit.ly/wine_csv_data")
wine.head()

네번째 열은 타깃값으로 레드와인이면 0, 화이트 와인이면 1이다
로지스틱 회귀로 훈련하기 전에 판다스의 유용한 메서드를 알아보자
info()
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
'''
info는 데이터프레임의 각 열에 누락된 데이터가 있는지 확인하는 함수이다.
전체 데이터가 6497개 인데 모두 6497 non-null인것을 보니 누락된 값은 없는 것을 알 수 있다.
describe()
wine.describe()
이 함수는 열에 대한 간략한 통계를 출력해준다

여기서 알 수 있는 것은 알코올 도수와 당도, pH 값의 스케일이 다르다는 것이다
이전에 했던 것 처럼 사이킷런의 StandardScaler 클래스를 사용해 특성을 표준화 시켜야 한다!
그 전에 먼저 판다스 데이터프레임을 넘파이 배열로 바꾸고 훈련 세트와 테스트 세트로 나누자
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.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target)) # 0.7808350971714451
print(lr.score(test_scaled, test_target)) # 0.7776923076923077
결과가 그렇게 좋지 않다...
둘다 점수가 낮아서 과소적합으로 보인다
결정트리
순서도를 그리며 모델을 설명하는 방법을 결정트리라고 한다.
사이킷런은 DecisioniTreeClassifier 클래스를 사용해 결정 트리 모델을 훈련시킬 수 있다.
동일한 데이터 셋을 결정 트리 모델을 훈련해보자
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(random_state = 42)
'''
사이킷런의 결정 트리 알고리즘은 노드에서 최적의 분할을 찾기 전에 특성의 순서를 섞는다.
따라서 약간의 무작위성이 주입되는데 실행할 때마다 점수가 조금씩 달라질 수 있기 때문이다.
여기서는 실습결과와 책 내용 결과 유지를 위해 random_state를 지정하지만 실전에선 필요하지 않다.
'''
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target)) # 0.996921300750433
print(dt.score(test_scaled, test_target)) # 0.8592307692307692
훈련 세트의 점수가 엄청나게 높아서 과대적합된 모델이다
이 모델을 그림으로 표현하려면 plot_tree함수를 사용하면 된다
import matplotlib.pyplot as plt
from sklearn.tree import plot_tree
plt.figure(figsize = (10,7))
plot_tree(dt)
plt.show()

그림이 너무 복잡하니 트리의 깊이를 제한해서 출력해보자
plt.figure(figsize=(10,7))
plot_tree(dt, max_depth=1, filled=True, feature_names = ['alcohol', 'sugar', 'pH'])
plt.show()

노드의 조건을 만족하면 왼쪽 불만족하면 오른쪽으로 가는 방식이다.
샘플 5197개 중 음성클래스(레드 와인)은 1258개 이고, 양성 클래스(화이트 와인)은 3939개이다.
샘플 5197개 중 조건을 만족하는 샘플이 2922개가 왼쪽으로 갔고 불만족하는 샘플 2275가 오른쪽으로 갔다
결정 트리에서 예측하는 방법은 간단하다. 리프 노드에서 가장 많은 클래스가 예측 클래스가 된다.
k-최근접 이웃과 매우 비슷하다고 할 수 있다.
gini
노드 상자 안에 gini는 지니 불순도 (Gini impurity)를 의미한다.
DecisionTreeClassfier 클래스의 critierion 매개변수의 기본값이 'gini'이다.
critierion 매개변수의 용도는 노드에서 데이터를 분할할 기준을 정하는 것이다.
앞의 트리에서 루트노드는 어떻게 당도 -0.239를 기준으로 정했을까?
바로 criterion 매개변수에 지정한 지니 불순도를 사용한다
지니 불순도는 클래스의 비율을 제곱해서 더한 다음 1에서 빼면 된다
지니 불순도 = 1 - (음성 클래수 비율^2 + 양성 클래스 비율^2)
결정트리 모델은 부모 노드와 자식 노드의 불순도 차이가 가능한 크도록 트리를 성장시킨다
부모와 자식 노드 사이의 불순도 차이를 정보이득이라고 부른다
가지치기
결정 트리에서 가지치기를 하는 가장 간단한 방법은 자라날 수 있는 트리의 최대 깊이를 지정하는 것이다
max_depth 매개변수를 3으로 지정하여 모델을 만들어보자
dt = DecisionTreeClassifier(max_depth = 3, random_state = 42)
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target)) # 0.8454877814123533
print(dt.score(test_scaled, test_target)) # 0.8415384615384616
훈련 세트의 성능은 낮아졌지만 테스트 성능은 그대로다
plt.figure(figsize=(20,15))
plot_tree(dt, filled=True, feature_names=["alcohol", "sugar", "pH"])
plt.show()

깊이 3에 있는 노드가 최종 노드인 리프노드이다
왼쪽에서 세번째에 있는 노드만 음성 클래스가 많으므로 이 노드에 도착해야만 레드 와인으로 예측한다
결정트리는 표준화 전처리 과정이 필요 없다!!