머신러닝 스터디

[핸즈온 머신러닝 3판] 6장 결정트리

min-sk 2024. 10. 29. 10:26

 

결정 트리는 분류와 회귀 작업 그리고 다중 출력 작업까지 가능한 다목적 머신러닝 알고리즘입니다.

6.1 결정 트리 학습과 시각화

DecisionTreeClassifier를 훈련시키는 코드를 통해서 결정 트리를 이해해보겠습니다.

from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier

iris = load_iris(as_frame=True)
X_iris = iris.data[["petal length (cm)", "petal width (cm)"]].values
y_iris = iris.target

tree_clf = DecisionTreeClassifier(max_depth=2, random_state=42)
tree_clf.fit(X_iris, y_iris)

 

그 후 export_graphviz() 함수를 사용해 그래프 정의를 iris_tree.dot 파일로 출력하여 훈련된 결정 트리를 시각화할 수 있습니다.

from sklearn.tree import export_graphviz

export_graphviz(
        tree_clf,
        out_file=str(IMAGES_PATH / "iris_tree.dot"),  # 경로가 책과 다릅니다.
        feature_names=["petal length (cm)", "petal width (cm)"],
        class_names=iris.target_names,
        rounded=True,
        filled=True
    )
from graphviz import Source

Source.from_file("iris_tree.dot")

 

6.2 예측

트리가 어떻게 예측을 만들어낼까요?

먼저 루트 노드(깊이가 0인 맨 꼭대기의 노드)에서 시작합니다. 그 후 루트 노드에서 왼쪽의 자식 노드로 이동합니다. 만약 노드가 리프 노드(자식 노드를 가지지 않는 노드)라면 추가적인 검사를 하지 않습니다.

노드의 sample 속성은 얼마나 많은 훈련 샘플이 적용되었는지 헤아린 것이고, 노드의 value 속성은 노드에서 각 클래스에 얼마나 많은 훈련 샘플이 있는지 알려줍니다. 그 후 마지막으로 지니 불순도를 측정합니다.

6.3 클래스 확률 추정

결정 트리는 한 샘플이 특정 클래스 k에 속할 확률을 추정할 수도 있습니다.

 

6.4 CART 훈련 알고리즘

사이킷런은 결정 트리를 훈련시키기 위해 CART(classification and regression tree) 알고리즘을 사용합니다. 일단 훈련 세트를 하나의 특성 k의 임곗값 tk를 사용해 두 개의 서브셋으로 나눕니다.

 

이제 같은 방식으로 서브셋을 또 나누고 나눕니다. 이 과정은 최대 깊이가 되거나 불순도를 줄이는 분할을 찾을 수 없을 때 멈추게 됩니다.

 

6.5 계산 복잡도

예측을 하려면 결정 트리를 루트 노드에서부터 리프 노드까지 탐색해야 합니다. 일반적으로 결 정 트리는 거의 균형을 이루고 있으므로 결정 트리를 탐색하기 위해서는 약 O(log2(m))개의 노드를 거쳐야 합니다.

각 노드에서 모든 샘플의 모든 특성을 비교하면 훈련 복잡도는 O(n * mlong2(m))이 됩니다.

 

6.6 지니 불순도 또는 엔트로피?

criterion 매개변수를 "entropy"로 지정하여 엔트로피 불순도를 사용할 수 있습니다.

엔트로피는 분자의 무질서함을 측정하는 것으로 원래 열역학의 개념입니다. 분자가 안정되고 질서정연하면 엔트로피는 0에 가깝습니다.

여기서는 모든 메세지가 동일할 때 엔트로피가 0이 되고, 머신러닝에서는 불순도의 측정 방법으로 자주 사용됩니다.

6.7 규제 매개변수

결정 트리는 훈련 데이터에 대한 제약 사항이 거의 없습니다.

결정 트리는 모델 파라미터가 전혀 없는것이 아니라 훈련되기 전에 파라미터 수가 결정되지 않기 때문에 비파라미터 모델이라고 합니다. 반대로 선형 모델 같은 파라미터 모델은 모델 파라미터 수가 미리 정해져 있으므로 자유도가 제한되어 과대적합 될 위험이 줄어듭니다.

