Read dataset from TFRecord format

pgmmpk: <p>Hi,</p>
<p>I need to read data from TensorFlow protocol buffer format “TFRecord” (aka Example+Features, see <a href="https://www.tensorflow.org/api_docs/python/tf/python_io/TFRecordWriter" rel="nofollow noopener">https://www.tensorflow.org/api_docs/python/tf/python_io/TFRecordWriter</a>).</p>
<p>Is there a solution for that out there?</p>
<p>Thanks!</p>

ptrblck: <p>Could you read the data once and save it in another format?<br>
I just had a look at the code on how to read <code>TFRecord</code> files, and it looks like you need a <code>session</code> object and some transformations.<br>
The easiest way would probably be to read the data from another source.<br>
If that’s not possible, you could try to transform the data somehow from <code>tf.float32</code> to a <code>Tensor</code>.</p>
<p>I’m not sure, if you can fit this code somehow into a <code>Dataset</code> and <code>DataLoader</code>.</p>

pgmmpk: <p>No, session is not needed. TFRecord format is straightforward protocol buffers container. It can be read into array of integers or floats quite easily (and efficiently). The reason I am asking is to avoid coding, maybe someone already solved this?</p>
<p>One technical difficulty may be that TFRecords are streaming (one does not know the number of data points upfront), and Dataset interface requires len() and random-access indexing…</p>
<p>Thanks</p>

ptrblck: <p>Oh, ok, thanks for the information!<br>
Could you post a link to a simple code sample loading from <code>TFRecords</code> without a <code>session</code>?</p>
<p>It might get a bit ugly, bit if you really need to read from <code>TFRecords</code>, we could try out some approaches.<br>
Would it be possible if you host a small sample <code>TFRecords</code> file somewhere?</p>

pgmmpk: <p>Answering my own question:</p>
<p>One needs TensorFlow installed to read TFRecords (yack! I hoped to avoid this). The reason for this is that there are record guards and a checksum that they put into the file, in addition to the ProtocolBuffer payload. See <a href="https://github.com/tensorflow/tensorflow/blob/49c20c5814dd80f81ced493d362d374be9ab0b3e/tensorflow/core/lib/io/record_writer.cc#L104" rel="nofollow noopener">there</a>.</p>
<p>To demonstrate how to read/write TFRecords I put a tiny project <a href="https://github.com/pgmmpk/tfrecord" rel="nofollow noopener">here</a> - check it out.</p>
<p>It should be easy now to import TFRecord data into PyTorch by just wrapping arrays into torch.Tensors</p>
<p><a class="mention" href="/u/ptrblck">@ptrblck</a> I do not want to transform from TFRecord. I am planning to use the data as-is. Reason is that TFRecord io supports cloud storage out-of-the-box. Example - these are valid filenames: <code>gs://mybucket/training/data/blah.tfrecords</code>, or <code>s3://mybucket/training/data/foo.tfrecords</code>.</p>
<p>This is very convenient as my training process uses standard disposable cloud workers that should not store anything of a value on their local drives! So the plan is:</p>
<ul>
<li>install both tf and torch</li>
<li>read data from <code>TFRecord</code> into <code>torch.Tensor</code>
</li>
<li>hack <code>torch.util.data.DatasetLoader</code> to be able to read streaming data (no <strong>len</strong>!!!). And throw all the existing Torch Dataset machinery under the bus - it is based on random-access model, alas.</li>
<li>prove that this is reasonably fast and is not a bottleneck for training</li>
</ul>
<p>Thanks for reading!</p>

timchen: <p>Hi <a class="mention" href="/u/pgmmpk">@pgmmpk</a>. Thanks for sharing this solution. I just have a question about your code:</p>
<ol>
<li>
<p>In this code what package do you use for “from tfrecord import Writer” ? I couldn’t find this package nor in tensorflow official package.</p>
</li>
<li>
<p>Could you also elaborate, or have some examples, on how to interface tfrecord and pytorch dataloader? Thanks!</p>
</li>
</ol>
<aside class="onebox githubblob">
  <header class="source">
      <a href="https://github.com/pgmmpk/tfrecord/blob/master/tfrecord/test/test_tfrecord.py" target="_blank" rel="nofollow noopener">github.com</a>
  </header>
  <article class="onebox-body">
    <h4><a href="https://github.com/pgmmpk/tfrecord/blob/master/tfrecord/test/test_tfrecord.py" target="_blank" rel="nofollow noopener">pgmmpk/tfrecord/blob/master/tfrecord/test/test_tfrecord.py</a></h4>
<pre><code class="lang-py">import pytest
from tfrecord import (
    Writer, Reader,
    pack_int64_list, unpack_int64_list,
    pack_float_list, unpack_float_list,
    pack_bytes_list, unpack_bytes_list,
)

SAMPLES = [
    {
        'int_sample'  : [1, 2, 3],
        'float_sample': [0.1, 0.2, 0.3],
        'byte_sample' : [b'1', b'2', b'3']
    },
    {
        'int_sample'  : [4, 5],
        'float_sample': [0.4],
        'byte_sample' : [b'hello', b'world!']
    },
]
</code></pre>

  This file has been truncated. <a href="https://github.com/pgmmpk/tfrecord/blob/master/tfrecord/test/test_tfrecord.py" target="_blank" rel="nofollow noopener">show original</a>

  </article>
  <div class="onebox-metadata">
    
    
  </div>
  <div style="clear: both"></div>
</aside>


pgmmpk: <p>Hi <a class="mention" href="/u/timchen">@timchen</a></p>
<p>The module content is in the <code>__init__.py</code>:  <code>https://github.com/pgmmpk/tfrecord/blob/master/tfrecord/__init__.py</code></p>
<p>I do not have a DataLoader solution yet. That gonna be hard for two reasons:</p>
<ol>
<li>PyTorch DataLoader is using multiple workers</li>
<li>PyTorch code is not directly usable because TF dataset does not have <code>__len__</code> (size is indefinite)</li>
</ol>
<p>But, for a simple "read and convert to <code>torch.Tensor</code>" loop, the answer is very simple - the unit test shows how to get arrays from TFRecord files. What is left is to just wrap them into appropriate torch.Tensor (FloatTensor or IntTensor, depending on what is in the files).</p>
<p>To summarize: its easy to read it into torch.Tensor. But it is hard (for me) to do it very efficiently because of items above.</p>
<p>When I have some results on <code>TFRecordDatasetLoader</code> (and benchmarks), I will share on GitHub.</p>

npuichigo: <p>Nice work, and I’m also trying to find a efficient way to read large amount of data when training. I think tfrecord format is good way to save data chunks, and avoids reading lots of small files, which is especially slow on hdfs.</p>
<p>In fact, I want to find a so-called TFRecordDatasetLoader which can not only read tfrecord chunks but also have an internal buffer as tf.dataset to provide efficient data reading pipeline.</p>

advaza: <p>Hi Mike <a class="mention" href="/u/pgmmpk">@pgmmpk</a> ,</p>
<p>Thanks for posting your solution!<br>
Did you find a way to integrate tfrecord iterator with PyTorch DataLoader?</p>
<p>Also, <a class="mention" href="/u/ptrblck">@ptrblck</a> - is there a tfreocrd-like solution in PyTorch? Essentially, I would like to handle big datasets the same way tfrecord-based dataset works - read big files and shuffle the serialised samples (like tf example protobuf) rather than reading batch_size images in every step, which results in I/O overhead.<br>
Another issue is that our file system in which we store datasets is having trouble handling so many files, so this is another advantage for tfreocrd-based datasets.</p>
<p>Thank you!!<br>
Adva</p>

ptrblck: <p>I’m not really familiar with <code>tfrecord</code>, but from your description it sounds like the whole dataset is being loaded into the memory first and then just the current sample is loaded. Is that right?<br>
If so, you could load the data once and save it using <code>torch.save</code>. Afterwards, you could load it in your <code>Dataset</code>'s <code>__init__</code> and get a sample in <code>__getitem__</code>.</p>
<p>Please correct me, if I misunderstood the <code>tfrecord</code> approach.</p>

advaza: <p>Thanks for the fast reply!</p>
<p>The <code>tfreocrd</code> mechanism designed to handle large datasets. It based on protobuf serialisation protocol that is used for creating training “examples”. For instance, an “example” can be composed of training image and an integer label. All the examples are serialised and written to a tfrecord file (or files). This way, you can compress a dataset like ImageNet to only ~1000 records.<br>
The benefits are: (1) file system performance since it doesn’t have to handle millions of files, (2) training performance since instead of reading a lot of small files the training-process reads few big files, and (3) dataset mobility - transfer few big files instead of many small files.</p>
<p>These datasets usually don’t fit the CPU memory, so I can’t load the complete dataset before training.</p>

ptrblck: <p>So <code>tfrecord</code> splits your dataset into several chunks and stores these files in a binary format.<br>
E.g. for 10000 images you could have 10 tfrecord files each containing 1000 images?</p>
<p>I’m not sure about benefit 1 and 2.<br>
The io might be limited, if you load a lot of files, but on the other side you would have to load a huge file before even the first iteration can start. The same goes for point 2. While the GPU is busy your multiple workers can load a new batch of images instead of a large file.</p>
<p>Anyway, you could probably emulate such a behavior by loading some images, store them into a <code>tensor</code> and save it to your file system. Then you would have to create a logic to load a new chunk based on your current index, and finally get your sample.<br>
Let me know, if I misunderstood your use case or if you need some help figuring out the chunk loading.</p>

sal: <p>This is really intriguing.</p>
<p>Did you get the chance to benchmark your implementation ? <a class="mention" href="/u/pgmmpk">@pgmmpk</a></p>
<p>Specifically to read this huge dataset : <a href="https://research.google.com/youtube8m/download.html" rel="nofollow noopener">https://research.google.com/youtube8m/download.html</a><br>
(1.53 tb of tfrecords)<br>
What would be an optimum approach ?</p>
<ul>
<li>running a tf sess dataloader</li>
<li>Using the above implementation</li>
<li>saving as framework agnostic npy etc and then re reading into a pytorch dataloader ?</li>
</ul>
<p><a class="mention" href="/u/ptrblck">@ptrblck</a></p>

pgmmpk: <p>I did not benchmark this. It is just reading protocol buffers, which should be pretty fast. Running tf sess dataloader should be similar in speed, but then you incur transformation from TF tensor to numpy and  PyTorch tensors.</p>
<p>Converting to np offline and then using pytorch dataloader could be the best, as pytorch dataloader uses multiple workers.</p>
<p>PyTorch nightly has IterableDataset abstraction in works, that should allow plugging steraming readers into dataloader very easy, see <a href="https://pytorch.org/docs/master/data.html#dataset-types" rel="nofollow noopener">https://pytorch.org/docs/master/data.html#dataset-types</a></p>

tpatel0409: <p>Hi <a class="mention" href="/u/pgmmpk">@pgmmpk</a><br>
Were you able to find an efficient way of handling the tfrecord/protobuf file/s using the IterableDataset ?<br>
I am also trying to do something similar but no success yet.<br>
Thanks</p>

PCerles: <p>I made a working version of this with IterableDataset but it was unusably slow. It was not the TfRecord reader’s fault but IterableDataset, it seemed like it didn’t use queuing at all.</p>
<p>To provide more context on why I want to use something like TFRecords, I work with audio files that are all very small, so a large dataset ends up being 90MM files totaling 5Tb. That becomes very unwieldy to move around, so serializing the encoded FLACs into a protobuf format and chunking them makes them much easier to move around and read from a network.</p>
<p>Minimal example:</p>
<pre><code class="lang-auto">import tensorflow as tf
import torch
import random

class TfRecordDataset(torch.utils.data.IterableDataset):

def __init__(self, tfrecord_path):
        "tfrecord_path contains tf record files"""
        self.buffer_size = buffer_size
        self.tfrecord_list = sorted(glob.glob(tfrecord_path))
        super(TfRecordDataset, self).__init__()

def __iter__(self):
        worker_info = torch.utils.data.get_worker_info()
        if worker_info is None:
            tfrecord_list = self.tfrecord_list
        else:
            worker_id = worker_info.id
            num_workers = worker_info.num_workers
            tfrecord_list = self.tfrecord_list[worker_id::num_workers]
            random.seed(worker_info.seed)

        def tf_record_iterator():
            for filename in tfrecord_list:
                    record_iterator = tf.python_io.tf_record_iterator(filename,
                                            tf.python_io.TFRecordCompressionType.NONE)
                    for string_record in record_iterator:
                        example = tf.train.Example()
                        example.ParseFromString(string_record)
                        do_stuff_with_example(example)
                        yield example

        return tf_record_iterator()
</code></pre>
<p>Does something look obviously wrong this implementation to the PyTorch people?</p>

GdMacmillan: <p>Are there any plans to make a a tfrecord like object native to pytorch? I can see there is a fair amount of interest from this thread. It is not great having to work with 2 libraries (tensorflow, pytorch) like this.</p>

nareshr8: <p>TFRecords serialise the data and convert it to tensors before hand I believe. Plus it uses Protocol Buffers. It doesnt load all records to the memory but loads batched records as far as I know. I had to work on tabular data and I could get about 3X speed increase when using data source as TFRecords instead of CSV using TensorFlow. I am interested in having such faster datasource in Pytorch.</p>
<p>The combination of TFRecords and Dataset API in tensorflow is good. It would be great if we can have the same feature here.</p>

Stanislav_Pidhorskyi: <p>Hi,<br>
If anyone still interested in reading TFRecords, I’ve started a project <a href="https://github.com/podgorskiy/DareBlopy" rel="nofollow noopener">https://github.com/podgorskiy/DareBlopy</a>. It is capable of some basic TFRecords reading and has decent performance (weirdly, seems even faster than TensorFlow, at least in one particular test case)<br>
I used it to train my <a href="https://github.com/podgorskiy/StyleGan" rel="nofollow noopener">StyleGAN</a> PyTorch implementation using TFRecords of FFHQ dataset, as well as in some other projects of mine.<br>
Documentation is still an issue, but I’ll be fixing it soon.</p>

Huige_Cheng: <p><a class="mention" href="/u/stanislav_pidhorskyi">@Stanislav_Pidhorskyi</a> Great work, will it support var len feature (automatic padding) ?</p>