● 결정 트리(Decision Tree)

  • 분류와 회귀에 사용되는 지도 학습 방법
  • 데이터 특성으로부터 추론된 결정 규칙을 통해 값을 예측
  • if-then-else 결정 규칙을 통해 데이터 학습
  • 트리의 깊이가 깊을 수록 복잡한 모델
  • 결정 트리의 장점
    • 이해와 해석이 쉬움
    • 시각화 용이
    • 많은 데이터 전처리 필요 x
    • 수치형, 범주형 모두 다룰 수 있음
    • ...
  • 결정 트리에 필요한 라이브러리
# 필요 라이브러리
import pandas as pd
import numpy as np
import graphviz
import multiprocessing
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris, load_wine, load_breast_cancer, load_diabetes
from sklearn import tree
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import cross_val_score
from sklearn.pipeline import make_pipeline

 

1. 결정 트리를 활용한 분류(DecisionTreeClassifier())

  • DecisionTreeClassifier는 분류를 위한 결정 트리 모델
  • 두 개의 배열 X, y를 입력받음
    • X는 [n_samples, n_features] 크기의 데이터 특성 배열
    • y는 [n_samples] 크기의 정답 배열
X = [[0, 0], [1, 1]]
y = [0, 1]

# X가 [0, 0]일 때는 y가 0, X가 [1, 1]일 때는 y가 1 과 같이 분류
model = tree.DecisionTreeClassifier()
model = model.fit(X, y)

# X에 [2, 2]를 줬을 때 0과 1 중 어디로 분류될 지
model.predict([[2., 2.]])

# 출력 결과
array([1]) # 1로 분류됨


# X에 [2, 2]를 줬을 때 0과 1에 각각 분류될 확률
model.predict_proba([[2., 2.]])

# 출력 결과
array([[0., 1.]]) # 1이 선택될 확률이 100%로 나옴

 

 1) 붓꽃 데이터 분류(전처리 x)

model = DecisionTreeClassifier()
cross_val_score(
    estimator = model,
    X = iris.data, y = iris.target,
    cv = 5,
    n_jobs = multiprocessing.cpu_count()
)

# 출력 결과
array([0.96666667, 0.96666667, 0.9       , 1.        , 1.        ])

 

 2) 붓꽃 데이터 분류(전처리 o)

model = make_pipeline(
    StandardScaler(),
    DecisionTreeClassifier()
)

cross_val_score(
    estimator = model,
    X = iris.data, y = iris.target,
    cv = 5,
    n_jobs = multiprocessing.cpu_count()
)

# 출력 결과
array([0.96666667, 0.96666667, 0.9       , 1.        , 1.        ])

  - 전처리 한 것과 하지 않은 것의 결과에 차이가 없는데, 결정 트리는 규칙을 학습하기 때문에 전처리에 큰 영향을 받지 않음

 

 3) 학습된 결정 트리 시각화

  - 텍스트 형태로 시각화

# 트리를 텍스트로 추출
r = tree.export_text(decision_tree = model, feature_names = iris.feature_names)
print(r)

# 출력 결과
|--- petal length (cm) <= 2.45
|   |--- class: 0
|--- petal length (cm) >  2.45
|   |--- petal width (cm) <= 1.75
|   |   |--- petal length (cm) <= 4.95
|   |   |   |--- petal width (cm) <= 1.65
|   |   |   |   |--- class: 1
|   |   |   |--- petal width (cm) >  1.65
|   |   |   |   |--- class: 2
|   |   |--- petal length (cm) >  4.95
|   |   |   |--- petal width (cm) <= 1.55
|   |   |   |   |--- class: 2
|   |   |   |--- petal width (cm) >  1.55
|   |   |   |   |--- petal length (cm) <= 5.45
|   |   |   |   |   |--- class: 1
|   |   |   |   |--- petal length (cm) >  5.45
|   |   |   |   |   |--- class: 2
|   |--- petal width (cm) >  1.75
|   |   |--- petal length (cm) <= 4.85
|   |   |   |--- sepal width (cm) <= 3.10
|   |   |   |   |--- class: 2
|   |   |   |--- sepal width (cm) >  3.10
|   |   |   |   |--- class: 1
|   |   |--- petal length (cm) >  4.85
|   |   |   |--- class: 2

 

  - plot 형태로 시각화

tree.plot_tree(model)

  - graphviz 시각화

dot_data = tree.export_graphviz(decision_tree = model,
                                feature_names = iris.feature_names,
                                class_names = iris.target_names,
                                filled = True, rounded = True,
                                special_characters = True)
# filled는 상자에 색깔을 칠할 것인지
# rounded는 상자 모서리를 둥글게 할 것인지
# special_characters는 특수문자

graph = graphviz.Source(dot_data)
graph

 

  - 결정 경계 시각화

