SW/영상인식

CIFAR10 : 새로운 클래스 인식, 테스트 데이터 구성 : 실습

얇은생각 2019. 11. 4. 07:30
반응형

과제

"""
[HW3] CIFAR-10 classification
* CIFAR-10 testset 소개
32x32 three color = 3072 dimensions per sample
비행기, 자동차, 새, 고양이, 사슴, 개, 개구리, 말, 배, 트럭의 10가지 클래스로 이뤄져 있으며, 60000개(트레이닝셋 50000 / 테스트셋 10000)의 이미지(클래스당 6000개)를 포함한 데이터셋.

=== HW #3 내용 ===========================================

* 필수사항 (100점)
1. Model Saver 기능 포함 필요
2. Random sample or sample index 지정하여 visualization & 추정결과 plotting (MNIST 과제와 비슷)
3. Testset Accuracy 90% 이상 넘기기


* 도전과제 (20~100점)
1. 직접 찍은 실제 자동차, 고양이, 개 사진 등을 이용하여 테스트 결과 출력 (20점)
참고 자료: 업로드
2. Visualize Filters (20점)
관련 문서: https://hwiyong.tistory.com/35
3. 사람 얼굴 등 새로운 클래스를 인식하도록 만들기 (40점)
관련 문서: https://blog.naver.com/cenodim/220946688251
4. Few-shot learning 등으로 새로운 클래스를 인식하도록 만들기 (3의 hard case) (100점)
관련 문서: ① https://wewinserv.tistory.com/123 ② https://arxiv.org/pdf/1904.05046.pdf

① 해당 논문은 distractor라는 개념을 통해서 새로 들어온 샘플이 새로운 클래스로 분류되어야 하는지 아닌지를 먼저 판단하고, 
이를 반영하여 새로운 unknown 클래스로 자동으로 분류하는 모델에 대한 논문입니다.
이 논문을 이해하기 위해서는 clustering(k-means 등) 에 대한 지식이 있어야 합니다.

② few-shot learning 에 대한 survey paper 입니다.

도전과제는 모두 할 필요 없습니다. 중복으로 해도 됩니다. (단, 3과 4는 중복이 안 됩니다.)


* 제공 코드
1. CIFAR-10 예제코드 (Simple CNN)
2. 도전과제 1에 대한 예시 code (MNIST case)


* 제출방법: 코드(.ipynb or .py)와 보고서(.hwp or .docx)를 zip으로 압축하여 제출해주세요. 파일명은 '학번_성명_hw3.zip' 와 같이 해 주시길 바랍니다.


* 보고서: 필수사항에서의 결과 및 코드에서의 해당 부분을 표시하여 주시고,
도전과제 결과의 경우 구현 방법에 대한 간략한 설명과 함께 결과를 캡쳐해주시기 바랍니다.
"""

 

이번 포스팅에서는 도전과제 3번을 진행하도록 하겠습니다. CIFAR에서 제공해주는 예제가 아닌 저만의 클래스를 만들어서 문제를 해결하는 것입니다. 

저는 평소에 궁금했던, 사자와 호랑이를 분류하는 인공지능을 구현하기로 하였습니다. 대신 방식은 CIFAR10과 동일한 방식으로 데이터를 구성하고, 적용하는 것입니다. 

 

 

이미지 데이터 모으기

import os
from google_images_download import google_images_download   #importing the library

response = google_images_download.googleimagesdownload()   #class instantiation

pathDir = os.path.abspath(os.path.curdir)
pathDir += "\chromedriver.exe"
print(pathDir)


arguments = {
    "keywords":"tiger,lion",
    "limit":1000,
    "print_urls":True,
    "chromedriver":pathDir    
}  

paths = response.download(arguments)   #passing the arguments to the function
print(paths)   #printing absolute paths of the downloaded images

파싱 결과

