Tensorflow: загрузка данных в несколько потоков на cpu

у меня есть класс python SceneGenerator, который имеет несколько функций-членов для предварительной обработки и функцию генератора generate_data(). Основная структура такова:

class SceneGenerator(object):
    def __init__(self):
       # some inits

    def generate_data(self):
        """
        Generator. Yield data X and labels y after some preprocessing
        """
        while True:
            # opening files, selecting data
            X,y = self.preprocess(some_params, filenames, ...)            

            yield X, y

я использовал функцию-член класса sceneGenerator.generate_data () в модели keras.функция fit_generator () для чтения данных с диска, предварительной обработки и получения. В keras это делается на нескольких потоках CPU, если о model.fit_generator() имеет значение что-то > 1.

теперь я хочу используйте то же самое SceneGenerator класс в tensorflow. Мой нынешний подход таков:--10-->

sceneGenerator = SceneGenerator(some_params)
for X, y in sceneGenerator.generate_data():

    feed_dict = {ops['data']: X,
                 ops['labels']: y,
                 ops['is_training_pl']: True
                 }
    summary, step, _, loss, prediction = sess.run([optimization_op, loss_op, pred_op],
                                                  feed_dict=feed_dict)

это, однако, медленно и не использует несколько потоков. Я нашел tf.data.Dataset api с некоторыми документация, но я не могу реализовать методы.

Edit: обратите внимание, что я не работаю с изображениями, так что механизмы загрузки изображений с путями файлов и т. д. здесь не работают. Мой SceneGenerator загружает данные из файлов hdf5. Но не полностью наборы данных, но - в зависимости от параметров инициализации - только части набора данных. Я хотел бы сохранить функцию генератора как есть и узнать, как этот генератор может быть непосредственно использован в качестве входа для tensorflow и работает на нескольких потоках на CPU. Перезапись данных из файлов hdf5 в csv не является хорошим вариантом, потому что он дублировал много данных.

Edit 2:: я думаю, что-то похожее на это может помочь: parallelising tf.данные.Набор данных.from_generator

2 ответов


предполагая, что вы используете последний Tensorflow (1.4 на момент написания этой статьи), вы можете сохранить генератор и использовать tf.data.* API следующим образом (я выбрал произвольные значения для номера потока, размера буфера предварительной выборки, размера пакета и типов выходных данных):

NUM_THREADS = 5
sceneGen = SceneGenerator()
dataset = tf.data.Dataset.from_generator(sceneGen.generate_data, output_types=(tf.float32, tf.int32))
dataset = dataset.map(lambda x,y : (x,y), num_parallel_calls=NUM_THREADS).prefetch(buffer_size=1000)
dataset = dataset.batch(42)
X, y = dataset.make_one_shot_iterator().get_next()

чтобы показать, что на самом деле это несколько потоков, извлекаемых из генератора, я изменил ваш класс следующим образом:

import threading    
class SceneGenerator(object):
  def __init__(self):
    # some inits
    pass

  def generate_data(self):
    """
    Generator. Yield data X and labels y after some preprocessing
    """
    while True:
      # opening files, selecting data
      X,y = threading.get_ident(), 2 #self.preprocess(some_params, filenames, ...)            
      yield X, y

таким образом, создавая сеанс Tensorflow и получая один пакет показывает идентификаторы потоков потоков, получающих данные. На моем ПК работает:

sess = tf.Session()
print(sess.run([X, y]))

печать

[array([  8460.,   8460.,   8460.,  15912.,  16200.,  16200.,   8460.,
         15912.,  16200.,   8460.,  15912.,  16200.,  16200.,   8460.,
         15912.,  15912.,   8460.,   8460.,   6552.,  15912.,  15912.,
          8460.,   8460.,  15912.,   9956.,  16200.,   9956.,  16200.,
         15912.,  15912.,   9956.,  16200.,  15912.,  16200.,  16200.,
         16200.,   6552.,  16200.,  16200.,   9956.,   6552.,   6552.], dtype=float32),
 array([2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
        2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])]

Примечание: возможно, вы захотите поэкспериментировать с удалением map вызов (который мы используем только для нескольких потоков) и проверка, если prefetchбуфера достаточно, чтобы удалить узкое место во входном конвейере (даже с одним потоком, часто входная предварительная обработка быстрее, чем фактическое выполнение графика, поэтому буфер достаточно иметь предварительная обработка идет так быстро, как только может).


запуск сеанса с feed_dict действительно довольно медленно:

Feed_dict выполняет однопоточную memcpy содержимого из среды выполнения Python в среду выполнения TensorFlow.

более быстрый способ подачи данных с помощью tf.train.string_input_producer + *Reader + tf.train.Coordinator, который будет паковать данные в нескольких потоках. Для этого Вы читаете данные непосредственно в тензоры, например, вот способ чтения и процесс :

def batch_generator(filenames):
  filename_queue = tf.train.string_input_producer(filenames)
  reader = tf.TextLineReader(skip_header_lines=1)
  _, value = reader.read(filename_queue)

  content = tf.decode_csv(value, record_defaults=record_defaults)
  content[4] = tf.cond(tf.equal(content[4], tf.constant('Present')),
                       lambda: tf.constant(1.0),
                       lambda: tf.constant(0.0))

  features = tf.stack(content[:N_FEATURES])
  label = content[-1]

  data_batch, label_batch = tf.train.shuffle_batch([features, label],
                                                   batch_size=BATCH_SIZE,
                                                   capacity=20*BATCH_SIZE,
                                                   min_after_dequeue=10*BATCH_SIZE)
  return data_batch, label_batch

эта функция получает список входных файлов, создает считыватель и преобразования данных и выводит тензоров, которые оцениваются по содержанию этих файлов. Ваш генератор сцен, вероятно, будет выполнять различные преобразования, но идея одна и та же.

Далее, вы начинаете tf.train.Coordinator распараллелить этот:

with tf.Session() as sess:
    coord = tf.train.Coordinator()
    threads = tf.train.start_queue_runners(coord=coord)
    for _ in range(10):  # generate 10 batches
        features, labels = sess.run([data_batch, label_batch])
        print(features)
    coord.request_stop()
    coord.join(threads)

по моему опыту, этот способ подает данные намного быстрее и позволяет использовать вся доступная мощность GPU. Полный рабочий пример можно найти здесь.