갈루아의 반서재

결정트리와 타이타닉 가설 설명 (2) - 결정트리 분류기 훈련


결정트리 분류기 훈련


먼저 훈련과 테스트 데이터를 분리한다.

1
2
>>> from sklearn.cross_validation import train_test_split
>>> X_train, X_test, y_train, y_test = train_test_split(titanic_X, titanic_y, test_size=0.25, random_state=33)
cs

DecisionTreeClassifier 를 만들고 학습을 수행하기 위해 분류기의 fit 메소드를 사용한다.

1
2
3
>>> from sklearn import tree
>>> clf = tree.DecisionTreeClassifier(criterion='entropy', max_depth=3,min_samples_leaf=5)
>>> clf = clf.fit(X_train,y_train)
cs

DecisionTreeClassifier는 예측할 수 있는 일부 초평면을 받아들인다. 이 경우 학습데이터를 분리하기 위해 정보 이득 기준Information Gain을 사용한다. 최대 3개 레벨의 트리를 만들고 적어도 5개의 훈련 인스턴스를 포함한다면 leaf node 로 node 를 받아들인다. 그러면 만든 모델을 시각화해보자.

IPython 을 사용하고 pydot 모듈이 설치되어 있다고 가정한다. Graphviz 가 설치되어 있다면 pydot 는 트리로부터 Graphviz 코드를 생성한다. pydot 모듈 사용시 에러가 발생하는 경우 pydotplus 모듈을 사용한다.