저는 사자와 호랑이 사진을 모으기 위해, 구글 이미지 크롤러를 사용했습니다. 다만 제한없이 이 모듈을 사용하기 위해서는 크롬 드라이버를 다운받아야 합니다. 그래야먄, 브라우저 페이지를 조종하여, 페이지에 있는 이미지를 모드 파싱해올 수 있습니다.

저는 위 방식을 통해, 호랑이와 사자 사진을 약 650장을 모았습니다. 

 

 

이미지 데이터, 바이너리 파일로 바꾸기

import PIL
from PIL import Image
import numpy as np
import os
import matplotlib.pyplot as plt

# data directory
input = os.getcwd() + "/downloads"
output = os.getcwd() + "/downloads/data2.bin"
imageSize = 32
imageDepth = 3
debugEncodedImage = False

# show given image on the window for debug
def showImage(r, g, b):
    temp = []
    for i in range(len(r)):
        temp.append(r[i])
        temp.append(g[i])
        temp.append(b[i])
    show = np.array(temp).reshape(imageSize, imageSize, imageDepth)
    plt.imshow(show, interpolation='nearest')
    plt.show()

# convert to binary bitmap given image and write to law output file
def writeBinaray(outputFile, imagePath, label):
    img = Image.open(imagePath)
    reimg = img.resize((imageSize, imageSize), PIL.Image.ANTIALIAS)
    reimg = (np.array(reimg))
    
    if reimg.shape != (32, 32, 3):
        print("동일하지 않습니다.")
        return
    
    r = reimg[:,:,0].flatten()
    g = reimg[:,:,1].flatten()
    b = reimg[:,:,2].flatten()
    label = [label]

    out = np.array(list(label) + list(r) + list(g) + list(b), np.uint8)
    outputFile.write(out.tobytes())

    # if you want to show the encoded image. set up 'debugEncodedImage' flag
    if debugEncodedImage:
        showImage(r, g, b)

subDirs = os.listdir(input)
numberOfClasses = len(input)

try:
    os.remove(output)
except OSError:
    pass

outputFile = open(output, "ab")
label = -1
totalImageCount = 0
labelMap = []

for subDir in subDirs:
    subDirPath = os.path.join(input, subDir)

    # filter not directory
    if not os.path.isdir(subDirPath):
        continue

    imageFileList = os.listdir(subDirPath)
    label += 1

    print("writing %3d images, %s" % (len(imageFileList), subDirPath))
    totalImageCount += len(imageFileList)
    labelMap.append([label, subDir])

    for imageFile in imageFileList:
        imagePath = os.path.join(subDirPath, imageFile)
        writeBinaray(outputFile, imagePath, label)

outputFile.close()
print("Total image count: ", totalImageCount)
print("Succeed, Generate the Binary file")
print("You can find the binary file : ", output)
print("Label MAP: ", labelMap)

바이너리 파일 변환

 

이제 다운 받은 데이터를 위와 같이 하나의 바이너리 파일로 바꾸었습니다. 텐서플로우에서 제공해주는 방식으로 바이너리 파일을 바꾸어 구현하였습니다. 이미 인터넷에 좋은 코드들이 있어, 구현에 어려움이 없었습니다. 하지만, 현재 자신의 컴퓨터 디렉토리라던지 방식에 맞게 커스텀 마이징 하는 부분들이 주요했습니다.

또한, 구글 이미지 크롤러에서 받은 데이터들 중 유효하지 않은 것들이 있어, 어려움이 있었습니다. 이런 경우, 제가 원하는 이미지 형식이 아닌 경우, 동일하지 않다 판단하여, 바이너리 파일에 포함시키지 않고 넘어갔습니다.

 

 

구현한 데이터로 CIFAR10 방식으로 적용해보기

import numpy as np
import os
import matplotlib.pyplot as plt

# data directory
input = os.getcwd() + "/data/data.bin"
imageSize = 32
labelSize = 1
imageDepth = 3
debugEncodedImage = True

