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, кажется совершенно невозможным (см. здесь). Кроме того, я просмотрел from_generator() входы для tf.data.Dataset, но, похоже, это определенно не будет работать в нескольких потоках. Любые предложения более чем приветствуются.


person verified.human    schedule 17.01.2018    source источник
comment
tf.data.Dataset.map(your_map_function, num_parallel_calls=N) делает то, что вы хотите? Он будет запускать N потока вашей функции карты. Проблема, которую я вижу в этом, заключается в том, что теперь у вас есть 6 потоков, каждый из которых читает 1 файл HDF5, а это означает, что вам лучше иметь достаточно памяти для всех 6 файлов Full HDF5. Я пришел к этому вопросу из-за связанного вопроса, который я опубликовал, пытаясь решить проблему ограниченной памяти и больших файлов HDF5. stackoverflow.com/questions/48349409/   -  person David Parks    schedule 20.01.2018
comment
Память с файлами HDF5 не является проблемой, поскольку она не считывает весь файл в память. Но по-прежнему проблематично открывать-читать-закрывать-повторно открывать ... файлы все время, и это приведет к значительному снижению скорости.   -  person verified.human    schedule 20.01.2018


Ответы (2)


Я наткнулся на этот вопрос, когда занимался аналогичной проблемой. Я придумал решение, основанное на использовании генератора Python вместе с методом построения набора данных TF _ 1_. Поскольку мы используем генератор, файл 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. Для получения дополнительной информации о генераторах см. этот отличный вопрос и ответ.

Конечно, вам придется заменить что-либо внутри блока 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 и наследует те же ограничения. В частности, он требует, чтобы операции, связанные с набором данных и итератором, были размещены на устройстве в том же процессе, что и программа Python, которая вызвала Dataset.from_generator (). Тело генератора не будет сериализовано в GraphDef, и вам не следует использовать этот метод, если вам нужно сериализовать модель и восстановить ее в другой среде.

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

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

person mikkola    schedule 17.03.2018
comment
Есть ли способ загрузить несколько наборов данных из одного файла hdf? Например, hf [train_img] и hf [метки]. - person ShaneAhmed Siddiqui; 12.04.2018
comment
У меня есть дополнительный вопрос по этому поводу. Пример одного файла HDF5 работает правильно, но у меня проблемы с тем, чтобы заставить мульти-пример работать с чередованием. Проблема в том, что tf.data.Dataset.from_tensor_slices (filenames) возвращает коллекцию объектов Tensor, а не строк Python, и, следовательно, генератор не может с этим справиться. Как правильно с этим справиться? - person verified.human; 28.04.2018

Мне потребовалось время, чтобы понять это, поэтому я подумал, что мне стоит записать это здесь. Основываясь на ответе Микколы, вот как обрабатывать несколько файлов:

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, который оценивает тензорный поток и преобразует его в обычную переменную Python.

person Rong Ou    schedule 06.07.2018
comment
Я могу подтвердить, что мне понадобилась эта модификация, чтобы заставить ее работать. - person William J. Cunningham; 21.11.2018
comment
Здравствуйте, в приведенном выше генераторе вы не вернули метку изображения? как вернуть этикетку? я хочу сделать следующее: yield h5file [data_name], label, нужно ли вносить какие-либо изменения в форму возврата: tf.TensorShape ([427,561,3]), - person void; 29.04.2019
comment
Я сожалею, что не посмотрел на второй ответ (то есть на этот). У меня ушло несколько часов, чтобы попытаться получить оригинальную работу! - person armen; 10.05.2020
comment
@void, чтобы получить метку, я делаю то, что объединяю метку с остальной частью вывода в генераторе, а затем передаю ее через tf.data.Dataset.map (), которая просто разрезает ее на две части, особенности и метки. - person armen; 10.05.2020