다양한 알고리즘 중에서 우리는 앙상블 방법(Ensemble Method)을 다루겠다. 앙상블 방법의 기본 알고리즘이며 일반적으로 많이 사용하는것이 바로 결정 트리(Decision Tree) 이다.
결정 트리
ML 알고리즘 중 직관적으로 이해하기 쉽다.
위 사진은 결정 트리 구조를 간략하게 나타낸 것이다. 리프 노드(Leaf Node)는 결정된 클래스 값이다. 그리고 새로운 규칙마다 사진에보이는 Internal Node로 나눠진다. 많은 규칙은 과적합으로 이어질 수 있으니 Depth가 높으면 예측 성능이 저하될 수 있다.
간단한 동작 방식
1. 데이터 집합의 모든 아이템이 같은 분류에 속하는지 확인
2-1. if True : 리프 노드로 만들어서 분류를 결정
2-2. Else : 데이터를 분할하는 데 가장 좋은 속성과 분할 기준을 찾음 (정보이득, 지니계수 이용)
3. 해당 속성과 분할 기준으로 데이터 분할하야 branch 생성
4. 모든 데이터 집합의 분류가 결정될때 까지 다시 1로 돌아간다.
위 방식이 복잡해 보일 수 있다. 위를 이해하기해 좀 더 쉽게 풀겠다.
- 박스 안에 30개의 레고 가 있다. 우리는 무슨색 블록이 어떤 형태인지 예측해야한다.
i) 각 레고는 "형태" 속성으로 동그라미, 네모, 세모
ii) 각 레고는 "색깔" 속성으로 노랑, 빨강, 파랑iii) 노란색 블록은 모두 동그라미iiii) 빨강, 파랑블록은 동그라미, 네모, 세모가 고루 섞임
자 여기서 우리 상식대로만 봐도 가장먼저 분류할 것은 if 색깔 = "노란색"이다. 그 이유는 전부 동그라미 블록이라고 예측할 수 있기 때문이다. 나머지 블록또한 다시 균일도 조건을 찾아서 분류를 진행한다.
이러한 정보의 균일도를 측정하는 방법은 위에서 말한 정보이득, 지니계수이다.
결정 트리의 파라미터
사이킷런에서의 결정 트리 알고리즘 DecisionTreeClassifier에서 사용되는 파라미터를 알아보자.
min_samples_split :
- 노드를 분할하기 위한 최소한의 샘플 데이터 수 (과적합을 제어하는 데 사용)
- default value : 2, 작게 설정할수록 분할되는 노드가 많아져서 과적합 가능성 증가
min_samples_leaf :
- 말단 노드(Leaf Node)가 되기 위한 최소한의 샘플 데이터 수 (위와 유사하게 사용됨)
- 비대칭적 데이터의 경우 특정 클래스의 데이터가 극도로 작을 수 있을경우 작게 설정
max_features :
- 최적의 분할을 위해 고려할 최대 피쳐 개수
- default value : None, 데이터 세트의 모든 피처를 사용해 분할 수행
- "sqrt" : 이름그대로 전체 피쳐 개수의 루트를 씌운뒤 값 만큼 설정
- "auto" : "sqrt"와 동일
- "log" : log2(전체 피쳐 개수)
max_depth :
- 트리의 최대 깊이를 규정
- default value : None, 완벽하게 클래스 결정 값이 될 때까지 깊이를 계속키우며 분할
max_leaf_nodes :
말단 노드(Leaf Node)의 최대 개수
결정 트리 모델의 시각화
Graphviz를 사용해서 시각화 해보자.
파이썬 패키지가 아니기에 pip install 되는 것은 python Wrapper라고 생각하면된다. 설정 귀찮으니까 코랩에서 하도록한다. 아래 코드가 설치하는 코드이다.
!apt-get install -y graphviz && pip install pydot
코랩에서 실행해주면 뭐 윈도우 프로그램 깔고 환경변수 하고 안해도된다(나이스~)
이제 시각화 해보자. 아래는 붓꽃 품종 분류 모델을 매우 간단하게 만들어보자.
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
import warnings
warnings.filterwarnings('ignore')
# DecisionTree Classifier 생성
dt_clf = DecisionTreeClassifier(random_state=156)
# 붓꽃 데이터를 로딩하고, 학습과 테스트 데이터 셋으로 분리
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)
# DecisionTreeClassifer 학습.
dt_clf.fit(X_train , y_train)
from sklearn.tree import export_graphviz
# export_graphviz()의 호출 결과로 out_file로 지정된 tree.dot 파일을 생성함.
export_graphviz(dt_clf, out_file="tree.dot", class_names=iris_data.target_names , \
feature_names = iris_data.feature_names, impurity=True, filled=True)
여기 까지 잘 따라했으면 아마 tree.dot이라는 파일이 만들어 졌을 것 이다. 자 이걸 시각화 시켜보자.
import graphviz
# 위에서 생성된 tree.dot 파일을 Graphviz 읽어서 Jupyter Notebook상에서 시각화
with open("tree.dot") as f:
dot_graph = f.read()
graphviz.Source(dot_graph)
결정트리 가 한눈에 보인다. 하지만 뭐가 잔뜩있는걸 아직 잘모르니 한번 파악해보도록하자.
결정트리 분석
일단 자식이 없는 노드는 리프 노드이다. (위 빨간박스) 즉, 최종 값이 결정되는 노드라는 것이다. 리프노드가 되려면 클래스 값이 오직 하나여야하며 하이퍼 파라미터 조건을 충족하면 된다.
petal length(cm) <= ~~ : 피처 조건이 있는 것은 자식 노드를 만들기 위한 규칙 조건이다. 이것이 없으면 리프노드
gini : 다음의 value=[]로 주어진 데이터 분포에서의 지니 계수이다.
samples : 현 규칙에 해당하는 데이터 건수이다.
value = [ ] : 클래스 값 기반의 데이터 건수이다. 붓꽃 데이터 세트는 클래스 값으로 0, 1, 2 이다. 만약에 value = [41, 40, 39] 라면 클래스 값의 순서로 품종별로 개수를 뜻한다.
하나하나 분석해보자.
1번 노드 : 전체 데이터가 120개(samples) 이며 품종별로 순서대로 41, 40, 39개 존재한다. class는 하위 노드를 가질 경우 setosa개수가 제일 많은걸 의미한다.
petal length (cm) <= 2.45, True False로 분기
True :
2번 노드 : 모든 데이터가 setosa로 결정되었으며 리프노드가 된다.
False :
3번 노드 : 또 규칙을 생성해서 이제 또 나눠서 한다고한다.
설명 길어지니까 여기까지만 하겠다.(혼자서 하라는 뜻)
파라미터와의 관계를 보기위해서 하이퍼 파라미터를 변경해준다.
dt_clf = DecisionTreeClassifier(random_state=156, max_depth=3)
이후 학습하니 아래와 같은 결정 트리가 나타났다.
매우 간단하게 분류되었다.
자 다음은 최소한의 샘플 수를 정하는 min_samples_split을 조절해보자
아래는 하나로 결정되지않고 서로 다른 클래스를 보유하고있음에도 최소분할개수가 4개이기에 3개는 분할되지않고 리프노드가 되었다. 다음은 min_samples_leaf를 만져보자
sample이 4개 이하면 리프 노드가 되기에 지니계수가 크더라도 sample이 4인 조건으로 규칙 변경을 선호하게되어 노드가 줄었다.
이러한 결정들은 큰 역할을 차지한다는 걸 볼 수 있다.