결정트리(Decision Tree)

결정트리는 결론에 이르기 위해서 “예/아니오”의 답변을 요구하는 질문을 반복하면서 학습을 진행합니다. 마치 스무고개 질문과 비슷하죠. 최소한의 적은 질문으로 어떤 문제를 해결하기 위해서는 질문을 잘해야 하는것처럼 결정트리의 중요한 부분도 “어떻게 질문하는가?” 하는 것 중요합니다.

https://kr.akinator.com/

위의 프로그램은 “akinator”라는 게임입니다. 사용자는 아키네이터의 질문에 따라서 답을 고르게 됩니다. 이런 방법으로 몇차례 질문과 답을 주고 받으면 내가 머리속으로 그려낸 인물에 대해서 정확하게 예측합니다. 이것은 이미 이 프로그램을 통해 게임을 진행했던 사람들의 많은 데이터가 축적되어 있기 때문에 가능한 일입니다. 제가 테스트 했던 인물은 축구선수 손흥민이 이었는데 손흥민선수는 이미 222,308회 선택되어 졌기 때문에 많은 데이터가 축적되어 있었습니다. 그렇기 때문에 몇번 안돼서 아키네이터가 답을 맞췄습니다.

결정트리가 마치 이와 같습니다. 결정트리도 데이터셋에서 질문을 통해서 분류(classification) 작업을 수행합니다. 맨 마지막에 마지막 답이 남을 때까지 데이터를 분류합니다. 이렇게 남는 맨 마지막 노드를 리프(lefa) 노드라고 합니다. 그리고 각 노드들은 엣지(edge)로 연결되어 있습니다.

분류에도 여러 알고리즘들이 있는데 그렇다명 언제 결정트리(Decision Tree)를 사용하면 좋을까요?
예를 들어서 고객이 은행에 대출요청을 했고 은행이 고객에 대한 수락 요청을 거부했을 경우 고객에게 어떤 이유로 대출이 불가한지 설명해야 한다면 바로 이 의사결정트리가 유용한 알고리즘이 될 수있습니다.

해당 알고리즘이 가장 높은 성능을 발휘한다는 보장은 없지만 그래도 어떤 결정에 대해서 설명이 가능하다는 것은 중요한 이유가됩니다. 바로 한눈에 보고 이해할 수 있다는 이러한 정점이 있기 때문에 어떤 결정에 합리적인 판단 기준으로 제시하기 위해 해당 알고리즘을 사용합니다.

의사결정나무은 예측 변수를 기반으로 결과를 분류하거나 예측하는데 이러한 의사결정의 규칙(Decision Rule)을 나타내는 과정이 마치 나무구조와 같다고 해서 붙여진 이름입니다. 해당 알고리즘을 활용하면 분류(classification)나 예측(prediction)도 수행이 가능합니다.

아래의 예제는 붓꽃 데이터를 Decision-Tree 알고리즘을 통해 구성한 결과입니다.

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

# DecicionTreeClassifier 생성
tree_iris = DecisionTreeClassifier()

# 붓꽃 데이터를 로딩하고, 학습과 테스트 데이터 세트로 분리
iris_data = load_iris()
X_train, X_test, y_train, y_test = train_test_split(iris_data.data, iris_data.target, test_size=0.2, random_state=11)

# DecisionTreeClassifier 학습
tree_iris.fit(X_train, y_train)
from sklearn.tree import export_graphviz
export_graphviz(tree_iris, out_file='tree-iris.dot', class_names=['Setosa','Versicolour','Virginica'], feature_names=iris_data.feature_names, impurity=False, filled=True)

분류를 수행하고 그 결과를 ‘tree_iris.dot’이라는 파일로 기록합니다.

digraph Tree {
node [shape=box, style="filled", color="black"] ;
0 [label="petal length (cm) <= 2.45\nsamples = 120\nvalue = [41, 40, 39]\nclass = Setosa", fillcolor="#fffdfd"] ;
1 [label="samples = 41\nvalue = [41, 0, 0]\nclass = Setosa", fillcolor="#e58139"] ;
0 -> 1 [labeldistance=2.5, labelangle=45, headlabel="True"] ...

해당 파일을 읽어보면 위와 같이 코드의 형태로 표시됩니다. 이렇게 만들어진 코드를 바로 이해해도 되겠지만 무리가 있죠. 파이썬의 graphviz라는 패키지를 이용하면 이러한 데이터를 훌륭히 시각화 할 수 있습니다.

# 아래와 같이 패키지를 설치합니다.
conda install python-graphviz

패키지를 설치하고 tree-iris.dot 파일을 읽어보겠습니다.

import graphviz

with open('./tree-iris.dot') as f:
    dot_graph = f.read()
display(graphviz.Source(dot_graph))

위의 그림을 살펴보면 결정트리를 IF(petal length <= 2.45) THEN Setosa ELSE IF(petal width <= 1.55) and IF(petal length <= 5.25) THEN vesicolour … 와 같은 방법으로 표현할 수 있습니다. 그렇기 때문에 어떤 결과를 얻었을 때에 규칙을 통해서 데이터를 얻었다고 이야기 할 수 있는 것입니다.

가장 위에 있는 노드는 Root-Node 라고 하고 Parent-Child Node의 연결이 마치 나무의 구조와 같다고 해서 의사결정나무라고도 부릅니다. 그리고 맨 마지막에 있는 것이 Teminal-Node라고 부릅니다. 각 노드의 연결선을 엣지(Edge) 라고 하고 Yes(True)/No(False)를 의미합니다.

루트노드는 가장 첫번째 질문으로 매우 중요합니다. 이 질문은 가능한 많은 데이터를 분류 할 수 있는 질문이어야 합니다. 위의 경우는 Petal Length <= 2.45를 기준으로 총 120개의 샘플 데이터를 41:79로 분류했습니다. 그리고 41은 전부 “Setosa”라는 종류라는 것을 얻었습니다. 그리고 나머지 79개의 데이터를 다시 Petal Width <= 1.55라는 조건을 통해서 38:41의 데이터로 분류해 냅니다. 이런 과정을 반복하면서 데이터를 마지막가지 분류하게됩니다.

이 결정트리를 그동안 사용했던 fitness.csv를 통해서 적용해 보겠습니다.

dataset = pd.read_csv('./fitness.csv')
dataset.loc[ dataset['age'] < 40, 'ACODE']= 1
dataset.loc[ (dataset['age'] >= 40) & (dataset['age'] <50), 'ACODE']= 2
dataset.loc[ (dataset['age'] >= 50) & (dataset['age'] <60), 'ACODE']= 3

x_data = dataset[dataset.columns[1:-1]].values
y_data = dataset[dataset.columns[-1]].values

fit_tree = DecisionTreeClassifier()
fit_tree.fit(x_data, y_data)

from sklearn.tree import export_graphviz
export_graphviz(fit_tree, out_file='tree-fit.dot', class_names=['30','40','50'], feature_names=dataset.columns[1:-1], impurity=False, filled=True)

with open('./tree-fit.dot') as f:
    dot_graph = f.read()
display(graphviz.Source(dot_graph))

위에서 보시는것처럼 IF maxpulse <= 174 AND oxygen <= 46.723 THEN 50 라는 예측결과를 얻었습니다. 이런 방법으로 조건의 만족 여부를 따라서 이동하다보면 결국 class 값을 얻을 수 있습니다.

위 예제에서는 이지분리(Binary Split)을 사용했지만 알고리즘에 따라서 다지분리(Multi Split)도 가능합니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다