TensorFlow - tf.data.Dataset读取大型HDF5文件

时间:2018-01-17 20:39:21

标签: python video tensorflow hdf5 tensorflow-datasets

我正在设置TensorFlow管道,用于读取大型HDF5文件作为我的深度学习模型的输入。每个HDF5文件包含100个可变大小长度的视频,存储为压缩JPG图像的集合(以使磁盘上的大小可管理)。使用tf.data.Dataset和映射到tf.py_func,使用自定义Python逻辑从HDF5文件中读取示例非常简单。例如:

def read_examples_hdf5(filename, label):
    with h5py.File(filename, 'r') as hf:
        # read frames from HDF5 and decode them from JPG
    return frames, label

filenames = glob.glob(os.path.join(hdf5_data_path, "*.h5"))
labels = [0]*len(filenames) # ... can we do this more elegantly?

dataset = tf.data.Dataset.from_tensor_slices((filenames, labels))
dataset = dataset.map(
    lambda filename, label: tuple(tf.py_func(
        read_examples_hdf5, [filename, label], [tf.uint8, tf.int64]))
)

dataset = dataset.shuffle(1000 + 3 * BATCH_SIZE)
dataset = dataset.batch(BATCH_SIZE)
iterator = dataset.make_one_shot_iterator()
next_batch = iterator.get_next()

此示例有效,但问题是似乎tf.py_func一次只能处理一个示例。由于我的HDF5容器存储了100个示例,因此这种限制会导致显着的开销,因为文件经常需要打开,读取,关闭和重新打开。将所有100个视频示例读入数据集对象然后继续使用下一个HDF5文件(最好是在多个线程中,每个线程处理它自己的HDF5文件集合)会更有效率。

所以,我想要的是在后台运行多个线程,从HDF5文件中读取视频帧,从JPG解码它们,然后将它们提供给数据集对象。在引入tf.data.Dataset管道之前,使用RandomShuffleQueueenqueue_many操作非常简单,但似乎目前没有优雅的方法(或文档是不足)。

有谁知道实现目标的最佳方法是什么?我还使用tfrecord文件调查(并实现了)管道,但是采用tfrecord文件中存储的视频帧的随机样本似乎是不可能的(参见here)。另外,我查看了from_generator()的{​​{1}}输入,但这似乎不会在多个线程中运行。任何建议都非常受欢迎。

2 个答案:

答案 0 :(得分:14)

在处理类似问题时,我偶然发现了这个问题。我提出了一个基于使用Python生成器的解决方案,以及TF数据集构造方法from_generator。因为我们使用生成器,所以HDF5文件应该只打开一次并且只要有要读取的条目就保持打开状态。因此,每次调用都不会打开,读取和关闭它以获取下一个数据元素。

生成器定义

为了允许用户传入HDF5文件名作为参数,我生成了一个具有__call__方法的类,因为from_generator指定生成器必须是可调用的。这是发电机:

import h5py
import tensorflow as tf

class generator:
    def __init__(self, file):
        self.file = file

    def __call__(self):
        with h5py.File(self.file, 'r') as hf:
            for im in hf["train_img"]:
                yield im

通过使用生成器,代码应该从上次返回结果时的每次调用中从中断处开始,而不是从头开始再次运行。在这种情况下,它位于内部for循环的下一次迭代中。所以这应该跳过再次打开文件进行读取,只要有yield的数据就保持打开状态。有关生成器的更多信息,请参阅this excellent Q&A

当然,您必须替换with块内的任何内容,以匹配数据集的构造方式以及您希望获得的输出。

用法示例

ds = tf.data.Dataset.from_generator(
    generator(hdf5_path), 
    tf.uint8, 
    tf.TensorShape([427,561,3]))

value = ds.make_one_shot_iterator().get_next()

# Example on how to read elements
while True:
    try:
        data = sess.run(value)
        print(data.shape)
    except tf.errors.OutOfRangeError:
        print('done.')
        break

同样,在我的情况下,我在我的数据集中存储了uint8高度427,宽度5613颜色通道的图像,因此您需要修改这些在上面的调用中与您的用例匹配。

处理多个文件

我有一个处理多个HDF5文件的建议解决方案。基本思想是像往常一样从文件名构造Dataset,然后使用interleave方法同时处理许多输入文件,例如从每个输入文件中获取样本以形成批处理。 / p>

这个想法如下:

ds = tf.data.Dataset.from_tensor_slices(filenames)
# You might want to shuffle() the filenames here depending on the application
ds = ds.interleave(lambda filename: tf.data.Dataset.from_generator(
        generator(filename), 
        tf.uint8, 
        tf.TensorShape([427,561,3])),
       cycle_length, block_length)

这样做同时打开cycle_length个文件,并在移动到下一个文件之前从每个文件生成block_length个项目 - 有关详细信息,请参阅interleave文档。您可以在此处设置值以匹配适合您的应用程序的值:例如,您是需要一次处理一个文件还是同时处理多个文件,您是否只想从每个文件一次处理一个样本,依此类推

编辑:对于并行版本,请查看tf.contrib.data.parallel_interleave

可能的警告

如果您决定使用解决方案,请注意使用from_generator的特性。对于Tensorflow 1.6.0,documentation of from_generator提到了这两个音符。

在不同环境或分布式培训中应用此功能可能具有挑战性:

  

注意:Dataset.from_generator()的当前实现使用   tf.py_func并继承相同的约束。特别是它   需要放置与数据集和迭代器相关的操作   与调用的Python程序处于同一进程的设备   Dataset.from_generator()。发电机的主体不会被序列化   在GraphDef中,如果需要,不应使用此方法   序列化您的模型并在不同的环境中恢复它。

如果发电机依赖于外部状态,请小心:

  

注意:如果生成器依赖于可变全局变量或其他变量   外部状态,请注意运行时可能会调用生成器   多次(为了支持重复数据集)和任何   调用Dataset.from_generator()和生产之间的时间   来自发电机的第一个元素。变异全局变量或   外部状态可能导致未定义的行为,我们建议您   在调用之前显式缓存生成器中的任何外部状态   Dataset.from_generator()。

答案 1 :(得分:3)

我花了点时间来解决这个问题,所以我认为我应该在这里记录下来。根据mikkola的回答,这是处理多个文件的方法:

import h5py
import tensorflow as tf

class generator:
    def __call__(self, file):
        with h5py.File(file, 'r') as hf:
            for im in hf["train_img"]:
                yield im

ds = tf.data.Dataset.from_tensor_slices(filenames)
ds = ds.interleave(lambda filename: tf.data.Dataset.from_generator(
        generator(), 
        tf.uint8, 
        tf.TensorShape([427,561,3]),
        args=(filename,)),
       cycle_length, block_length)

关键是您不能直接将filename传递给generator,因为它是Tensor。您必须通过args传递它,tensorflow会对其求值并将其转换为常规python变量。