n_classes = 3
plot_colors = 'ryb'
plot_step = 0.02

# 결정경계 시각화
plt.figure(figsize = (16, 8))
# pairidx는 자동으로 1씩 늘어나는 숫자 변수
# pair는 그래프의 x축과 y축에 각각 [0번째 변수, 1번째 변수], [0번째 변수, 2번째 변수],...를 넣음을 의미
for pairidx, pair in enumerate([[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]]):
    X = iris.data[:, pair]
    y = iris.target

    model = DecisionTreeClassifier()
    model = model.fit(X, y)

    # 그래프는 2행 3열의 형태로 배치
    plt.subplot(2, 3, pairidx + 1)

    # meshgrid로 각 분류 영역 색깔로 구분
    x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, plot_step),
                         np.arange(y_min, y_max, plot_step))
    plt.tight_layout(h_pad = 0.5, w_pad = 0.5, pad = 2.5)

    Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    cs = plt.contourf(xx, yy, Z, cmap = plt.cm.RdYlBu)

    plt.xlabel(iris.feature_names[pair[0]])
    plt.ylabel(iris.feature_names[pair[1]])

    # 전체 분류 개수(3개의 클래스)에 각각 plot_colors('r(red)y(yello)b(blue)')를 부여
    for i, color in zip(range(n_classes), plot_colors):
        idx = np.where(y == i)
        plt.scatter(X[idx, 0], X[idx, 1], c = color, label = iris.target_names[i],
                    cmap = plt.cm.RdYlBu, edgecolor = 'b', s = 15)

plt.suptitle("Decision surface")
plt.legend(loc = 'lower right', borderpad = 0, handletextpad = 0)
# loc: 범례의 위치는 오른쪽 아래
# borderpad: 범례 경계에 부분적인 빈 공간 입력
# handletextpad: 범례의 handle과 text 사이의 공간
plt.axis('tight')

  - max_depth를 2로 줬을 때

 

 

 4) 와인 데이터 분류(전처리 x)

model = DecisionTreeClassifier()
cross_val_score(
    estimator = model,
    X = wine.data, y = wine.target,
    cv = 5,
    n_jobs = multiprocessing.cpu_count()
)

# 출력 결과
array([0.94444444, 0.86111111, 0.88888889, 0.91428571, 0.85714286])

 

 5) 와인 데이터 분류(전처리 o)

model = make_pipeline(StandardScaler(), DecisionTreeClassifier())

cross_val_score(
    estimator = model,
    X = wine.data, y = wine.target,
    cv = 5,
    n_jobs = multiprocessing.cpu_count()
)

# 출력 결과
array([0.94444444, 0.80555556, 0.91666667, 0.91428571, 0.85714286])

 

 6) 학습된 결정 트리 시각화

  - 텍스트 형식으로 시각화

model = DecisionTreeClassifier()
model.fit(wine.data, wine.target)

r = tree.export_text(decision_tree = model, feature_names = wine.feature_names)
print(r)

# 출력 결과
|--- proline <= 755.00
|   |--- od280/od315_of_diluted_wines <= 2.11
|   |   |--- hue <= 0.94
|   |   |   |--- flavanoids <= 1.58
|   |   |   |   |--- class: 2
|   |   |   |--- flavanoids >  1.58
|   |   |   |   |--- class: 1
|   |   |--- hue >  0.94
|   |   |   |--- ash <= 2.45
|   |   |   |   |--- class: 1
|   |   |   |--- ash >  2.45
|   |   |   |   |--- class: 2
|   |--- od280/od315_of_diluted_wines >  2.11
|   |   |--- flavanoids <= 0.80
|   |   |   |--- class: 2
|   |   |--- flavanoids >  0.80
|   |   |   |--- alcohol <= 13.17
|   |   |   |   |--- class: 1
|   |   |   |--- alcohol >  13.17
|   |   |   |   |--- magnesium <= 98.50
|   |   |   |   |   |--- class: 1
|   |   |   |   |--- magnesium >  98.50
|   |   |   |   |   |--- class: 0
|--- proline >  755.00
|   |--- flavanoids <= 2.17
|   |   |--- hue <= 0.80
|   |   |   |--- class: 2
|   |   |--- hue >  0.80
|   |   |   |--- class: 1
|   |--- flavanoids >  2.17
|   |   |--- magnesium <= 135.50
|   |   |   |--- class: 0
|   |   |--- magnesium >  135.50
|   |   |   |--- class: 1

 

  - plot 형태로 시각화

tree.plot_tree(model)

 

  - graphviz로 시각화

 

  - 결정 경계 시각화

n_classes = 3
plot_colors = 'ryb'
plot_step = 0.02