따라서 사이킷런에서는 max_depth 매개변수로 규제를 합니다. 즉, 결정 트리의 최대 깊이를 제어한다는 말입니다. 

from sklearn.datasets import make_moons

X_moons, y_moons = make_moons(n_samples=150, noise=0.2, random_state=42)

tree_clf1 = DecisionTreeClassifier(random_state=42)
tree_clf2 = DecisionTreeClassifier(min_samples_leaf=5, random_state=42)
tree_clf1.fit(X_moons, y_moons)
tree_clf2.fit(X_moons, y_moons)

규제가 없는 왼쪽 모델은 확실히 과대 적합이며, 규제를 추가한 오른쪽 모델이 일반화가 더 잘 됩니다.

 

6.8 회귀

결정 트리는 회귀 문제에서도 사용됩니다. 사이킷런의 DecisionTreeRegressor를 사용해보겠습니다.

from sklearn.tree import DecisionTreeRegressor

np.random.seed(42)
X_quad = np.random.rand(200, 1) - 0.5  # 간단한 랜덤한 입력 특성
y_quad = X_quad ** 2 + 0.025 * np.random.randn(200, 1)

tree_reg = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg.fit(X_quad, y_quad)

 

CART 알고리즘은 훈련 세트를 불순도를 최소화하는 방향으로 분할하는 대신 MSE를 최소화 하도록 분할하는 것을 제외하고는 앞서 설명한 것과 거의 비슷하게 작동합니다.

 

6.9 축 방향에 대한 민감성

결정 트리는 비교적 이해하고 해석하기 쉬우며 시용하기 편하고, 여러 용도로 사용할 수 있으며, 성능도 뛰어납니다. 하지만 몇 가지 제한 사항이 있습니다. 눈치겠을지 모르겠지만 결정 트리는 계단 모양의 결정 경계를 만듭니다.

이 문제를 제한하는 한 가지 방법은 데이터의 스케일을 조정한 디음 주성분 분석(PCA) 변환을 적용하는 것입니다.

데이터의 스케일을 조정하고 PCA를 시용하여 데이터를 회전시키는 작은 파이프라인을 만든 다음 이 데이터에서 DecisionTreeClassifier를 훈련해보겠습니다.

axes = [-2.2, 2.4, -0.6, 0.7]
z0s, z1s = np.meshgrid(np.linspace(axes[0], axes[1], 100),
                       np.linspace(axes[2], axes[3], 100))
X_iris_pca_all = np.c_[z0s.ravel(), z1s.ravel()]
y_pred = tree_clf_pca.predict(X_iris_pca_all).reshape(z0s.shape)

plt.contourf(z0s, z1s, y_pred, alpha=0.3, cmap=custom_cmap)
for idx, (name, style) in enumerate(zip(iris.target_names, ("yo", "bs", "g^"))):
    plt.plot(X_iris_rotated[:, 0][y_iris == idx],
             X_iris_rotated[:, 1][y_iris == idx],
             style, label=f"Iris {name}")

plt.xlabel("$z_1$")
plt.ylabel("$z_2$", rotation=0)
th1, th2 = tree_clf_pca.tree_.threshold[[0, 2]]
plt.plot([th1, th1], axes[2:], "k-", linewidth=2)
plt.plot([th2, th2], axes[2:], "k--", linewidth=2)
plt.text(th1 - 0.01, axes[2] + 0.05, "Depth=0",
         horizontalalignment="right", fontsize=15)
plt.text(th2 - 0.01, axes[2] + 0.05, "Depth=1",
         horizontalalignment="right", fontsize=13)
plt.axis(axes)
plt.legend(loc=(0.32, 0.67))
save_fig("pca_preprocessing_plot")

plt.show()

 

6.10 결정 트리의 분산 문제

일반적으로 결정 트리의 주요 문제는 분산이 상당히 크다는 것입니다. 즉 하이퍼파라미터나 데이터를 조금만 변경해도 매우 다른 모델이 생성될 수 있습니다.

 

다행히도 여러 결정 트리의 예측을 평균하면 분산을 크게 줄일 수 있습니다. 이러한 결정 트리의 앙상블을 랜텀 포레스트라고 합니다.