갈루아의 반서재

결정트리와 타이타닉 가설 설명 (1) - 데이터 전처리


결정트리는 매우 간단하지만 강력한 지도학습기법이다. 이 모델의 중요한 장점은 사람이 쉽게 이해할 수 있고, 새로운 인스턴스의 목적 범주를 이해하기 위해 결정트리를 따라 결정한다는 것이다. 훈련데이터가 무엇을 제시하느냐보다 결정의 이유를 보여주길 원하는 카드 승인이나 의료 진찰과 같은 태스크에 매우 유용하다.

해결하고자하는 문제는 탑승객의 나이, 선실 등급, 성별을 고려하여 타이타닉호의 탑승객이 생존했는지를 결정하는 것이다. 데이터는 아래 링크에서 다운로드할 수 있다.

http://biostat.mc.vanderbilt.edu/wiki/pub/Main/DataSets/titanic.txt

데이터셋의 각 인스턴스는 다음과 같은 형태를 갖는다.

1
2
3
4
5
6
7
8
"row.names","pclass","survived","name","age","embarked","home.dest","room","ticket","boat","sex"
"1","1st",1,"Allen, Miss Elisabeth Walton",29.0000,"Southampton","St Louis, MO","B-5","24160 L221","2","female"
"2","1st",0,"Allison, Miss Helen Loraine", 2.0000,"Southampton","Montreal, PQ / Chesterville, ON","C26","","","female"
"3","1st",0,"Allison, Mr Hudson Joshua Creighton",30.0000,"Southampton","Montreal, PQ / Chesterville, ON","C26","","(135)","male"
"4","1st",0,"Allison, Mrs Hudson J.C. (Bessie Waldo Daniels)",25.0000,"Southampton","Montreal, PQ / Chesterville, ON","C26","","","female"
"5","1st",1,"Allison, Master Hudson Trevor", 0.9167,"Southampton","Montreal, PQ / Chesterville, ON","C22","","11","male"
"6","1st",1,"Anderson, Mr Harry",47.0000,"Southampton","New York, NY","E-12","","3","male"
"7","1st",1,"Andrews, Miss Kornelia Theodosia",63.0000,"Southampton","Hudson, NY","D-7","13502 L77","10","female"
cs


속성리스트는 다음과 같다.

['row.names' 'pclass' 'survived' 'name' 'age' 'embarked' 'home.dest' 'room'
 'ticket' 'boat' 'sex']


데이터셋을 numpy 배열로 만든다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> import csv
>>> import numpy as np
>>> with open('data/titanic.csv''rb') as csvfile:
>>>     titanic_reader = csv.reader(csvfile, delimiter=',',
>>>     quotechar='"')
>>>     
>>>     row = titanic_reader.next()
>>>     feature_names = np.array(row)
>>>    
>>>     titanic_X, titanic_y = [], []
>>>     for row in titanic_reader:  
>>>         titanic_X.append(row)
>>>         titanic_y.append(row[2]) 
            "survived"
>>>    
>>>     titanic_X = np.array(titanic_X)
>>>     titanic_y = np.array(titanic_y)
cs


1
2
3
4
5
6
7
8
9
>>> print feature_names
 
['row.names' 'pclass' 'survived' 'name' 'age' 'embarked' 'home.dest' 'room'
 'ticket' 'boat' 'sex']
 
>>> print titanic_X[0], titanic_y[0]
 