# show given image on the window for debug
def showImage(r, g, b):
    temp = []
    for i in range(len(r)):
        temp.append(r[i])
        temp.append(g[i])
        temp.append(b[i])
    show = np.array(temp).reshape(imageSize, imageSize, imageDepth)
    plt.imshow(show, interpolation='nearest')
    plt.show()
    

def load_one_data(data, offset):   
    eachColorSize = imageSize * imageSize
    offset = labelSize + (labelSize + eachColorSize * 3) * offset

    rgb = []
    for i in range(3):
        color = eachColorSize * i
        rgbData = data[offset + color : offset + color + eachColorSize]
        rgb.append(rgbData)
    
    # showImage(rgb[0], rgb[1], rgb[2])
    
    retData = np.array([rgb[0], rgb[1], rgb[2]])
    retData = retData.reshape(32, 32, 3)
        
    return retData, data[offset-1]
    


def load_batch(path, num_train_samples):
    data = np.fromfile(path, dtype='u1')
    
    retData = []
    retLabels = []
    
    for i in range(num_train_samples):
        d, l = load_one_data(data, i)
        retData.append(d)
        retLabels.append(l)
    
    retData = np.array(retData)
    retLabels = np.array(retLabels)
    
    print(retData.shape)
    
    return retData, retLabels
    

def load_data():
    dirname = 'downloads'
    path = os.path.join(dirname, "data2.bin")
    
    # 저는 총 671개의 데이터로 진행했습니다.
    num_train_samples = 671
    
    x_train = np.empty((num_train_samples, 3, 32, 32), dtype='uint8')
    y_train = np.empty((num_train_samples,), dtype='uint8')
    
    x_train, y_train = load_batch(path, num_train_samples)
        
    y_train = np.reshape(y_train, (len(y_train), 1))            
    
    return x_train, y_train
    
from __future__ import print_function
import keras
from keras.datasets import cifar10
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation, Flatten
from keras.layers import Conv2D, MaxPooling2D
import os
import PIL
from PIL import Image
import numpy as np
import os
import matplotlib.pyplot as plt

batch_size = 32
num_classes = 10
epochs = 50
data_augmentation = True
num_predictions = 20
save_dir = os.path.join(os.getcwd(), 'saved_models')
model_name = 'lion_tiger_cifar10_trained_model.h5'

# The data, split between train and test sets:yr
x_train, y_train = load_data()
print('x_train shape:', x_train.shape)
print(x_train.shape[0], 'train samples')

# Convert class vectors to binary class matrices.
y_train = keras.utils.to_categorical(y_train, num_classes)

model = Sequential()
model.add(Conv2D(32, (3, 3), padding='same',
                 input_shape=x_train.shape[1:]))
model.add(Activation('relu'))
model.add(Conv2D(32, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Conv2D(64, (3, 3), padding='same'))
model.add(Activation('relu'))
model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes))
model.add(Activation('softmax'))

# initiate RMSprop optimizer
opt = keras.optimizers.RMSprop(lr=0.0001, decay=1e-6)

# Let's train the model using RMSprop
model.compile(loss='categorical_crossentropy',
              optimizer=opt,
              metrics=['accuracy'])

x_train = x_train.astype('float32')
x_train /= 255

if not data_augmentation:
    print('Not using data augmentation.')
    model.fit(x_train, y_train,
              batch_size=batch_size,
              epochs=epochs,
              shuffle=True)
