차밍이

[Keras] CNN ImageDataGenerator : 손글씨 글자 분류 본문

파이썬/Tensorflow & Keras

[Keras] CNN ImageDataGenerator : 손글씨 글자 분류

2020. 5. 5. 00:01
반응형

안녕하세요. 이전 포스팅을 통해서 CNN을 활용한 직접 만든 손글씨 이미지 분류 작업을 진행했습니다. 생각보다 데이터가 부족했음에도 80% 정도의 정확도를 보여주었습니다. 이번 포스팅에서는 ImageDataGenerator를 사용해서 한번 진행해보겠습니다.

이전 포스팅 : [Keras] CNN 이미지 분류 실습 : 손글씨 이미지 분류 : 간단한 CNN모델

불러오는 중입니다...

 

이미지 데이터

현재 파일이 한 폴더에 모두 담겨있는 상태입니다. ImageDataGenerator가 각 분류 데이터를 읽어올 때, 폴더별로 category를 인식합니다. 그래서 가, 다, 라, 카, 사 5개의 글자를 각 5개의 폴더에 넣어주어야 합니다.

 

이미지 카테고리화

데이터를 읽어오겠습니다.

from keras.preprocessing.image import ImageDataGenerator


from keras.utils import np_utils
from keras.models import Sequential
from keras.preprocessing.image import array_to_img, img_to_array, load_img
from keras.layers import *
from keras.callbacks import ModelCheckpoint, EarlyStopping

import os
import pandas as pd
import numpy as np

from sklearn.metrics import accuracy_score

 

os.listdir(path+'train')
# 총 180개의 jpg 파일의 이름
['aa_bc_01_02.jpg',
 'aa_bc_02_02.jpg',
 'aa_chj_01_14.jpg',
 'aa_chj_02_14.jpg',
 'aa_cjh_01_12.jpg',
 'aa_cjh_02_12.jpg',

...

 'sa_pkh_02_03.jpg',
 'sa_say_01_17.jpg',
 'sa_say_02_17.jpg',
 'sa_ykw_01_13.jpg',
 'sa_ykw_02_13.jpg'] 

shutil

shutil 이라는 라이브러가 있습니다. 파일의 위치를 옮겨줄 수 있습니다. jpg 파일을 다른 폴더로 옮기기 위해서 사용하였습니다.

import shutil

trainFileList = [x for x in trainFileList if 'jpg' in x]
testFileList = [x for x in testFileList if 'jpg' in x]
word = {'ga' : 0, 'da' : 1, 'sa' : 2, 'aa' : 3, 'ka' : 4}
# 폴더 만들어주기 train
currentPath = path + 'train'  # train 파일이 있는 현재 경로

print(word.keys()) # ga, da, sa ..

# 카테고리화 된 폴더가 없으면 폴더 생성
try:
    for key in word.keys(): 
        os.makedirs(currentPath + f'\\{key}')
# 이미 존재하면 패스
except:
    pass

# jpg 파일 옮기기
for file in trainFileList: # Train data들 각각
    label = file.split('_')[0] 
    # 예시 aa_bc_01_02.jpg
    # label = 'aa'
    targetPath = currentPath + f'\\{label}' # 목표 위치인 aa폴더에 넣기
    try :
        shutil.move(currentPath+f'\\{file}', targetPath+f'\\{file}')
    except:
        pass

 

각 폴더에 이미지들을 모두 옮겨주었습니다.

각 데이터가 폴더 안에 안착하였습니다.

테스트 데이터 또한 똑같이 진행합니다.

# 폴더 만들어주기 test
currentPath = path + 'test'
print(word.keys())
try:
    for key in word.keys(): # ga da ...
        os.makedirs(currentPath + f'\\{key}')
except:
    pass

for file in testFileList:
    label = file.split('_')[0]
    targetPath = currentPath + f'\\{label}'
    try :
        shutil.move(currentPath+f'\\{file}', targetPath+f'\\{file}')
    except:
        pass

 

 

ImageDataGenerator : 이미지 데이터 생성

ImageDataGenerator를 통해서 데이터를 만들어줄 것입니다. 어떤 방식으로 데이터를 증식시킬 것인지 아래와 같은 옵션을 통해서 설정합니다.

trainDataGen = ImageDataGenerator(rescale=1./255,
                                 rotation_range = 30,
                                 width_shift_range=0.1,
                                 height_shift_range=0.1,
                                 shear_range=0.2,
                                 zoom_range=0.2,
                                 horizontal_flip=False,
                                 vertical_flip=False,
                                 fill_mode='nearest'
                                 )
trainGenSet = trainDataGen.flow_from_directory(
    path + 'train',
    batch_size=32,
    target_size=(28,28),
    class_mode='categorical'
)
Found 180 images belonging to 5 classes.

180개의 image데이터를 인식했고 5개의 classes로 분류하였습니다.

 

테스트 데이터의 경우 확인용 데이터이므로 증식하지 않고 resclae을 통해서 정규화만 진행하고 그대로 불러옵니다.

