딥러닝이 딥러닝이라 불리기 전에, 머신러닝의 기초 중 하나는 “결정 트리(Decision Tree)” 모델이었습니다. 결정 트리는 설명이 직관적이고 구현이 쉬운 편이라 입문자에게 적합한 학습 모델입니다. 이 글에서는 결정 트리가 무엇인지, 그리고 딥러닝 프레임워크인 파이토치(PyTorch)를 이용해 결정 트리를 어떻게 구현할 수 있는지 알아보겠습니다. 이론적 배경부터 시작해 파이토치로의 구현까지, 다양한 예제를 통해 이해하기 쉽게 설명하겠습니다.
1. 결정 트리란?
결정 트리는 이름 그대로, 나무(tree) 모양의 구조를 통해 데이터를 분류하거나 예측하는 모델입니다. 트리는 “뿌리 노드(root node)”에서 시작해 여러 개의 “가지(branch)”와 “노드(node)”로 나뉩니다. 각 노드는 데이터의 특정 특성(feature)에 대한 질문을 나타내며, 질문의 대답에 따라 데이터를 다음 가지로 보냅니다. 결국 리프 노드(leaf node)에 도달하면 데이터의 분류 결과나 예측 값을 얻을 수 있습니다.
결정 트리는 그 단순함 덕분에 설명력이 뛰어나며, 모델의 각 단계에서 어떤 결정을 내렸는지를 명확하게 이해할 수 있습니다. 이 때문에 결정 트리는 의료 진단이나 금융 분석 등, 의사 결정 과정의 설명이 중요한 문제들에서 자주 사용됩니다.
예제: 결정 트리를 이용한 간단한 분류 문제
예를 들어, 우리는 학생들이 어떤 과목을 좋아할지를 예측하고 싶다고 가정해봅시다. 트리는 다음과 같은 질문을 통해 학생들을 분류할 수 있습니다:
- “학생이 수학을 좋아하나요?”
- 예: 과학 과목으로 이동
- 아니요: 인문학 과목으로 이동
이렇게 각 질문을 거쳐 나무의 끝에 도달하면, 우리는 학생이 어떤 과목을 선호하는지를 예측할 수 있습니다.
2. 결정 트리의 장점과 단점
결정 트리는 몇 가지 장점과 단점이 있습니다.
장점:
- 직관적: 결정 트리는 시각적으로 표현할 수 있어 이해가 쉽습니다.
- 설명력: 각 결정이 명확하기 때문에 결과를 설명하기 용이합니다.
- 비정규화 데이터 처리: 데이터 전처리가 비교적 덜 필요합니다.
단점:
- 과적합(overfitting): 결정 트리는 깊이가 깊어질수록 훈련 데이터에 과적합되기 쉬워, 일반화 능력이 떨어질 수 있습니다.
- 복잡한 결정 경계: 고차원의 데이터에서는 결정 트리의 경계가 너무 복잡해질 수 있습니다.
이러한 이유로 결정 트리는 단일 모델로는 한계가 있을 수 있지만, 앙상블 학습(예: 랜덤 포레스트)과 같은 기법과 결합하면 매우 강력한 모델이 될 수 있습니다.
3. 파이토치로 결정 트리 구현하기
파이토치는 딥러닝 모델을 개발하기 위한 매우 강력한 프레임워크이지만, 결정 트리 같은 고전적인 머신러닝 모델도 파이토치를 이용해 학습시킬 수 있습니다. 다만, 파이토치에는 결정 트리를 직접적으로 구현하는 기능이 없으므로, 이를 위해 다른 라이브러리와의 결합이 필요합니다. 일반적으로는 결정 트리를 위한 scikit-learn
을 사용하고, 파이토치와 함께 사용하여 보다 복잡한 모델로 확장할 수 있습니다.
예제: XOR 문제 해결하기
import numpy as np
from sklearn.tree import DecisionTreeClassifier
import torch
import matplotlib.pyplot as plt
# 데이터 생성
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([0, 1, 1, 0])
# 결정 트리 모델 생성 및 학습
model = DecisionTreeClassifier()
model.fit(X, y)
# 예측
predictions = model.predict(X)
print("Predictions:", predictions)
# 파이토치 텐서로 변환
tensor_X = torch.tensor(X, dtype=torch.float32)
tensor_predictions = torch.tensor(predictions, dtype=torch.float32)
print("Tensor Predictions:", tensor_predictions)
# 시각화
plt.figure(figsize=(8, 6))
for i, (x, label) in enumerate(zip(X, y)):
plt.scatter(x[0], x[1], c='red' if label == 0 else 'blue', label=f'Class {label}' if i < 2 else "")
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.title('XOR Problem Visualization')
plt.legend()
plt.grid(True)
plt.show()
위 코드에서는 scikit-learn
의 DecisionTreeClassifier
를 사용해 XOR 문제를 해결한 후, 그 결과를 파이토치 텐서로 변환해 딥러닝 모델과 결합할 수 있는 형태로 만들었습니다. 시각화를 추가하여 각 데이터 포인트와 클래스 레이블을 시각적으로 확인할 수 있도록 했습니다. 이런 방식으로 결정 트리의 출력 값을 다른 딥러닝 모델의 입력으로 사용하는 등, 결정 트리와 파이토치 모델을 결합할 수 있습니다.
결정 트리 구조 시각화하기
결정 트리의 학습 결과를 더 잘 이해하기 위해서는 결정 트리 자체의 구조를 시각화하는 것도 중요합니다. scikit-learn
의 plot_tree()
함수를 이용하면 결정 트리의 분기 과정을 시각적으로 쉽게 확인할 수 있습니다.
from sklearn import datasets
from sklearn.tree import DecisionTreeClassifier, plot_tree
import matplotlib.pyplot as plt
# 데이터셋 로드
iris = datasets.load_iris()
X, y = iris.data, iris.target
# 결정 트리 모델 생성 및 학습
model = DecisionTreeClassifier()
model.fit(X, y)
# 결정 트리 시각화
plt.figure(figsize=(12, 6))
plot_tree(model, filled=True, feature_names=iris.feature_names, class_names=iris.target_names)
plt.title("결정 트리 시각화")
plt.show()
위 코드에서는 iris
데이터셋을 이용해 결정 트리를 학습시킨 후, plot_tree()
함수를 사용해 결정 트리의 구조를 시각화했습니다. 이 시각화를 통해 각 노드에서 어떤 기준으로 데이터를 분할했는지, 각 리프 노드에서 어떤 클래스에 속하는지를 명확히 알 수 있습니다. 이를 통해 결정 트리 모델의 결정 과정을 쉽게 이해하고 설명할 수 있습니다.
4. 결정 트리와 신경망의 결합
결정 트리와 신경망을 함께 사용하면 모델의 성능을 더욱 향상시킬 수 있습니다. 결정 트리는 데이터를 전처리하거나 특성을 선택하는 데 유용하고, 이후 파이토치로 구성된 신경망은 비선형 문제를 해결하는 데 뛰어난 성능을 보입니다. 예를 들어, 결정 트리를 사용해 주요 특성을 추출한 후, 이 특성을 파이토치 신경망에 입력하여 최종 예측을 수행할 수 있습니다.
예제: 결정 트리 출력값을 신경망 입력으로 사용하기
import torch.nn as nn
import torch.optim as optim
# 간단한 신경망 정의
class SimpleNN(nn.Module):
def __init__(self):
super(SimpleNN, self).__init__()
self.fc1 = nn.Linear(2, 4)
self.fc2 = nn.Linear(4, 1)
def forward(self, x):
x = torch.relu(self.fc1(x))
x = torch.sigmoid(self.fc2(x))
return x
# 신경망 모델 생성
nn_model = SimpleNN()
criterion = nn.BCELoss()
optimizer = optim.SGD(nn_model.parameters(), lr=0.01)
# 결정 트리의 예측값을 신경망의 학습 데이터로 사용
inputs = tensor_X
labels = tensor_predictions.unsqueeze(1)
# 학습 과정
for epoch in range(100):
optimizer.zero_grad()
outputs = nn_model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
if (epoch + 1) % 10 == 0:
print(f'Epoch [{epoch+1}/100], Loss: {loss.item():.4f}')
# 학습 결과 시각화
plt.figure(figsize=(8, 6))
with torch.no_grad():
outputs = nn_model(inputs).squeeze().numpy()
for i, (x, label, output) in enumerate(zip(X, y, outputs)):
plt.scatter(x[0], x[1], c='red' if output < 0.5 else 'blue', marker='x' if label == 0 else 'o', label=f'Predicted Class {int(output >= 0.5)}' if i < 2 else "")
plt.xlabel('Feature 1')
plt.ylabel('Feature 2')
plt.title('Neural Network Predictions After Training')
plt.legend()
plt.grid(True)
plt.show()
위 예제에서는 간단한 신경망 모델을 정의하고, 결정 트리의 예측 값을 신경망의 입력 데이터로 사용해 학습을 진행합니다. 학습 결과를 시각화하여 각 데이터 포인트의 예측 클래스 레이블을 시각적으로 확인할 수 있습니다. 이를 통해 결정 트리와 신경망이 결합된 형태의 모델을 만들 수 있습니다.
5. 결론
결정 트리는 간단하면서도 강력한 머신러닝 모델로, 데이터의 구조를 쉽게 이해하고 설명할 수 있습니다. 파이토치와 같은 딥러닝 프레임워크와 결합하면, 결정 트리의 강점과 신경망의 비선형 문제 해결 능력을 함께 활용할 수 있습니다. 이번 글에서는 결정 트리의 기본 개념부터 파이토치를 사용한 구현 방법까지 살펴보았습니다. 다양한 예제를 통해 결정 트리와 파이토치의 결합 가능성을 이해하셨기를 바랍니다.
결정 트리와 딥러닝의 결합은 매우 흥미로운 연구 주제이며, 이를 실제 프로젝트에 적용할 수 있는 많은 가능성이 열려 있습니다. 다음에는 앙상블 학습 기법과 파이토치의 응용에 대해 다뤄보는 것도 좋은 공부가 될 것입니다.