4 minute read

손모양 구분 AI

  • 지금까지 공부한 CNN을 활용하여 손모양 구분 AI를 개발하자.
  • 필자는 아직 실력이 부족하여 Kaggle의 faraz munshi 님이 개발한 내용을 바탕으로 개발했다.
  • 데이터 셋은 kaggle에서 받을 수 있으니 사용하실 분들은 아래의 주소로 가셔서 받으시길 바랍니다.
  • 데이터셋 주소 kaggle

데이터 특징

  • 펼친 손가락의 갯수와 오른손, 왼손으로 총 12개로 구분된다.
  • 모든 데이터는 흑백에 (128 x 128)의 데이터 셋이다.
  • train 되는 데이터의 총 개수는 18,000개, test 되는 데이터의 총 개수는 3,600개이다.
  • 데이터 총 용량은 177MB
  • 파일별로 이름 맨 마지막에 어떤 파일인지 라벨로 구분되어있다.
  • ex) 파일이름_2L.png dataset

추가할 모듈

  • 자주사용하는 numpy, pandas, os를 추가
  • 이미지 처리를 위해 matplotlib, cv2, PIL을 추가
  • 레이어 제작을 위해 tensorflow와 keras 추가
  • 데이터 전처리를 위해 sklearn, pathlib을 추가
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow import keras
import os
import cv2
from sklearn import preprocessing
from pathlib import Path
from PIL import Image

데이터 전처리

  • 데이터 셋 불러오기
  • extract_label 함수는 받아올 데이터의 경로와 라벨을 분리시킨다.
  • train_set_label과 test_set_label에 라벨을 각각 받아온다.
  • 라벨은 왼손은 L, 오른손은 R로 표시하고, 펼친 손가락의 개수가 함께 포함된다.
  • ex) 0L -> 손가락을 하나도 안펼친 왼손
#----------------------------------데이터 전처리 -------------------------------
def extract_label(base):
    #base -> 폴더 경로
    path = [] #파일 경로
    label = [] # 0L, 0R, 1L, 1R, 2L, 2R, 3L, 3R, 4L, 4R, 5L, 5R,
    #라벨과 파일 경로 입력
    for filename in os.listdir(base):
        label.append(filename.split('.')[0][-2:]) #맨끝에 붙어있는 라벨을 가져옴
        path.append(base + filename) #파일 위치와 파일 이름

    return path, label

#파일 경로
train_base  = "./../../../fingers/train/"
test_base = "./../../../fingers/test/"

#파일 경로와 라벨 받아오기
train_set_path, train_set_label = extract_label(train_base)
test_set_path, test_set_label = extract_label(test_base)

print("Number of training set examples : ", len(train_set_path))
print("Number of test set examples: ", len(test_set_path))
  • 출력되는 데이터 갯수를 확인하자
    • 학습시킬 데이터 : 18,000개
    • 테스트할 데이터 : 3,600개

1

데이터 미리보기

  • 첫번째 데이터를 미리 보기 해보자
  • matplot에서 지원하느 show()기능을 활용하여 출력한다.
  • 출력에 함께 라벨도 함께 출력했다.
# 첫번째 이미지 미리보기-------------------------------
index = 0
image = cv2.imread(train_set_path[index])

plt.imshow(image)
plt.title(train_set_label[index], fontsize = 20)
plt.show()

미리보기

  • 0L인 사진이 정확하게 나오는 것을 알 수 있다.

사진 데이터를 숫자화 하기

  • png 형태의 사진 데이터는 바로 텐서플로우에 넣을 수 없다.
  • 따라서 사진을 학습 가능한 형태로 변형시켜야한다.
  • OpenCV2에서 지원하는 imread()를 사용하여 사진데이터를 이진화시킨다.
  • 파일 경로 내의 모든 사진을 모두 넣기 위해 반복문으로 feature_set에 리스트 형태로 저장한다.
#데이터를 학습 가능한 형태로 분리시켜 저장
def feature_data_split(path):
    feature_set = []
    for p in path:
        image = cv2.imread(p)
        feature_set.append(image)
    return feature_set

#데이터 분리
X_train = feature_data_split(train_set_path)
X_test = feature_data_split(test_set_path)
  • 단순한 list로 된 데이터는 텐서플로우에 넣을 수 없다.
  • 따라서 numpy array형태로 변환시켜준다.
  • numpy array가 어떤 형태인지 확인해본다.
X_train = np.array(X_train)
X_test = np.array(X_test)

print(X_train.shape)
print(X_test.shape)

2

  • 128 x 128 크기의 이미지, 흑백(3)

라벨 처리

  • 현재 라벨 상태는 학습에 사용이 불가능한 형태이다. 3

  • 위에서 받아온 라벨을 정수로 바꾸어 사용가능하게 하자
  • preprocessing에서 LabelEncoder()를 사용하면 라벨을 쉽게 정수로 바꿀 수 있다.
#--------------- 라벨 처리  ------------------------------

print("학습시킬 데이터 라벨 :", list(np.unique(train_set_label)))
print("테스트할 데이터 라벨 :", list(np.unique(test_set_label)))
num_classes = len(np.unique(train_set_label))