else:
    print('Using real-time data augmentation.')
    # This will do preprocessing and realtime data augmentation:
    datagen = ImageDataGenerator(
        featurewise_center=False,  # set input mean to 0 over the dataset
        samplewise_center=False,  # set each sample mean to 0
        featurewise_std_normalization=False,  # divide inputs by std of the dataset
        samplewise_std_normalization=False,  # divide each input by its std
        zca_whitening=False,  # apply ZCA whitening
        zca_epsilon=1e-06,  # epsilon for ZCA whitening
        rotation_range=0,  # randomly rotate images in the range (degrees, 0 to 180)
        # randomly shift images horizontally (fraction of total width)
        width_shift_range=0.1,
        # randomly shift images vertically (fraction of total height)
        height_shift_range=0.1,
        shear_range=0.,  # set range for random shear
        zoom_range=0.,  # set range for random zoom
        channel_shift_range=0.,  # set range for random channel shifts
        # set mode for filling points outside the input boundaries
        fill_mode='nearest',
        cval=0.,  # value used for fill_mode = "constant"
        horizontal_flip=True,  # randomly flip images
        vertical_flip=False,  # randomly flip images
        # set rescaling factor (applied before any other transformation)
        rescale=None,
        # set function that will be applied on each input
        preprocessing_function=None,
        # image data format, either "channels_first" or "channels_last"
        data_format=None,
        # fraction of images reserved for validation (strictly between 0 and 1)
        validation_split=0.0)

    # Compute quantities required for feature-wise normalization
    # (std, mean, and principal components if ZCA whitening is applied).
    datagen.fit(x_train)

    # Fit the model on the batches generated by datagen.flow().
    model.fit_generator(datagen.flow(x_train, y_train,
                                     batch_size=batch_size),
                        epochs=epochs,
                        steps_per_epoch=671)

# Save model and weights
if not os.path.isdir(save_dir):
    os.makedirs(save_dir)
model_path = os.path.join(save_dir, model_name)
model.save(model_path)
print('Saved trained model at %s ' % model_path)

테스트 결과 약 97% 정도로 호랑이와 사자를 구별하는 것을 알 수 있었습니다. 구별해야할 클래스가 적은 경우, 성능이 쉽고 높게 나올 수 있다는 것을 알 수 있었습니다. 그리고, 해당 데이터가 적기 때문인 것도 어느정도 작용한 것 같습니다. 하지만, 현재 구글에서 제공해주는 사진은 97% 정도의 정확도로 호랑이와 사자를 구별해 낼 수 있다고도 할 수 있습니다.

 

 

임의 데이터 뽑아서 결과 확인하기

%matplotlib inline
import math
import matplotlib.pyplot as plt
import random

# Get one and predict
r = random.randint(0, 671 - 1)
input_val = x_train[r:r+1]
output_val = model.predict(input_val)

print(r, "Prediction : ", np.argmax(output_val))
# Selected sample showing
print(input_val[0].shape)
plt.imshow(
    input_val[0],
)
plt.show()

테스트 결과

해당 데이터를 뽑아 테스트를 진행해보았습니다. 사자를 잘 예측하는 것으로 확인하였지만, 해상도가 낮아 식별하기 어려운 부분도 존재하는 것 같습니다.

 

 

결론

데이터를 수집하여, 새로운 클래스를 직접 만들어보았습니다. CIFAR 데이터 크기는 32*32 이므로 비슷한 종류의 클래스들은 사람 눈으로도 식별하기 어렵다는 것을 알게 되었고, 따라서, 상당히 고사양 이미지를 학습하기 위해서는 어마어마한 학습 시간과 성능이 필요하다나는 것을 느끼게 되었습니다. 

또, 텐서플로우, 파이토치, 케라스 등에서 제공해주는 데이터 셋의 구성은 다 다르다는 것을 알게 되었습니다. 따라서 저는 처음에 텐서플로우 코드를 통해 데이터를 형성해서, 케라스 방식으로 로드하다보니 어려움이 있었습니다. 결국 내가 사용할 머신 러닝 플랫폼에 코드를 직접 까 본 다음에, 활용하면 좀 더 손쉽게 작업을 할 수 있다는 것을 알게 되었고, 다음에 이런 기회가 된다면 그러한 방식으로 구현하여, 좀 더 깔끔하고 정확한 구현이 될 것 같습니다.

반응형