빅데이타 & 머신러닝/머신러닝

텐서플로우 트레이닝 데이타 포맷인 *.tfrecord 파일 읽고 쓰기

Terry Cho 2017. 8. 10. 22:49

TFRecord


조대협 (http://bcho.tistory.com)


텐서플로우를 접하게 다 보면 필히 만나는 부분이 텐서플로우 학습 데이타 포맷인 TFRecord라는 파일 포맷이다. 마침 얼굴 인식 모델을 이번에는 텐서플로우에서 미리 개발되어 제공되는 물체 인식 API인 Tensorflow Object Detection API를 사용해서 얼굴을 학습시켜보려고 하니 데이타 포맷을 TFRecord 포맷으로 변경해야 한다. 그래서, TFRecord 파일을 만들어보고, 테스트를 위해서 데이타 내용도 직접 읽는 코드를 작성해보았다. (전체 코드는 https://github.com/bwcho75/objectdetection/tree/master/tfrecord 에 다.)

TFRecord 파일 포맷이란

TFRecord 파일은 텐서플로우의 학습 데이타 등을 저장하기 위한 바이너리 데이타 포맷으로, 구글의 Protocol Buffer 포맷으로 데이타를 파일에 Serialize 하여 저장한다.

CSV 파일에서와 같이 숫자나 텍스트 데이타를 읽을때는 크게 지장이 없지만, 이미지를 데이타를 읽을 경우 이미지는 JPEG나 PNG 형태의 파일로 저장되어 있고 이에 대한 메타 데이타와 라벨은 별도의 파일에 저장되어 있기 때문에, 학습 데이타를 읽을때 메타데이타나 라벨용 파일 하나만 읽는 것이 아니라 이미지 파일도 별도로 읽어야 하기 때문에, 코드가 복잡해진다.


또한 이미지를 JPG나 PNG 포맷으로 읽어서 매번 디코딩을 하게 되면, 그 성능이 저하되서 학습단계에서 데이타를 읽는 부분에서 많은 성능 저하가 발생한다.


이와 같이 성능과 개발의 편의성을 이유로 TFRecord 파일 포맷을 이용하는 것이 좋다.


그러면 간단한 예제를 통해서 TFRecord 파일을 쓰고 읽는 방법에 대해서 알아보도록 하자

본 예제는 http://warmspringwinds.github.io/tensorflow/tf-slim/2016/12/21/tfrecords-guide/ 글과 https://github.com/tensorflow/models/blob/master/object_detection/g3doc/using_your_own_dataset.md 글을 참고하였다.


TFRecord 파일 생성

TFRecord 파일 생성은 tf.train.Example에 Feature를 딕셔너리 형태로 정의한 후에, tf.train.Example 객체를 TFRecord 파일 포맷 Writer인 tf.python_io.TFRecordWriter를 통해서 파일로 저장하면 된다.


다음 코드를 보자, 이 코드는 Tensorflow Object Detection API를 자신의 데이타로 학습시키기 위해서 데이타를 TFRecord 형태로 변환하여 저장하는 코드의 내용이다.

이미지를 저장할때 사물의 위치를 사각형 좌표로 지정하고 저장한다.


def create_cat_tf_example(encoded_image_data):


 height = 1032

 width = 1200

 filename = 'example_cat.jpg'

 image_format = 'jpg'


 xmins = [322.0 / 1200.0]

 xmaxs = [1062.0 / 1200.0]

 ymins = [174.0 / 1032.0]

 ymaxs = [761.0 / 1032.0]

 classes_text = ['Cat']

 classes = [1]


 tf_example = tf.train.Example(features=tf.train.Features(feature={

     'image/height': dataset_util.int64_feature(height),

     'image/width': dataset_util.int64_feature(width),

     'image/filename': dataset_util.bytes_feature(filename),

     'image/source_id': dataset_util.bytes_feature(filename),

     'image/encoded': dataset_util.bytes_feature(encoded_image_data),

     'image/format': dataset_util.bytes_feature(image_format),

     'image/object/bbox/xmin': dataset_util.float_list_feature(xmins),

     'image/object/bbox/xmax': dataset_util.float_list_feature(xmaxs),

     'image/object/bbox/ymin': dataset_util.float_list_feature(ymins),

     'image/object/bbox/ymax': dataset_util.float_list_feature(ymaxs),

     'image/object/class/text': dataset_util.bytes_list_feature(classes_text),

     'image/object/class/label': dataset_util.int64_list_feature(classes),

 }))

 return tf_example


저장되는 내용은 이미지의 높이와 너비 (height,weight), 파일명 (filename), 인코딩 포맷 (format), 이미지 바이너리 (encoded), 이미지내에서 물체의 위치를 가르키는 사각형 위치 (xmin,ymin,xmax,ymax) 와 라벨 값등이 저장된다.


코드를 유심이 살표보면 생각보다 이해하기어렵지 않다.

tf.train.Example 객체를 만들고 이때 인자로 features에 TFRecord에 저장될 갚들의 목록을 딕셔너리 형태로 저장한다.

이때 저장되는 데이타의 이름과 값을 지정해야 하는데


'image/height': dataset_util.int64_feature(height),


를 보면 'image/height' 이 데이타의 이름이 되고, dataset_util.int64_feature(height), 가 height 값을 텐서플로우용 리스트형으로 변형하여 이를 학습용 피쳐 타입으로 변환하여 저장한다.

이 예제는 Object Detection API의 일부이기 때문에, dataset_util이라는 모듈을 사용했지만, 실제로 이 함수의 내부를 보면  tf.train.Feature(int64_list=tf.train.Int64List(value=values)) 로 구현이 되어 있다.


다음 이렇게 생성된 tf.train.Example 객체를 tf.python_io.TFRecordWriter 를 이용해서 다음과 같이 파일에 써주면 된다.

   writer = tf.python_io.TFRecordWriter(tfrecord_filename)

   writer.write(tf_example.SerializeToString())


다음은 코드 전체이다.


import tensorflow as tf

from PIL import Image

from object_detection.utils import dataset_util


def create_cat_tf_example(encoded_image_data):


 height = 1032

 width = 1200

 filename = 'example_cat.jpg'

 image_format = 'jpg'


 xmins = [322.0 / 1200.0]

 xmaxs = [1062.0 / 1200.0]

 ymins = [174.0 / 1032.0]

 ymaxs = [761.0 / 1032.0]

 classes_text = ['Cat']

 classes = [1]


 tf_example = tf.train.Example(features=tf.train.Features(feature={

     'image/height': dataset_util.int64_feature(height),

     'image/width': dataset_util.int64_feature(width),

     'image/filename': dataset_util.bytes_feature(filename),

     'image/source_id': dataset_util.bytes_feature(filename),

     'image/encoded': dataset_util.bytes_feature(encoded_image_data),

     'image/format': dataset_util.bytes_feature(image_format),

     'image/object/bbox/xmin': dataset_util.float_list_feature(xmins),

     'image/object/bbox/xmax': dataset_util.float_list_feature(xmaxs),

     'image/object/bbox/ymin': dataset_util.float_list_feature(ymins),

     'image/object/bbox/ymax': dataset_util.float_list_feature(ymaxs),

     'image/object/class/text': dataset_util.bytes_list_feature(classes_text),

     'image/object/class/label': dataset_util.int64_list_feature(classes),

 }))

 return tf_example


def read_imagebytes(imagefile):

   file = open(imagefile,'rb')

   bytes = file.read()


   return bytes

   

def main():

   print ('Converting example_cat.jpg to example_cat.tfrecord')

   tfrecord_filename = 'example_cat.tfrecord'

   bytes = read_imagebytes('example_cat.jpg')

   tf_example = create_cat_tf_example(bytes)


   writer = tf.python_io.TFRecordWriter(tfrecord_filename)

   writer.write(tf_example.SerializeToString())


main()


참고로 이 예제는 앞에서도 언급하였듯이 Object Detection API에 대한 의존성을 가지고 있기 때문에 일반적인 텐서플로우 개발환경에서는 실행이 되지 않는다. Tensorflow Object Detection API 를 인스톨해야 dataset_util 를 사용할 수 있기 때문에 Object Detection API 설치가 필요하다.

만약에 Object Detection API 설치 없이 TFRecord Writer를 짜보고 싶은 경우에는 http://warmspringwinds.github.io/tensorflow/tf-slim/2016/12/21/tfrecords-guide/ 문서에 예제가 간단하게 잘 정리되어 있으니 참고하기 바란다.

TFRecord에서 데이타 읽기

데이타를 읽는 방법도 크게 다르지 않다. 쓰는 순서의 반대라고 보면 되는데, TFReader를 통해서 Serialized 된 데이타를 읽고, 이를 Feature 목록을 넣어서 파싱한 후에, 파싱된 데이타셋에서 각 피쳐를 하나하나 읽으면 된다.


코드를 보자


def readRecord(filename_queue):

   reader = tf.TFRecordReader()

   _,serialized_example = reader.read(filename_queue)

   

   #'''

   keys_to_features = {

       'image/height': tf.FixedLenFeature((), tf.int64, 1),

       'image/width': tf.FixedLenFeature((), tf.int64, 1),

       'image/filename': tf.FixedLenFeature((), tf.string, default_value=''),

       #'image/key/sha256': tf.FixedLenFeature((), tf.string, default_value=''),

       'image/source_id': tf.FixedLenFeature((), tf.string, default_value=''),

       'image/encoded': tf.FixedLenFeature((), tf.string, default_value=''),

: (중략)

   }

   

   features = tf.parse_single_example(serialized_example,features= keys_to_features)

   

   height = tf.cast(features['image/height'],tf.int64)

   width = tf.cast(features['image/width'],tf.int64)

   filename = tf.cast(features['image/filename'],tf.string)

   source_id = tf.cast(features['image/source_id'],tf.string)

   encoded = tf.cast(features['image/encoded'],tf.string)

: (중략)

   return height,width,filename,source_id,encoded,image_format


TFRecoderReader를 이용하여 파일을 읽는데, 파일을 직접읽지 않고 filename_queue를 이용해서 읽는다. 코드 전체를 보면, 이 큐는

filename_queue = tf.train.string_input_producer([tfrecord_filename])

로, 파일 이름 목록을 가지고 리턴하는 string_input_producer를 사용하였다.

파일을 읽으면 데이타는 직렬화 된 상태로 리턴이 되어 serialized_example 에 저장된다.

이를 각 개별 피쳐로 디코딩 하기 위해서 피쳐 목록을 keys_to_features 딕셔너리에 저장한다.

이때, 각 피쳐의 이름과, 타입을 정의한다.

다음은 ‘image/height’ 피쳐를 정의하여 int 64 타입으로 읽어드리는 부분이다.


       'image/height': tf.FixedLenFeature((), tf.int64, 1),


피쳐 목록과, 데이타 타입은 TFRecord 파일을 쓸때 사용한 이름과 데이타 타입을 그대로 사용하면 된다.

피쳐 목록이 정의되었으면


   features = tf.parse_single_example(serialized_example,features= keys_to_features)


를 통해서 피쳐를 파싱해낸다. 파싱된 피쳐는 features에 딕셔너리 형태로 저장된다.

다음 리턴 받은 텐서를 각 변수에 저장한다. 이때 타입 캐스팅을 해서 저장한다. (이미 타입을 맞춰서 데이타를 꺼냈기 때문에, 별도의 캐스팅은 필요없지만 확실히 하기 위해서 캐스팅을 한다.)

다음은 ‘image/height’ 를 피쳐를 배열에서 뽑아서, int64 타입으로 변환하여 height 에 저장하는 부분이다.


   height = tf.cast(features['image/height'],tf.int64)


리턴값은 텐서가 되는데, 이 값을 출력하는 코드를 보자

당연히 텐서이기 때문에 Session 을 시작해야 하는데, 먼저 파일에서 데이타를 읽기 위해서 filename queue를 정의한다.

    filename_queue = tf.train.string_input_producer([tfrecord_filename])

다음 filename_queue를 인자로 넘겨서 height,source_id 등의 값을 *.tfrecord 파일에서 읽어서 텐서로 리턴 받는다.


    height,width,filename,source_id,encoded,image_format = readRecord(filename_queue)


다음 세션을 시작하고, readRecord 에서 리턴된 값을 받아온다.         vheight,vwidth,vfilename,vsource_id,vencoded,vimage_format = sess.run([height,width,filename,source_id,encoded,image_format])


전체 코드는 https://github.com/bwcho75/objectdetection/tree/master/tfrecord/reader.py 를 참고하기 바란다.


마지막으로 받은 값을 화면에 출력한다.



간단하게 tfRecord 파일 포맷으로 학습용 데이타를 쓰는 방법을 알아보았다. 텐서플로우 코드가 간단해 지고 성능에 도움이 되는 만큼 데이타 전처리 단계에서 가급적이면 학습 데이타를 tfrecord 타입으로 바꿔서 학습하는 것을 권장한다. (특히 이미지 데이타!!)


참고 자료


그리드형