TensorFlow - tf.data.Dataset чтение больших файлов HDF5

Я настраиваю конвейер TensorFlow для чтения больших файлов HDF5 в качестве входных данных для моих моделей глубокого обучения. Каждый файл HDF5 содержит 100 видео размера переменной длины, хранящихся в виде коллекции сжатых изображений JPG (чтобы сделать размер на управляемом диске). Используя tf.data.Dataset и карту tf.py_func, чтение примеров из файла HDF5 с использованием пользовательской логики Python довольно просто. Например:

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 это было довольно просто, используя RandomShuffleQueue и enqueue_many, но похоже, что в настоящее время нет элегантного способа сделать это (или документации не хватает).

Кто-нибудь знает, что будет лучшим способом достижения моей цели? Я также изучил (и реализовал) конвейер с использованием файлов tfrecord, но tfrecord случайной выборки видеокадров, хранящихся в файле tfrecord кажется совершенно невозможным (см. Здесь). Кроме того, я просмотрел from_generator() данные from_generator() для tf.data.Dataset но это определенно не будет работать в нескольких потоках. Любые предложения более чем приветствуются.

Ответ 1

Я наткнулся на этот вопрос, имея дело с подобной проблемой. Я придумал решение, основанное на использовании генератора 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. Подробнее об генераторах см. В этом отличном 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, ширину 561 и 3 цветовых канала в моем наборе данных, поэтому вам нужно будет изменить их в приведенном выше вызове, чтобы соответствовать вашему варианту использования.

Обработка нескольких файлов

У меня есть предлагаемое решение для обработки нескольких файлов HDF5. Основная идея состоит в том, чтобы создать Dataset из имен файлов, как обычно, а затем использовать метод interleave для обработки многих входных файлов одновременно, получая образцы из каждого из них, чтобы сформировать пакет, например.

Идея такова:

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 документация from_generator упоминает эти две заметки.

Может быть сложно применить это в разных средах или с распределенным обучением:

ПРИМЕЧАНИЕ. Текущая реализация Dataset.from_generator() использует tf.py_func и наследует те же ограничения. В частности, для выполнения операций Dataset- и Iterator на устройстве выполняется тот же процесс, что и программа Python, которая называется Dataset.from_generator(). Тело генератора не будет сериализовано в GraphDef, и вы не должны использовать этот метод, если вам нужно сериализовать свою модель и восстановить ее в другой среде.

Будьте осторожны, если генератор зависит от внешнего состояния:

ПРИМЕЧАНИЕ. Если генератор зависит от изменяемых глобальных переменных или другого внешнего состояния, имейте в виду, что среда выполнения может вызывать генератор несколько раз (для поддержки повторения набора данных) и в любое время между вызовом Dataset.from_generator() и производством первый элемент из генератора. Мутирование глобальных переменных или внешнего состояния может вызвать неопределенное поведение, и мы рекомендуем вам явно кэшировать любое внешнее состояние в генераторе до вызова Dataset.from_generator().

Ответ 2

Я взял время, чтобы понять это, поэтому я подумал, что должен записать это здесь. Основываясь на ответе 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.