IT/AI\ML

[python/Tensorflow2.x] TFRecord의 사용법

개발자 두더지 2021. 7. 8. 00:05
728x90

TFRecord란?


 TFRecord는 데이터 세트의 포맷의 하나로 TFRecord형식은 바이너리 코드의 시리즈를 저장하기 위한 단순한 형식이다. 프로토콜 버퍼는 구조화 데이터를 효율적으로 시리얼라이즈하는 플랫폼이나 언어에 의존하지 않는 라이브러리이다.

 TFRecord를 사용하는 것으로, 대규모 데이터를 효율적으로 학습할 수 있게 된다. 이번 포스팅을 통해 TFRecord의 사용 방법이나 마스터할 수 있도록 읽고 쓰기하는 방법에 대해 살펴 볼 것이다. 그리고 마지막으로 실제 예를 이용해서 어떻게 사용할 수 있는지에 대해서 알아볼 것이다.

 

 

TFRecord를 사용하는 이유


 TFRecord 안은 Protocol Buffer이라는 바이너리 포맷으로 되어 있다. 한 번 TFRecord를 작성하는 것으로 데이터의 생성, 가공 비용을 줄일 수 있는 경우가 있다. 또한, TFRecord의 형식을 사용하여 Cloud ML Engine용의 입력 데이터 형식으로써 사용하는 것도 할 수 있다.

 Tensorflow로 기계학습을 할 때에, 학습 데이터 세트를 읽어 들이는 방법은 아래와 같이 네 종류가 있다.

(1) 사전에 메모리를 모든 데이터에 로드한다.

(2) Python 코드로 조금씩 읽어들여가면서 feed_dict에 그래프에 입력한다.

(3) TFRecord로 부터 그래프상에 Threading와 Queues를 활용하면서 읽어들인다.

(4) Dataset API를 사용한다.

 (1)은 데이터세트가 작은 경우에 효과적인다. 오직 한 번으로 파일을 메모리 상에 읽어들이면, 고속으로 그래프에 입력하는 것이 가능하다. 그러나 데이터가 큰 경우에는 메모리가 부족하게 되는 상황이 발생해 속도가 낮아지거나 메모리 할당 오류가 발생하는 경우가 있다.

 (2)도 프로토타입으로써 간단히 구현하고 싶은 경우에는 몇 번이고 TFRecord를 작성하는 수고를 덜 수 있으므로 좋은 아이디어이다. 그러나 싱글 스레드을 이용하고 있는 경우에는 데이터 읽어들이기와 학습을 동시에 하게 되는 경우가 있기 때문에 전체 학습 시간이 길어져 버리게 되는 경우가 많다. 또한, 기계학습 모델을 변경하거나, 학습할 때에는 동일한 처리를 몇 번이고 하는 경우가 있다. 매번 동일한 데이터 가공 처리를 작동시키고 있는 경우에는 TFRecord를 작성하는 것을 검토하도록 하자.

TFRecord를 사용하는 경우, (3)이나 (4)의 방법으로 Tensorflow의 계산 그래프에 입력해 나갈 것이다. 계산 그래프상에서는 멀티 스레드의 큐가 사용되기 위해서, 학습 데이터 세트/가공을 비동기로 할 수 있다.

 

 

TFRecord의 작성법


 이제 TFRecord를 작성해보자. 이번에는 Fashion MNIST를 사용하여 TFRecord의 작성법에 배워나가자. Fashon MNIST는 아래와 같이 28x28의 옷 이미지를 10가지 종류에 분류한 데이터 세트이다.

 커맨드 라인으로 아래의 내용을 입력하여 Fashion MNIST의 페이지에 링크를 통해 데이터 세트를 확보하고, data/fashion 디렉토리에 저장한다.

$ mkdir -p data/fashin
$ cd data/fashion
$ wget http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz
$ wget http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-labels-idx1-ubyte.gz
$ wget http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-images-idx3-ubyte.gz
$ wget http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz
$ cd ../..

 그러면 tensorflow에서 두 줄의 코드로 데이터를 읽어들일 수 있게 된다.

from tensorflow.examples.tutorials.mnist import input_data

fashion_mnist = input_data.read_data_sets('data/fashion')

 

 

Example레코드와 SequenceExample 레코드


 TFRecord는 tf.train.Example과 tf.train.SequenceExample를 하나의 레코드의 단위로써 쓴다. tf.train.Example는 수치나 이미지등의 고정 길이의 리스트로 다룬다. 각 레코드의 값은 tf.train.Feature로 지정한다. tf.train.Feature의 사용할 수 있는 데이터형은 아래와 같다. 

  • tf.train.Int64List
  • tf.train.FloatList
  • tf.train.BytesList

아래와 같이 [value]와 리스트의 안에 값을 지정한다.

tf.train.Example(features=tf.train.Features(feature={
    'height': tf.train.Feature(int64_list=tf.train.Int64List(value=[height])),
    'width' : tf.train.Feature(int64_list=tf.train.Int64List(value=[width])),
    'depth' : tf.train.Feature(int64_list=tf.train.Int64List(value=[depth])),
    'image' : tf.train.Feature(bytes_list=tf.train.BytesList(value=[image]))
}))

tf.train.SequenceExample는 고정 길이의 contex와 가변 길이의 feature_lists를 가지는 데이터 형식이 된다. 텍스트나 시계열 등의 시퀀셜 데이터를 학습하는 경우에는 tf.train.SequenceExample를 사용하자.

example = tf.train.SequenceExample()
# 고정 길이의 값은context경유
example.context.feature["length"].int64_list.value.append(len(data))

