본문 바로가기

머신러닝 + 딥러닝

[머신러닝] 결정트리를 이용한 이미지 분류

728x90

어떤 사람 머리에서 나온 멋진 아이디어 : 결정 트리로 이미지 분류하기


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
리프 노드가 순수노드이기 때문에 훈련세트의 정확도가 100%이다
 

트리 그래프로 그려보면

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

아무래도 다른 모델을 사용을 해서 향상을 시켜야할 것 같다(?)

728x90