Machine Learning - Decision Tree
결정 트리(Decision Tree)
!pip install mglearn
!pip install --upgrade joblib==1.1.0
Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/ Requirement already satisfied: mglearn in /usr/local/lib/python3.7/dist-packages (0.1.9) Requirement already satisfied: scikit-learn in /usr/local/lib/python3.7/dist-packages (from mglearn) (1.0.2) Requirement already satisfied: imageio in /usr/local/lib/python3.7/dist-packages (from mglearn) (2.9.0) Requirement already satisfied: pandas in /usr/local/lib/python3.7/dist-packages (from mglearn) (1.3.5) Requirement already satisfied: pillow in /usr/local/lib/python3.7/dist-packages (from mglearn) (7.1.2) Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from mglearn) (1.21.6) Requirement already satisfied: joblib in /usr/local/lib/python3.7/dist-packages (from mglearn) (1.1.0) Requirement already satisfied: cycler in /usr/local/lib/python3.7/dist-packages (from mglearn) (0.11.0) Requirement already satisfied: matplotlib in /usr/local/lib/python3.7/dist-packages (from mglearn) (3.2.2) Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib->mglearn) (3.0.9) Requirement already satisfied: python-dateutil>=2.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib->mglearn) (2.8.2) Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib->mglearn) (1.4.4) Requirement already satisfied: typing-extensions in /usr/local/lib/python3.7/dist-packages (from kiwisolver>=1.0.1->matplotlib->mglearn) (4.1.1) Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.7/dist-packages (from python-dateutil>=2.1->matplotlib->mglearn) (1.15.0) Requirement already satisfied: pytz>=2017.3 in /usr/local/lib/python3.7/dist-packages (from pandas->mglearn) (2022.4) Requirement already satisfied: threadpoolctl>=2.0.0 in /usr/local/lib/python3.7/dist-packages (from scikit-learn->mglearn) (3.1.0) Requirement already satisfied: scipy>=1.1.0 in /usr/local/lib/python3.7/dist-packages (from scikit-learn->mglearn) (1.7.3) Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/ Requirement already satisfied: joblib==1.1.0 in /usr/local/lib/python3.7/dist-packages (1.1.0)
지니 불순도 (Gini Impurity)¶
지니 불순도는 결정 트리의 분할기준 중 하나입니다.
<img src=https://s3.amazonaws.com/codecademy-content/programs/data-science-path/decision-trees/comparison_1.svg width=300px><img src=https://s3.amazonaws.com/codecademy-content/programs/data-science-path/decision-trees/comparison_2.svg width=300px>
지니 불순도를 찾기 위해서는 1
에서 시작해서 세트의 각 class 비율의 제곱을 빼면 됩니다.
위 식에서 $K$은 class label의 개수이며, $p_i$은 $i$번째 class label의 비율입니다.
예를 들어, A
class인 instance가 3개 있고 B
class인 instance가 1개 있는 데이터의 경우에는 지니 불순도는 아래와 같이 계산됩니다.
만약 데이터가 하나의 class만 있다면, 지니 불순도는 0
이 됩니다. 불순도가 낮으면 낮을수록 결정 트리의 성능은 더 좋아집니다.
실습 1¶
위 정리에서 주어진 Tree의 불순도 계산
1 - (4/6)**2 - (2/6)**2
0.4444444444444445
sample_labels
리스트의 지니 불순도 계산
sample_labels = ["unacc", "unacc", "acc", "acc", "good", "good"]
impurity = 1
sample labels에 포함되어있는 class의 개수
from collections import Counter
label_counts = Counter(sample_labels)
print(label_counts)
Counter({'unacc': 2, 'acc': 2, 'good': 2})
데이터셋에서 각 label
의 확률 계산
for label in label_counts:
print(label)
prob = label_counts[label]/len(sample_labels)
print(prob)
unacc 0.3333333333333333 acc 0.3333333333333333 good 0.3333333333333333
확률을 이용하여 sample_labels
의 불순도 계산
for label in label_counts:
prob = label_counts[label]/len(sample_labels)
impurity -= prob ** 2
print(impurity)
0.6666666666666665
지니 불순도를 계산하는 코드 함수 제작
def gini(dataset):
impurity = 1
label_counts =Counter(dataset)
for label in label_counts:
prob_of_label = label_counts[label] / len(dataset)
impurity -= prob_of_label ** 2
return impurity
정보증가량 (Information Gain)¶
이제 지니 불순도가 낮은 끝마디(leaf node)를 만들기 위해서 어떠한 feature에 따라 데이터를 나누어야하는지 결정해야 합니다.
예를 들어, 학생들의 수면 시간 또는 학생들의 공부 시간 둘 중 어느 feature을 기준으로 학생들을 나누어야 더 좋은 tree를 만들 수 있을까요?
위 질문에 답하기 위해 어떠한 feature에 대하여 데이터를 나누었을 때의 정보증가량
을 계산해야 합니다.
정보증가량은 데이터 분할 전과 후의 불순도 차이를 측정합니다.
예를 들어, 불순도가 0.5
인 데이터를 어떠한 feature에 대해 나누었을 때, 불순도가 각각 0
, 0.375
, 0
인 끝마디가 생긴다고 가정해봅니다.
<img src=https://s3.amazonaws.com/codecademy-content/programs/data-science-path/decision-trees/info.svg width=300px>
이 경우에 데이터를 나누는 정보증가량은 0.5 - 0 - 0.375 - 0 = 0.125
입니다.
데이터를 나누었을때의 정보 증가량은 양수입니다. 따라서, 위처럼 결정 지점을 나눈 것은 결과적으로 불순도를 낮추었기 때문에 좋은 결정 지점입니다.
정보증가량은 크면 클수록 좋습니다.
실습 2¶
unsplit_labels
라는 임의의 데이터를 두가지 다른 분할 지점으로 나누었습니다. 이는 split_labels_1
와 split_labels_2
입니다.
각 분할에 대해 information gain 계산
unsplit_labels = ["unacc", "unacc", "unacc", "unacc", "unacc", "unacc", "good",
"good", "good", "good", "vgood", "vgood", "vgood"]
split_labels_1 = [["unacc", "unacc", "unacc", "unacc", "unacc", "unacc", "good", "good", "vgood"],
[ "good", "good"],
["vgood", "vgood"]]
split_labels_2 = [["unacc", "unacc", "unacc", "unacc","unacc", "unacc", "good", "good", "good", "good"],
["vgood", "vgood", "vgood"]]
# unsplit_labels의 지니 불순도를 계산해봅니다.
info_gain_1 = gini(unsplit_labels)
info_gain_1
0.6390532544378698
split_labels_1
의 각 부분집합에 대하여 지니 불순도을 계산하여 정보 증가량 계산
for subset in split_labels_1:
info_gain_1 -= gini(subset)
print(info_gain_1)
0.14522609394404257
split_labels_2
에 대해 정보증가량을 계산
info_gain_2 = gini(unsplit_labels)
for subset in split_labels_2:
info_gain_2 -= gini(subset)
print(info_gain_2)
0.15905325443786977
정보증가량을 계산하는 함수
def information_gain(starting_labels, split_labels):
info_gain = gini(starting_labels)
for subset in split_labels:
info_gain -= gini(subset)
return info_gain
가중 정보증가량 (Weighted Information Gain)¶
만약 정보증가량이 0
이라면 그 feature에 대해 데이터를 나누는 것은 소용이 없습니다. 때에 따라서 데이터를 나누었을 때 정보증가량이 음수가 될 수 있습니다. 이 문제를 해결하기 위해서 가중 정보증가량 (weighted information gain)을 사용합니다.
분할 후에 생성되는 데이터의 부분집합의 크기 또한 중요합니다. 예를 들어서, 아래 이미지에서는 불순도가 같은 두 부분집합이 있습니다.
<img src=https://s3.amazonaws.com/codecademy-content/programs/data-science-path/decision-trees/impurity-0.svg width=300px>
어느 부분집합을 결정 트리의 끝마디로 정하는게 좋은 결정트리를 만들 수 있을까요?
두 부분집합은 모두 불순도가 0으로써 완전하지만, 두 번째 부분집합이 더욱 의미있습니다. 두 번째 부분집합에는 많은 개수의 instance들이 있기 때문에 이 부분집합이 구성된 것이 우연이 아님을 알수 있습니다.
그 반대를 생각해보는 것도 도움이 됩니다. 아래 그림에서 같은 값의 불순도를 가지고 있는 두 부분집합이 있습니다.
<img src=https://s3.amazonaws.com/codecademy-content/programs/data-science-path/decision-trees/impurity-5.svg width=300px>
이 두 부분집합의 불순도는 굉장히 높습니다. 그렇지만 어느 부분집합의 불순도가 더 큰 의미를 가질까요? 왼쪽의 부분집합을 분할하는 것보다는 오른쪽 부분집합을 분할하여 불순도가 없는 집합을 만드는 것이 정보증가량이 더 클 것입니다. 따라서, 집합의 instance 개수를 고려하여 정보증가량을 계산해야 합니다.
집합의 크기까지 고려하도록 정보증가량 함수를 수정할 것 입니다. 단순히 불순도를 빼는 것에서 더 나아가 분할된 부분집합의 가중 불순도를 뺄 것입니다. 만약 분할 전의 데이터가 10
개의 instance을 가지고 있고 하나의 부분집합이 2
개의 instance가 있다면, 그 부분집합의 가중 불순도는 2/10 * impurity
가 되어 instance 숫자가 적은 세트의 중요도를 낮춥니다.
가중 정보증가량 계산의 예시는 아래와 같습니다.
<img src=https://s3.amazonaws.com/codecademy-content/programs/data-science-path/decision-trees/weighted_info.svg>
실습 3¶
아래는 데이터셋의 각 feature와 class label에 대한 설명입니다. Car dataset은 class에 해당하는 4가지 label과 각 차량의 특징을 나타내는 6개의 feature을 갖고 있습니다.
Label은 4개의 class, unacc
(unacceptable), acc
(acceptable), good
, vgood
로 이루어져 있으며, 각 class는 차량 구매시의 만족도(acceptability)를 나타냅니다.
각 차량은 6개의 feature을 가지고 있고, 아래와 같습니다.
- buying (차량의 가격):
"vhigh"
,"high"
,"med"
, or"low"
. - maint (차량 유지 비용):
"vhigh"
,"high"
,"med"
, or"low"
. - doors (차의 문 갯수):
"2"
,"3"
,"4"
,"5more"
. - persons (차량의 최대 탑승 인원):
"2"
,"4"
, or"more"
. - lug_boot (차량 트렁크의 사이즈):
"small"
,"med"
, or"big"
- safety (차량의 안전성 등급):
"low"
,"med"
, or"high"
.
# 샘플 데이터
cars = [['med', 'low', '3', '4', 'med', 'med'],
['med', 'vhigh', '4', 'more', 'small', 'high'],
['high', 'med', '3', '2', 'med', 'low'],
['med', 'low', '4', '4', 'med', 'low'],
['med', 'low', '5more', '2', 'big', 'med'],
['med', 'med', '2', 'more', 'big', 'high'],
['med', 'med', '2', 'more', 'med', 'med'],
['vhigh', 'vhigh', '2', '2', 'med', 'low'],
['high', 'med', '4', '2', 'big', 'low'],
['low', 'low', '2', '4', 'big', 'med']]
car_labels = ['acc', 'acc', 'unacc', 'unacc', 'unacc', 'vgood', 'acc', 'unacc', 'unacc', 'good']
information_gain
함수를 수정하여 가중 정보증가량을 계산
가중치: 부분집합의 label 갯수 `len(subset)` / 분할 전의 집합의 label 갯수 `len(starting_labels)`
def weighted_information_gain(starting_labels, split_labels):
info_gain = gini(starting_labels)
for subset in split_labels:
info_gain -= gini(subset) * (len(subset) / len(starting_labels))
return info_gain
- 아래
split()
함수를 살펴보겠습니다.
def split(dataset, labels, column):
data_subsets = []
label_subsets = []
# empty list
counts = list(set([data[column] for data in dataset]))
# list 의 중복 항목 제거를 위한 set 변환
for k in counts: # k=counts element ['2', '4', 'more']
new_data_subset = []
new_label_subset = []
for i in range(len(dataset)): # data set len -> all looping
if dataset[i][column] == k:
new_data_subset.append(dataset[i])
new_label_subset.append(labels[i])
data_subsets.append(new_data_subset)
label_subsets.append(new_label_subset)
return data_subsets, label_subsets
# split 함수 호출
split_data, split_labels = split(cars, car_labels, 3)
split_data
[[['med', 'vhigh', '4', 'more', 'small', 'high'], ['med', 'med', '2', 'more', 'big', 'high'], ['med', 'med', '2', 'more', 'med', 'med']], [['med', 'low', '3', '4', 'med', 'med'], ['med', 'low', '4', '4', 'med', 'low'], ['low', 'low', '2', '4', 'big', 'med']], [['high', 'med', '3', '2', 'med', 'low'], ['med', 'low', '5more', '2', 'big', 'med'], ['vhigh', 'vhigh', '2', '2', 'med', 'low'], ['high', 'med', '4', '2', 'big', 'low']]]
len(split_data)
3
split_labels
를 사용, index 3
에 대해 스플릿한 information gain
# index 3으로 데이터를 분할하였을 때 정보증가량을 출력
weighted_information_gain(car_labels, split_labels)
0.30666666666666675
정보증가량을 찾는 과정을 모든 feature에 대해서 적용
# 데이터에 있는 모든 feature들에 대하여 `split()` 함수와 `information_gain()` 함수를 호출
# 4th feature(persons feature)가 가장 큰 영향을 미친다.
for i in range(0,6):
split_data, split_labels = split(cars, car_labels, i)
print(weighted_information_gain(car_labels, split_labels))
0.2733333333333334 0.040000000000000036 0.10666666666666666 0.30666666666666675 0.15000000000000002 0.29000000000000004
실습 4: 재귀 트리 만들기 (Recursive Tree Building)¶
데이터를 분할하였을 때 정보증가량이 가장 높은 feature을 찾을 수 있습니다. 이 방법을 반복하는 재귀 알고리즘을 통하여 트리를 구성할 수 있습니다. 데이터의 모든 instance에서 시작하여 데이터를 분할할 가장 좋은 feature을 찾고, 그 feature에 대해서 데이터를 나눈 후에 생성된 부분집합에 대해서 재귀적으로 위의 순서를 되풀이합니다.
정보증가량이 일어나지 않는 feature을 찾을 때까지 재귀를 반복합니다. 다른 말로, 우리는 더이상 불순도가 없는 부분집합을 만드는 분할이 존재하지 않을 때 결정 트리의 끝마디를 생성합니다. 이 끝마디는 전체 데이터에서 분류된 instance의 class을 담고 있습니다.
# 위의 함수들을 종합하여 가장 적합한 분할 feature을 찾는 함수 작성
def find_best_split(dataset, labels):
best_gain = 0
best_feature = 0
for feature in range(len(dataset[0])):
data_subset, label_subset = split(dataset, labels, feature)
gain = weighted_information_gain(labels, label_subset)
if gain > best_gain:
best_gain, best_feature = gain, feature
return best_gain, best_feature
위 함수를 cars
와 car_labels
에 대해 호출
best_gain, best_feature = find_best_split(cars, car_labels)
best_feature
3
best_gain
0.30666666666666675
data
와 labels
를 파라미터로 받는 build_tree()
라는 함수를 선언
이 함수는 재귀적으로 트리를 구성합니다.
def build_tree(data, labels):
best_gain, best_feature = find_best_split(data, labels)
if best_gain == 0:
return Counter(labels)
data_subsets, label_subsets = split(data, labels, best_feature)
branches = []
for i in range(len(data_subsets)):
branch = build_tree(data_subsets[i], label_subsets[i])
branches.append(branch)
return branches
만들어진 build_tree
함수 테스트
def print_tree(node, spacing=""):
question_dict = {0: "Buying Price", 1:"Price of maintenance",
2:"Number of doors", 3:"Person Capacity",
4:"Size of luggage boot", 5:"Estimated Saftey"}
# Base case: 끝노드에 도달함
if isinstance(node, Counter):
print (spacing + str(node))
return
print (spacing + "Splitting")
# 분할 지점에서 각 브랜치에 대해 재귀적으로 print_tree 함수를 호출
for i in range(len(node)):
print (spacing + '--> Branch ' + str(i)+':')
print_tree(node[i], spacing + " ")
# `build_tree` 함수와 `print_tree` 함수를 출력해봅니다.
tree = build_tree(cars, car_labels)
print_tree(tree)
Splitting --> Branch 0: Splitting --> Branch 0: Counter({'acc': 1}) --> Branch 1: Counter({'acc': 1}) --> Branch 2: Counter({'vgood': 1}) --> Branch 1: Splitting --> Branch 0: Counter({'unacc': 1}) --> Branch 1: Counter({'good': 1}) --> Branch 2: Counter({'acc': 1}) --> Branch 2: Counter({'unacc': 4})
실습 5: scikit-learn으로 구현하는 결정트리¶
scikit-learn에서 제공되는 make_moons 데이터를 사용합니다.
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import make_moons
X, y = make_moons(noise=0.32, random_state=42, n_samples=250)
sns.scatterplot(x=X[:, 0], y=X[:, 1],
hue=y, marker="o", s=25,
edgecolor="k", legend=False).set_title("Moon Data")
plt.show()
scikit-learn 패키지로 결정트리 구현
DecisionTreeClassifier` 분류기를 사용합니다.
[scikit-learn DecisionTreeClassifier documentation] (https://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html?highlight=decisiontreeclassifier#sklearn.tree.DecisionTreeClassifier)
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier()
.fit()
메소드를 통해 데이터를 Tree에 훈련
dt.fit(X,y)
DecisionTreeClassifier()
정확도 확인
dt.score(X,y)
1.0
완성된 결정트리 시각화
# classifier 결정트리를 시각화
from sklearn.tree import export_graphviz # drawing graphs specified in DOT language scripts
from six import StringIO
from IPython.display import Image
import pydotplus
dot_data = StringIO()
export_graphviz(dt, out_file=dot_data)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_png('/tree.png')
Image(graph.create_png())
결정 경계 시각화
from mglearn import plot_interactive_tree
ax = plot_interactive_tree.plot_tree_partition(X, y, dt)
ax.set_title("first decision tree")
Text(0.5, 1.0, 'first decision tree')
실습 6: 결정 트리 가지치기 (pruning)¶
가지치기란 최대트리로 형성된 결정트리의 특정 노드 밑의 하부 트리를 제거하여 일반화 성능을 높히는 것을 의미합니다. 모든 끝노드의 불순도가 0인 트리를 full tree라고 하는데, 이 경우에는 분할이 너무 많이 과적합의 위험이 발생합니다. 과적합은 학습 데이터에 과하게 학습하여 실제 데이터에 오차가 증가하는 현상입니다. 이를 방지하기 위해서 적절한 수준에서 끝노드를 결합해주는 기법을 가지치기(pruning)이라고 합니다.
scikit-learn DecisionTreeClassifier documentation
새로운 결정트리를 생성 (깊이를 지정)
pruned_dt = DecisionTreeClassifier(max_depth = 3)
pruned_dt.fit(X, y)
print(pruned_dt.score(X,y))
0.888
가지치기된 트리를 시각화
이렇게 끝노드의 개수를 지정해주면 트리가 데이터에 더욱 잘 일반화됩니다.
dot_data = StringIO()
export_graphviz( pruned_dt, out_file=dot_data)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_png('/tree.png')
Image(graph.create_png())
결정 경계 시각화를 통한 비교
ax = plot_interactive_tree.plot_tree_partition(X, y, pruned_dt)
ax.set_title("first decision tree")
Text(0.5, 1.0, 'first decision tree')
import pandas as pd
data_url = "https://raw.githubusercontent.com/inikoreaackr/ml_datasets/main/titanic.csv"
data = pd.read_csv(data_url)
data
PassengerId | Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 0 | 3 | Braund, Mr. Owen Harris | male | 22.0 | 1 | 0 | A/5 21171 | 7.2500 | NaN | S |
1 | 2 | 1 | 1 | Cumings, Mrs. John Bradley (Florence Briggs Th... | female | 38.0 | 1 | 0 | PC 17599 | 71.2833 | C85 | C |
2 | 3 | 1 | 3 | Heikkinen, Miss. Laina | female | 26.0 | 0 | 0 | STON/O2. 3101282 | 7.9250 | NaN | S |
3 | 4 | 1 | 1 | Futrelle, Mrs. Jacques Heath (Lily May Peel) | female | 35.0 | 1 | 0 | 113803 | 53.1000 | C123 | S |
4 | 5 | 0 | 3 | Allen, Mr. William Henry | male | 35.0 | 0 | 0 | 373450 | 8.0500 | NaN | S |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
886 | 887 | 0 | 2 | Montvila, Rev. Juozas | male | 27.0 | 0 | 0 | 211536 | 13.0000 | NaN | S |
887 | 888 | 1 | 1 | Graham, Miss. Margaret Edith | female | 19.0 | 0 | 0 | 112053 | 30.0000 | B42 | S |
888 | 889 | 0 | 3 | Johnston, Miss. Catherine Helen "Carrie" | female | NaN | 1 | 2 | W./C. 6607 | 23.4500 | NaN | S |
889 | 890 | 1 | 1 | Behr, Mr. Karl Howell | male | 26.0 | 0 | 0 | 111369 | 30.0000 | C148 | C |
890 | 891 | 0 | 3 | Dooley, Mr. Patrick | male | 32.0 | 0 | 0 | 370376 | 7.7500 | NaN | Q |
891 rows × 12 columns
data.columns
data = data[['Survived', 'Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare']]
data.describe()
Survived | Pclass | Age | SibSp | Parch | Fare | |
---|---|---|---|---|---|---|
count | 891.000000 | 891.000000 | 714.000000 | 891.000000 | 891.000000 | 891.000000 |
mean | 0.383838 | 2.308642 | 29.699118 | 0.523008 | 0.381594 | 32.204208 |
std | 0.486592 | 0.836071 | 14.526497 | 1.102743 | 0.806057 | 49.693429 |
min | 0.000000 | 1.000000 | 0.420000 | 0.000000 | 0.000000 | 0.000000 |
25% | 0.000000 | 2.000000 | 20.125000 | 0.000000 | 0.000000 | 7.910400 |
50% | 0.000000 | 3.000000 | 28.000000 | 0.000000 | 0.000000 | 14.454200 |
75% | 1.000000 | 3.000000 | 38.000000 | 1.000000 | 0.000000 | 31.000000 |
max | 1.000000 | 3.000000 | 80.000000 | 8.000000 | 6.000000 | 512.329200 |
data = data.dropna()
data.shape
(714, 7)
data.dtypes
Survived int64 Pclass int64 Sex object Age float64 SibSp int64 Parch int64 Fare float64 dtype: object
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
data.Sex = le.fit_transform(data.Sex)
/usr/local/lib/python3.7/dist-packages/pandas/core/generic.py:5516: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame. Try using .loc[row_indexer,col_indexer] = value instead See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy self[name] = value
data.dtypes
Survived int64 Pclass int64 Sex int64 Age float64 SibSp int64 Parch int64 Fare float64 dtype: object
y = data['Survived']
X = data.drop(columns = ['Survived'])
기계학습 모델을 훈련시키고 성능을 파악하기 위해서는 데이터를 훈련 데이터와 테스트 데이터로 나누어야 합니다.
<img src=https://upload.wikimedia.org/wikipedia/commons/b/bb/ML_dataset_training_validation_test_sets.png width=500px>
scikit-learn에서 지원하는 train_test_split 을 사용합니다. scikit-learn train_test_split documentation
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3)
DecisionTreeClassifier
분류기를 사용해 결정트리를 생성
dt = DecisionTreeClassifier()
- 트레이팅 데이터에
.fit()
메소드를 호출함으로써 트리를 데이터에 훈련,.fit()
메소드는training_points
와training_labels
을 파라미터로 받음.
dt.fit(X_train, y_train)
DecisionTreeClassifier()
testing_points
와testing_labels
에 대한 결정 트리의 정확도(.score()
)를 출력
print(dt.score(X_test, y_test))
0.8046511627906977
- 훈련된 트리 시각화
dot_data = StringIO()
export_graphviz(dt, out_file=dot_data)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_png('/tree.png')
Image(graph.create_png())
과제 1
max_leaf_nodes : 트리가 가질 수 있는 최대 leaf node의 수를 지정하는 parameter입니다. 해당 parameter가 지정되지 않은 경우 leaf node 수의 제한을 두지 않습니다.
max_depth : 트리의 최대 깊이 parameter가 None일 때는 node가 모든 잎들이 pure해질 때까지 혹은, min_samples_split 이 지정하는 값 이하의 수를 포함하도록 확장됩니다.
min_sample_split : node를 분할하기 위해 필요한 최소 샘플 수를 지정하는 parameter로 정수일 때는 최소 숫자로 간주되고 float일 때는 전체 샘플에 대한 min_sample_split의 비율만큼을 최소 샘플로 합니다.
min_sample_leaf : leaf nodes에 필요한 최소 샘플의 수르 지정하는 parameter로 양쪽 가지에 필요한 최소 training sample을 의미합니다. 특히, regression에서는 모형을 smoothing하는데 효과가 있습니다.
min_impurity_decrease : impurity가 감소하는 경우 node를 분할하도록 합니다.
from sklearn.tree import DecisionTreeClassifier
df = DecisionTreeClassifier()
dt_mxlf = DecisionTreeClassifier(max_leaf_nodes = 1000)
dt_mxdth = DecisionTreeClassifier(max_depth = 6)
dt_mss = DecisionTreeClassifier(min_samples_split = 8)
dt_msl = DecisionTreeClassifier(min_samples_leaf = 32)
dt_mid = DecisionTreeClassifier(min_impurity_decrease = 0.02)
# prunig parameter를 조절하지 않은 Decision tree
dt.fit(X_train, y_train)
dt.score(X_test, y_test)
0.8232558139534883
# `max_leaf_nodes` parameter를 조절한 Decision tree
dt_mxlf.fit(X_train, y_train)
dt_mxlf.score(X_test, y_test)
0.8186046511627907
# `max_depth` parameter를 조절한 Decision tree
dt_mxdth.fit(X_train, y_train)
dt_mxdth.score(X_test, y_test)
0.8372093023255814
#`min_sample_split` parameter를 조절한 Decision tree
dt_mss.fit(X_train, y_train)
dt_mss.score(X_test, y_test)
0.8
#`min_sample_leaf` parameter를 조절한 Decision tree
dt_msl.fit(X_train, y_train)
dt_msl.score(X_test, y_test)
0.827906976744186
#`min_impurity_decrease` parameter를 조절한 Decision tree
dt_mid.fit(X_train, y_train)
dt_mid.score(X_test, y_test)
0.8
과제3
- prunig 파라미터를 조정하지 않은 Decision tree의 경우 분류 정확도가 [0.7488372093023256]로 pruning 파라미터를 조정한 Decision tree의 분류 정확도가 각각 [0.7813953488372093, 0.8, 0.7813953488372093, 0.813953488372093, 0.7813953488372093] 인 것과 비교해 볼 때 정확도가 떨어집니다.
# `max_leaf_nodes` parameter를 조절한 Decision tree
dot_data = StringIO()
export_graphviz(dt_mxlf, out_file=dot_data)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_png('/tree.png')
Image(graph.create_png())
# `max_depth` parameter를 조절한 Decision tree
dot_data = StringIO()
export_graphviz(dt_mxdth, out_file=dot_data)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_png('/tree.png')
Image(graph.create_png())
#`min_sample_split` parameter를 조절한 Decision tree
dot_data = StringIO()
export_graphviz(dt_mss, out_file=dot_data)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_png('/tree.png')
Image(graph.create_png())
#`min_sample_leaf` parameter를 조절한 Decision tree
dot_data = StringIO()
export_graphviz(dt_msl, out_file=dot_data)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_png('/tree.png')
Image(graph.create_png())
#`min_impurity_decrease` parameter를 조절한 Decision tree
dot_data = StringIO()
export_graphviz(dt_mid, out_file=dot_data)
graph = pydotplus.graph_from_dot_data(dot_data.getvalue())
graph.write_png('/tree.png')
Image(graph.create_png())
- 과제 5번
max_leaf_nodes
, max_depth
, min_samples_split
parameter를 조정한 모델은 모두 트리 모델의 끝마디에서 Gini index는 높으나 node가 가지고 있는 sample의 수가 1 또는 2로 overfitting 되었을 확률이 높은 노드들을 다수 가지고 있으므로 적합하게 학습된 모델이라 할 수 없습니다. 한편 min_impurity_decrease
parameter를 조절한 Decision tree 모델의 경우에는 tree 의 끝마디 noder가 가진 gini index가 다소 높게 측정되었기 때문에 적합되었다고 보기 어렵습니다.
반면 'min_samples_leaf` parameter를 조절한 Decision tree는 하나의 끝마디에서 gini index가 다소 높게 측정되기는 했지만 각각의 끝마디 gini index가 낮게 나왔으며 각 끝마디의 샘플 수도 적정하기 때문에 다른 parameter를 조절한 Decision tree에 비교해 가장 적합하게, 가장 일반화를 잘 실행한 모델이라 볼 수 있을 것입니다.
Comments