# 가변 길이의 데이터는 feature_lists경유로 지정
words_list = example.feature_lists.feature_list["words"]
for word in words:
    words_list.feature.add().int64_list.value.append(word_id(word))

 

 

Fashion MNIST를 TFRecord화한다.


 Fashion MNIST를 TERecord의 형식으로 저장해본다. 아래와 같이 numpy배열의 경우에는 tobytes()메소드를 사용하는 것으로 리스트를 Bytes형식으로 변환할 수 있다.

import numpy as np
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

def make_example(image, label):
    return tf.train.Example(features=tf.train.Features(feature={
        'image' : tf.train.Feature(bytes_list=tf.train.BytesList(value=[image])),
        'label' : tf.train.Feature(bytes_list=tf.train.BytesList(value=[label]))
    }))

def write_tfrecord(images, labels, filename):
    writer = tf.python_io.TFRecordWriter(filename)
    for image, label in zip(images, labels):
        labels = labels.astype(np.float32)
        ex = make_example(image.tobytes(), label.tobytes())
        writer.write(ex.SerializeToString())
    writer.close()

def main():
    fashion_mnist = input_data.read_data_sets('data/fashion', one_hot=True)
    train_images  = fashion_mnist.train.images
    train_labels  = fashion_mnist.train.labels
    test_images   = fashion_mnist.test.images
    test_labels   = fashion_mnist.test.labels
    write_tfrecord(train_images, train_labels, 'fashion_mnist_train.tfrecord')
    write_tfrecord(test_images, test_labels, 'fashion_mnist_test.tfrecord')

if __name__ == '__main__':
    main()

 

 

TFRecord의 내용을 확인하는 방법


예전에 작성했던 TFRecord의 안의 구조가 알고 싶은 경우에는, tf.train.Example.FromString이 편리하다.

In [1]: import tensorflow as tf

In [2]: example = next(tf.python_io.tf_record_iterator("fashion_mnist_train.tfrecord"))

In [3]: tf.train.Example.FromString(example)
Out[3]:
features {
  feature {
  feature {
    key: "image"
    value {
      bytes_list {
        value: "\000...\000"
      }
    }
  }
  feature {
    key: "label"
    value {
      bytes_list {
        value: "\000...\000"
      }
    }
  }
}

 features의 내용에 image, label, height, width의 feature가 들어가 있는 것을 알 수 있다.

 

 

TFRecord를 읽어들이는 방법


 TFRecord는 tf.parse_single_example를 사용하여 읽어들일 수 있다. BytesList로 쓴 것은 tf.string으로 읽어들인다는 점을 주의하자.

def read_tfrecord(filename):
    filename_queue = tf.train.string_input_producer([filename])
    reader = tf.TFRecordReader()
    _, serialized_example = reader.read(filename_queue)

    features = tf.parse_single_example(
        serialized_example,
        features={
            'image': tf.FixedLenFeature([], tf.string),
            'label': tf.FixedLenFeature([], tf.string)
        })

    image = tf.decode_raw(features['image'], tf.float32)
    label = tf.decode_raw(features['label'], tf.float64)

    image = tf.reshape(image, [28, 28, 1])
    label = tf.reshape(label, [10])

    image, label = tf.train.batch([image, label],
            batch_size=16,
            capacity=500)

    return image, label

 

 

실제로 구현해보기


TFRecord로 만들기 전의 데이터를 사용하는 기존의 방법과 다른 TFRecord로 만들어서 실행시켜보자. Fashion MNIST의 경우에는 그렇게 데이터 양이 많지 않으므로, 모든 데이터 메모리에 넣지만, 계산 그래프의 입력 부분은 비동기가 될 것이다.

import numpy as np
import tensorflow as tf
import tfrecord_io
from tensorflow.examples.tutorials.mnist import input_data
from tensorflow.contrib import slim

def model(image, label):
    net = slim.conv2d(image, 48, [5,5], scope='conv1')
    net = slim.max_pool2d(net, [2,2], scope='pool1')
    net = slim.conv2d(net, 96, [5,5], scope='conv2')
    net = slim.max_pool2d(net, [2,2], scope='pool2')
    net = slim.flatten(net, scope='flatten')
    net = slim.fully_connected(net, 512, scope='fully_connected1')
    logits = slim.fully_connected(net, 10,
            activation_fn=None, scope='fully_connected2')

    prob = slim.softmax(logits)
    loss = slim.losses.softmax_cross_entropy(logits, label)

    train_op = slim.optimize_loss(loss, slim.get_global_step(),
            learning_rate=0.001,
            optimizer='Adam')

    return train_op

def main():
    train_images, train_labels = tfrecord_io.read_tfrecord('fashion_mnist_train.tfrecord')
    train_op = model(train_images, train_labels)

    step = 0
    with tf.Session() as sess:
        init_op = tf.group(
            tf.local_variables_initializer(),
            tf.global_variables_initializer())
        sess.run(init_op)
        coord = tf.train.Coordinator()
        threads = tf.train.start_queue_runners(sess=sess, coord=coord)
        while step < 3000:
            sess.run([train_op])

            if step % 100 == 0:
                print('step: {}'.format(step))

            step += 1

        coord.request_stop()
        coord.join(threads)

if __name__ == '__main__':
    main()

참고자료

https://qiita.com/wasnot/items/9b64550237a3c5267bfd

https://deepage.net/tensorflow/2017/10/07/tfrecord.html

https://qiita.com/everylittle/items/11140a5706387e8e78ec

https://qiita.com/IchiLab/items/8ad0cdb9c2006f416c3b

 

728x90