본문 바로가기

Computer Science/AI Exploration

[E-01] rock_scissor_papaer classification

728x90

미니 프로젝트: 가위바위보 분류기를 만들자.

모듈 import

import os
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow import keras
from PIL import Image
import glob

data load하기

# data load하기
def load_data(img_path, number_of_data=300):  # 가위바위보 이미지 개수 총합에 주의하세요.
    # 가위 : 0, 바위 : 1, 보 : 2
    img_size = 28
    color = 3

    #이미지 데이터와 라벨(가위 : 0, 바위 : 1, 보 : 2) 데이터를 담을 행렬(matrix) 영역을 생성합니다.
    imgs = np.zeros(number_of_data*img_size*img_size*color,
                    dtype=np.int32).reshape(number_of_data, img_size, img_size, color)
    # (300, 28, 28, 3)
    labels = np.zeros(number_of_data, dtype=np.int32)

    idx = 0
    for file in glob.iglob(img_path+'/scissor/*.jpg'):
        img = np.array(Image.open(file), dtype=np.int32)
        imgs[idx, :, :, :] = img    # 데이터 영역에 이미지 행렬을 복사
        labels[idx] = 0   # 가위 : 0
        idx = idx+1

    for file in glob.iglob(img_path+'/rock/*.jpg'):
        img = np.array(Image.open(file), dtype=np.int32)
        imgs[idx, :, :, :] = img    # 데이터 영역에 이미지 행렬을 복사
        labels[idx] = 1   # 바위 : 1
        idx = idx+1

    for file in glob.iglob(img_path+'/paper/*.jpg'):
        img = np.array(Image.open(file), dtype=np.int32)
        imgs[idx, :, :, :] = img    # 데이터 영역에 이미지 행렬을 복사
        labels[idx] = 2   # 보 : 2
        idx = idx+1

    print("학습데이터(x_train)의 이미지 개수는", idx, "입니다.")
    return imgs, labels

image를 28 x 28 사이즈로 변경하기

# image를 resize하는 함수
def resize_images(img_path):
    images = glob.glob(img_path+ "/*.jpg")
    print(len(images), "images to be resized")

    # file 모두 28 28 사이즈로
    target_size = (28, 28)
    for img in images:
        old_img = Image.open(img)
        new_img = old_img.resize(target_size, Image.ANTIALIAS)
        new_img.save(img, "JPEG")

    print(len(images), "images resized.")

# 가위 바위 보 이미지가 저장된 디렉토리 아래의 모든 jpg 파일을 읽어들여서 resize하기
image_dir_path = os.getenv("HOME") + "/aiffel/rock_scissor_paper/scissor"
resize_images(image_dir_path)

image_dir_path = os.getenv("HOME") + "/aiffel/rock_scissor_paper/rock"
resize_images(image_dir_path)

image_dir_path = os.getenv("HOME") + "/aiffel/rock_scissor_paper/paper"
resize_images(image_dir_path)

print("가위바위보 이미지 모두 resize 완료!")
100 images to be resized
100 images resized.
100 images to be resized
100 images resized.
100 images to be resized
100 images resized.
가위바위보 이미지 모두 resize 완료!

변경한 이미지를 정규화, reshape 하기

# loading train dataset

image_dir_path = os.getenv("HOME") + "/aiffel/rock_scissor_paper"
(x_train, y_train) = load_data(image_dir_path)

x_train_norm = x_train/255.0   # 입력은 0~1 사이의 값으로 정규화
x_train_reshaped = x_train_norm.reshape(-1, 28, 28, 3) #reshape

print("x_train shape: {}".format(x_train.shape))
print("y_train shape: {}".format(y_train.shape))
학습데이터(x_train)의 이미지 개수는 300 입니다.
x_train shape: (300, 28, 28, 3)
y_train shape: (300,)

building model

n_channel_1 = 32
n_channel_2 = 32
n_dense = 9
n_train_epoch = 30

