차밍이
[Keras] CNN, 이미지 증식 : 손글씨 작성자 맞추기 본문
이전 포스팅을 통해서 손글씨의 어떤 글자인지 분류하는 모델을 만들어보았습니다.
> [Keras] CNN 이미지 분류 실습 : 손글씨 이미지 분류
또한, 데이터 증식을 통해서 성능이 훨씬 좋아지는 것을 확인해보았습니다.
> [Keras] CNN ImageDataGenerator : 손글씨 글자 분류
이번에는 새로운 분류 모델로 손글씨를 보고 작성자를 맞추는 모델을 만들어보겠습니다.
데이터 형식 및 파일은 첫 번째 포스팅에 설명되어있으니 궁금하시면 해당 글을 참고해주시면 되겠습니다.
Keras 작성자 맞추기
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
path = r"Q:\이미지공유폴더2\\"
trainFileList = os.listdir(path+'train')
testFileList = os.listdir(path+'test')
trainFileList = [x for x in trainFileList if 'jpg' in x]
testFileList = [x for x in testFileList if 'jpg' in x]
파일명에서 고유 번호 부분을 추출하여 label 데이터를 만들었습니다. 각 개인마다 정해진 고유번호가 있으므로 이를 통해서 어떤 작성자인지 구분하도록 할 것입니다.
label_list = list(set(map(lambda x : x[-6:-4], trainFileList)))
이미지 카테고리화 / 폴더 분류
전체 이미지를 카테고리 별로 구분하여 폴더에 담아두었습니다.
ImageDataGenerator를 사용하기 위해서 해당 작업을 진행하였습니다.
# 폴더 만들어주기 train
currentPath = path + 'train'
try:
for key in label_list: # ga da ...
os.makedirs(currentPath + f'\\{key}')
except:
pass
for file in trainFileList:
label = file.split('_')[3].split('.')[0]
targetPath = currentPath + f'\\{label}'
try :
shutil.move(currentPath+f'\\{file}', targetPath+f'\\{file}')
except:
pass
# 폴더 만들어주기 train
currentPath = path + 'test'
try:
for key in label_list: # ga da ...
os.makedirs(currentPath + f'\\{key}')
except:
pass
for file in testFileList:
label = file.split('_')[3].split('.')[0]
targetPath = currentPath + f'\\{label}'
try :
shutil.move(currentPath+f'\\{file}', targetPath+f'\\{file}')
except:
pass
이미지 증식 : ImageDataGenerator 사용
trainDataGen = ImageDataGenerator(rescale=1./255,
rotation_range = 10,
width_shift_range=0.1,
height_shift_range=0.1,
shear_range=0.1,
zoom_range=0.1,
horizontal_flip=False,
vertical_flip=False,
fill_mode='nearest'
)
trainGenSet = trainDataGen.flow_from_directory(
path + 'train',
batch_size=64,
target_size=(28,28),
class_mode='categorical'
)
Found 180 images belonging to 18 classes.
테스트 데이터는 정규화만 진행하였습니다.
testDataGen = ImageDataGenerator(rescale=1./255)
testGenSet = testDataGen.flow_from_directory(
path + 'test',
target_size=(28,28),
batch_size=64,
class_mode='categorical'
)
Found 90 images belonging to 18 classes.
Validation Data로 사용하기 위해서 Train데이터에 조금 더 넓은 범위로 증식한 데이터를 만들어 주었습니다.
valDataGen = ImageDataGenerator(rescale=1./255,
rotation_range = 15,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.1,
horizontal_flip=False,
vertical_flip=False,
fill_mode='nearest')
valGenSet = valDataGen.flow_from_directory(
path + 'train',
target_size=(28,28),
batch_size=64,
class_mode='categorical'
)
Found 180 images belonging to 18 classes.
모델 생성
# 모델 구성
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.2))
model.add(Conv2D(128, kernel_size=(3,3),padding='same', activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Dropout(0.2))
model.add(Conv2D(256, kernel_size=(3,3), padding='same', activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Dropout(0.2))
model.add(Flatten())
model.add(Dense(256, activation='relu'))
model.add(Dropout(0.2))
model.add(Dense(18, activation='softmax'))
model.summary()
Model: "sequential_8"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d_22 (Conv2D) (None, 28, 28, 64) 1792
_________________________________________________________________
max_pooling2d_22 (MaxPooling (None, 14, 14, 64) 0
_________________________________________________________________
dropout_29 (Dropout) (None, 14, 14, 64) 0
_________________________________________________________________
conv2d_23 (Conv2D) (None, 14, 14, 128) 73856
_________________________________________________________________
max_pooling2d_23 (MaxPooling (None, 7, 7, 128) 0
_________________________________________________________________
dropout_30 (Dropout) (None, 7, 7, 128) 0
_________________________________________________________________
conv2d_24 (Conv2D) (None, 7, 7, 256) 295168
_________________________________________________________________
max_pooling2d_24 (MaxPooling (None, 3, 3, 256) 0
_________________________________________________________________
dropout_31 (Dropout) (None, 3, 3, 256) 0
_________________________________________________________________
flatten_8 (Flatten) (None, 2304) 0
_________________________________________________________________
dense_15 (Dense) (None, 256) 590080
_________________________________________________________________
dropout_32 (Dropout) (None, 256) 0
_________________________________________________________________
dense_16 (Dense) (None, 18) 4626
=================================================================
Total params: 965,522
Trainable params: 965,522
Non-trainable params: 0
_________________________________________________________________
모델 학습
#모델 학습 설정
model.compile(loss='categorical_crossentropy',
optimizer='adam',
metrics=['accuracy'])
학습을 진행해본 결과 정확도가 높지 않아서 전체적인 구조를 수정하였습니다.
또한, 증식 데이터의 양을 매우 많이 만들었습니다. 현재 batch_size = 64
를 주었고 step_per_epoch = 40
, epoch = 400
을 주었습니다. 실제로는 이렇게나 많이 데이터를 증식시키지 않는다고 들었습니다만, 주어진 데이터 자체가 매우 적기 때문에 많이 학습을 시켜보았습니다.
# fig_generator
model.fit_generator(
trainGenSet,
steps_per_epoch=40,
epochs=400,
validation_data=valGenSet,
validation_steps=20,
)
Epoch 1/400
40/40 [==============================] - 4s 106ms/step - loss: 2.8753 - accuracy: 0.0653 - val_loss: 2.7522 - val_accuracy: 0.1142
Epoch 2/400
40/40 [==============================] - 4s 103ms/step - loss: 2.2914 - accuracy: 0.2040 - val_loss: 1.9692 - val_accuracy: 0.2893
Epoch 3/400
40/40 [==============================] - 4s 104ms/step - loss: 1.8396 - accuracy: 0.3274 - val_loss: 1.7970 - val_accuracy: 0.3980
...
Epoch 399/400
40/40 [==============================] - 5s 127ms/step - loss: 0.0185 - accuracy: 0.9929 - val_loss: 0.2471 - val_accuracy: 0.9607
Epoch 400/400
40/40 [==============================] - 6s 150ms/step - loss: 0.0212 - accuracy: 0.9929 - val_loss: 0.3541 - val_accuracy: 0.9445
결과 확인 및 결론
scores = model.evaluate_generator(testGenSet)
print(scores)
>> [4.4587812423706055, 0.644444465637207]
정확도 64.44%의 결과물을 얻을 수 있었습니다. 구조나 숫자를 바꾸면서 최대 70%의 정확도 까지 결과물을 얻었습니다.
확실히 비슷한 글자들 속에서 어떤 사람이 작성했는지를 잡아내는 것은 쉽지 않은 것 같습니다. 70%의 정답률을 바라보기도 상당히 어려운 것을 느꼈습니다. 상당히 많은 이미지 증식과 학습에도 크게 정답률이 높아지지 않는 것을 확인했습니다. 물론, 모델에서 보완해야 할 점이 많이 있습니다. 그래도 더 많은 데이터가 주어진다면 조금은 더 나은 학습을 진행할 수 있지 않을까?라는 생각을 해보았습니다.
지금까지 손글씨 데이터를 통해서 Keras와 CNN을 공부해보았습니다.
다음으로는 Tensorflow를 사용해서 CNN 구조와 모델을 만들고 학습하는 내용을 포스팅하겠습니다.
감사합니다.
'파이썬 > Tensorflow & Keras' 카테고리의 다른 글
[Keras] CNN ImageDataGenerator : 손글씨 글자 분류 (20) | 2020.05.05 |
---|---|
[Keras] CNN 이미지 분류 실습 : 손글씨 이미지 분류 (0) | 2020.04.29 |
[텐서플로/기초] 로지스틱 회귀 모델 구현으로 분류모델 만들기 실습 (0) | 2020.04.18 |
[텐서플로우/기초] 경사 하강법 구현 (0) | 2020.04.17 |
[텐서플로우/기초] 단순 선형회귀 모델 및 손실함수 시각화 (0) | 2020.04.16 |