['1' '1st' '1' 'Allen, Miss Elisabeth Walton' '29.0000' 'Southampton'
 'St Louis, MO' 'B-5' '24160 L221' '2' 'female'1
cs


데이터 전처리


학습을 위해 사용할 속성을 선택한다. 다른 속성들은 생존여부에 관련이 없다는 가정하에 여기서는 1, 4, 10번째 속성인 등급, 연령, 그리고 성별을 사용한다. 알고리즘이 입력값으로 적절하지 못한 속성을 가지지 못하고 있으면 아무리 좋은 알고리즘을 가지고 있다고 해도 좋은 결과를 도출해낼 수 없다.

1
2
>>> titanic_X = titanic_X[:, [1410]]
>>> feature_names = feature_names[[1410]]
cs


매우 특별한 속성은 과적합된다(예를 들어, 이름이 x라면 생존했다고 분류하는 트리가 있다고 생각해보자). 그리고 작은 수의 인스턴스 속성은 유사한 문제를 갖는다(이러한 속성은 일반화에 유용하지 않다).

학습데이터는 다음과 같다.

1
2
3
4
5
6
>>> print feature_names
 
>>> print titanic_X[12], titanic_y[12]
 
['pclass' 'age' 'sex']
['1st' 'NA' 'female'1
cs

위는 색인번호 12의 인스턴스이다. 속성의 일부는 그대로 사용할 수 없다. 나이 속성에는 결측치가 있고 데이터셋에 문제가 된다. 이런 경우에는 훈련데이터의 평균 나이로 결측치를 대체하기로 한다. 아니면 중앙값을 사용할 수도 있다. 유념해야할 점은 결측치를 대체할 때 본래의 문제가 변경될 수 있다는 점이다. 최종 결과가 왜곡되지 않게 데이터를 변경할 때 변경하는 것이 무엇인지 명확히 알아야 한다.

나이 속성이 결측치를 가지므로, 결측치에 평균값을 넣는다.

1
2
3
>>> ages = titanic_X[:, 1]
>>> mean_age = np.mean(titanic_X[ages != 'NA'1].astype(np.float))
>>> titanic_X[titanic_X[:, 1== 'NA'1= mean_age
cs


scikit-learn의 결정트리의 구현은 실수값 속성을 입력해야 하고, 모델의 결정 규칙은 다음 형태가 된다.

속성 <= 값


예를 들어, age <= 20 이다. 나이를 제외한 속성은 범주적이다. 즉, 속성은 남성, 여성과 같은 이산 집합 중의 하나이다. 그래서 범주적 데이터를 실수값으로 변환해야 한다.

성별 속성으로 시작해보자. scikit-learn의 preprocessing 모듈에는 LabelEncoder가 있고, fit 메소드는 범주적 집합을 0, ... ,  K-1 정수로 변환한다(K는 집합에서 다른 범주의 수다. 성별의 경우 0, 1이 된다).


1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> from sklearn.preprocessing import LabelEncoder
>>> enc = LabelEncoder()
>>> label_encoder = enc.fit(titanic_X[:, 2])
>>> print "Categorical classes:", label_encoder.classes_
 
Categorical classes: ['female' 'male']
 
>>> integer_classes = label_encoder.transform(label_encoder.classes_)
>>> print "Integer classes:", integer_classes
 
Integer classes: [0 1]
 
>>> t = label_encoder.transform(titanic_X[:, 2])
>>> titanic_X[:, 2= t 
cs

라인 13, 14는 성별 속성값을 0과 1로 변환하고 훈련 데이터도 변경한다.


1
2
3
4
5
>>> print feature_names
>>> print titanic_X[12], titanic_y[12
 
['pclass' 'age' 'sex']
['1st' '31.1941810427' '0'1
cs


또 다른 범주적 속성은 선실 등급이다. 같은 방법으로 3개의 범주를 0, 1, 2로 변환한다. 추가적인 인코더를 만들고 범주 속성을 새로운 3개의 이진 속성으로 변환한다. 각 이진 속성은 인스턴스의 속성값이 1이라면 그 속성에 해당한다. 이를 one hot encoding 이라고 하며, 실수 기반 기법을 위한 범주 속성을 관리하는 일반적인 방법이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
>>> from sklearn.preprocessing import OneHotEncoder
>>> enc = LabelEncoder()
>>> label_encoder = enc.fit(titanic_X[:, 0])
>>> print "Categorical classes:", label_encoder.classes_
 
Categorical classes: ['1st' '2nd' '3rd']
 
>>> integer_classes = label_encoder.transform(label_encoder.classes_).reshape(31)
>>> print "Integer classes:", integer_classes
 
Integer classes: [[0]
 [1]
 [2]]
 
>>> enc = OneHotEncoder()
>>> one_hot_encoder = enc.fit(integer_classes)
>>> 
>>> num_of_rows = titanic_X.shape[0]
>>> t = label_encoder.transform(titanic_X[:, 0]).reshape(num_of_rows, 1)
>>> 
>>> new_features = one_hot_encoder.transform(t)
>>> 
>>> titanic_X = np.concatenate([titanic_X, new_features.toarray()], axis = 1)
>>> 
>>> titanic_X = np.delete(titanic_X, [0], 1)
>>> 
>>> feature_names = ['age''sex''first_class''second_class''third_class']
>>> 
>>> titanic_X = titanic_X.astype(float)
>>> titanic_y = titanic_y.astype(float)
>>> 
>>> print feature_names
>>> print titanic_X[0], titanic_y[0
 
['age''sex''first_class''second_class''third_class']
29.   0.   1.   0.   0.1.0
cs


위의 코드는 먼저 범주를 정수로 변환하고 속성 배열을 추가하기 위해 3개의 새로운 속성을 만드는데 OneHotEncoder 클래스를 사용했다. 마지막으로 본래의 클래스 속성에서 훈련데이터를 제거한다.