본문 바로가기

DeepLearning/OCR_

[KR_OCR] 최종 Training

반응형

영어데이터로 재학습 된 crnn모델이 있기때문에, 성능을 높이기 위해

재학습 된 가중치 위에 다시 한국어데이터를 학습시킨다.

 

 

 

< CR_data CRNN Training >

1> 데이터 준비

- 데이터 위치 : /data/KSIGN/

- 데이터 갯수 : Train 데이터 (1,249장), Validation 데이터 (312장), Total 데이터 (1,561장)

- 데이터 출처 : AI_HUB, 구글과 네이버 간판데이터 크롤링

- 어노테이션 파일 직접 제작 (이미지 속 텍스트에대한 박스 정보)

 

 

2> 재 학습 이유

- 현재 한국어 데이터가 라벨마다 데이터 불균형이 심하기 떄문에 이를 보완하기 위해 기존 loss 계산법 변경했는데, 이를 기반으로 CRNN 영어데이터로 재학습을 시켰기 때문에 한국어 데이터로도 재학습 필요

- 데이터 불균형에 적합한 focal_ctc_loss 사용

- 처음과 달리 학습시키는 과정에서 데이터 증식에 대한 필요성이 보여서 새롭게 추가함(training한정)

- 학습시 발생하는 잘못된 박스 모양에 대한 에러 처리

 

 

3> CR_data Training

# 필요한 라이브러리 로딩

import numpy as np
import matplotlib.pyplot as plt
import os
import editdistance
import pickle
import time

from keras.optimizers import SGD, Adam
from keras.callbacks import ModelCheckpoint, TensorBoard

from crnn_model_focal_ctc_loss import CRNN
from crnn_data_fcl_aug_merge import InputGenerator
from crnn_utils import decode
from utils.training import Logger, ModelSnapshot

- crnn_model 에서 crnn_model_focal_ctc_loss로 변경

- crnn_data 에서 crnn_data_fcl_aug_merge로 변경

 

 

# Train / Val에 대한 피클파일 로딩

from data_cracker import GTUtility

file_name = 'gt_util_final_data.pkl'

with open(file_name, 'rb') as f:
    gt_util_cracker = pickle.load(f)
    
gt_util_train, gt_util_val = GTUtility.randomSplit(gt_util_cracker)

 - 피클 파일 load 하기

 - 데이터들이 섞여있지 않고, 카테고리별로 뭉쳐져 있기 때문에 랜덤하게 Train data/ Val data를 가져온다.

 

 

randomSplit
def randomSplit(self, split=0.8):
  gtu1 = BaseGTUtility()
  gtu1.gt_path = self.gt_path
  gtu1.image_path = self.image_path
  gtu1.classes = self.classes

  gtu2 = BaseGTUtility()
  gtu2.gt_path = self.gt_path
  gtu2.image_path = self.image_path
  gtu2.classes = self.classes

  n = int(round(split * len(self.image_names)))

  idx = np.arange(len(self.image_names))

  np.random.seed(0)

  np.random.shuffle(idx)

  train = idx[:n]
  val = idx[n:]

  gtu1.image_names = [self.image_names[t] for t in train]
  gtu2.image_names = [self.image_names[v] for v in val]
  gtu1.data = [self.data[t] for t in train]
  gtu2.data = [self.data[v] for v in val]
  if hasattr(self, 'text'):
  gtu1.text = [self.text[t] for t in train]
  gtu2.text = [self.text[v] for v in val]

  gtu1.init()
  gtu2.init()
  return gtu1, gtu2

- 기존의 ssd_data에 있는 split를 약간 변형하여 randomSplit 함수 생성

 

< 각 이미지의 갯수 확인 >

 

 

# 라벨이 있는 딕셔너리 로딩

from crnn_util2 import dict838
from crnn_utils import alphabet87
print(len(alphabet87))
print(len(dict838))

< 영어 Dict > 

- 영어에 대한 라벨 값이기 때문에 총 87개 이다.

- 소문자 / 대문자 / 숫자 / 특수기호 / blank 가 포함 된 딕셔너리

 

< 한글 Dict >

- 기존의 트레이닝 데이터 셋에 존재하는 라벨의 수 = 838개

 

 

# 학습 전 필요한 변수 선언

input_width = 256
input_height = 32
batch_size = 128
input_shape = (input_width, input_height, 1)
max_string_len = 62

- 최대 글자 수는 62

