[머신러닝] 결정트리를 이용한 이미지 분류
어떤 사람 머리에서 나온 멋진 아이디어 : 결정 트리로 이미지 분류하기
1. MNIST dataset of handwritten digits
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.layers import (Dense, BatchNormalization, Dropout)
from tensorflow.keras.datasets.mnist import load_data
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_digits
digits = load_digits()
import matplotlib.pyplot as plt
plt.gray()
plt.matshow(digits.images[0])
plt.show()
일단 뭐든 import 하기
대충 이렇게 생긴 이미지들이 있다.
print(digits.images.shape)
데이터의 배열의 크기 확인
(1797, 8, 8)
digits
'data': array([[ 0., 0., 5., ..., 0., 0., 0.],
[ 0., 0., 0., ..., 10., 0., 0.],
[ 0., 0., 0., ..., 16., 9., 0.],
...,
[ 0., 0., 1., ..., 6., 0., 0.],
[ 0., 0., 2., ..., 12., 0., 0.],
[ 0., 0., 10., ..., 12., 1., 0.]]),
'feature_names': ['pixel_0_0',
'pixel_0_1',
'pixel_0_2',
'pixel_0_3',
'pixel_0_4',
'pixel_0_5',
'pixel_0_6',
'pixel_0_7',
'pixel_1_0',
'pixel_1_1',
'pixel_1_2',
'pixel_1_3',
'pixel_1_4',
'pixel_1_5',
'pixel_1_6',
'pixel_1_7',
...
'pixel_6_0',
'pixel_6_1',
'pixel_6_2',
'pixel_6_3',
'pixel_6_4',
'pixel_6_5',
'pixel_6_6',
'pixel_6_7',
'pixel_7_0',
'pixel_7_1',
'pixel_7_2',
'pixel_7_3',
'pixel_7_4',
'pixel_7_5',
'pixel_7_6',
'pixel_7_7'],
'frame': None,
'images': array([[[ 0., 0., 5., ..., 1., 0., 0.],
[ 0., 0., 13., ..., 15., 5., 0.],
[ 0., 3., 15., ..., 11., 8., 0.],
...,
[ 0., 4., 11., ..., 12., 7., 0.],
[ 0., 2., 14., ..., 12., 0., 0.],
[ 0., 0., 6., ..., 0., 0., 0.]],
[[ 0., 0., 0., ..., 5., 0., 0.],
[ 0., 0., 0., ..., 9., 0., 0.],
[ 0., 0., 3., ..., 6., 0., 0.],
...,
[ 0., 0., 1., ..., 6., 0., 0.],
[ 0., 0., 1., ..., 6., 0., 0.],
[ 0., 0., 0., ..., 10., 0., 0.]],
[[ 0., 0., 0., ..., 12., 0., 0.],
[ 0., 0., 3., ..., 14., 0., 0.],
[ 0., 0., 8., ..., 16., 0., 0.],
...,
[ 0., 9., 16., ..., 0., 0., 0.],
[ 0., 3., 13., ..., 11., 5., 0.],
[ 0., 0., 0., ..., 16., 9., 0.]],
...,
[[ 0., 0., 1., ..., 1., 0., 0.],
[ 0., 0., 13., ..., 2., 1., 0.],
[ 0., 0., 16., ..., 16., 5., 0.],
...,
[ 0., 0., 16., ..., 15., 0., 0.],
[ 0., 0., 15., ..., 16., 0., 0.],
[ 0., 0., 2., ..., 6., 0., 0.]],
[[ 0., 0., 2., ..., 0., 0., 0.],
[ 0., 0., 14., ..., 15., 1., 0.],
[ 0., 4., 16., ..., 16., 7., 0.],
...,
[ 0., 0., 0., ..., 16., 2., 0.],
[ 0., 0., 4., ..., 16., 2., 0.],
[ 0., 0., 5., ..., 12., 0., 0.]],
[[ 0., 0., 10., ..., 1., 0., 0.],
[ 0., 2., 16., ..., 1., 0., 0.],
[ 0., 0., 15., ..., 15., 0., 0.],
...,
[ 0., 4., 16., ..., 16., 6., 0.],
[ 0., 8., 16., ..., 16., 8., 0.],
[ 0., 1., 8., ..., 12., 1., 0.]]]),
'target': array([0, 1, 2, ..., 8, 9, 8]),
'target_names': array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])}
digits 데이터에는 0부터 9까지의 타깃이 있고, 특성의 이름은 pixel_5_7 이런 식으로 되어있다. -> 57번째 픽셀값이라는 의미
from sklearn import metrics
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
n_samples = len(digits.images)
data = digits.images.reshape((n_samples, -1))
x_train, x_test, y_train, y_test = train_test_split(data, digits.target, test_size=0.2, shuffle=False)
훈련세트와 테스트 세트를 나누기
print(x_train.shape, x_test.shape)
훈련세트와 테스트세트의 크기 확인
(1437, 64) (360, 64)
from sklearn import tree
dt_classifier = tree.DecisionTreeClassifier()
dt_classifier.fit(x_train, y_train)
사이킷런의 결정트리 클래스인 DecisionTreeClassifier()를 통해 결정트리 모델 훈련하기
print(dt_classifier.score(x_train, y_train)) #훈련세트
print(dt_classifier.score(x_test, y_test)) #테스트세트
테스트 세트의 점수가 많이 낮다
1.0
0.7833333333333333
트리 그래프로 그려보면
import matplotlib.pyplot as plt
from sklearn.tree import plot_tree
plt.figure(figsize=(10, 7))
plot_tree(dt_classifier)
plt.show()
max_depth 매개변수로 트리를 가지치기 즉, 훈련세트에는 잘 맞고 테스트세트에는 잘 맞지 않게 하기 위해 트리의 최대 깊이를 지정한다.
루트 노드 아래로 최대 n개의 노드까지만 성장할 수 있다
max_depth가 1인 노드 그래프를 그려보면
plt.figure(figsize=(20, 15))
plot_tree(dt_classifier, max_depth=1, filled=True)
plt.show()
i) 루트 노드
테스트 조건이 픽셀 36번째 값이 0.5인지 아닌지로 나눈다.
샘플수는 1437개이며 value값으로 각각 0부터 9까지 몇 개의 샘플이 있는지 알 수 있다.
ii) 왼쪽 노드
테스트 조건이 픽셀 42번째 값이 3.5보다 크거나 같은지, 작은지에 따라 노드를 나눈다.
샘플 수는 217개이며, value 값을 확인해보았을 때 0이 제일 많고 그다음 9, 5순이다. 아마도 다른 값들은 오른쪽 노드에서 많이 걸러지는 듯 하다
gini 불순도가 많이 내려갔음을 확인할 수 있다.
iii) 오른쪽 노드
테스트 조건은 픽셀 60번째 값이 2.5보다 크거나 같은지, 작은지에 따라 나뉜다.
샘플 수가 1220이며 value 값을 확인해보았을 때 0빼고 나머지 숫자들이 아직은 덜 나뉘어졌음을 확인할 수 있다.
불순도는 여전히 높다
전체 트리그래프를 그려보면
plt.figure(figsize=(20, 15))
plot_tree(dt_classifier, filled=True)
plt.show()
이렇게 나뉘었음을 알 수 있다.
predicted = dt_classifier.predict(x_test)
_, axes = plt.subplots(2, 4)
images_and_labels = list(zip(digits.images, digits.target))
for ax, (images, label) in zip(axes[0, :], images_and_labels[:4]) :
ax.set_axis_off()
ax.imshow(images, cmap=plt.cm.gray_r, interpolation='nearest')
ax.set_title('Training: %i' %label)
images_and_predictions = list(zip(digits.images[n_samples//2:], predicted))
for ax, (image, prediction) in zip(axes[1, :],
images_and_predictions[:4]):
ax.set_axis_off()
ax.imshow(image, cmap=plt.cm.gray_r, interpolation='nearest')
ax.set_title('Prediction: %i' %prediction)
disp = metrics.plot_confusion_matrix(dt_classifier, x_test, y_test)
disp.figure_.suptitle("Confusion Matrix")
print(disp.confusion_matrix)
print(dt_classifier.score(x_test, y_test))
plt.show()
[[33 0 0 0 1 0 1 0 0 0]
[ 0 22 0 5 0 0 0 0 5 4]
[ 0 0 28 0 1 1 1 1 2 1]
[ 0 2 1 25 0 3 0 1 5 0]
[ 0 0 0 0 30 0 0 3 3 1]
[ 0 0 0 2 4 28 1 0 0 2]
[ 0 2 0 0 0 0 33 0 2 0]
[ 0 0 0 0 3 0 2 29 0 2]
[ 0 3 1 0 0 0 0 0 26 3]
[ 0 0 0 2 0 4 0 4 0 27]]
훈련 세트의 점수 :
0.7805555555555556
print(dt_classifier.feature_importances_)
특성 중요도를 살펴보면
[0. 0. 0. 0.00353176 0.00377717 0.02936961
0.00927207 0. 0. 0.00231906 0.04900172 0.00452985
0.00583679 0.0064422 0. 0. 0. 0.00817824
0.00505996 0.00482825 0.0253593 0.10346618 0. 0.
0. 0.0080003 0.08346567 0.05351064 0.00762054 0.02750071
0.00592855 0. 0. 0. 0.06402483 0.00518746
0.0725908 0.00694133 0.05015536 0. 0. 0.00152736
0.0789698 0.06114632 0.01370093 0.00414665 0.00154867 0.
0.00131137 0. 0.00921165 0. 0.01490092 0.00713765
0.07095377 0. 0. 0. 0.00732192 0.
0.0657452 0.0044627 0.01201676 0. ]
대충 36, 42번째 특성의 값이 높은 것으로 보아 이 특성값이 가장 중요함을 알 수 있다. 이는 위의 루트, 왼쪽 노드의 테스트 조건 값과 동일하다.
from sklearn.tree import DecisionTreeClassifier
dt_classifier = tree.DecisionTreeClassifier(max_depth=10, random_state=42, criterion='entropy')
dt_classifier.fit(x_train, y_train)
print(dt_classifier.score(x_train, y_train))
print(dt_classifier.score(x_test, y_test))
criterion 매개변수는 불순도를 지정하는데, 기본값은 gini이지만 이를 엔트로피 불순도로 바꿔보았다.
1.0
0.7805555555555556
from sklearn.metrics import f1_score
f1 = f1_score(y_test, predicted, average= "weighted")
print("F1 score : {}".format(f1))
결정트리를 평가하는 지표는 f1 score를 통해 이루어진다
sklearn의 metrics에서 f1-score를 가져온 후 score를 측정하면 답이 나온다
f1 score : 성과를 평가하기 위한 통계적 척도, 해당 모델이 얼마나 잘 작동하는지 통계적으로 확인, 0~9사이의 값
F1 score : 0.7817980741079288
아무래도 다른 모델을 사용을 해서 향상을 시켜야할 것 같다(?)