#라벨을 숫자로 바꾸기 ex) 0L = 1, 0R = 2
label_encoder = preprocessing.LabelEncoder()
y_train = label_encoder.fit_transform(train_set_label)
y_test = label_encoder.fit_transform(test_set_label)

print("숫자로 바뀐 학습   라벨 : ", np.unique(y_train))
print("숫자로 바뀐 테스트 라벨 : ", np.unique(y_test))
  • 정수로 잘 바뀐것을 알 수 있다. 4
  • 정수로 변환된 라벨을 원 핫 인코딩 하자.
  • 원핫 인코딩 하면 학습에 사용가능한 형태로 변환된다.
#원 핫 인코딩
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

print(y_train.shape)
print(y_test.shape)

5

학습

  • 2D 컨볼루션 레이어를 제작한다.
    • 커널 사이즈 : 3 x 3
    • padding : 필요없음(‘same’)
    • 활성함수 : relu
    • input shape : 128 x 128에 흑백 (128, 128, 3)
    • 각각의 레이어 특성(노드) 갯수 : 32, 64, 128
  • 2D 컨볼루션 레이어 다음에는 항상 polling layer를 삽입
    • MaxPolling 방법 사용
    • Polling Layer 크기 : 2 x 2
  • 컨볼루션 레이어가 끝나고 나면 1차원으로 flatten 해준다.
  • Dense 레이어로 정확도를 높힌다.
    • 레이어 특성(노드) 갯수 : 128개의 크기
    • 활성함수 : relu
  • Dense로 높힌 정확도를 바탕으로 정형화된 데이터를 처낸다.
    • Drop시킬 데이터는 20%
  • 출력 레이어로 Dense레이어를 사용
    • 출력층의 갯수는 라벨 갯수 : 12개
    • 12개를 각각 0 - 1 사이의 값을 출력하기 위해 활성함수로 softmax 사용
model = tf.keras.Sequential([
    tf.keras.layers.Conv2D(32, (3, 3), padding = "same", activation='relu', input_shape = (128, 128, 3)),
    tf.keras.layers.MaxPool2D( (2, 2) ),
    tf.keras.layers.Conv2D(64, (3, 3), padding = "same", activation='relu', input_shape = (128, 128, 3)),
    tf.keras.layers.MaxPool2D( (2, 2) ),
    tf.keras.layers.Conv2D(128, (3, 3), padding = "same", activation='relu', input_shape = (128, 128, 3)),
    tf.keras.layers.MaxPool2D( (2, 2) ),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(128, activation= 'relu'),
    tf.keras.layers.Dropout(0.2),
    tf.keras.layers.Dense(num_classes, activation="softmax")
])
model.summary()

6

  • 컴파일 과정
    • 오차 : categorical_crossentropy(원 핫 인코딩된 softmax)
    • 옵티마이저 : adam
    • 검사 결과 : 정확도
      model.compile(loss = "categorical_crossentropy", optimizer= 'adam', metrics= ['accuracy'])
      
  • 텐서보드를 활용한 데이터 분석
  • log값을 시간을 바탕으로 저장
  • fit 시킬때 배치 사이즈는 128, 학습 횟수는 5회로 한다.
from tensorflow.keras.callbacks import TensorBoard
import time

tensorboard = TensorBoard( log_dir = 'logs/{}'.format( '첫모델' + str( int( time.time()) ) ) )

model.fit(X_train, y_train, batch_size= 128, epochs=5, validation_data=(X_test, y_test), callbacks = [tensorboard])

model.save('./model1')

score = model.evaluate( X_test, y_test)
print("테스트 결과 :", score)

7

  • 텐서보드의 정보
  • 파란색이 validation, 회색이 실제 학습 정확도이다. 9

검증

  • 학습 모델의 정확도가 무려 1.0이 나왔다.
  • 제대로된 학습이 이루어 졌는지 테스트 모델과 검증을 해보자
  • 테스트 데이터중 100개만 확인해봤다.
  • 100개 중에서도 10번째 부터 20번째까지 이미지로 확인해보자
model = tf.keras.models.load_model('./model1/')
sample_predictions = model.predict(X_test[:100])
sample_predictions[10:20]
fig, axs = plt.subplots( 2, 5, figsize = [24, 12])

count = 20

for i in range(2):
    for j in range(5):

        img = cv2.imread(test_set_path[count])
        results = np.argsort(sample_predictions[count])[::-1]
        labels = label_encoder.inverse_transform(results)
        
        axs[i][j].imshow(img)
        axs[i][j].set_title(labels[0], fontsize = 20)
        axs[i][j].axis('off')

        count += 1

plt.suptitle("샘플 예측 결과 ", fontsize = 24)
plt.show()

8

  • 10개의 사진 모두 정확하게 구분했다.

후기

  • 학습 방법에 따라 정확도가 다른건 알았지만, 100%가 될줄은 몰랐다.
  • 연습으로 하기 좋은 데이터 셋과 간단한 데이터 전처리로 쉽게 할 수 있었다.
  • 다음 포스트는 RNN 방법을 공부 해보겠습니다.


😺 오타나 논리적 오류 지적은 언제든지 환영합니다!😺   
항상 읽어주셔서 감사합니다~ 🙏

Leave a comment