- 모델에 들어가는 input의 형태는 ( 256, 32, 1 )의 3차원 배열이다.

 

 

# 가중치가 저장되는 파일 명 설정

experiment = 'crnn_ksignboard_best_v1'

 

 

# 모델 생성하기

alpha=0.75
gamma=0.5

model, model_pred = CRNN(input_shape, len(alphabet87), gru=False, alpha=alpha, gamma=gamma)

model.load_weights('checkpoints/201806162129_crnn_lstm_synthtext/weights.300000.h5')

max_string_len = 62

- 그 동안의 학습에서 alpha=0.75, gamma = 0.5가 최대 성능을 보였음

- 모델에 들어가는 가중치는 깃에 올라와 있다.  (https://github.com/mvoelk/ssd_detectors)

- 위에서 사용한 가중치는 synthtext로 학습 된 가중치이다

 

 

# Transfer Learning ( 전이 학습 )

freeze = ['conv1_1',
          'conv2_1',
          'conv3_1', 'conv3_2', 
          'conv4_1',
          #'conv5_1',
          #'conv6_1',
          #'lstm1',
          #'lstm2'
         ]

- 위에서 생성한 모델에 미리 학습 된 가중치를 load하고, 여기서 freeze를 걸어준다.

- 모델의 convnet층은 가중치를 동결시키고, 모델의 마지막 부분인 conv5_1, conv6_1, LSTM은 동결을 해제하여 학습될 수 있도록 한다.

- 이렇게 하는 이유는, 기존에 학습 된 가중치는 영어에 맞춰 학습이 되었으나, 전이 학습으로 한국어 데이터에 대한 학습이 필요로 하기 때문에 큰 feature를 찾은 컨브넷은 가중치를 동결시키고, 작은 feature를 찾는 컨브넷은 재학습 할 수 있게 한다.

- 따라서 미리 학습 된 가중치를 모델에 올리고 전이학습 진행

 

 

# 데이터 제너레이터 생성

gen_train = InputGenerator(gt_util_train, batch_size, alphabet87, input_shape[:2], 
                           grayscale=True, max_string_len=max_string_len)
gen_val = InputGenerator(gt_util_val, batch_size, alphabet87, input_shape[:2], 
                         grayscale=True, max_string_len=max_string_len)

- Train과 Val데이터 각각 제너레이터를 만든다. 

 

 

# 모델 컴파일

optimizer = Adam(lr=0.001, epsilon=0.001,decay=1e-5, clipnorm=1.)

for layer in model.layers:
    layer.trainable = not layer.name in freeze

model.compile(loss={'focal_ctc_loss': lambda y_true, y_pred: y_pred}, optimizer=optimizer)

- 옵티마이져는 Adam을 사용

- 아까 설정한 레이어를 제외하고는 레이어 동결을 해제한다.

- 모델을 컴파일한다. 로스는 모델안의 lambda에서 계산된다.

 

 

# 모델 학습

hist = model.fit_generator( generator=gen_train.generate(),
                            steps_per_epoch=gt_util_train.num_objects // batch_size,
                            epochs=300,
                            validation_data=gen_val.generate(), # batch_size here?
                            validation_steps=gt_util_val.num_objects // batch_size,
                            callbacks=[
                                ModelCheckpoint(checkdir+'/weights.{epoch:03d}.h5', verbose=1, save_weights_only=True),
                                Logger(checkdir),
                                PlotLossesCallback(),
                                EarlyStopping(monitor='val_loss', mode='auto', restore_best_weights=True, verbose=1, patience=20)
                            ],
                            initial_epoch=0)

- 모델을 학습시킨다.

- epochs = 300

- 한 에폭당 가중치를 저장한다.

- 학습을 실시간으로 볼수있게 PlotLossesCallback()을 사용한다.

- 실시간으로 loss와 val_loss에 대해 그래프로 확인가능하다

- 현재 20회 동안 val_loss가 향샹되지 않으면 early stopping이 되도록 해서, 54 epoch때 학습이 멈췄다. 

 

 

# 학습이 완료 된 후 로스값 그래프로 확인하기

loss = hist.history['loss']
val_loss = hist.history['val_loss']

epochs = range(len(loss))
plt.figure(figsize=(15,10))
plt.plot(epochs, loss, 'r', label='Training loss')
plt.plot(epochs, val_loss, 'b',label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

- loss와 val_loss 그래프

 

 

# 학습하면서 실시간으로 그래프 확인

from livelossplot.keras import PlotLossesCallback

- 모델의 학습시킬때 콜백 함수에 PlotLossesCallback()를 추가하면 바로 사용가능

 

 

# 예측 결과 시각화

d = next(g)

res = model_pred.predict(d[0]['image_input'])

mean_ed = 0
mean_ed_norm = 0

plot_name = 'crnn_cocotext'

for i in range(32):
    chars = [alphabet87[c] for c in np.argmax(res[i], axis=1)]
    gt_str = d[0]['source_str'][i]
    res_str = decode(chars)
    
    ed = editdistance.eval(gt_str, res_str)
    ed_norm = ed / len(gt_str)
    mean_ed += ed
    mean_ed_norm += ed_norm
    
    img = d[0]['image_input'][i][:,:,0].T
    plt.figure(figsize=[10,1.03])
    plt.imshow(img, cmap='gray', interpolation=None)
    ax = plt.gca()
    plt.text(0, 45, '%s' % (''.join(chars)) )
    plt.text(0, 60, 'GT: %-24s RT: %-24s %0.2f' % (gt_str, res_str, ed_norm))
    
    plt.show()
    
mean_ed /= len(res)
mean_ed_norm /= len(res)

print('\nmean editdistance: %0.3f\nmean normalized editdistance: %0.3f' % (mean_ed, mean_ed_norm))

- 선명한 텍스트에 대해서는 어느정도 학습이 잘되어있으나, 선명하지않은 이미지에 대해서는 잘 예측하지 못함

 

 

< OUTPUT >

- GT:라벨 RT:예측값

 

 

4> 하이퍼 파라미터 튜닝

1> KR_CRNN_v1

1. Focal-CTC-Loss ( alpha : 0.75, gamma : 0.5)
2. act : LeakyReLU ( 0.05 )
3. lstm-dropout : 0.1, 0.1
4. data augmentation : blur / sharpenning
5. optimizer : Adam(lr=0.001, epsilon=0.001,decay=1e-4, clipnorm=1.)
6. freeze : con1_1, conv2_1, conv3_1, conv3_2, conv4_1
7. batch_size = 64

 

< Version 1 >

 최소 validation loss : 5.854

 

V1 그래프

- 첫번째 그래프는 실시간으로 확인되는 그래프

- 에폭을 100번 주었으나 early stopping으로 56epoch에서 멈췄다.

- 두번째 그래프는 loss와 val_loss를 좀 더 자세하게 확인한 그래프

 

 

 

2> KRCRNN_v2

1. Focal-CTC-Loss ( alpha : 0.75, gamma : 0.5)
2. act : LeakyReLU ( 0.05 )
3. lstm-dropout : 0.1, 0.1
4. data augmentation : blur / sharpenning
5. optimizer : Adam(lr=1e-3, epsilon=0.001,decay=1e-4, clipnorm=1.)
6. freeze : con1_1, conv2_1, conv3_1, conv3_2, conv4_1
7. batch_size = 64

- 변경사항 : learning rate을 0.001 > 0.01로 낮춤

 

< Version 2>

최소 validation loss : 5.263

 

V2 그래프

- learning rate를 줄였더니 약간의 VAL_LOSS가 낮아졌다.

- dropout층이 있어서 학습 초반에는 training loss와 validataion loss. 시간이 지나면서 val_loss가 더 높아짐

 

 

 

3> KR_CRNN_v3

1. Focal-CTC-Loss ( alpha : 0.25, gamma : 1)
2. act : LeakyReLU ( 0.05 )
3. lstm-dropout : 0.1, 0.1
4. data augmentation : blur / sharpenning
5. optimizer : Adam(lr=1e-3, epsilon=0.001,decay=1e-5, clipnorm=1.)
6. freeze : con1_1, conv2_1, conv3_1, conv3_2
7. batch_size = 64

- 변경사항 : 동결층을 원래보다 적게 주었다.

                 : 기존 층은 원래 보다 동결 해제를 많이 풀었다.

- 한국어가 라벨의 불균형이 심해 Focal-CTC-Loss를 적용시켰으나, 상대적으로 영어는 라벨이 불균형이 심하지 않기 때문에 다른 alpha와 gamma값을 다르게 적용해야한다고 생각이 들어 값을 변경함

- data augmentation은 Train data에만 적용

 

< Version 3 >

최소 validation loss : 5.088

 

V3 그래프

- 동결층을 조금 더 풀었더니, LOSS의 값이 낮아졌다.

 

 

 

반응형