model = keras.models.Sequential()
model.add(keras.layers.Conv2D(n_channel_1, (3, 3),
          activation='relu', input_shape=(28, 28, 3)))
model.add(keras.layers.MaxPool2D(2, 2))
model.add(keras.layers.Conv2D(n_channel_2, (3, 3), activation='relu'))
model.add(keras.layers.MaxPooling2D((2, 2)))
model.add(keras.layers.Conv2D(n_channel_2, (3, 3), activation='relu'))
model.add(keras.layers.MaxPooling2D((2, 2)))

model.add(keras.layers.Flatten())
model.add(keras.layers.Dense(n_dense, activation='relu'))
model.add(keras.layers.Dense(3, activation='softmax'))

model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

model.summary()
Model: "sequential_16"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_46 (Conv2D)           (None, 26, 26, 32)        896       
_________________________________________________________________
max_pooling2d_43 (MaxPooling (None, 13, 13, 32)        0         
_________________________________________________________________
conv2d_47 (Conv2D)           (None, 11, 11, 32)        9248      
_________________________________________________________________
max_pooling2d_44 (MaxPooling (None, 5, 5, 32)          0         
_________________________________________________________________
conv2d_48 (Conv2D)           (None, 3, 3, 32)          9248      
_________________________________________________________________
max_pooling2d_45 (MaxPooling (None, 1, 1, 32)          0         
_________________________________________________________________
flatten_13 (Flatten)         (None, 32)                0         
_________________________________________________________________
dense_26 (Dense)             (None, 9)                 297       
_________________________________________________________________
dense_27 (Dense)             (None, 3)                 30        
=================================================================
Total params: 19,719
Trainable params: 19,719
Non-trainable params: 0
_________________________________________________________________

training model

## 모델을 훈련시킨다
model.fit(x_train_reshaped, y_train, epochs=n_train_epoch)
Epoch 1/30
10/10 [==============================] - 1s 13ms/step - loss: 1.1013 - accuracy: 0.2967
Epoch 2/30
10/10 [==============================] - 0s 3ms/step - loss: 1.0967 - accuracy: 0.3467
Epoch 3/30
10/10 [==============================] - 0s 3ms/step - loss: 1.0970 - accuracy: 0.3333
Epoch 4/30
10/10 [==============================] - 0s 3ms/step - loss: 1.0951 - accuracy: 0.3467
Epoch 5/30
10/10 [==============================] - 0s 3ms/step - loss: 1.0963 - accuracy: 0.3533
Epoch 6/30
10/10 [==============================] - 0s 3ms/step - loss: 1.0915 - accuracy: 0.4767
Epoch 7/30
10/10 [==============================] - 0s 3ms/step - loss: 1.0920 - accuracy: 0.3700
Epoch 8/30
10/10 [==============================] - 0s 3ms/step - loss: 1.0894 - accuracy: 0.4433
Epoch 9/30
10/10 [==============================] - 0s 3ms/step - loss: 1.0860 - accuracy: 0.4400
Epoch 10/30
10/10 [==============================] - 0s 3ms/step - loss: 1.0805 - accuracy: 0.5333
Epoch 11/30
10/10 [==============================] - 0s 3ms/step - loss: 1.0774 - accuracy: 0.3833
Epoch 12/30
10/10 [==============================] - 0s 3ms/step - loss: 1.0701 - accuracy: 0.6033
Epoch 13/30
10/10 [==============================] - 0s 3ms/step - loss: 1.0727 - accuracy: 0.4467
Epoch 14/30
10/10 [==============================] - 0s 3ms/step - loss: 1.0603 - accuracy: 0.5500
Epoch 15/30
10/10 [==============================] - 0s 3ms/step - loss: 1.0458 - accuracy: 0.5233
Epoch 16/30
10/10 [==============================] - 0s 5ms/step - loss: 1.0279 - accuracy: 0.4833
Epoch 17/30
10/10 [==============================] - 0s 4ms/step - loss: 1.0174 - accuracy: 0.5200
Epoch 18/30
10/10 [==============================] - 0s 3ms/step - loss: 0.9879 - accuracy: 0.7033
Epoch 19/30
10/10 [==============================] - 0s 3ms/step - loss: 0.9553 - accuracy: 0.6967
Epoch 20/30
10/10 [==============================] - 0s 3ms/step - loss: 0.9135 - accuracy: 0.6633
Epoch 21/30
10/10 [==============================] - 0s 3ms/step - loss: 0.9173 - accuracy: 0.5367
Epoch 22/30
10/10 [==============================] - 0s 3ms/step - loss: 0.8301 - accuracy: 0.7333
Epoch 23/30
10/10 [==============================] - 0s 3ms/step - loss: 0.8114 - accuracy: 0.7500
Epoch 24/30
10/10 [==============================] - 0s 3ms/step - loss: 0.7811 - accuracy: 0.7333
Epoch 25/30
10/10 [==============================] - 0s 3ms/step - loss: 0.7913 - accuracy: 0.6767
Epoch 26/30
10/10 [==============================] - 0s 3ms/step - loss: 0.7455 - accuracy: 0.7300
Epoch 27/30
10/10 [==============================] - 0s 3ms/step - loss: 0.6553 - accuracy: 0.8533
Epoch 28/30
10/10 [==============================] - 0s 3ms/step - loss: 0.6334 - accuracy: 0.8000
Epoch 29/30
10/10 [==============================] - 0s 3ms/step - loss: 0.6063 - accuracy: 0.8300
Epoch 30/30
10/10 [==============================] - 0s 3ms/step - loss: 0.5728 - accuracy: 0.8300





<keras.callbacks.History at 0x7f55ac1fca00>

test data를 load하기

## test data

def load_test_data(img_path, number_of_data=90):  # 가위바위보 이미지 개수 총합에 주의하세요.
    # 가위 : 0, 바위 : 1, 보 : 2
    img_size = 28
    color = 3
    #이미지 데이터와 라벨(가위 : 0, 바위 : 1, 보 : 2) 데이터를 담을 행렬(matrix) 영역을 생성합니다.
    imgs = np.zeros(number_of_data*img_size*img_size*color,
                    dtype=np.int32).reshape(number_of_data, img_size, img_size, color)
    # (300, 28, 28, 3)
    labels = np.zeros(number_of_data, dtype=np.int32)

### test를 앞에 붙였음
    idx = 0 
    for file in glob.iglob(img_path+'/test/scissor/*.jpg'):
        img = np.array(Image.open(file), dtype=np.int32)
        imgs[idx, :, :, :] = img    # 데이터 영역에 이미지 행렬을 복사
        labels[idx] = 0   # 가위 : 0
        idx = idx+1

    for file in glob.iglob(img_path+'/test/rock/*.jpg'):
        img = np.array(Image.open(file), dtype=np.int32)
        imgs[idx, :, :, :] = img    # 데이터 영역에 이미지 행렬을 복사
        labels[idx] = 1   # 바위 : 1
        idx = idx+1

    for file in glob.iglob(img_path+'/test/paper/*.jpg'):
        img = np.array(Image.open(file), dtype=np.int32)
        imgs[idx, :, :, :] = img    # 데이터 영역에 이미지 행렬을 복사
        labels[idx] = 2   # 보 : 2
        idx = idx+1

    print("학습데이터(x_test)의 이미지 개수는", idx, "입니다.")
    return imgs, labels


## resize test data
def resize_test_images(img_path):
    images = glob.glob(img_path+ "/*.jpg")
    print(len(images), "images to be resized")

    # file 모두 28 28 사이즈로
    target_size = (28, 28)
    for img in images:
        old_img = Image.open(img)
        new_img = old_img.resize(target_size, Image.ANTIALIAS)
        new_img.save(img, "JPEG")

    print(len(images), "images resized.")

이미지를 불러서, 정규화하고 reshape하기

# x_test, y_test를 만드는 방법은 x_train, y_train을 만드는 방법과 아주 유사합니다.
# [[YOUR CODE]]
import numpy as np

def load_test_data(img_path, number_of_data=90):  # 가위바위보 이미지 개수 총합에 주의하세요.
    # 가위 : 0, 바위 : 1, 보 : 2
    img_size=28
    color=3
    #이미지 데이터와 라벨(가위 : 0, 바위 : 1, 보 : 2) 데이터를 담을 행렬(matrix) 영역을 생성합니다.
    imgs=np.zeros(number_of_data*img_size*img_size*color,dtype=np.int32).reshape(number_of_data,img_size,img_size,color)
    # (300, 28, 28, 3)
    labels=np.zeros(number_of_data,dtype=np.int32)

    idx=0
    for file in glob.iglob(img_path+'/scissor/*.jpg'):
        img = np.array(Image.open(file),dtype=np.int32)
        imgs[idx,:,:,:]=img    # 데이터 영역에 이미지 행렬을 복사
        labels[idx]=0   # 가위 : 0
        idx=idx+1
    # int(imgs.shape)
    for file in glob.iglob(img_path+'/rock/*.jpg'):
        img = np.array(Image.open(file),dtype=np.int32)
        imgs[idx,:,:,:]=img    # 데이터 영역에 이미지 행렬을 복사
        labels[idx]=1   # 바위 : 1
        idx=idx+1  

    for file in glob.iglob(img_path+'/paper/*.jpg'):
        img = np.array(Image.open(file),dtype=np.int32)
        imgs[idx,:,:,:]=img    # 데이터 영역에 이미지 행렬을 복사
        labels[idx]=2   # 보 : 2
        idx=idx+1

    print("학습데이터(x_test)의 이미지 개수는", idx,"입니다.")
    return imgs, labels

def resize_images(img_path):
    images=glob.glob(img_path + "/*.jpg")  

    print(len(images), " images to be resized.")

    # 파일마다 모두 28x28 사이즈로 바꾸어 저장합니다.
    target_size=(28,28)
    for img in images:
        old_img=Image.open(img)
        new_img=old_img.resize(target_size,Image.ANTIALIAS)
        new_img.save(img, "JPEG")

    print(len(images), " images resized.")
    plt.imshow(new_img)
    print(new_img.size)

# 가위 바위 보 이미지가 저장된 디렉토리 아래의 모든 jpg 파일을 읽어들여서
image_dir_path = os.getenv("HOME") + "/aiffel/rock_scissor_paper/test/scissor"
resize_test_images(image_dir_path)

image_dir_path = os.getenv("HOME") + "/aiffel/rock_scissor_paper/test/rock"
resize_test_images(image_dir_path)

image_dir_path = os.getenv("HOME") + "/aiffel/rock_scissor_paper/test/paper"
resize_test_images(image_dir_path)

# 데이터
image_dir_path = os.getenv("HOME") + "/aiffel/rock_scissor_paper/test" # 내 이미지
(x_test, y_test)=load_test_data(image_dir_path)

x_test_norm = x_test/255.0
x_test_reshaped=x_test_norm.reshape( -1, 28, 28, 3)

print(f"x_test_reshaped shape: {x_test_reshaped.shape}")
print(f"y_test shape: {y_test.shape}")
30 images to be resized
30 images resized.
30 images to be resized
30 images resized.
30 images to be resized
30 images resized.
학습데이터(x_test)의 이미지 개수는 90 입니다.
x_test_reshaped shape: (90, 28, 28, 3)
y_test shape: (90,)

모델 평가하기

# 모델 시험
test_loss, test_accuracy = model.evaluate(x_test_reshaped, y_test, verbose=13)
print("test_loss: {} ".format(test_loss))
print("test_accuracy: {}".format(test_accuracy))
test_loss: 0.4908536374568939 
test_accuracy: 0.7555555701255798

회고

  • 이번 프로젝트에서 어려웠던 점,
    • 정확도가 0.6을 넘지 않아 이를 올리기 위해 애를 썼다.
    • 사이즈를 알맞은 형식에 지정하는 게 조금 어려웠다.
    • 깃헙에 올리려는데, 처음에 push하는 건 잘 됐으나 그 이후에 오류가 나서 고치기 힘들었다 ㅠㅠ
    • test data를 마련하고 있는데, 이를 분류하는 게 조금 어려웠다.
  • 프로젝트를 진행하면서 알아낸 점 혹은 아직 모호한 점.

    1. 알아낸 점

      • 데이터 reshape를 할 때 (-1)을 넣어주면 남은 숫자를 자동으로 채워준다는 걸 처음 알았다.
      • model 공부를 하다가, layer를 더 쌓아서 maxpooling을 하였다. 이렇게 되면 more abstract feature를 추출할 수 있게 되고, 가위 바위 보 이미지를 더 잘 추출할 수 있게 됨을 알게 되었다.
    2. 모호한 점

      • 아직 클라우드 서버에서 github로 올리는 게 잘 안되는데, 아직도 이를 해결하지 못했다.
      • train data, test data의 size를 56x56, 112x112로 바꿔보려 했는데, 오류가 뜨면서 학습이 진행되지 않았고, 학습이 잘 진행된다고 하더라도 정확도가 더욱 낮아졌기 때문에 다시 28x28 사이즈로 데이터를 사용했다.
      • layer를 더 쌓게 되면 왜 더욱 abstract한 feature들을 추출할 수 있게 되는지 해답을 얻지 못했다.
  • 루브릭 평가 지표를 맞추기 위해 시도한 것들.

    • convolution layer와 maxpooling layer를 하나 더 추가하니 정확도가 아주 높게 상승하였다.
    • 그리고, dense parameter와 epoch 또한 여러 번 바꿔주어서 정확도를 높였다.
    • dataset의 size를 56x56, 128x128로 바꾸어 보았다.
  • 만약에 루브릭 평가 관련 지표를 달성 하지 못했을 때, 이유에 관한 추정.

    1. 이미지 분류기 모델이 성공적으로 만들어지고, 트레이닝이 정상적으로 수행되었다.(이상 x)

    2. 데이터셋에서 여러 각도를 바꾸고, 모양도 바꾸고 손등, 손바닥 등을 보여주면서 데이터셋을 다양하게 설정하고, 0~1까지 정규화를 진행하였다. 만약 달성하지 못할 확률이 큰 루브릭 기준을 고르라고 하면 2번일 것 같다. 그 이유는, 데이터셋이 내 손만 존재하고, 타인들의 데이터셋을 사용하진 않았기 때문이라고 생각한다.

    3. 분류모델의 test accuarcy가 0.75가 나와서 60% 이상 도달하였다.

  • 자기 다짐

이것저것 모델들을 둘러보고, 데이터를 직접 찍어서 학습을 시켜보는 게 참 좋은 과정이었다. 아이펠에 오기 전에는 모델들의 architecture나 이론적인 부분만 학습하고 지나갔기 때문에, 이것이 코드로써 어떻게 구현되는지는 잘 몰랐었다. 하지만 이번 exploration을 통해서 데이터를 직접 수집하고 어떻게 다루는지에 대해 알게 되어서 좋았다.

그리고 주피터노트북을 처음 써봐서 애를 썼는데, 이제 조금은 익숙해진 것 같아 좋았고, 이를 토대로 블로그를 더 열심히 꾸며야겠다는 생각이 들었다.

728x90

'Computer Science > AI Exploration' 카테고리의 다른 글

[E-06]Sentiment analysis  (0) 2022.02.22
[E-05] Kaggle  (0) 2022.02.22
[E-04] RNN  (0) 2022.02.22
[E-03] camera_stickerapp_project  (0) 2022.02.22
[E-02] iris_classification  (0) 2022.02.22