# 결정경계 시각화
plt.figure(figsize = (16, 8))
for pairidx, pair in enumerate([[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]]):
    X = wine.data[:, pair]
    y = wine.target

    model = DecisionTreeClassifier()
    model = model.fit(X, y)

    plt.subplot(2, 3, pairidx + 1)

    x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, plot_step),
                         np.arange(y_min, y_max, plot_step))
    plt.tight_layout(h_pad = 0.5, w_pad = 0.5, pad = 2.5)

    Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    cs = plt.contourf(xx, yy, Z, cmap = plt.cm.RdYlBu)

    plt.xlabel(iris.feature_names[pair[0]])
    plt.ylabel(iris.feature_names[pair[1]])

    for i, color in zip(range(n_classes), plot_colors):
        idx = np.where(y == i)
        plt.scatter(X[idx, 0], X[idx, 1], c = color, label = iris.target_names[i],
                    cmap = plt.cm.RdYlBu, edgecolor = 'b', s = 15)

plt.suptitle("Decision surface")
plt.legend(loc = 'lower right', borderpad = 0, handletextpad = 0)
plt.axis('tight')

 

  - max_depth를 2로 줬을 때

 

 7) 유방암 데이터 분류(전처리 x)

model = DecisionTreeClassifier()
cross_val_score(
    estimator = model,
    X = cancer.data, y = cancer.target,
    cv = 5,
    n_jobs = multiprocessing.cpu_count()
)

# 출력 결과
array([0.9122807 , 0.90350877, 0.92982456, 0.93859649, 0.89380531])

 

 8) 유방암 데이터 분류(전처리 o)

model = make_pipeline(StandardScaler(), DecisionTreeClassifier())

cross_val_score(
    estimator = model,
    X = cancer.data, y = cancer.target,
    cv = 5,
    n_jobs = multiprocessing.cpu_count()
)

# 출력 결과
array([0.90350877, 0.9122807 , 0.92105263, 0.93859649, 0.89380531])

 

 9) 학습된 결정 트리 시각화

  - 텍스트 형식으로 시각화

model = DecisionTreeClassifier()
model.fit(cancer.data, cancer.target)

r = tree.export_text(decision_tree = model)
print(r)

# 출력 결과
|--- feature_20 <= 16.80
|   |--- feature_27 <= 0.14
|   |   |--- feature_29 <= 0.06
|   |   |   |--- class: 0
|   |   |--- feature_29 >  0.06
|   |   |   |--- feature_13 <= 38.60
|   |   |   |   |--- feature_14 <= 0.00
|   |   |   |   |   |--- feature_26 <= 0.19
|   |   |   |   |   |   |--- class: 1
|   |   |   |   |   |--- feature_26 >  0.19
|   |   |   |   |   |   |--- class: 0
|   |   |   |   |--- feature_14 >  0.00
|   |   |   |   |   |--- feature_21 <= 33.27
|   |   |   |   |   |   |--- class: 1
|   |   |   |   |   |--- feature_21 >  33.27
|   |   |   |   |   |   |--- feature_21 <= 33.56
|   |   |   |   |   |   |   |--- class: 0
|   |   |   |   |   |   |--- feature_21 >  33.56
|   |   |   |   |   |   |   |--- class: 1
|   |   |   |--- feature_13 >  38.60
|   |   |   |   |--- feature_5 <= 0.06
|   |   |   |   |   |--- class: 0
|   |   |   |   |--- feature_5 >  0.06
|   |   |   |   |   |--- feature_13 <= 39.15
|   |   |   |   |   |   |--- class: 0
|   |   |   |   |   |--- feature_13 >  39.15
|   |   |   |   |   |   |--- class: 1
|   |--- feature_27 >  0.14
|   |   |--- feature_21 <= 25.67
|   |   |   |--- feature_23 <= 810.30
|   |   |   |   |--- feature_4 <= 0.12
|   |   |   |   |   |--- class: 1
|   |   |   |   |--- feature_4 >  0.12
|   |   |   |   |   |--- class: 0
|   |   |   |--- feature_23 >  810.30
|   |   |   |   |--- feature_3 <= 621.80
|   |   |   |   |   |--- class: 0
|   |   |   |   |--- feature_3 >  621.80
|   |   |   |   |   |--- class: 1
|   |   |--- feature_21 >  25.67
|   |   |   |--- feature_6 <= 0.10
|   |   |   |   |--- feature_1 <= 19.44
|   |   |   |   |   |--- class: 1
|   |   |   |   |--- feature_1 >  19.44
|   |   |   |   |   |--- class: 0
|   |   |   |--- feature_6 >  0.10
|   |   |   |   |--- class: 0
|--- feature_20 >  16.80
|   |--- feature_1 <= 16.11
|   |   |--- feature_27 <= 0.15
|   |   |   |--- class: 1
|   |   |--- feature_27 >  0.15
|   |   |   |--- class: 0
|   |--- feature_1 >  16.11
|   |   |--- feature_24 <= 0.09
|   |   |   |--- class: 1
|   |   |--- feature_24 >  0.09
|   |   |   |--- feature_26 <= 0.18
|   |   |   |   |--- feature_13 <= 37.05
|   |   |   |   |   |--- class: 1
|   |   |   |   |--- feature_13 >  37.05
|   |   |   |   |   |--- class: 0
|   |   |   |--- feature_26 >  0.18
|   |   |   |   |--- class: 0

 

  - plot 형태로 시각화