1
2
3
4
5
6
7
>>> import pydotplus,StringIO
>>> dot_data = StringIO.StringIO() 
>>> tree.export_graphviz(clf, out_file=dot_data,feature_names=['age','sex','1st_class','2nd_class','3rd_class']) 
>>> graph = pydotplus.graph_from_dot_data(dot_data.getvalue()) 
>>> graph.write_png('titanic.png'
>>> from IPython.core.display import Image 
>>> Image(filename='titanic.png')
cs



생성된 결정트리는 훈련데이터를 기반으로 결정의 연속을 나타낸다. 인스턴스를 분류하기 위해 각 노드에서 질문에 관해 답해야 한다. 예를 들어, 루트 노드에서 성별 <= 0.50 인지 (즉, 여성인지) 묻는다. 그렇다면 좌측 자식 노드로 이동한다. 그렇지 않다면 우측 노드로 이동한다. 질문에 계속 답하다 보면 마지막 잎 노드까지 도착한다. 마지막 잎 노드에 도착하면, 예측은 대부분 인스턴스가 가진 목적 범주에 해당한다. 이 경우, 탑승객은 여성이고 선실 2등급이상이면 대답은 1이 된다.

그럼 각 단계에서는 어떤 기준으로 질문을 하게되는가? 그 기준은 바로 정보 이득이다. IG 측정은 질문에 답할 경우, 얼마나 엔트로피를 잃는지를 측정한다. 또는 그렇지 않으면 대답한 후 얼마나 유지되는지 측정한다.

엔트로피는 집합의 무작위성을 측정하는 방법이다. 엔트로피가 0 이라면 모든 값(여기서는 인스턴스의 목적 범주)이 같지만, 엔트로피가 최대일 때 각 범주(여기서 생존자수와 사망자수는 반반이 된다)의 인스턴스의 개수는 같게 된다.

각 노드에서 인스턴스의 일정 수를 가지고(전체 데이터셋으로 시작한다) 엔트로피를 시작한다. 기법은 좀 더 동질 분할(가장 낮은 엔트로피)이 될 수 있는 질문을 선택한다. 질문에 대한 대답은 '예'나 '아니오'다. 즉, 엔트로피는 질문에 답하면 줄어든다.

결정트리 해석


트리 내부를 살펴보면, 결정 트리의 최상단을 보면 훈련데이터 984개 인스턴스 중 662개 인스턴스는 범주 0 (사망자)에 해당하고, 322개 인스턴스는 범주 1 (생존자)이다. 이 초기그룹에 대해 측정된 엔트로피는 약 0.632 이다.

가장 큰 정보 이득을 만드는 질문은 여자였는가이다. 여성 범주는 0 으로 인코딩했다. 대답이 '그렇다'면 엔트로피는 거의 같지만, 대답이 '아니오'면 엔트로피는 많이 줄어든다(죽은 남성의 비율은 사망자의 일반 비율보다 크다). 이런 의미에서 여성인지에 대한 질문은 최적의 질문이 된다.

트리를 본다면 각 노드에서의 질문, 최초 샤논 엔트로피Shannon entropy, 고려해야할 인스턴스의 수, 목적 범주에 따른 분포가 있다, 각 단계에서, 인스턴스의 수는 각 노드에서 질문에 따라 '예'와 '아니오'라는 대답으로 줄어든다. 특정 멈춤 기준에 도달할 때까지 이 과정을 진행한다.

예를 들어 선실 1등급의 10살 소녀의 생존 여부를 결정하는 질문을 고려해보자.

1. 여성인가? → 그렇다 (왼쪽가지로 이동)

2. 선실 3등급인가? → 그렇다 (왼쪽가지로 이동)

3. 선실 1등급인가? → 아니오 (오른쪽가지로 이동)

위와 같은 속성을 가진 102명 97명이 생존했으므로 생존했다고 볼 수 있다.



일반적으로 이성적인 결과를 얻는다. 사망자의 그룹 (496중 449)은 어른이며 선실 2등급 또는 3등급에 해당한다. 한편 선실 1등급의 대부분의 소녀는 생존했다. 훈련데이터에서 이 기법의 정확도를 알아보자. 분류기의 성능을 측정하기 위한 함수를 정의하자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
>>> from sklearn import metrics
>>> 
>>> def measure_performance(X,y,clf, show_accuracy=True, show_classification_report=True, show_confussion_matrix=True):
>>> 
>>>     y_pred=clf.predict(X)   
>>> 
>>>     if show_accuracy:
>>>         print "Accuracy:{0:.3f}".format(
>>>             metrics.accuracy_score(y, y_pred)
>>>         ),"\n"
>>> 
>>>     if show_classification_report:
>>>         print "Classification report"
>>>         print metrics.classification_report(y,y_pred),"\n"
>>>       
>>>     if show_confussion_matrix:
>>>         print "Confussion matrix"
>>>         print metrics.confusion_matrix(y,y_pred),"\n"
>>> 
>>> measure_performance(X_train,y_train,clf, True, False, False)
 
Accuracy:0.838 
cs

트리는 훈련데이터에 대해 0.838의 정확도를 얻었다. 좋은 지시점은 아니다.

평가데이터를 구별하지 않았기 때문에 교차검증을 해야한다. 극단적인 교차검증인 단일 잔류 leave-one-out 교차검증을 사용해보자. 훈련데이터에서 하나의 인스턴스를 남긴 모든 인스턴스로 훈련하고 남겨둔 하나의 인스턴스로 모델을 평가한다. 훈련인스턴스만큼 분류를 실행한 후 남겨진 인스턴스의 범주를 정확하게 예측한 빈도로서 정확도를 계산한다. 그리고 이는 훈련데이터에 대한 정확도보다 매우 낮다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> from sklearn.cross_validation import cross_val_score, LeaveOneOut
>>> from scipy.stats import sem
>>> 
>>> def loo_cv(X_train, y_train,clf):
>>>     loo = LeaveOneOut(X_train[:].shape[0])
>>>     scores = np.zeros(X_train[:].shape[0])
>>>     for train_index, test_index in loo:
>>>         X_train_cv, X_test_cv = X_train[train_index],X_train[test_index]
>>>         y_train_cv, y_test_cv = y_train[train_index],y_train[test_index]
>>>         clf = clf.fit(X_train_cv,y_train_cv)
>>>         y_pred = clf.predict(X_test_cv)    
>>>         scores[test_index] = metrics.accuracy_score(y_test_cv.astype(int), y_pred.astype(int))
>>>     print ("Mean score: {0:.3f} (+/-{1:.3f})").format(np.mean(scores), sem(scores))
>>> 
>>> loo_cv(X_train, y_train,clf)
 
Mean score: 0.837 (+/-0.012)
cs

단일 잔류 교차 검증의 주요 장점은 가능한 한 모든 데이터로 훈련해서 적은 데이터에 유용하다는 것이다. 주요 단점은 각 인스턴스에 대한 분류기의 훈련은 계산 시간의 관점에서 매우 소비적이라는 것이다.


랜덤 포레스트 : 무작위 결정


결정 트리에 대한 일반적인 비판은 일단 질문에 답한 후 훈련데이터를 나누면 이 결정에 대해 다시 고려하지 않는다는 것이다. 예를 들어 남성과 여성으로 나눈다면, 기법은 다른 질문(성별과 상관없이 특정 나이보다 낮은 연령)을 고려할 수 없다. 랜덤 포레스트는 각 단계에서 대안적인 트리를 제안하고 최종 예측을 얻고자 그들을 조합하는 무작위의 레벨을 얻는다. 같은 질문에 답하는 몇몇 분류기를 고려하는 이러한 알고리즘의 유형을 앙상블ensemble 기법이라고 한다. 타이타닉 예제에서는 속성이 매우 적기 때문에 이러한 문제로 보기가 어려웠다. 그러나 속성의 개수가 수천개라면 이 기법을 고려한다.

랜덤 포레스트는 훈련 인스턴스의 부분 집합을 기반으로 속성 집합에서 각 집합의 작은 임의적인 수를 사용해서 결정트리를 만들도록 한다(무작위로 선택하고 대체한다). 이 트리 성장의 과정은 여러 번 반복되고 몇몇 분류기가 생성된다. 예측시 각기 성장한 트리는 인스턴스를 고려해 결정트리가 하듯이 정확히 목적 범주를 예측한다. 대부분의 트리들이 투표한 버무(즉, 트리들이 가장 많이 예측한 범주)가 앙상블 분류기가 제안한 범주가 된다.

scikit-learn에서 랜덤 포레스트의 사용은 sklearn.ensemble 모듈의 RandomForestClassifier 를 임포트하면 된다. 다음과 같이 훈련데이터를 나눈다.

1
2
3
4
5
6
>>> from sklearn.ensemble import RandomForestClassifier
>>> clf = RandomForestClassifier(n_estimators=10, random_state=33)
>>> clf = clf.fit(X_train, y_train)
>>> loo_cv(X_train, y_train, clf)
 
Mean score: 0.817 (+/-0.012)
cs

결과는 별로 좋지 않다. 속성 개수가 너무 적기 때문에 소개한 무작위는 좋지 않다. 그러나 데이터셋이 크고 속성이 많다면 랜덤 포레스트는 빠르고 간단하며 결정 트리의 미덕을 유지한 채 정확도를 향상시키는 장점이 있다.


성능평가


모든 지도학습의 마지막 단계는 미지의 데이터로 가장 최적의 분류기를 평가하는 일이다. 이 단계는 매개변수나 경쟁하는 기법을 선택하는 데 사용해서는 안 된다. 그래서 이 경우, 테스터 데이터에 대해 결정 트리의 성능을 측정하기로 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>> clf_dt = tree.DecisionTreeClassifier(criterion='entropy', max_depth=3, min_samples_leaf=5)
>>> clf_dt.fit(X_train, y_train)
>>> measure_performance(X_test, y_test, clf_dt)
 
Accuracy:0.793 
 
Classification report
             precision    recall  f1-score   support
 
        0.0       0.77      0.96      0.85       202
        1.0       0.88      0.54      0.67       127
 
avg / total       0.81      0.79      0.78       329
 
 
Confussion matrix
[[193   9]
 [ 59  68]] 
cs

분류 결과와 혼돈 매트릭스에서 이 기법은 너무 많은 사람을 생존하지 못했다고 측정하는 경향이 있다.