차밍이
[Keras] CNN ImageDataGenerator : 손글씨 글자 분류 본문
안녕하세요. 이전 포스팅을 통해서 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를 통해서 그려보면 더 자세하게 오답의 원인을 찾을 수 있습니다.
다음 포스팅에서는 이번에는 작성자를 맞추는 모델을 만들어 보겠습니다.
'파이썬 > Tensorflow & Keras' 카테고리의 다른 글
[Keras] CNN, 이미지 증식 : 손글씨 작성자 맞추기 (3) | 2020.05.04 |
---|---|
[Keras] CNN 이미지 분류 실습 : 손글씨 이미지 분류 (0) | 2020.04.29 |
[텐서플로/기초] 로지스틱 회귀 모델 구현으로 분류모델 만들기 실습 (0) | 2020.04.18 |
[텐서플로우/기초] 경사 하강법 구현 (0) | 2020.04.17 |
[텐서플로우/기초] 단순 선형회귀 모델 및 손실함수 시각화 (0) | 2020.04.16 |