tree.plot_tree(model)

  - graphviz로 시각화

dot_data = tree.export_graphviz(decision_tree = model,
                                feature_names = cancer.feature_names,
                                class_names = cancer.target_names,
                                filled = True, rounded = True,
                                special_characters = True)

graph = graphviz.Source(dot_data)
graph

  - 결정 경계 시각화

n_classes = 2
plot_colors = 'ryb'
plot_step = 0.02

# 결정경계 시각화
plt.figure(figsize = (16, 8))
# 경계에 레이블이 두 개 밖에 없으므로 레이블의 개수를 수정해야함
for pairidx, pair in enumerate([[0, 1], [0, 2], [0, 3]]):
    X = cancer.data[:, pair]
    y = cancer.target

    model = DecisionTreeClassifier()
    model = model.fit(X, y)

    plt.subplot(2, 3, pairidx + 1)

    x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, plot_step),
                         np.arange(y_min, y_max, plot_step))
    plt.tight_layout(h_pad = 0.5, w_pad = 0.5, pad = 2.5)

    Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    cs = plt.contourf(xx, yy, Z, cmap = plt.cm.RdYlBu)

    plt.xlabel(iris.feature_names[pair[0]])
    plt.ylabel(iris.feature_names[pair[1]])

    for i, color in zip(range(n_classes), plot_colors):
        idx = np.where(y == i)
        plt.scatter(X[idx, 0], X[idx, 1], c = color, label = iris.target_names[i],
                    cmap = plt.cm.RdYlBu, edgecolor = 'b', s = 15)

plt.suptitle("Decision surface")
plt.legend(loc = 'lower right', borderpad = 0, handletextpad = 0)
plt.axis('tight')

  - max_depth를 2로 줬을 때

 

 

1. 결정 트리를 활용한 회귀(DecisionTreeRegressor())

 1) 당뇨병 데이터 분류(전처리 x)

diabetes = load_diabetes()
model = DecisionTreeRegressor()

cross_val_score(
    estimator = model,
    X = diabetes.data, y = diabetes.target,
    cv = 5,
    n_jobs = multiprocessing.cpu_count()
)

# 출력 결과
array([-0.38141731, -0.05774227, -0.16941196, -0.01293649, -0.21747911])

 

 2) 붓꽃 데이터 분류(전처리 o)

model = make_pipeline(StandardScaler(), DecisionTreeRegressor())

cross_val_score(
    estimator = model,
    X = diabetes.data, y = diabetes.target,
    cv = 5,
    n_jobs = multiprocessing.cpu_count()
)

# 출력 결과
array([-0.26382084, -0.04666825, -0.24234995, -0.11229066, -0.1568879 ])

 

 

 3) 학습된 결정 트리 시각화

  - 텍스트 형태로 시각화

# 트리를 텍스트로 추출
model = DecisionTreeRegressor()
model.fit(diabetes.data, diabetes.target)

r = tree.export_text(decision_tree = model)
print(r)

 

  - plot 형태로 시각화

tree.plot_tree(model)

 

  - graphviz 시각화

dot_data = tree.export_graphviz(decision_tree = model,
                                feature_names = iris.feature_names,
                                class_names = iris.target_names,
                                filled = True, rounded = True,
                                special_characters = True)
graph = graphviz.Source(dot_data)
graph

 

  - 회귀식 시각화

plt.figure(figsize = (16, 8))

for pairidx, pair in enumerate([0, 1, 2]):
    X = diabetes.data[:, pair].reshape(-1, 1)
    y = diabetes.target

    model = DecisionTreeRegressor()
    model.fit(X, y)

    X_test = np.arange(min(X), max(X), 0.1)[:, np.newaxis]
    predict = model.predict(X_test)

    plt.subplot(1, 3, pairidx + 1)
    plt.scatter(X, y, s = 20, edgecolors = 'k', c = 'darkorange', label = 'data')
    plt.plot(X_test, predict, color = 'royalblue', linewidth = 2)
    plt.xlabel(diabetes.feature_names[pair])
    plt.ylabel('Target')

  - max_depth를 3으로 줬을 때

 

+ Recent posts