* 교재: 혼자 공부하는 머신러닝+딥러닝 (hanbit.co.kr)
* 문제: 결정트리로 레드 와인과 화이트 와인을 분류한다.
알코올 도수, 당도, pH 데이터로 화이트 와인을 골라내는 이진 분류 로지스틱 회귀 모델을 훈련한 후 더 좋은 성능을 위해 결정 트리로 레드 와인과 화이트 와인을 분류한다.
1. 로지스틱 회귀로 와인 분류하기
레드 와인과 화이트 와인을 구분하는 이진 분류 문제이다.
먼저 6497개의 와인 샘플 데이터를 불러오자.
import pandas as pd
wine = pd.read_csv('https://bit.ly/wine_csv_data')
wine.head() # 처음 5개 행 출력
와인 데이터셋을 판다스 데이터프레임으로 제대로 읽어 들였는지 처음 5개의 샘플을 출력하였다.
alcohol은 도수, sugar는 당도, pH는 pH 값, class는 타깃값이다.
타깃값이 0이면 레드 와인, 1이면 화이트 와인이다.
즉 전체 와인 데이터에서 화이트 와인을 골라내는 문제이다.
데이터프레임 각 열의 데이터 타입과 누락된 데이터가 있는지 출력해 보자.
wine.info() # 판다스 데이터프레임의 info() 메서드, 데이터프레임의 요약 정보 출력
출력 결과 총 6497개의 샘플이 있고 4개의 열은 모두 실수값(float)이다.
또한 Non-Null Count가 모두 6497이므로 누락된 값은 없다.
열에 대한 간단한 통계를 출력해 보자.
wine.describe() # 판다스 데이터프레임의 describe() 메서드, 데이터프레임 열의 통계 출력
평균(mean), 표준편차(std), 최소(min), 중간값(50%) 등이 출력되었다.
출력 결과 알코올 도수, 당도, pH 값의 스케일 모두 다르기 때문에 특성을 표준화해야 한다.
특성을 표준화 전처리 하기 전에 판다스 데이터프레임을 넘파이 배열로 바꾸고 훈련 세트와 테스트 세트를 만들어보자.
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)
# 만들어진 훈련 세트와 테스트 세트의 크기 출력
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)) # 훈련 세트 점수 출력: 0.7808350971714451
print(lr.score(test_scaled, test_target)) # 테스트 세트 점수 출력: 0.7776923076923077
표준 점수로 변환된 train_scaled와 test_scaled로 로지스틱 회귀 모델을 훈련하였다.
출력 결과 훈련 세트와 테스트 세트의 점수가 모두 낮으므로 과소적합되었다.
로지스틱 회귀가 학습한 계수와 절편을 출력해 보자.
print(lr.coef_, lr.intercept_)
# 출력: [[ 0.51270274 1.6733911 -0.68767781]] [1.81777902]
알코올 도수 x 0.51270274 + 당도 x 1.6733911 + pH x -0.68767781 + 1.81777902
위의 식의 값이 0보다 작으면 레드 와인, 크면 화이트 와인이다.
하지만 출력된 숫자가 정확히 어떤 의미인지 알기 어렵다.
이를 해결하기 위해 결정 트리 알고리즘을 사용해 보자.
2. 결정 트리로 와인 분류하기
결정 트리는 예 또는 아니오에 대한 질문을 이어나가면서 정답을 찾아 학습하는 알고리즘이다.
사이킷런의 DecisionTreeClassifier 클래스로 결정 트리 모델을 훈련해 보자.
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(random_state=42)
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target)) # 훈련 세트 점수 출력: 0.996921300750433
print(dt.score(test_scaled, test_target)) # 테스트 세트 점수 출력: 0.8592307692307692
출력 결과 훈련 세트 점수가 매우 높지만 테스트 세트는 점수가 낮으므로 과대적합되었다.
결정 트리를 이해하기 쉽게 그림으로 출력해 보자.
import matplotlib.pyplot as plt
from sklearn.tree import plot_tree # plot_tree() 함수는 트리 그림 출력
plt.figure(figsize=(10,7))
plot_tree(dt)
plt.show()
사이킷런의 plot_tree() 함수는 결정 트리를 이해하기 쉬운 트리 그림으로 출력한다.
결정 트리 나무는 위에서 아래로 자라기 때문에 맨 위를 루트 노드(root node), 맨 아래를 리프 노드(leaf node)라고 한다.
좀 더 간단한 그림을 보기 위해 트리의 깊이를 제한하여 출력해 보자.
plt.figure(figsize=(10,7))
plot_tree(dt, max_depth=1, filled=True, feature_names=['alcohol', 'sugar', 'pH'])
plt.show()
max_depth 매개변수를 1로 주면 루트 노드를 제외하고 노드를 확장하여 그린다.
또한 filled 매개변수에서 클래스에 맞게 노드 색을 칠할 수 있고, features_names 매개변수에는 특성의 이름을 전달할 수 있다.
노드를 하나씩 살펴보자.
루트 노드의 총 샘플 수(samples)는 5197개, 음성 클래스(레드 와인)는 1258개, 양성 클래스(화이트 와인)는 3939개이다.
어떤 샘플의 당도(sugar)가 -0.239보다 작거나 같으면 왼쪽 가지로, 그렇지 않으면 오른쪽 가지로 이동한다.
샘플의 당도가 -0.802보다 작거나 같으면 다시 왼쪽가지로, 그렇지 않으면 오른쪽 가지로 이동한다.
이 노드에서 음성 클래스(레드 와인)는 1177개, 양성 클래스(화이트 와인)는 1745개이다.
루트 노드보다 화이트 와인의 양이 3939개에서 1745개로 크게 줄었다.
이 노드에서 음성 클래스(레드 와인)는 81개, 양성 클래스(화이트 와인)는 2194개이다.
왼쪽 노드의 화이트 와인 샘플이 오른쪽 노드로 이동했기 때문에 왼쪽 노드의 화이트 와인이 적었던 것이다.
또한 이 노드가 배경 색이 가장 진한 이유는 클래스 비율이 높아졌기 때문이다.
노드 상자 내 'gini'는 지니 불순도를 의미한다.
불순도는 결정 트리가 최적의 질문을 찾기 위한 기준이다.
DecisionTreeClassifier 클래스의 criterion 매개변수의 기본값이 'gini'이다.
criterion 매개변수는 노드에서 데이터를 분할할 기준을 정한다.
루트 노드에서 당도를 -0.239 기준으로 왼쪽과 오른쪽 노드로 나눈 것이 바로 criterion 매개변수에 지정한 지니 불순도를 사용한 것이다.
지니 불순도 계산 방법은 클래스의 비율을 제곱하여 더한 다음 1에서 빼면 된다.
* 지니 불순도 계산 방법
1 - ((음성 클래스 비율)^ + (양성 클래스 비율)^) = 지니 불순도
* 루트 노드의 지니 불순도
1 - ((1258 / 5197)^ + (3939 / 5197)^) = 0.367
열매를 잘 맺기 위해 과수원에서 가지치기를 하는 것처럼 결정 트리도 가지치기를 해야 한다.
결정 트리를 가지치기해 보자.
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
가지치기를 하는 방법은 트리의 최대 깊이를 지정하면 된다.
DecisionTreeClassifier 클래스의 max_depth 매개변수를 3으로 지정하여 루트 노드 아래로 최대 3개의 노드만 성장하도록 하였다.
출력 결과 훈련 세트의 성능은 낮아졌지만 테스트 세트의 성능은 거의 변하지 않았다.
출력 결과를 이해하기 쉽게 트리 그래프를 그려보자.
plt.figure(figsize=(20,15))
plot_tree(dt, filled=True, feature_names=['alcohol', 'sugar', 'pH'])
plt.show()
루트 노드 바로 아래에 있는 깊이 1의 노드 2개 모두 당도(sugar)를 기준으로 훈련 세트를 나누었다.
그다음에 있는 깊이 2의 노드는 맨 왼쪽 노드만 당도를 기준으로, 2번째 노드는 알코올 도수(alcohol)를 기준으로, 맨 오른쪽 2개의 노드는 pH를 기준으로 훈련 세트를 나누었다.
가장 아래에 있는 깊이 3의 노드가 최종 노드인 리프 노드이다.
왼쪽에서 3번째에 있는 노드만 음성 클래스가 798개로 양성 클래스보다 더 많다.
즉 이 노드에 도착해야 레드 와인으로 예측한다.
루트 노드부터 이 노드까지 도착하려면 '-0.802 < sugar < -0.239', 'alcohol <= 0.454'이어야 한다.
불순도는 클래스별 비율로 계산할 때 특성값의 스케일이 계산에 영향을 미치지 않는다.
따라서 표준화 전처리 과정이 필요 없다. 이것이 결정 트리 알고리즘의 장점 중 하나이다.
전처리 하기 전의 훈련 세트와 테스트 세트로 결정 트리 모델을 다시 훈련해 보자.
dt = DecisionTreeClassifier(max_depth=3, random_state=42)
dt.fit(train_input, train_target)
print(dt.score(train_input, train_target)) # 훈련 세트 점수 출력: 0.8454877814123533
print(dt.score(test_input, test_target)) # 테스트 세트 점수 출력: 0.8415384615384616
train_scaled와 test_scaled가 아닌 train_input고 test_input으로 모델을 훈련하였다.
전처리하기 전과 출력 결과가 같다.
트리를 그려보자.
plt.figure(figsize=(20,15))
plot_tree(dt, filled=True, feature_names=['alcohol', 'sugar', 'pH'])
plt.show()
전처리 했을 때의 트리와 모양은 같지만 음수가 아닌 양수가 나와 훨씬 이해하기 쉽다.
당도가 1625보다 크고 4325보다 작은 와인 중 도수가 11025와 같거나 작은 것이 레드 와인(리프 노드의 왼쪽에서 3번째 노드), 나머지는 모두 화이트 와인으로 예측하였다!
결정 트리는 어떤 특성이 가장 유용한지 특성 중요도를 계산한다.
마지막으로 특성 중요도를 출력해 보자.
print(dt.feature_importances_)
# 출력: [0.12345626 0.86862934 0.0079144 ]
출력 결과 2번째 특성인 당도가 0.87 정도로 특성 중요도가 가장 높다.
이 트리의 루트 노드와 깊이 1의 노드에서 당도를 사용했기 때문에 당도가 가장 유용한 특성 중 하나일 것이다.
* 용어 정리
1. 결정 트리(Decision Tree): 예 또는 아니오에 대한 질문을 이어나가면서 정답을 찾아 학습하는 알고리즘
2. 불순도: 결정 트리가 최적의 질문을 찾기 위한 기준
3. 특성 중요도: 결정 트리에 사용된 특성이 불순도를 감소하는데 기여한 정도
4. DecisionTreeClassifier: 사이킷런의 결정 트리 분류 클래스
5. plot_tree(): 트리 그림을 출력하는 함수(결정 트리 모델 시각화)
6. info(): 판다스 데이터프레임의 요약 정보를 출력하는 메서드
7. describe(): 판다스 데이터프레임 열의 통계를 출력하는 메서드
'Python > ML' 카테고리의 다른 글
젯슨나노 부팅 오류 (0) | 2024.07.31 |
---|---|
[머신러닝] 경사하강법 (2) | 2024.07.15 |
[머신러닝] 확률적 경사 하강법 (0) | 2023.02.18 |
[머신러닝] 로지스틱 회귀(생선 확률 예측) (0) | 2023.02.10 |
[머신러닝] 릿지, 라쏘(농어 무게 예측3) (0) | 2023.01.31 |
댓글