testDataGen = ImageDataGenerator(rescale=1./255)
testGenSet = testDataGen.flow_from_directory(
    path + 'test',
    target_size=(28,28),
    batch_size=32,
    class_mode='categorical'
)
Found 90 images belonging to 5 classes.

 

모델 생성

기존 모델과 이미지 증식과의 차이를 보기 위한 과정입니다. 이전에 진행했던 CNN 모델 구성을 그대로 가져와서 진행하였습니다.

# 모델 구성
# 기존 모델 형식 그대로 가져왔음
model = Sequential()
model.add(Conv2D(64, kernel_size=(3,3), padding='same', input_shape=(28,28,3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Dropout(0.25))

model.add(Conv2D(128, kernel_size=(3,3),padding='same', activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Dropout(0.25))

model.add(Conv2D(256, kernel_size=(3,3), padding='same', activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(256, activation='relu'))
model.add(Dropout(0.25))
model.add(Dense(5, activation='softmax'))
model.summary()
Model: "sequential_3"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_7 (Conv2D)            (None, 28, 28, 64)        1792      
_________________________________________________________________
max_pooling2d_7 (MaxPooling2 (None, 14, 14, 64)        0         
_________________________________________________________________
dropout_9 (Dropout)          (None, 14, 14, 64)        0         
_________________________________________________________________
conv2d_8 (Conv2D)            (None, 14, 14, 128)       73856     
_________________________________________________________________
max_pooling2d_8 (MaxPooling2 (None, 7, 7, 128)         0         
_________________________________________________________________
dropout_10 (Dropout)         (None, 7, 7, 128)         0         
_________________________________________________________________
conv2d_9 (Conv2D)            (None, 7, 7, 256)         295168    
_________________________________________________________________
max_pooling2d_9 (MaxPooling2 (None, 3, 3, 256)         0         
_________________________________________________________________
dropout_11 (Dropout)         (None, 3, 3, 256)         0         
_________________________________________________________________
flatten_3 (Flatten)          (None, 2304)              0         
_________________________________________________________________
dense_5 (Dense)              (None, 256)               590080    
_________________________________________________________________
dropout_12 (Dropout)         (None, 256)               0         
_________________________________________________________________
dense_6 (Dense)              (None, 5)                 1285      
=================================================================
Total params: 962,181
Trainable params: 962,181
Non-trainable params: 0
_________________________________________________________________

 

모델 학습

generator 데이터이므로 fit_generator를 통해서 학습을 진행합니다.

#모델 학습 설정
model.compile(loss='categorical_crossentropy',
             optimizer='adam',
             metrics=['accuracy'])
# fig_generator
model.fit_generator(
    trainGenSet,
    steps_per_epoch=20,
    epochs=200,
    validation_data=testGenSet,
    validation_steps=10,
)
Epoch 1/200
20/20 [==============================] - 1s 52ms/step - loss: 1.6400 - accuracy: 0.2003
Epoch 2/200
20/20 [==============================] - 1s 32ms/step - loss: 1.6131 - accuracy: 0.2128
Epoch 3/200
20/20 [==============================] - 1s 34ms/step - loss: 1.6107 - accuracy: 0.2086
Epoch 4/200
20/20 [==============================] - 1s 35ms/step - loss: 1.6102 - accuracy: 0.1904

...

20/20 [==============================] - 1s 38ms/step - loss: 0.1039 - accuracy: 0.9645
Epoch 197/200
20/20 [==============================] - 1s 34ms/step - loss: 0.1307 - accuracy: 0.9513
Epoch 198/200
20/20 [==============================] - 1s 33ms/step - loss: 0.1150 - accuracy: 0.9645
Epoch 199/200
20/20 [==============================] - 1s 42ms/step - loss: 0.1576 - accuracy: 0.9487
Epoch 200/200
20/20 [==============================] - 1s 51ms/step - loss: 0.0825 - accuracy: 0.9730

 

결과 및 정확도 확인

scores = model.evaluate_generator(testGenSet)
print(scores)
[0.05801161751151085, 0.9777777791023254]

정확도 97.77%의 정확도를 가지는 것을 볼 수 있습니다. 기존 80%의 정확도에서 이미지 증식만 추가하였더니 97%로 급격하게 증가한 것을 확인하였습니다. 확실히 데이터 증식의 힘이 크긴 큰 것 같습니다.

그중에서도 rotation 부분이 어느 정도 영향을 많이 주지 않았을까 생각이 들었습니다. 전체 이미지 데이터를 쭉 한번 보았는데 45 ~ 90 degree 정도 회전된 글자들이 여럿 있었습니다. 이런 부분을 rotation을 통해서 회전된 글씨를 학습하면서 그런 부분을 잘 맞추지 않았을까 예상해보았습니다.

사실 실제로 알아보기 위해서는 틀린 이미지를 직접 imshow를 통해서 그려보면 더 자세하게 오답의 원인을 찾을 수 있습니다.

다음 포스팅에서는 이번에는 작성자를 맞추는 모델을 만들어 보겠습니다.

반응형

관련된 글 보기

Comments