차밍이

[Keras] CNN, 이미지 증식 : 손글씨 작성자 맞추기 본문

파이썬/Tensorflow & Keras

[Keras] CNN, 이미지 증식 : 손글씨 작성자 맞추기

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

이전 포스팅을 통해서 손글씨의 어떤 글자인지 분류하는 모델을 만들어보았습니다.

[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 구조와 모델을 만들고 학습하는 내용을 포스팅하겠습니다.

감사합니다.

반응형

관련된 글 보기

Comments