Tensorflow: [Улучшение] Перепроектирование входных конвейеров TensorFlow

Созданный на 28 февр. 2017  ·  134Комментарии  ·  Источник: tensorflow/tensorflow

[ TL; DR: мы разрабатываем новый API конвейера ввода для TensorFlow, и мы хотели бы собрать ваши запросы функций по этой проблеме.]

Мы заметили, что одна из самых больших проблем при начале работы с TensorFlow - это загрузка ваших собственных данных в ваши программы. Хотя в TensorFlow есть несколько методов, которые можно использовать для создания сложных конвейеров ввода (например, tf.train.string_input_producer() , tf.train.batch() и т. Д.), Они были разработаны для конкретного случая использования (обработка статического набора файлов неоднократно), и средний пользовательский опыт работы с этими методами невелик. Например:

  • Как только вы достигнете конца конвейера, он станет закрытым, и вы больше не сможете использовать его в том же сеансе. Это требует от пользователей использования неестественных обходных путей - с потоком управления или несколькими сеансами - для получения сигнала после обработки всей эпохи или переключения между обработкой двух наборов данных (например, данных обучения и проверки) в одной программе.

    • См. # 2514 и # 4535 для запросов функций об обработке нескольких эпох.

    • См. # 7902 и многочисленные вопросы о переполнении стека, где приведены примеры обработки разных наборов данных в одной программе.

  • Текущие конвейеры используют очереди TensorFlow и несколько потоков Python, что может привести к снижению производительности (конфликт блокировок в очередях и Python GIL) и трудным для понимания исключениям ( tf.errors.OutOfRangeError ).
  • Если вы забыли вызвать tf.train.start_queue_runners(sess) конвейеры будут вести себя плохо: на самом деле они зависают на неопределенное время и блокируют пользовательскую программу.

Мы решили начать с чистого листа и переделать API конвейера ввода. Существующие методы останутся до TF 2.0 (по крайней мере), но мы планируем добавить новый набор методов для загрузки и управления наборами данных. Мы все еще готовим детальный дизайн, которым планируем поделиться в ближайшее время, но ожидаем, что появятся два новых API:

  • Dataset представляет собой набор элементов данных. Каждый элемент может быть кортежем из одного или нескольких тензоров (например, изображение и его метка). Мы предоставим методы для создания наборов данных из тензоров и получения их из другого набора данных (например, путем нарезания его элементов, повторения его элементов, перетасовки его элементов, пакетирования его элементов, сопоставления функции с ее элементами и т. Д.).
  • Iterator можно создать из Dataset . Итератор представляет текущую позицию в наборе данных и предоставляет операцию (например, tf.QueueBase.dequeue() ), которую можно запустить для получения следующего элемента. Будут явные операции для инициализации итератора, чтобы его можно было повторно использовать после того, как вы обработали все элементы в наборе данных.

Подобный шаблон используется во многих различных настройках, включая Java Stream API , коллекции Scala (и, следовательно, RDD Spark) и языковой интегрированный запрос .NET .

Мы анонсируем этот план заранее, потому что хотим собрать отзывы о том, какие функции вы, как пользователи TensorFlow, хотели бы видеть в API конвейера ввода. Какие еще болевые точки мы упустили? Какие функции вам не хватает в других системах? Какие еще у вас есть предложения?

Мы с нетерпением ждем вашего ответа!

Самый полезный комментарий

Лично я очень большой поклонник метода feed_dict для ввода данных в график. Он, безусловно, самый гибкий, упрощает отладку и значительно упрощает код. Таким образом, моим самым большим желанием было бы сделать этот метод более производительным. Прямо сейчас этот метод постоянно истощает мой графический процессор, что очень досадно, потому что большинству других фреймворков DL (даже основанных на вычислительных графах) удается сделать это намного более производительно. Я предполагаю, что в фоновом режиме происходит больше операций копирования / обработки, чем было бы необходимо.

Все 134 Комментарий

Обязательным условием для одного из наших вариантов использования является специальное создание элементов данных с помощью функции обратного вызова (которая создает тензоры на лету, например, с помощью py_func () или другими способами).

В частности, в настоящее время у нас есть вариант использования, когда мы используем две очереди; внешнюю, используя string_input_producer (с перетасовкой), где каждая строка обозначает / указывает на «набор данных», а внутренняя очередь затем создается путем генерации переменного количества выборок из каждого «набора данных». Какие и сколько сэмплов создаются, различаются в зависимости от эпохи (возможно, в зависимости от поведения при обучении в прошлом). Фактически, мы больше даже не используем номенклатуру эпох, поскольку одни и те же данные никогда не отображаются дважды, а вышеупомянутая генерация / выборка выходит за рамки обычного увеличения данных.

Короче говоря: при несколько необычном варианте использования мы столкнулись практически со всеми проблемами, о которых вы упомянули выше, и наши обходные пути оказались не очень хорошими. Мы были бы очень счастливы увидеть очень гибкий механизм, в котором такие случаи поддерживаются, и генерация данных не должна быть привязана к навязанным концепциям, таким как эпохи, конечноповторяющиеся очереди и т. Д. (Хотя они могут быть смоделированы с помощью его примитивы).
Я не уверен, насколько хорошо запланированный Dataset / Iterator API будет поддерживать это.

Изменить: вещи, которые нам все еще нужны, конечно, включают многопоточную генерацию данных и многопоточные случайные очереди производителей-потребителей. Но без проклятия GIL - может быть, с помощью простых хуков C ++ / Eigen и управления потоками на нативной стороне? Взад и вперед через pybind?

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

@kmhofmann Мы, безусловно, будем поддерживать tf.py_func() внутри конвейера ввода нового стиля (а также, в общем, композиции любых других операций TensorFlow). Однако я хотел бы больше узнать о вашем варианте использования. Как часто вы переходите от одного внешнего «набора данных» к другому? Есть ли какие-то конкретные операции, которые вы выполняете в конце «набора данных», или ваш цикл обучения может обрабатывать конкатенацию записей из разных «наборов данных»?

Мы планируем создать несколько вложенных итерационных примитивов, чтобы вы могли написать функцию, отображающую элемент (например, строку, представляющую ваш внешний «набор данных») в Dataset (представляющую записи в этих «данных»). set "), а затем сведет их к одному Dataset . (Подумайте о SelectMany() в C #, flatMap() в Java и Scala.) Итак, я думаю, вы могли бы реализовать свою логику для выборки из "набора данных" в одной из этих flatMap() функций. .

Сообщите мне, если что-то из этого неясно!

О, хорошее время! Теперь я могу перестать писать свой собственный (ужасный) класс набора данных. Многие из сказанного уже перекликаются с моим опытом.

Насколько это возможно, я хотел бы кодировать вычисления тензорного потока, не зависящие от набора данных. Я не хочу иметь 3 разных класса gan: каждый со своим собственным графом создания и методами подгонки просто потому, что один набор данных не помещается в памяти, другой - np.array, а другой создается на лету.

Вариант использования, который меня больше всего затрагивает, - это [сделать n раз: тренироваться для достижения цели / эпохи, проверить модель, повторить]. Как вы сказали, есть явные проблемы с очередями. Незначительная проблема, для которой я не предлагаю решения, заключается в том, что хотя планирование (сколько времени нужно тренироваться до проверки) выполняется, возможно, каким-либо методом класса модели, который я хотел бы быть независимым от набора данных, имеет ли смысл говорить в сроки итера или эпохи определяются набором данных, что частично разрушает независимость.

Некоторые другие идеи, которые я записал, проводя мозговой штурм в собственном классе:

  • Класс набора данных (а не модель), вероятно, должен иметь переданный ему batch_size. Неловко запрашивать batch_size в качестве параметра во время подгонки и во время создания набора данных / очереди, и в идеале граф вычислений не имеет запеченного batch_size.
  • Класс «подробного» набора данных должен отслеживать собственную статистику. Он должен поддерживать свои собственные счетчики (тензоры), которые отслеживают итерации, выборки и эпохи. В большинстве случаев я предполагаю, что они восстанавливаются одновременно с восстановлением параметров модели.
  • Самое главное, что нам нужно решить проблему удаления из очереди. Я видел десятки и десятки случаев, когда (в профилировщике; iirc MEMCpyWhatever) было очень медленно. В основном это была проблема, когда графический процессор получал данные от процессора.
  • Было бы здорово, если бы все еще оставался способ иметь входной канал, исходящий от многопоточного или многопроцессорного питона . Вот отличный и надежный способ сделать это в настоящее время:
    session.run(enqueue_op, feed_dict=$some_numpy_batch_input) Где можно асинхронно кормить очередь из python.
  • Было бы здорово иметь резидентные очереди GPU .

Хорошая точка зрения. Одна вещь заключается в том, что в настоящее время операции с очередями «запекаются» в графе вычислений, поэтому сложно что-либо изменить на ходу. Более высокая абстракция может значительно упростить задачу без учета использования потоков управления или других приемов.

Во многих случаях мои входные данные либо 1. не находятся в файловой системе, либо 2. требуют сложной предварительной обработки, недоступной в TensorFlow. В обоих случаях существующий конвейер ввода не может помочь, поэтому я использую поток ввода с enqueue / feed_dict + обучающий поток с множеством исключений из очереди.

Давайте предположим , что в большинстве случаев, вам не нужно использовать саму модель для получения данных (хотя иногда это не так). Тогда решение, которое мне действительно нравится, - это возможность получать (аналогично удалению из очереди) тензоры от другого процесса. (Как # 4836)
Преимущества:

  1. Может использовать любые инструменты / языки для получения данных из любых источников, если они, наконец, отправлены с определенным протоколом сообщений.
  2. (теоретически) не требует дополнительного потока Python в процессе обучения.
  3. Если протокол сообщений поддерживает pub / sub, то (1) несколько обучающих сессий могут подписывать и повторно использовать одни и те же входные данные, что очень полезно при испытании новых моделей. (2) данные могут быть сгенерированы на разных машинах, если предварительная обработка слишком сложна для одного процессора.

Это те функции, которые мне действительно не хватало в частной системе, которую я использовал.
Одним из недостатков является то, что IPC / сокет имеет меньшую пропускную способность, чем RAM, но обычно это не узкое место.
Я знаю, что эта функция может быть слишком далеко, но я надеюсь, что новый дизайн может позволить такую ​​возможную будущую функцию.

@mrry Один «набор данных» может состоять из примерно 500–30 000 динамически генерируемых выборок. На данный момент мы не выполняем определенные операции в конце каждого набора данных, то есть все помещается в одну (большую) очередь случайного перемешивания для смешивания выборок между наборами данных. Но я мог также представить себе случаи, когда разделение множеств могло бы быть полезным.

Пожалуйста, поддержите чтение файла hdf5 напрямую.

Лично я очень большой поклонник метода feed_dict для ввода данных в график. Он, безусловно, самый гибкий, упрощает отладку и значительно упрощает код. Таким образом, моим самым большим желанием было бы сделать этот метод более производительным. Прямо сейчас этот метод постоянно истощает мой графический процессор, что очень досадно, потому что большинству других фреймворков DL (даже основанных на вычислительных графах) удается сделать это намного более производительно. Я предполагаю, что в фоновом режиме происходит больше операций копирования / обработки, чем было бы необходимо.

Я рад этой инициативе. Входной конвейер, безусловно,
самая крутая часть кривой обучения.

Я хотел бы:

  • Унифицированный API для управления как набором данных в памяти ( feed_dict ), так и большим, так что один и тот же масштаб кода и ваша модель должны взаимодействовать только с одним API. Хотя я еще не использовал его, мне понравилось то, что я прочитал в документации по входному конвейеру.
  • Еще итераторы! Они великолепны. Асинхронные итераторы были бы еще лучше (см. PEP492 ). Итераторы, реализующие __len__ , отлично подходят для отчета о проделанной работе.
  • многопроцессорность, а не многопоточность
  • Пожалуйста, не используйте Dataset class, потому что, IMHO, концепция "набора данных" нечетко определена. Класс Dataset описанный в исходном посте, уже существует в Python: это список кортежей. И вообще, что такое «набор данных»? Набор данных поезда / действительных / тестовых данных или просто набор данных? Это просто файл? каталог? генератор? Каждый элемент данных (вход / цель) пара? Это всегда правда? Является ли словарь частью текстового набора данных?
    Выбор контейнера данных обусловлен множеством ограничений в зависимости от его размера и среды выполнения. Вместо контейнера Dataset я бы предпочел иметь богатый набор контейнеров, предлагающих различные компромиссы в отношении сложности памяти / времени. Кроме того, я хотел бы иметь богатый набор итераторов, сплиттеров, загрузчиков, дамперов, срезов, повторителей, серверов, генераторов для реальной работы с данными, поступающими из различных источников.
  • Понятие «эпоха» также не имеет четкой смысловой нагрузки. По моему опыту, это лучше всего определяется с помощью epoch = global_step / steps_per_epoch и steps_per_epoch = dataset_size / batch_size .

Здесь я пытаюсь преобразовать в небольшой набор данных в памяти некоторые из подпрограмм, доступных во входном конвейере TF для большого набора данных. Вот несколько примеров того, что я хотел бы видеть доступным в TensorFlow:

Эти процедуры демонстрируют, как далеко вы можете зайти с помощью простых итераторов по списку индексов.

+1 к чему-то вроде feed_dict. Это единственный способ учиться, взаимодействуя с внешним миром (тренировочные роботы, игры Atari, Вселенная ).

Это можно было бы сделать более эффективным, отказавшись от копий. Как PyTorch, тензоры которого разделяют буферы памяти с базовыми массивами numpy

Я не знаю TF так же хорошо, как другие здесь, поэтому, пожалуйста, отнеситесь к моим комментариям с некоторым скептицизмом:

  • С помощью tf.py_func я смог решить большинство моих проблем, связанных с вводом, например, загрузку файлов .mat в символической манере. То, с чем я сейчас борюсь, - это интеграция tf.train.batch с возможностью выбора источника, из которого должен поступать ввод, для наличия данных train / val в одной и той же символической переменной. # 8168

    Я понимаю, что эти функции изначально задумывались для простых случаев использования, но было бы неплохо иметь больший контроль над конвейером без бремени управления _everything_ (например, используя tf.QueueBase.from_list но будучи вынужденным кормить очереди и управлять потоками вроде вручную).

  • Я не уверен, что TensorFlow оптимизирует операцию удаления из очереди под капотом, но, если нет, я думаю, мы могли бы получить большую выгоду от параллельной операции удаления из очереди, которая загружает данные (то есть следующий пакет) в память графического процессора, пока он обрабатывает предыдущие данные (т.е. текущая партия).

  • Я думаю, что feed_dict -подобные решения не оптимальны для передачи больших фрагментов данных в функцию поезда, например, пакета изображений, поскольку они, по сути, являются паузой в графике выполнения, чтобы заставить TF взаимодействовать с _python runtime_ . Решение в графе звучит лучше, с указателями для управления выполнением графа, например feed_dict={is_training = True} чтобы указать, что ввод должен поступать из обучающего конвейера, модель должна установить batchnorm и dropout (и др.) В режим обучения и т. Д. Таким образом, TF может лучше оптимизировать / распараллелить выполнение, и все решения будут масштабироваться.

  • Стандартные функции для создания пакетов, по-видимому, не предоставляют индекса, чтобы указать, какой пакет мы обрабатываем. Например, slice_input_producer получает количество эпох, которые должны быть сгенерированы, но, похоже, нет способа узнать эпоху одной выборки, не подсчитав, сколько мы уже оценили.

прямо сейчас есть два очень разных пути получения данных в Tensorflow: feed_dict и очереди. очереди прекрасны до тех пор, пока у вас не будет возможности управлять своими данными изначально - например, если вы хотите загрузить файл .wav, разделить его на части и преобразовать в спектрограмму. в этот момент вы должны написать операцию C ++ (выполнимая, но переключение контекста + это делает очень негибкий конвейер) или вернуться в мир Python (медленнее, но очень просто и гибко).

похоже, что лучший компромисс между скоростью и гибкостью - это создать очередь TF, а затем создать кучу потоков Python, которые снабжают ее данными. это позволяет вам выполнять гибкую обработку данных в Python (примерно распараллеленную на ЦП, за исключением проблем с GIL), сохраняя при этом некоторое преимущество в скорости.

что, если вы просто формализовали это? интерфейс будет выглядеть следующим образом: push_data, end_of_data (для сигнализации о конце эпохи) и функция dequeue_batch, которая питает модель. тогда ваш код может просто загружать данные в Python и параллельно помещать их в очередь, в то время как модель полностью отделена от всего этого.

Мы должны сделать feed_dict быстрее (вероятно, не копируя массивы numpy.arrays, подобные упомянутому @yaroslavvb ), но это ортогонально этому изменению. Независимо от того, насколько мы его оптимизируем, feed_dict никогда не будет самым быстрым способом ввода данных в учебное задание.

feed_dict может не иметь особого значения. Чтобы быть более точным, нам нужна поддержка конвейеров, где обучение осуществляется в режиме онлайн, а данные обучения генерируются системой, реагирующей на действия сети TensorFlow (обучающий симулятор Atari, симулятор робототехники, взаимодействие робота с реальным миром и т. Д.) . Это необходимо для большинства приложений OpenAI, вот один пример - https://github.com/openai/universe-starter-agent

Самым быстрым вариантом было бы создание операции TensorFlow, которая поддерживает состояние, принимает действия в качестве входных данных и генерирует данные для обучения. Затем добавьте заполнитель, чтобы указать действие.

Я предполагаю, что вы ищете что-то, что можно сделать полностью на Python. Между ними может быть какая-то середина.

Я не уверен, что эта концепция еще обсуждалась, но я, по крайней мере, сформулирую проблему в своих терминах.

Имея дело с проблемами RL и буфером воспроизведения обучения, я не мог найти простого способа использовать очереди для ускорения подачи образцов через feed_dict. Кроме того, при случайном создании набора образцов казалось, что образцы были использованы, когда я хотел, чтобы они оставались в буфере.

Я надеялся передать (возможно, через feed_dict или файл) очередь с новым образцом, и как только размер буфера будет превышен, самый старый образец будет удален из буфера. Так что некоторая концепция «возраста выборки» была бы неплохой. Я уверен, что использование кольцевого буфера будет работать для исправления ряда образцов, но также может быть интересен "возраст", который может быть передан как часть кортежа, но в случае RL просто последовательность добавляемого образца может охватывать возраст (FIFO).

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

Я могу не понимать распределенные настройки, которые API конвейера ввода данных TF стремится решить. Возможно ли иметь простой дизайн API, как у Pytorch: всего три простых класса. Я могу подобрать API набора данных pytorch за 5 минут, и этого достаточно для всех популярных академических наборов данных. http://pytorch.org/docs/data.html

Приятно видеть новые усилия по устранению болевых точек в API набора данных TF. С нетерпением жду простого / красивого / гибкого API с минимальным количеством представленных классов / концепций. Спасибо.

@lming Да, первые два комментария здесь охватывают это: создание реализации набора данных, использующего py_func , будет эквивалентно реализации PyTorch.

Я разделяю мнение

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

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

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

  • Использование feed_dict и любые соответствующие падения производительности
  • Загрузка из отдельного потока и работа с каким-то одноразовым шаблоном очереди (за исключением того, что это совсем не ускорило работу, когда мы это попробовали)
  • Повторная реализация нашего конвейера загрузки и преобразования данных с примитивами TF, возможно, с py_func , но все еще с использованием API TF для управления обработчиками очередей

API потоковой передачи Python не идеален, но в целом, когда мы выполняем в NumPy или чем-то еще в основном задачи, не связанные с GIL, API очереди TF кажется скорее обузой, чем помощью.

Вот пара конкретных примеров использования, которые нам подойдут:

  • Одна из наших моделей - это модель локализации. Мы используем объекты scikit-image AffineTransform для применения операций обрезки и изменения размера для этой модели, потому что эти объекты позволяют нам легко переводить выходные данные нашей модели обратно в исходное входное пространство координат. Кажется сложным согласовать это с полностью основанным на тензор API.
  • У нас есть данные, которые поступают из большого количества несбалансированных сегментов, и при обучении мы используем некоторую настраиваемую логику стратифицированной выборки, чтобы гарантировать, что мы представляем примеры из каждого сегмента равномерно. В Python относительно просто сгенерировать новую отрисовку из нашего исходного набора данных для каждой эпохи, но похоже, что в API, предложенном выше, нам нужно было бы выяснить, как реализовать это поведение как Iterator , что кажется менее простым.

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

Мы смогли справиться с этим, используя текущую систему очередей, TF ops, batch_join / и т. Д., Чтобы включить несколько потоков предварительной обработки на лету с смешением примеров между файлами. Я должен сказать, что это действительно удобно и гибко для настройки гиперпараметров в архитектуре ввода, и мне нравится, что весь конвейер живет в графе, передает данные в ответ на вызовы sessions.run (train_op) и может быть восстановлен из общей контрольной точки с помощью модель.

Если вы планируете отказаться от текущей парадигмы очередей, я хотел бы знать, что Dataset и Iterator обеспечат такую ​​же гибкость. Для моего варианта использования кажется, что Dataset может представлять собой набор временных рядов, а Iterator будет вести себя как итератор / генератор python и может обрабатывать любую предварительную обработку для формирования пакетов примеров?

запрос функции: механизм управления очередями, особенно в сочетании с методом read () TFRecordReader / TextFileReader, который автоматически удаляет из очереди. Причина: автоматическая предварительная оценка ожидающих постановок в очередь.

MXNet IIterator является более подходящим примером, чем Java Stream API, коллекции Scala (и, следовательно, RDD Spark) и .NET Language Integrated Query. Дизайн позволяет гибко комбинировать различные компоненты входного конвейера, такие как ImageRecordIter , ImageNormalizeIter , BatchLoader , PrefetcherIter и ImageAugmenter .

MXNET_REGISTER_IO_ITER(ImageRecordIter)
.describe("Create iterator for dataset packed in recordio.")
.add_arguments(ImageRecParserParam::__FIELDS__())
.add_arguments(ImageRecordParam::__FIELDS__())
.add_arguments(BatchParam::__FIELDS__())
.add_arguments(PrefetcherParam::__FIELDS__())
.add_arguments(ListDefaultAugParams())
.add_arguments(ImageNormalizeParam::__FIELDS__())
.set_body([]() {
    return new PrefetcherIter(
        new BatchLoader(
            new ImageNormalizeIter(
                new ImageRecordIter<real_t>())));
  });

Caffe DB проще, но все же можно использовать.

В идеале, недавно разработанный API должен иметь возможность загружать существующие наборы данных Caffe и MXNet с помощью простых в реализации плагинов .

Только мои 2 цента. Рад этому решению. Я думаю, что в учебные пособия следует вложить огромные усилия: самая большая трудность, с которой я сталкиваюсь - и некоторые мои коллеги со мной - заключаются в том, что документация, которую вы можете найти, довольно паршивая и не очень самодостаточная. Я, конечно, буду рад помочь.

Набор данных представляет собой набор элементов данных.

Я не уверен, подразумевается ли это, но требуется потоковая передача больших наборов данных с диска. Dataset явно содержащий все элементы данных, похоже, этого не поддерживает.

В последних примечаниях к выпуску я вижу, что вы добавили новый класс RecordInput, который, по-видимому, является новым классом, предназначенным для использования в качестве поставщика ввода? К сожалению, в документации до сих пор отсутствуют дополнительные пояснения. Я могу найти только базовую информацию в документации C ++ API.
Было бы действительно интересно прочитать что-нибудь для Python API + некоторый пример кода. Если вам нужна помощь, не стесняйтесь обращаться ко мне или, например, @petrux также предложил помощь. Я думаю, что он прав, что расширение документации и предоставление лучших руководств очень важно. Потому что в противном случае люди будут придерживаться вводов feed_dict до TensorFlow 3.0 и стонать о плохой производительности TF.

В итоге я провел сравнительный анализ по другим причинам и наблюдал сопоставимую производительность между feed_dict и использованием очередей: https://github.com/tensorflow/tensorflow/issues/9322#issuecomment -295775991

Накладные расходы Feed dict - это, по сути, затраты на выполнение дополнительного memcpy (Python-> TensorFlow CPU-> TensorFlow GPU) по сравнению с использованием собственных операций, таких как очереди, которые есть (TensorFlow CPU-> TensorFlow GPU). Так что, если этот memcpy невелик, им должно быть пренебрежимо мало.

Это имеет смысл, и это то, что я предполагал.

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

@yaroslavvb поправьте меня, если я ошибаюсь, но это не совсем так. Если вы не реализовали какой-либо конвейер ввода с использованием библиотеки Pythons Queue или чего-то подобного, это будет дополнительно время загрузки данных с диска в память и, в конечном итоге, их предварительной обработки.

Для изображений и особенно больших пакетов это может занять довольно много времени. Вот где вы действительно можете ускорить процесс, используя входные очереди TF, потому что они будут загружать, например, изображения на диск (+ предварительная обработка) на CPU, пока вы тренируете / оцениваете свою сеть на GPU. Когда вычисления завершены, вы можете напрямую получить следующий пакет, скопировав данные на GPU, не дожидаясь, пока собственный Python загрузит новые данные в память.

@kratzert , это именно то, что @taion подразумевает под

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

Трудно правильно выполнить асинхронную предварительную обработку, поэтому большинству пользователей выгодно, чтобы tf.train выполнял это за них.

Это? Я ни в коем случае не хочу защищать входные очереди TF, но, читая сообщение @yaroslavvb, он заявляет, что дополнительное время приходит (только) из-за передачи памяти между собственным Python и TF (+ GPU). Единственное, что я хотел добавить, это то, что если вы не можете хранить все данные о тренировках в памяти, загрузка памяти с диска в память увеличивает время одного цикла тренировки. Я не могу найти ни одного из этих утверждений в сообщении @taion , но это может быть неправильное понимание с моей стороны, поскольку я не являюсь носителем английского языка.

И я знаю, что во многих случаях асинхронная предварительная обработка невозможна, но в некоторых случаях (простое обучение классификации изображений CNN) очереди ввода TF очень помогают.

Единственное, что я хотел добавить, это то, что если вы не можете хранить все данные о тренировках в памяти, загрузка памяти с диска в память увеличивает время одного цикла тренировки.

Это также относится к tf.train . Данные не просто появляются на графике, не считывая их с диска, независимо от того, делаете ли вы это через Python или механизм выполнения TensorFlow.

В любом случае, я нахожу сопоставленные массивы NumPy через joblib.cache.Memory и feed_dict достаточно производительными (загрузка графического процессора более 90% на протяжении всего сеанса обучения), несмотря на дополнительную копию.

Ну ладно, может быть недоразумение. Думал, я выразился яснее.

Я знаю, что данные не появляются в памяти волшебным образом, если я использую TF. Но TF позволяет довольно легко разместить, например, загрузку изображений + предварительную обработку явно на CPU и вычисление графиков на GPU, и то и другое выполняется параллельно. Таким образом, пока графический процессор вычисляет операции с некоторыми данными, процессор уже загружает следующие данные в память. А поскольку это выполняется параллельно, мы эффективно сокращаем время вычислений за счет загрузки данных с диска (поскольку это обычно занимает меньше времени, чем один прямой + обратный проход по сетевому графу). Но да, это применимо только для работы с CPU + GPU и не имеет никакого эффекта, если вы используете только CPU.

edit: Возможно, единственное, что мне нравится из входных очередей TF, это то, что состояние очередей (и производителей пакетов) можно наблюдать в Tensorboard. В остальном я довольно долго боролся, чтобы заставить их работать со всей предварительной обработкой, которую я хотел, и с очередями для тестирования и проверки за один прогон.

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

  1. Достойный способ сделать индивидуальную предварительную обработку данных, будь то на основе очереди или нет, идея возможности предвидеть все возможные потребности ввода данных обречена.
  2. Простой и устоявшийся способ изменить конвейер ввода графа после его создания, потому что это наиболее типичный шаблон использования. Текущий input_map предоставляет такую ​​функциональность, но она довольно хакерская. И тоже задокументирован (когда дело доходит до import_meta_graph ).
  3. Способ контроля и мониторинга эпох - в настоящее время они довольно глубоко скрыты и недоступны даже для простых проверок.

Я бы поддержал @nicolasdespres за пригодным для будущего, а также - несовместим с парадигмой TF о предоставлении небольших, стабильных, четко определенных и собираемых блоки для построения нестандартных моделей. При наличии некоторых пакетов следует приветствовать предопределенные обертки "easy-starter".

Я думаю, что TF на самом деле не требует еще одной попытки унифицировать предварительную обработку данных, чтобы поместить ее прямо в график. Ситуация ухудшается, если нужны нестандартные вещи и генерация / модификация данных «на лету». Обычно эти модификации не являются частью прямой модели по уважительной причине: эти операции не требуют обратного распространения. Следовательно, они должны быть только слабосвязанными.

Итак, идеальный конвейер ввода (все без обратного распространения) должен быть довольно простым и тонким: он должен состоять из операции очереди, которая получает данные (список тензоров) из некоторого источника (сокетов). # 8728 - хороший шаг в этом направлении вместе с профессионалами:

  • вы можете использовать любую библиотеку для предварительной обработки (opencv, nltk, ...)
  • предварительная выборка полностью параллельна и может выполняться на любой / нескольких машинах
  • код отправителя может быть помещен в любое место - даже непосредственно в игровые движки или движки рендеринга
  • генерация данных может выполняться на любом языке программирования (без пользовательских операций)

Я не уверен, если вам действительно нужно что - то другое , и я не понимаю , почему вам действительно нужно tf.image.random_* на графике.

Я не думаю, что здесь предлагается полностью избавиться от очередей, не так ли?

Абстракции в стиле наборов данных довольно распространены в этой сфере, и они весьма полезны. Существование абстракции более высокого уровня не препятствует существованию API нижнего уровня.

Фактически, для такого рода абстракций более высокого уровня рано кажется лучше, чем позже - одно из самых больших разочарований при чтении опубликованного кода исследования TF заключается в том, что подавляющее большинство кодовых баз используют свою собственную библиотеку идиосинкразических слоев, в отличие, например, от библиотек в tf.layers или tf.contrib.layers , и все эти библиотеки разные, что затрудняет совместное использование работы.

Я часто пытаюсь использовать TensorFlow на очень больших входных данных (потенциально> 1 ГБ мини-пакета) с относительно легкими вычислениями для каждого мини-пакета. Эти входные данные находятся в файле HDF5 или массиве Numpy на диске или в памяти, поэтому я обычно загружаю с помощью feed_dict , потенциально асинхронно в очередь. При работе с несколькими графическими процессорами TensorFlow не может даже увеличить пропускную способность PCI-e для графических процессоров из-за memcpy от feed_dict к тензору процессора.

Как упоминал @yaroslavvb , feed_dict memcpy (на одном ядре ЦП?) Может быть огромным узким местом производительности, и я хотел бы, чтобы это решалось при любом рефакторинге обработки ввода TensorFlow.

@jhseu Вы упомянули, что считаете удаление копии feed_dict ортогональной этой проблеме. Знаете ли вы, есть ли какие-либо проблемы или работа по удалению копии (по крайней мере, в некоторых случаях, например, массивы Numpy с большими строками с хорошими шагами)?

@eamartin с начала марта @alextp внес ряд изменений, чтобы ускорить feed_dict; когда память выровнена с 16 байтами, я думаю, что мы разделяем буферы с numpy, поэтому ночные выпуски могут быть для вас быстрее.

Проблема с 16-байтовым выравниванием, к сожалению, исходит от Eigen, который требует, чтобы начало адресов памяти было выровнено с 16-ю байтами. Я не уверен, почему Eigen не был написан для обработки невыровненных первых и последних «пакетов», поэтому это не имело бы значения. : /

Возможно ли, чтобы numpy делился буферами с переменными тензорного потока
когда они возвращаются после запуска сеанса?

Я понимаю, что это, вероятно, вызовет всевозможные изменчивости и состояния
проблемы, но их следует избегать, установив флаг WRITABLE на
вернул массивы numpy в false.

29 апреля 2017 года в 1:37 утра «Виджай Васудеван» [email protected] написал:

@eamartin https://github.com/eamartin внесен ряд изменений
с начала марта @alextp https://github.com/alextp до
ускорить feed_dict; когда память выровнена с 16 байтами, я думаю, что мы
разделяйте буферы с numpy, поэтому ночные выпуски могут быть для вас быстрее.

Проблема с 16-байтовым выравниванием, к сожалению, исходит от Eigen, для чего требуется
начало адресов памяти должно быть выровнено с 16 байтами. я не
конечно, почему Eigen не был написан для заполнения первого и последнего "пакетов", поэтому он
не имеет значения. : /

-
Вы получаете это, потому что подписаны на эту ветку.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/tensorflow/tensorflow/issues/7951#issuecomment-298129805 ,
или отключить поток
https://github.com/notifications/unsubscribe-auth/ADXd5AD4yuTBPXo8pp-uvVbr9QyYnJhDks5r0nhMgaJpZM4MO9tN
.

Спасибо за информацию @vrv (и за особенности @alextp ). Я немного осмотрелся, и похоже, что https://github.com/tensorflow/tensorflow/commit?author=alextp&since=2017-03-01T06 : 00: 00Z & until = 2017-04-01T05: 00: 00Z - это соответствующие коммиты. Судя по моей проверке, они не попали в TF 1.1, но, надеюсь, будут в 1.2.

Еще одна вещь, которая была бы крутой, - это возможность для Session возвращать Futures, которые затем можно было бы использовать в качестве входных данных для других запусков Session. Затем указанное будущее может быть пропущено через граф до тех пор, пока тензор, который он представляет, не потребует оценки.

with tf.Session() as S:
   # Note executor semantics
   future = S.submit(op_1, feed_dict={'input_1': 1.0, 'input_2':2.0})
   result = S.run(op_2, feed_dict={'data': future})

Эта концепция вдохновлена распределенной dask и другими

@sjperkins, к сожалению, то, как написаны наши текущие модульные тесты, заставляет их ломаться, если переменные возвращаются без дополнительных копий (потому что многие тесты делают a = session.run (variable); session.run (update_variable); b = session.run (variable) ; assertDifferent (a, b) (что не удается, если они совместно используют буферы).

Я подумал о создании опции ConfigProto для совместного использования буферов, даже если они не принадлежат исключительно мосту C-python, но не принадлежат. Если вам интересно, это должно быть легко сделать из перечисленных выше коммитов.

@sjperkins для фьючерсов, следите за обновлениями, поскольку мы создаем прототипы в этом направлении.

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

Пример: я тренировался с использованием TFRecords и очередей ввода; Я получил свои веса / модель. Я хочу выполнить вывод, выполнив операцию прогнозирования; но потому что обработчики входной очереди и т. д. являются частью графа до этого; Я застрял на этом механизме вывода.

См. Проблему здесь: http://stackoverflow.com/questions/43708616/tensorflow-inference

Мне нравится функция tf record и queue runner теперь, когда я к ней привык; проблема в жесткой привязке к графику ....

Вот почему были разработаны такие инструменты, как tf.estimator.Estimator, которые позволяют
более легкое разделение проблем между обучением и умозаключением и позволяет
для перестановки входных трубопроводов. Можете ли вы использовать Оценщик для написания вашей модели?

В пн, 1 мая 2017 г., в 14:54, Дэвид Крук [email protected]
написал:

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

Пример: я тренировался с использованием TFRecords и очередей ввода; Я получил свой
вес / модель. Я хочу выполнить вывод, выполнив свой прогноз
операция; но поскольку бегуны входной очереди и т. д. являются частью графика
до этого; Я застрял на этом механизме вывода.

См. Проблему здесь: http://stackoverflow.com/questions/43708616/tensorflow-
вывод

Мне нравится функция tf record и queue runner теперь, когда я к ней привык; в
Проблема в жесткой привязке к графику ....

-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/tensorflow/tensorflow/issues/7951#issuecomment-298442742 ,
или отключить поток
https://github.com/notifications/unsubscribe-auth/AAATxXYK7comPG84UqyKek2mbzhnifD4ks5r1lSWgaJpZM4MO9tN
.

-

  • Алекс

В настоящее время меня беспокоит, что предварительная обработка моего обучения / прогнозирования со временем будет отличаться.

Мне был бы интересен конвейер, в котором:

  • Предварительная обработка и постобработка могут быть сериализованы с выводом в одной модели, а затем использоваться с другого языка (нет tf.py_func , но может обеспечить реализацию во время выполнения)
  • Существует более четкое различие между входными формами, для обучения вам обычно нужны пакеты, но для прогнозирования вас часто интересуют только отдельные примеры.

Предварительная обработка и постобработка не требуют обратного распространения, но они все равно должны нести с собой некоторые значения (делители нормализации или одно горячее сопоставление). Было бы идеально иметь некие Op которые могут train некоторые значения, подобные этому, во время обучения, переносить их в производство в сериализованном графике, а затем я мог бы предоставить реализацию для этой предварительной / постобработки. Op на целевом производственном языке.

@mirosval , мне кажется, что это может быть то, для чего в конечном итоге предназначена tf.Estimator .

Не уверен, что это было упомянуто выше, и я пропустил это, но я был бы признателен за гораздо более простой способ переключения между наборами данных для обучения и проверки. При использовании feed_dict это очень просто, и я к этому привык. Недавно я пытался переключиться с feed_dict , но это было (по крайней мере, в моем ограниченном опыте) серьезной трудностью. Страница руководства здесь в основном просто предлагает использовать отдельные процессы, но это может быть проблемой, особенно если я хочу выполнить раннюю остановку на основе данных проверки. Если бы я мог создать какой-то метод ввода (Queue, Dataset, что угодно), где я мог бы чисто переключаться между входами обучения и проверки, это было бы намного лучше (опять же, feed_dict отлично подходит для этого, но если он будет всегда быть медленнее, было бы неплохо иметь более производительную альтернативу).

@neighthan : да, @mrry тоже это запланировала :)

@neighthan Используя текущие конструкции, мы SwitchableDataSet который позволяет вам во время выполнения переключаться между обучением, проверкой и тестированием.

@ kdavis-mozilla звучит интересно, но ссылка кажется мертвой. Есть еще одна ссылка?

@neighthan Извините, исправлено.

Очень многообещающе! Это одна из моих самых больших проблем с текущим API.
Тем не менее, глядя на SwitchableDataSet, кажется, что реализация новых видов сценариев использования с подачей данных будет в основном осуществляться за счет реализации классов, специфичных для конкретных случаев. Будет ли новая модель программирования также включать API, чтобы, скажем, реализовывать то, что предлагает SwitchableDataSet, и помимо более общих примитивов более низкого уровня? Мне просто интересно, что придумают пользователи (относительно генерации и использования данных), что в противном случае потребовало бы (конкретных) дополнений к API ...

Правильно ли я предполагаю, что https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/learn/python/learn/dataframe является частью инициативы нового конвейера ввода? Есть ли какой-нибудь код, который использует это, даже если этот код недокументирован? Я хотел бы увидеть пример его использования.

Я также предполагаю, что фреймы данных и преобразования предназначены для тесной интеграции с оценщиками? На данный момент я не вижу, насколько оценщики подходят (хорошо) к фреймам данных и преобразованиям. Я вижу, где у вас есть способы сгенерировать feed_fns из фреймов данных для оценщиков, но это больше похоже на переходник к другому подходу, а не на часть проекта конвейера.

Я понимаю, что это все очень новое и находится в стадии разработки. Мне очень нравится то, что я вижу! Продолжайте хорошую работу.

@vonclites Это для взаимодействия с Pandas DataFrame / Series, а не с новой конструкцией Dataset.

@jimfleming Это кажется более общим, чем это. Существуют методы создания TensorFlowDataFrames из файлов csv, dicts, массивов numpy, TFRecords, а также из pandas. Он следует номенклатуре панд, но также напоминает конвейер Spark, как упоминал @mrry , и имеет многие функции, которые он описал в исходном посте.

Я согласен с тем, что это довольно обобщенно и может быть основой для будущих
работают, но это было доступно довольно давно, и большинство файлов
не обновлялся несколько месяцев.
В субботу, 13 мая 2017 г., в 9:46 vonclites [email protected] написал:

@jimfleming https://github.com/jimfleming Это кажется более общим
чем это. Существуют методы создания TensorFlowDataFrames из файлов csv,
dicts, массивы numpy, TFRecords, а также из pandas. Это следует за
номенклатура панд, но она также напоминает конвейер Spark, поскольку @mrry
https://github.com/mrry, упомянутый, и имеет многие функции, которые он
описано в исходном посте.

-
Вы получаете это, потому что вас упомянули.

Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/tensorflow/tensorflow/issues/7951#issuecomment-301259803 ,
или отключить поток
https://github.com/notifications/unsubscribe-auth/AAN0-Uztf2Y5vav67n_-YYSpISvvTEOBks5r5d5ngaJpZM4MO9tN
.

@jimfleming Хорошее замечание

Первая документация по мастеру под tf.contrib.data : https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/data

Все это кажется потрясающим!

Действительно, очень мило. Следующие итераторы были бы замечательными:

Планируется ли поддержка файлового формата с возможностью поиска, который можно начать читать с произвольного смещения?

Вот зачем нам это нужно:
Предположим, есть большой набор обучающих данных в текстовом формате, и нам нужно преобразовать его в формат tfrecord. Затем мы начали работу по уменьшению карты, преобразовали ее в 10 файлов tfrecord, запустили 10 рабочих процессов для их чтения, отлично! Затем мы хотим запустить его быстрее, мы бы изменили количество рабочих на 20, 30, 40, ... было бы здорово, если бы мы могли сделать это без повторной генерации обучающих данных.

Решение:
Во-первых, нам нужно передать смещение и длину вместе с именем файла в конструктор Dataset. Недостаточно только имени файла.

Во-вторых, сам формат файла должен быть доступен для поиска (он же разделяется). это должно быть одно из следующих:

  1. Текстовый формат
  2. Заблокированный двоичный формат с заполнением.
  3. Имеет индексный файл.

Этот новый API быстрее старого. Я интегрировал этот новый API-интерфейс набора данных в свой тренажер по факторингу, и он сэкономил мне 20% времени на обучение. Спасибо @mrry

Мне кажется, что код по умолчанию для цикла по набору данных немного уродлив с исключением, выходящим из цикла while True :

sess.run(iterator.initializer)
while True:
  try:
    sess.run(train_op)
  except tf.errors.OutOfRangeError:
    break

Разве не было бы способа получить тензор is_empty указывающий, когда итератор пуст? Нравиться:

sess.run(iterator.initializer)
is_empty = False
while not is_empty:
  _, is_empty = sess.run([train_op, iterator.is_empty])

Если вы используете MonitoredSession и его варианты, вы все равно сможете это сделать:

with tf.train.SingularMonitoredSession() as sess:
    while not sess.should_stop():
        sess.run(train_op)

Изменить: извините, это полностью работает. Я только что понял, что он полностью выходит из контекста сеанса, но программа продолжает работать. Вызов .end () для хуков также работает. @jimfleming, ты снова прав;)

У меня не получается воспользоваться предложением о контролируемом сеансе.

with tf.train.MonitoredTrainingSession() as sess:
    while not sess.should_stop():
        data = sess.run(next_batch)
    print('stopped')

приведенный выше код ничего не печатает, просто завершает работу после попытки пройти мимо последней партии. Однако следующее будет печатать «исключение попадания» навсегда, если не используется break .

with tf.train.MonitoredTrainingSession() as sess:
    while not sess.should_stop():
        try:
            data = sess.run(next_batch)
        except tf.errors.OutOfRangeError:
            print('hit exception')
    print('stopped')

Не уверены, следует ли мне делать запрос функции или отчет об ошибке?

Применяются ли рекомендации примера High Performance Benchmarks теперь, когда существует новый Dataset API ?

Например, в тесте RecordInput разделен на мини-пакеты , которые я попытался включить через # 10143 незадолго до того, как нашел это. Если все эти рекомендации верны, API наборов данных может выиграть от добавления тех же самых рекомендаций.

Могу я предложить обновить тесты в соответствии с этим API и наоборот?

Я уже некоторое время опускаю голову, так что здесь есть на что ответить:

  • @sjperkins : Добавление Dataset.chain() и Dataset.product() не должно быть слишком сложным. Я хотел бы немного лучше понять ваш вариант использования. Как вы думаете, в большинстве случаев будет объединено ровно два набора данных (и, следовательно, мы могли бы использовать цепочку методов для их объединения, например, ds1.chain(ds2) , ds1.product(ds2) ), или будет более обычным объединение большего количества наборов данных (и следовательно, мы использовали бы аналогичный подход к Dataset.zip() , например, Dataset.chain([ds1, ds2]) , Dataset.product([ds1, ds2]) )? Также обратите внимание, что если вам нужно product() в краткосрочной перспективе, я думаю, вы можете написать ds1.flat_map(lambda x: tf.contrib.data.Dataset.zip((tf.contrib.data.Dataset.from_tensors(x).repeat(), ds2))) . Вы также можете подделать chain() с помощью Dataset.flat_map() и tf.cond() но это было бы довольно некрасиво :).

  • @snnn : Спасибо, что пинали шины! Приятно слышать, что новый API ускорил ваш код ... мы определенно предпочли гибкость производительности начальной версии API, но ждем улучшений в следующих версиях.

    Идея поддержки файловых форматов, доступных для поиска, очень привлекательна, и мы разрабатываем для этого хороший API. Прямо сейчас вы можете использовать Dataset.skip() и Dataset.take() для выбора поднабора данных из файлов, но это не очень эффективно, потому что они материализуют пропущенные входные данные перед их отбрасыванием. Я мог бы представить себе добавление внутреннего метода Iterator::Seek(size_t n) , который позволил бы итераторам специализировать свое поведение в этом случае (или вернуться к использованию GetNext() в цикле). Это также кажется важным для итераторов контрольных точек, что может быть полезно для обеспечения отказоустойчивости.

  • @omoindrot : Я согласен с тем, что конструкция try-except довольно уродлива, и мы должны попытаться найти способы ее улучшить. Текущая версия предназначена для замены очередей, которые используют tf.errors.OutOfRangeError для сигнализации о завершении, а различные другие классы предназначены для перехвата этого исключения. Раскрытие свойства iterator.is_empty было бы возможно, но было бы сложно заставить его работать так, как вы предлагаете, потому что (чтобы избежать возникновения исключения) вам нужно будет защитить обучающий подграф с помощью tf.cond(iterator.is_empty, make_train_op, lambda: tf.no_op()) . Другой возможностью было бы изменить Python API для итераторов, чтобы использовать две операции: Iterator.move_next() и Iterator.get_current() (например, как протокол C ++ IEnumerator ), но это приведет к дополнительному sess.run() call и усложняют совместное использование итератора между потоками.

    Одна из возможностей, которую я рассмотрел, - это создание оболочки, которая превращает Iterator -потребляющий шаг в итератор Python. например, какой-то код соломенного человека:

    def iterate_step(sess, fetches):
     cached_step = sess.make_callable(fetches)
     try:
       while True:
         yield cached_step.run()
     except tf.errors.OutOfRangeError:
       pass
    
    # ...
    for _, step, loss in iterate_step(sess, [train_op, global_step, loss]):
     if step % 100 == 0:
       # Run periodic eval, e.g.
    
  • @jimfleming и @vonclites : Спасибо, что изучили интеграцию MonitoredSession . Приятно слышать, что он «просто работает»! Я думаю, нам все равно нужно что-то улучшить для более сложных случаев, когда мы можем захотеть повторно инициализировать итератор в том же сеансе.

  • @ahundt : пиковая производительность конвейера ввода тестов все еще немного выше, чем та, которую вы получаете при использовании tf.contrib.data API. Однако пиковая производительность намного выше, чем пропускная способность фактического использования данных для обучения такой модели, как Inception или ResNet, поэтому вы можете не заметить разницы при регулярном использовании. Мы исследуем, как восполнить пробел, и весьма вероятно, что мы включим некоторые идеи из кода теста в реализацию Dataset .

    В частности, одним из текущих ограничений реализации Dataset и Iterator является то, что весь конвейер работает на одном устройстве, тогда как более явный код в тестах может разделять обработку между несколькими ЦП. и устройства GPU. Для достижения наилучшей производительности будет важно интегрировать такие оптимизации, как StagingArea для предварительной выборки данных в графический процессор до того, как это потребуется, и мы работаем над тем, чтобы сделать это более прозрачным. На данный момент вы можете вручную конвейеризовать вывод Iterator.get_next() op с StagingArea.put() op аналогично тесту.

Это фантастика! Я использую это сейчас, и мне это нравится. @vrv @mrry - это возможность переключаться между наборами данных для поездов и проверок, которые уже есть, или это все еще происходит? Я вижу, что повторно инициализируемые итераторы дают нам возможность использовать один и тот же итератор с несколькими наборами данных, но каждый раз, когда вы запускаете операцию инициализации, она, по сути, начинается с этого набора данных. В настоящее время я использую новые Dataset и Iterator apis вместе с tf.cond для достижения этой цели, но есть ли в работе более прямой / естественный способ?

Привет @mrry

У Iterator API нет интерфейса, подобного read_up_to / enqueue_many. Вы добавите его?

@sjperkins : Добавление итераторов Dataset.chain () и Dataset.product () не должно быть слишком сложным. Я хотел бы немного лучше понять ваш вариант использования. Как вы думаете, в большинстве случаев будет объединено ровно два набора данных (и, следовательно, мы могли бы использовать цепочку методов для их объединения, например, ds1.chain (ds2), ds1.product (ds2)), или будет более обычным объединение большего количества наборов данных (и следовательно, мы бы применили аналогичный подход к Dataset.zip (), например, Dataset.chain ([ds1, ds2]), Dataset.product ([ds1, ds2]))?

@mrry Подход, аналогичный Dataset.zip (), пожалуйста, для гибкости. Спасибо за обходные пути. :-)

Хотел сообщить, что перешел из очередей в набор данных, и это было здорово. Определенно иду в правильном направлении. В настоящее время не хватает нескольких вещей, и мне пришлось работать над этим:

  • Возможность использовать словари для тензорных структур в дополнение к кортежам и спискам (это было бы очень естественно при синтаксическом анализе примеров, которые имеют именованные функции и предоставляют вам словари)
  • Поддержка SparseTensors. tf.batch поддерживает SparseTensor, а автоматическое пакетирование SparseTensor сделало бы мою жизнь намного проще

Кроме того, у меня была идея, где вы могли бы реализовать функцию случайного разделения теста / поезда прямо в Dataset. Это тоже могло бы упростить жизнь. Что-то вроде

dataset_train, dataset_test = dataset.split_train_test(test_ratio=0.2, seed=1234)

Продолжайте хорошую работу!

@lhlmgr Насколько я понимаю, этот пример состоит в том, что итератор необходимо buffer_size что означает, что каждая инициализация выполняется довольно медленно. Я считаю, что подход tf.cond с двумя отдельными наборами данных / итераторами в этом случае работает лучше / быстрее. Таким образом, мы можем периодически просматривать данные проверки, не теряя места в обучающем наборе.

Может быть, вот хорошее место для ответа на мои вопросы, связанные с набором данных:
https://stackoverflow.com/questions/44132579/feed-data-into-a-tf-contrib-data-dataset-like-a-queue
https://stackoverflow.com/questions/44132307/tf-contrib-data-dataset-repeat-with-shuffle-notice-epoch-end-mixed-epochs

Мне очень нравится новый Dataset / Iterator API! Вот функция, которая поможет моему варианту использования:

Я хотел бы иметь возможность создавать итераторы, которые совместно используют часть конвейера данных. В качестве простого примера, примерно так:

a = np.arange(12)

data = tf.contrib.data.Dataset.from_tensor_slices(a)
data = data.shuffle(12)
data0 = data.map(my_func0)
data1 = data.map(my_func1)

iter0 = data0.make_one_shot_iterator()
iter1 = data1.make_one_shot_iterator()

op0 = iter0.get_next()
op1 = iter1.get_next()

Я бы хотел, чтобы op0 и op1 выводили элементы в одном порядке (потому что они разделяют шаг shuffle ), но с разными функциями ( my_func0 / my_func1 ) применено. То есть я хотел бы создать конвейеры ввода, которые разделяют некоторую обработку, а затем расходятся в какой-то момент для дополнительной обработки.

То, что предлагает

Я только начинаю изучать новый API, но хочу поделиться двумя проблемами, которые у меня были со старыми очередями ввода, одновременно с использованием MonitoredSession с SessionRunHooks.
Мы также использовали две отдельные очереди, одна обрабатывала input data_files как имена строк, а другая - результирующие входные данные с предварительной обработкой, выполняемой между этими двумя.
Нам нужно было убедиться, что операции постановки в очередь заполняют, по крайней мере, определенное кратное размеру пакета в первую очередь, чтобы наш код работал без проблем (в противном случае вторая очередь ввода остановилась)
Выглядит примерно так

queue_size_train = sess.run([ipc.train.rsq_pre_size)
            while queue_size_train <= batch_size * 5:
                sess.run([ipc.train.rsq_pre_enq],
                         feed_dict={ipc.train.ph_in: dataset.train.inputs,
                                    ipc.train.ph_tgt: dataset.train.targets})
                queue_size_train = sess.run([ipc.train.rsq_pre_size])[0]

Теперь, когда я переключился с обычного сеанса на использование MonitoredSession и добавил ловушку регистрации и сказал ему регистрировать тензор «точности», он тщетно пытался оценить первый вызов запуска сеанса, поскольку ловушка добавила этот тензор в список выборки, но пока очередь была пустой, оценить точность еще не было.
Проблема заключалась в том, что программа просто остановилась и ждала, пока какой-то процесс начнет заполнять очередь, но этого не было, поэтому она просто ничего не делала, но также не генерировала исключение и не выдавала никаких предупреждений, что помогало понять, что происходит. немного сложно.
В конечном итоге проблема была легко решена, просто выполнив одну операцию постановки в очередь в качестве самого первого вызова sessions.run () в блоке with MonitoredSession(...) as sess: , но было бы неплохо, если бы, например, входные очереди могли быть предварительно заполнены некоторыми начальными значениями. при создании, чтобы эта проблема не возникала.

Помимо этого, мы используем два разных конвейера ввода для данных обучения и проверки, которые мы подключаем к сетевой части нашего графа, чередуя переключение, реализованное через tf.QueueBase.from_list .
Теперь в блоке with ... as sess: вы можете легко реализовать блок if, какой конвейер выбрать на основе global_step % interval == 0 но это означает копирование, вставляя тот же код (вызовы sessions.run () и add_summary ()) раз количество используемых вами различных входных конвейеров (например, train, validation_1, validation_2, ...)
Было бы неплохо интегрировать это напрямую в MonitoredSession с помощью хуков (т.е. каждый 10-й шаг создает и сохраняет сводки с использованием входного конвейера validation_1, каждый 25-й шаг ...)

  1. Все, что помогает мне проанализировать, есть ли узкое место во входном конвейере и где именно, было бы здорово. Например, было бы полезно отслеживать количество примеров в буферах вдоль входного конвейера. Или количество элементов, обрабатываемых в потоке операции .map (). По сути, что-то вроде того, как очереди создают сводки для количества изображений, которые они хранят.
  2. Я думаю, что другие упоминали нечто подобное, но способ создания набора данных из потокового источника данных. Что-то, что обеспечивает аналогичную функциональность, что и функция подачи генератора в https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/estimator/inputs/queues/feeding_functions.py (но было бы здорово, если бы источник данные могут быть из произвольного источника, возможно, с использованием какой-то модели издатель / подписчик?)

Спасибо за тяжелую работу. Копаю новый API.

В последние пару дней я немного поигрался с новым API ввода, и я думаю, что классы Dataset и Iterator улучшают четкость и читаемость кода (по сравнению со старым вводом). очереди). Кроме того, переключение между, например, набором данных для обучения и проверки в рамках одного сеанса работает довольно легко.

Но у меня также есть вопросы и предложения.
Может быть, сначала вопрос: реализован ли класс Dataset на основе очередей? Потому что из сообщения здесь мне не ясно, если нет. В Tensorboard с новым API не добавляется дополнительная информация о статусе какой-либо очереди (сколько объектов в настоящее время поставлено в очередь). Также, наблюдая за моими ресурсами / рабочей нагрузкой процессора / графического процессора, я вижу, что рабочая нагрузка графического процессора часто падает до нуля (я думаю, между пакетами).

Тогда предложение:
Я думаю, что dataset.shuffle() можно улучшить, если перетасовка выполняется не только для образцов _n_ (= buffer_size) в памяти, но каким-то образом для всего списка входных данных. Например: я сохраняю путь к изображениям и меткам в текстовом файле. Если перемешивание еще не выполнено в текстовом файле, вы можете часто ставить тысячи строк одного и того же класса друг за другом. Если я теперь работаю только с dataset.shuffle() легко может случиться (в зависимости от buffer_size), что все элементы, которые перетасовываются, так или иначе принадлежат к одному классу. Единственный. Может быть, какой-нибудь игрушечный пример (игнорирование ярлыков и работа только с путями изображений), чтобы прояснить мою мысль. Для удобства чтения я работаю с очень маленьким buffer_size и списком имен файлов. Но я думаю, каждый может представить то же самое, только с тысячами имен файлов в списке и размером buffer_size, например, 5000.

import tensorflow as tf

image_paths = tf.constant(['train/class1/img1.png',
                          'train/class1/img2.png',
                          'train/class1/img3.png',
                          'train/class1/img4.png',
                          'train/class1/img5.png',
                          'train/class1/img6.png',
                          'train/class1/img7.png',
                          'train/class2/img1.png', 
                          'train/class2/img2.png',
                          'train/class2/img3.png',
                          'train/class2/img4.png',
                          'train/class2/img5.png',
                          'train/class1/img6.png',
                          'train/class1/img7.png'])

dataset = tf.contrib.data.Dataset.from_tensor_slices(image_paths)
dataset = dataset.shuffle(3)
iterator = dataset.make_one_shot_iterator()
next_element = iterator.get_next()

with tf.Session() as sess:
    while True:
        try:
            path = sess.run(next_element)
            print(path)
        except tf.errors.OutOfRangeError:
            break

Это напечатает что-то вроде:

'train/class1/img1.png'
'train/class1/img2.png'
'train/class1/img4.png'
'train/class1/img3.png'
'train/class1/img5.png'
'train/class1/img7.png'
'train/class2/img1.png'
'train/class1/img6.png'
'train/class2/img3.png'
'train/class2/img4.png'
'train/class1/img6.png'
'train/class2/img5.png'
'train/class1/img7.png'
'train/class2/img2.png'

Таким образом, поскольку в буфере происходит только перетасовка между тремя примерами, все первые выборки (то же самое для партий) будут иметь выборки только одного класса. Так что, если перетасовка еще не сделана в списке имен файлов, у вас возникнут проблемы с обучением любой сети. И если доступный набор данных изображений огромен, увеличение buffer_size часто не является решением.
Другая проблема, которую я вижу, заключается в том, что, как и в настоящее время, перетасовка всего набора данных невозможна. Единственный обходной путь, который я нашел, - это предварительная перетасовка списка файлов, который я прочитал из текстового файла, перед созданием набора данных. Но как только набор данных создан, его можно перемешать только в диапазоне buffer_size.

@mrry Спасибо за предварительный просмотр нового API; Думаю, это хорошая отправная точка!
Одна функция, которая все еще кажется отсутствующей, но была бы необходима для одного из наших основных вариантов использования (см. Комментарий выше: https://github.com/tensorflow/tensorflow/issues/7951#issuecomment-283186552), является

Dataset.map_to_multiple()
используется как в
dataset2 = dataset1.map_to_multiple(func)

функция, где один элемент dataset1 сопоставляется с одним или несколькими элементами dataset2 ; т.е. # dataset2 > = # dataset1 .
Насколько я понимаю, Dataset.map() сохраняет отображение 1: 1, чего недостаточно для нашего варианта использования.

Один конкретный пример того, почему это может быть полезно:
Предположим, что dataset1 - это список больших изображений (например, 8192x8192 каждое) [с соответствующими ярлыками]. Затем dataset2 создается (случайным образом) путем итерации по каждому элементу d1 из dataset1 , и для каждого из этих элементов функция func выбирает (переменную ) количество суб-изображений [и суб-меток] (например, 256x256 каждое) из d1 , взятых из различных регионов d1 . Например, в одном случае func может вернуть 142 новых пары {image, label}, которые будут добавлены к dataset2 . В другом случае он может вернуть 389 новых пар и т. Д. Количество элементов, генерируемых каждый раз, является переменным и зависит от свойств элемента d1 .

@kmhofmann Я думаю, вы можете сопоставить один пример с несколькими примерами с помощью Dataset.flat_map() . Внутри функции плоской карты создайте новый объект набора данных с одним или несколькими примерами для каждого примера ввода.

@EdeMeijer Это могло быть - действительно трудно сказать, поскольку документация довольно скудная и без примеров. (Кажется, есть два места, содержащих некоторое количество отдельной документации: либо на GitHub, либо на tensorflow.org )
Я заметил одну вещь: аргументы num_threads и output_buffer_size из map() отсутствуют в flat_map() ; Означает ли это, что параллельная обработка невозможна? Или это TODO? Трудно охватить как функциональность, так и набор функций ...

Я основал свое предложение на одном примере кода flat_map на странице github, которую вы связали. Там входят тензоры одной строки (имена файлов), а целые наборы данных испускаются в функции карты, так что это кажется довольно ясным. Я предполагаю, что параллельная обработка из map() - это TODO, я уверен, что им понравится пиар :)

Ах, спасибо, я это пропустил, как это было в примере с обработкой текста. Тем не менее, описание функции в документации кажется немного скудным, состоящим из Maps map_func across this dataset and flattens the result .

input_fn должен возвращать только функции и метки. Как насчет дополнительных параметров, которые можно использовать во время обучения, чтобы настроить потерю для заданного ввода?

Другой запрос функции: было бы здорово, если бы был оператор iterator.peek() , который возвращал бы текущее значение итератора (например, iterator.get_next() ), но не продвигал бы итератор. Это упростило бы координацию нескольких элементов модели, которые все хотят прочитать из итератора, прежде чем продвигать ее на один шаг.

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

Однако я не понимаю, как это сделать в этой новой парадигме. Например, я не вижу возможности «получить итератор» в середине набора данных. В качестве примера приведу фрагмент кода, демонстрирующий то, что я хотел бы сделать. Каждые несколько шагов в эпоху я хотел бы запускать операцию проверки, но выходные данные этого кода показывают, что итератор никогда не опережает элемент 0 в любом наборе данных. Как это сделать?

training_dataset = tf.contrib.data.Dataset.range(100)
validation_dataset = tf.contrib.data.Dataset.range(900,950)
iterator = tf.contrib.data.Iterator.from_structure(training_dataset.output_types,
                                   training_dataset.output_shapes)
next_element = iterator.get_next()

training_init_op = iterator.make_initializer(training_dataset)
validation_init_op = iterator.make_initializer(validation_dataset)

# Run 3 epochs with 10 steps each
for e in range(3):
  print("Epoch: %d" %e)
  for step in range(11):
    sess.run(training_init_op)
    ne = sess.run(next_element)
    print("Step: %d Training set: next_element: %d " %(step, ne))
    # Run validation every 5 steps only
    if step % 5 == 0:
      sess.run(validation_init_op)
      ne = sess.run(next_element)
      print("Step: %d Validation set: next_element: %d " %(step, ne))

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

@nirmalthacker Каждый раз, когда вы запускаете здесь . Этот подход имеет ограничения, о которых я говорил здесь .

@jasonkriss , понятно - спасибо! Это то, что у меня есть сейчас, и оно выполняет то, что я хотел. Вы это имели в виду?

training_dataset = tf.contrib.data.Dataset.range(100)
validation_dataset = tf.contrib.data.Dataset.range(900,950)

t_iterator = tf.contrib.data.Iterator.from_structure(training_dataset.output_types,
                                   training_dataset.output_shapes)
v_iterator = tf.contrib.data.Iterator.from_structure(validation_dataset.output_types,
                                   validation_dataset.output_shapes)
is_validating = tf.placeholder(dtype=bool,shape=())
next_element = tf.cond(is_validating, lambda:v_iterator.get_next(), lambda:t_iterator.get_next())

training_init_op = t_iterator.make_initializer(training_dataset)
validation_init_op = v_iterator.make_initializer(validation_dataset)

sess.run([training_init_op, validation_init_op])
# Run 3 epochs with 10 steps each
for e in range(3):
  print("Epoch: %d" %e)
  for step in range(11):
    ne = sess.run(next_element, feed_dict={is_validating: False})
    print("Step: %d Training set: next_element: %d " %(step, ne))
    if step % 5 == 0:
      ne = sess.run(next_element, feed_dict={is_validating: True})
      print("Step: %d Validation set: next_element: %d " %(step, ne))

@nirmalthacker Ага, по сути, я имел в виду именно это. Хотя на каждом этапе проверки я буду повторно запускать итератор проверки и проходить через полный набор данных проверки. Но в этом вся суть.

Я пытаюсь перенести конвейер ввода с tf.train.string_input_producer и tf.train.shuffle_batch на API набора данных. Параметр «allow_smaller_final_batch» в tf.train.shuffle_batch (...) полезен, когда я хочу убедиться, что все партии равномерно делятся на количество графических процессоров. (Я делаю распараллеливание данных на нескольких графических процессорах, а размер batch_size кратен num_gpus). Есть ли какие-либо настройки для API набора данных, чтобы удалить последнюю меньшую партию, если таковая имеется?

@ winston-li Думаю, для этого можно было бы просто использовать .filter() . Если вы знаете, что размер вашей партии, например, 32, тогда что-то вроде

dataset = dataset.filter(lambda batch: tf.shape(batch)[0] == 32)

должен сделать свое дело.

@ppwwyyxx Я считаю, что пользовательский конвейер может быть разработан с использованием TF-сервера с распределенной средой выполнения, то есть отправки / получения IPC. Он включен в C API, поэтому его не составит труда перенести на другие языки. Обратной стороной является то, что вам нужно упаковать всю среду выполнения TF везде, где вам нужен этот конвейер.

На самом деле мы работаем над внеполосным уровнем данных для TF, но это еще большая текущая работа. Дизайн будет аналогичен ExternalShuffleService в Spark, с использованием хранилища в памяти, такого как LevelDB или LMDB, и клиента чтения в TF. Если производительность является основной проблемой, она должна быть тесно интегрирована с оборудованием, например, с GPU / NVMe / RNIC и т. Д.

@EdeMeijer Спасибо. Я думал, что он должен работать, но после некоторых экспериментов я не могу заставить его работать должным образом. Я следовал рекомендациям набора данных README.md с псевдокодом, подобным следующему:

def _parse_function(example_proto):
    features = {"image": tf.FixedLenFeature((), tf.string, default_value=""),
                "label": tf.FixedLenFeature((), tf.int32, default_value=0)}
    parsed_features = tf.parse_single_example(example_proto, features)
    return parsed_features["image"], parsed_features["label"]

BATCH_SIZE = 256
filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]
dataset = tf.contrib.data.TFRecordDataset(filenames)
dataset = dataset.map(_parse_function)
dataset = dataset.batch(BATCH_SIZE)
dataset = dataset.filter(lambda imgs, lbls: tf.shape(imgs)[0] == BATCH_SIZE)
iterator = dataset.make_initializable_iterator()
next_element = iterator.get_next()
images, labels = next_element

# Training cycles for 100 epochs.
for _ in range(100):
    sess.run(iterator.initializer)
    while True:
        try:
            images_r, labels_r = sess.run([images, labels])
            print(images_r.shape)
        except tf.errors.OutOfRangeError:
            break

После применения фильтра данные в циклах обучения отсутствуют. Я обнаружил, что набор данных (после партии, до фильтрации) был в такой форме:

(<tf.Tensor 'arg0:0' shape=(?, 43200) dtype=float32>, <tf.Tensor 'arg1:0' shape=(?, 36) dtype=float32>)

Похоже, размер партии равен "?" (Нет?), Значит, предикат всегда не работает ... или я что-то сделал не так?

@ winston-li видит "?" as shape - это потому, что в этот момент вы смотрите на «статическую» форму тензора, которая не всегда определяется (график не знает заранее, сколько примеров будет). Однако tf.shape() оценивает динамическую форму тензора в реальном времени, поэтому я подумал, что это должно сработать.

В любом случае, я попытался создать минимальный пример, но у меня возникают внутренние ошибки ядра, когда мой фильтр исключает какой-либо элемент, поэтому для начала я открыл https://github.com/tensorflow/tensorflow/issues/10725. Однако то , что я нашел в том , что, очевидно, мы должны использовать Tensorflow оп вместо стандартных сравнений , так как результат tf.shape() является тензором , а не обычный массив.

Фильтр моей версии tf не работает, но если у вас работает, можете ли вы попробовать это вместо этого?

dataset = dataset.filter(lambda imgs, lbls: tf.equal(tf.shape(imgs)[0], BATCH_SIZE))

@EdeMeijer Большое спасибо, в моем случае это работает :-)

Для данных последовательностей обычно используются пакетные последовательности одинаковой (или аналогичной) длины. Обычно это достигается простой сортировкой набора данных по длине последовательности. Однако это не представляется возможным с текущим API (если да, дайте мне знать, как это сделать).

Было бы неплохо увидеть что-нибудь в этом аромате:

dataset = dataset.sort(lambda a, b: tf.shape(a)[0] < tf.shape(b)[0], buffer_size=10000)

который будет сортировать элементы по 10000 (аналогично dataset.shuffle ) с использованием заданной функции сравнения.

Я хотел бы спросить еще раз (поскольку на мой комментарий выше не было ответа: я прав, что новый конвейер ввода не реализован с использованием очередей? Я провел несколько тестов с использованием нового конвейера ввода для загрузки изображений предварительной обработки, но он Кажется, все выполняется последовательно, и это лишь незначительное улучшение производительности по сравнению с использованием, например, OpenCV для загрузки и предварительной обработки изображений. Я надеялся, что новый конвейер ввода будет построен поверх очередей, поскольку они обеспечивают значительное повышение производительности, но затрудняют работать (например, иметь отдельные входные конвейеры с очередями для переключения между обучающими и проверочными наборами данных). Это довольно просто с новым API, но похоже, что реального повышения производительности нет. Кто-нибудь наблюдал то же самое или противоположное?

@kratzert У меня тоже были проблемы со 100% использованием графических процессоров и их сохранением. API набора данных, похоже, реализован менее эффективно, и хотя это долгожданное изменение в отношении ясности и простоты кода, а также более естественного способа обучения и проверки, он не может (пока) заменять очереди для сценариев использования с высокой скоростью передачи данных, таких как как компьютерное зрение.

@kratzert Вы правы. У нового входного конвейера нет очередей. Если вас не устраивает перетасовка, которую он предоставляет, вы можете сделать это вне TF: вы можете загрузить все имена файлов в память, а затем перетасовать их любым способом.

@snnn Да, я знаю и делаю именно это. Но, делая это, я не могу найти способ перетасовать все данные, например, в моих тренировочных данных каждую эпоху. Я могу перетасовать, например, список имен файлов перед созданием набора данных, но как только я начинаю сеанс, насколько мне известно, я могу только перетасовать данные из набора данных, который у меня есть в памяти, используя dataset.shuffle (buffer_size). Но с изображениями это вряд ли можно сделать для всего набора данных в памяти. И я не могу снова перетасовать имена файлов и создать из них новый набор данных, находясь внутри сеанса, или я ошибаюсь?

@vvekic Спасибо за ваш ответ, поэтому я знаю, что не только у меня проблемы с производительностью. Конечно, ясность кода и простота работы с новым классом набора данных - огромный шаг вперед и очень желанный. Но похоже, что для обучения сетей компьютерного зрения очереди все еще подходят (к сожалению), поскольку прирост производительности огромен.

@kratzert Использование очередей увеличивает вашу производительность за счет перекрытия задержки загрузки данных, и это не зависит от того, что вы используете для загрузки данных. Вы всегда можете вставлять очереди или StagingArea во входной конвейер, независимо от того, выполняется ли фактическая загрузка данных с помощью API набора данных, старых операторов ввода или Python.

@byronyi Я имел в виду получение тензоров от процессов, не относящихся к TF. Поскольку, как указал @PatWie выше, обработка данных на самом деле не должна происходить на графике.

@ppwwyyxx есть ли у вас пример кода для объединения очередей и нового api набора данных? Звучит потрясающе, и я обязательно попробую это позже.

@snnn Хорошо, я разберусь с этим, но, поскольку мои познания в C ++ не столь глубоки, мы увидим, насколько я буду успешен. В любом случае, я думаю, что это может быть функция, которая может быть интересна большему количеству людей, чем я, и должна / могла бы быть интегрирована в master.

@kratzert https://www.tensorflow.org/performance/performance_models и связанный код показывает, как использовать StagingArea.

@kratzert, конечно, можно перетасовать имена файлов для каждой эпохи, я делаю именно это для собственного обучения. Для этого вы можете использовать инициализируемый итератор вместе с заполнителем. Я делаю примерно так:

filenames_op = tf.placeholder(tf.string, shape=[None])
dataset = tf.contrib.data.TFRecordDataset(filenames)
iterator = dataset.make_initializable_iterator()
next_elem = iterator.get_next()

filenames = ['file1', 'file2', 'file3']

# For every epoch, shuffle the file names and initialize the iterator
random.shuffle(filenames)
sess.run(iterator.initializer, {filenames_op: filenames})

@EdeMeijer Это умно. Большое тебе спасибо. Я должен был прийти к этому самостоятельно! Вот полный фрагмент рабочего кода для всех, кого это интересует:

import tensorflow as tf
import numpy as np

num_epochs = 2

image_paths = ['train/class1/img1.png',
               'train/class1/img2.png',
               'train/class1/img3.png',
               'train/class1/img4.png',
               'train/class1/img5.png',
               'train/class1/img6.png',
               'train/class1/img7.png',
               'train/class2/img1.png', 
               'train/class2/img2.png',
               'train/class2/img3.png',
               'train/class2/img4.png',
               'train/class2/img5.png',
               'train/class1/img6.png',
               'train/class1/img7.png']

num_samples = len(image_paths)

filenames_op = tf.placeholder(tf.string, shape=[None])

dataset = tf.contrib.data.Dataset.from_tensor_slices(filenames_op)

iterator = dataset.make_initializable_iterator()
next_element = iterator.get_next()

with tf.Session() as sess:

    for epoch in range(num_epochs):
        print("Starting epoch %i" % (epoch))
        np.random.shuffle(image_paths)
        sess.run(iterator.initializer, {filenames_op: image_paths})

        for i in range(num_samples):
            path = sess.run(next_element)
            print(path)

Это перетасовывает по желанию в каждую эпоху весь набор данных и дает результат, например, такой:

Starting epoch 0
'train/class2/img4.png'
'train/class2/img1.png'
'train/class1/img2.png'
'train/class1/img7.png'
'train/class1/img1.png'
'train/class1/img6.png'
'train/class1/img6.png'
'train/class1/img5.png'
'train/class1/img4.png'
'train/class1/img7.png'
'train/class1/img3.png'
'train/class2/img2.png'
'train/class2/img3.png'
'train/class2/img5.png'
Starting epoch 1
'train/class1/img7.png'
'train/class1/img4.png'
'train/class2/img1.png'
'train/class1/img6.png'
'train/class1/img2.png'
'train/class1/img3.png'
'train/class1/img6.png'
'train/class2/img5.png'
'train/class2/img2.png'
'train/class1/img1.png'
'train/class2/img3.png'
'train/class2/img4.png'
'train/class1/img5.png'
'train/class1/img7.png'

В настоящее время в руководстве говорится, что мы можем использовать

dataset = dataset.repeat()
dataset = dataset.shuffle(buffer_size=10000)

чтобы перетасовать данные. Шаблон также используется в tf.contrib.data.read_batch_features

Однако вызов repeat перед shuffle может привести к перемешиванию нескольких эпох.
Например, следующий код

import tensorflow as tf

data = tf.contrib.data

dataset = data.Dataset.from_tensor_slices(['file_0', 'file_1'])

repeat_count = 5
shuffle_buffer_size = 100

repeat_then_shuffle = dataset.repeat(count=repeat_count)
repeat_then_shuffle = repeat_then_shuffle.shuffle(
    buffer_size=shuffle_buffer_size)
repeat_then_shuffle_iter = repeat_then_shuffle.make_one_shot_iterator()
get_next = repeat_then_shuffle_iter.get_next()

with tf.Session() as sess:
    result = []
    try:
        while True:
            result.append(sess.run(get_next))
    except tf.errors.OutOfRangeError:
        pass
    print(result) # [b'file_0', b'file_0', b'file_0', b'file_1', b'file_0', b'file_1', b'file_0', b'file_1', b'file_1', b'file_1']

получает 3 file_0 перед получением file_1 .

Есть ли опасения по поводу звонка на shuffle до repeat ?

Просто хочу , чтобы добавить что - то здесь, я реализовала данные многопроцессных на https://hanxiao.github.io/2017/07/07/Get-10x-Speedup-in-Tensorflow-Multi-Task-Learning-using-Python-Multiprocessing/

Это уже было сделано в TensorPack некоторое время @ppwwyyxx. Там вы также получите дальнейшее ускорение с помощью ZMQ - плюс у него хороший интерфейс с использованием генераторов Python. Для меня способ обработки входных данных tenorpack - самый элегантный. Я надеюсь увидеть что-то подобное в будущем TF.

@PatWie, спасибо, что указали на это! Я только что быстро проверил репозиторий @ppwwyyxx, действительно

Было бы здорово иметь резидентные очереди GPU.

Было бы здорово иметь резидентные очереди GPU.

@xieqihuiPG См. StagingArea и MapStagingArea

Был бы очень признателен:

  1. Эффективная случайная выборка:
    Dataset.sample_random()
  2. Методы динамического изменения и изменения размера:
    Dataset.update(), Dataset.pop() и т. Д., Например, для создания буферов потоковой передачи, объектов памяти воспроизведения ...
  3. Интеграция мета- и описательной статистики в объект набора данных и вспомогательные методы, такие как Dataset.describe()
  4. В любом случае более тесная интеграция с HDF5

11591 Нам нужна эффективная выборка / перемешивание для больших наборов данных

А как насчет поддержки пользовательских операций для создания набора данных? Например, предположим, что у меня есть функция Python, которая возвращает новый пакет при каждом вызове (генератор). Я хочу обернуть эту функцию, используя tf.py_func и использовать ее для создания Dataset . Кажется, это не поддерживается?

В настоящее время я использую этот метод с tf.train.*batch* ops, и он отлично работает, но я хотел бы найти способ сделать это и для оценки (и решил, что, возможно, Dataset - хороший способ сделать это с помощью "реинициализации" итератор).

@mrry Это отличная работа и определенно очень полезная для создания хороших обучающих API поверх TensorFlow. Однако у меня есть пара основных проблем:

  • В настоящее время я не вижу способа «распаковать» набор данных. Допустим, у нас есть обучаемая модель, в которой есть и метод train / fit метод infer / predict . Назовем тип (потенциально) вложенной структуры входных данных в нашу модель I и тип обучающих входных данных, которые необходимы только при обучении (например, контрольные метки), TI . В этом случае мы хотим, чтобы метод train принимал наборы данных с элементами типа (I, TI) (т.е. кортеж из I и TI ) и predict метод для приема наборов данных с элементами типа I или (I, TI) (в этом случае метки игнорируются). Мы также хотим, чтобы у модели был только один базовый граф, поддерживающий все эти типы входных данных. Как я мог видеть, это было сделано для базовой модели, чтобы построить два итератора (один с типом элементов I и один с типом TI ) и инициализировать их в соответствии с предоставленными наборами данных. Однако, если кто-то предоставляет набор данных с элементами типа (I, TI) train методу Dataset.map , что неэффективно (я думаю, но, пожалуйста, поправьте меня, если я ошибаюсь), и что также может не извлекать совпадающие элементы из наборов данных (если каждое извлечение продвигает текущий индекс в оригинальный первый набор данных - я не уверен, что это произойдет).
  • Было бы неплохо поддерживать итераторы над тензорами, определенными на других языках, как упоминалось в @sirfz . Я не вижу эффективного способа сделать это с текущим API. Пожалуйста, поправьте меня, если я ошибаюсь, но в настоящее время нужно создавать новый TensorDataset каждой партии и повторно инициализировать существующий итератор.
    Я думаю, что @fchollet может прокомментировать мой первый пункт, поскольку в настоящее время я понимаю, что они думают или создают совершенно новый график только для обучения в таких случаях (третий шаг описан здесь ).

Кроме того, если мое описание ужасно непонятно, дайте мне знать, и я постараюсь уточнить.

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

Таким образом, мы использовали старый интерфейс FIFOQueue следующим образом (псевдокод):

# Set up queues
kwargs = {'capacity': ..., 'dtypes': ..., 'names': ..., 'shapes': ...}
train_queue = tf.FIFOQueue(**kwargs)
valid_queue = tf.FIFOQueue(**kwargs)
queue_index = tf.Variable(0, trainable=False)
queue = tf.QueueBase.from_list(queue_index, [train_queue, valid_queue])
batch_size = ...
batch = queue.dequeue_many(batch_size)

# Build model
output = build_model(batch['X'])
loss = evaluate_loss(output, batch['y'])

# Fill queues
train_data_stream = some_iterable_for_training_data()
validation_data_stream = some_iterable_for_training_data()
start_filling_queues_in_background_thread(train_queue, train_data_stream)
start_filling_queues_in_background_thread(validation_queue, validation_data_stream)

Наличие двух разных очередей с from_list позволяет нам переключаться между очередью обучения и проверки, либо устанавливая queue_index либо загружая его в feed_dict .

some_interable_for_xxx_data обычно являются генераторами, которые получают данные от группы рабочих, сидящих за балансировщиком нагрузки (например, используя ZeroMQ, RabbitMQ или PubSub). Этот подход работает хорошо (потому что очереди предоставляют буфер), но у нас нет никакого способа узнать, когда итератор исчерпан. Некоторые обходные пути

  • закрытие очереди в фоновом потоке так, что tf.errors.OutOfRangeError поднимается, когда очередь исчерпана (но тогда мы не можем снова открыть ее снова # 4535)
  • установка тайм-аута для session.run обучающей операции и предположение, что тайм-аут связан с исчерпанием очереди (но сетевое соединение может быть отключено или наши рабочие могут быть слишком медленными)
  • подсчет количества элементов, которые мы обработали, и сравнение с ожидаемым количеством элементов в итераторе (но это утомительно, и иногда мы даже не знаем, как долго у итератора)
  • добавление поля exhausted в очередь names и разрешение фоновому потоку поставить элемент в очередь с exhausted=True вместе с утверждением об операции удаления из очереди (но с использованием dequeue_many удалит элементы из следующей эпохи, если количество элементов в эпоху не является целым числом, кратным размеру пакета, см. также # 2514)

Ни один из них не является удовлетворительным, и было бы здорово увидеть либо возможность построить Dataset s из итератора python с очередью для буферизации, либо исправить # 4535 (что автоматически исправит # 2514).

С нетерпением жду ответа, правильно ли мы используем API наборов данных.

Думаю, очереди достаточно хороши. Я бы хотел, чтобы две вещи улучшились:

Более простой способ ввода данных из собственного Python, кроме использования заполнителей и управления потоками.

Может быть, класс InputQueue(delegate, fn, n_filler_threads) который принимает делегат очереди тензорного потока и функцию Python fn . fn возвращает (возможно, вложенный) кортеж np.array или списков. InputQueue запускает n_filler_threads который вызывает fn и помещает их в delegate . Потоки - это демоны, поэтому они отключаются, когда это делает основной процесс.

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

Сейчас я использую новый api Dataset . Но все же проблема заключается в том, как динамически передавать данные в набор данных. Есть два похожих вопроса здесь и здесь @albertz.

Как видите, реальные проблемы - это не просто ввод в серию изображений или текстов. Так что я был бы очень признателен, если бы вы позволили мне свободно передавать данные с точки зрения того, когда и как .

Я могу представить два варианта. Один из них - эффективное распределенное чтение через feed_dict . Хотя это и медленно, но с многопроцессорной обработкой, это всего лишь вопрос машины. Другой - обернуть какую-то зрелую и широко принятую реализацию.

используйте заполнитель в качестве входных данных в очередь, и модель считывает входные данные из очереди, а затем использует поток выполнения сеанса для подачи входных данных (возможно, созданных с помощью hadoop mapreduce) в очередь. используя промежуточную область, вы даже можете скрыть всю предварительную обработку и время ввода.

Я пытаюсь проверить пример в документе

Но похоже, что этот вызов передает только 1 аргумент функции:

набор данных = dataset.map (_parse_function)

Вместо этого функция определяется с двумя параметрами

@eaplatanios один из соответствующих PR для zip / unzip - https://github.com/tensorflow/tensorflow/issues/10837

@mrry Вы тестировали этот раздел документации с помощью python3?

@bhack Мне не удалось заставить его работать с более чем одним параметром в ответ на функцию, переданную py_func. Я использую python3 и не пробовал с python2.
Ваша проблема похожа?

@AMairesse Первая проблема была решена с помощью https://github.com/tensorflow/tensorflow/commit/2139e7d8b10764f2245f34548f6fbfc25d29bff8

@bhack Спасибо, скоро попробую, я использовал обходной путь, которым не горжусь :-)
Исправление в документации сделано один месяц назад, и до выпуска v1.3 веб-сайт tensorflow.org не обновлялся при выходе нового выпуска? В официальном документе нет исправления

@AMairesse Предлагаю вам сообщить об этом в https://github.com/tensorflow/tensorflow/issues/11786

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

И набор данных не стабильно инициализирует переменную, определенную в функции карты как https://github.com/tensorflow/tensorflow/issues/12648

Я хотел бы еще раз поднять предыдущий вопрос @kratzert , связанный с

@ppwwyyxx заявил, что очереди и StagingArea все еще можно использовать с API набора данных, но я до сих пор не видел рабочего примера этого. У нас есть такой?

Какой цели служит новый API, если все еще нужно включать очереди, data_flow_ops или сложности StagingArea?

@vvekic , я немного поэкспериментировал с очередями и Dataset API после того, как с ужасом осознал, что из 0,8 с / шаг в моем цикле вывода 0,2 с - это выборка данных (при использовании графического процессора 0%), увеличиваясь почти до 2 секунд, если Жесткий диск одновременно используется чем-то другим.
Мой пайплайн выглядит следующим образом:

  def preprocess_image(fn):
    im_s = tf.read_file(fn)
    im = tf.image.decode_jpeg(im_s, channels=3)
    im = inception_preprocessing.preprocess_for_eval(im, width=299, height=299)
    return fn, im

  dataset = tf.contrib.data.Dataset.list_files('{}/*/*.jpg'.format(FLAGS.dataset_dir))
  dataset.map(preprocess_image, num_threads=FLAGS.num_threads)
  iterator = dataset.make_one_shot_iterator()
  input_queue = tf.FIFOQueue(capacity=100*FLAGS.batch_size,
                             dtypes = iterator.output_types,
                             shapes=iterator.output_shapes)
  enqueue_sample = input_queue.enqueue(iterator.get_next())
  tf.train.add_queue_runner(tf.train.QueueRunner(input_queue, [enqueue_sample]*FLAGS.num_threads))

  filenames, images = input_queue.dequeue_up_to(FLAGS.batch_size)

Мне все еще нужно запустить это на большом наборе данных и проверить, есть ли улучшение производительности, но, по крайней мере, похоже, что он выполняется правильно. Загвоздка в том, что я не смог найти способ перебрать данные более одного раза (что, к счастью, не является моим вариантом использования), потому что единственный итератор, который не вызовет ошибку, когда QueueRunner s порождение потоков - это one_shot_iterator .

Не знаю, здесь ли я, но у меня есть вопрос об API набора данных. Мой набор данных содержит один столбец с последовательностями и один с длиной последовательности, которую я хочу обрабатывать по-разному, потому что я хочу дополнить последовательности. Можно ли адресовать один столбец в наборе данных, чтобы он обрабатывался иначе, чем другой столбец? Например:

two_column_dataset = ... # This contains the column sequence and sequence length
first_column_dataset = two_column_dataset[0].padded_batch(64, ...) # Pad only first column
second_column_dataset = two_column_dataset[1].batch(64) # Get corresponding sequence length for sequences
two_column_dataset = Dataset.zip((first_column_dataset, second_column_dataset))

Изменить: написав это, я узнал:

def flat_map_func(sequence, sequence_length):
    first_column_dataset = Dataset.from_tensors(sequence).padded_batch(64, ...)
    second_column_dataset = Dataset.from_tensors(sequence_length).padded_batch(64)
    zipped_dataset = Dataset.zip((first_column_dataset, second_column_dataset))
    return zipped_dataset

two_column_dataset = two_column_dataset.flat_map(flat_map_func)

Эта тема становится немного громоздкой, и становится все труднее отслеживать отдельные обсуждения, поэтому я собираюсь заблокировать ее после ответа на несколько недавних комментариев. Не стесняйтесь открывать новый выпуск о любых конкретных темах запросов функций, связанных с tf.contrib.data и мы можем продолжить обсуждение там.

В ответ на несколько недавних вопросов:

  • @GPhilo ( ссылка ) и @kratzert ( ссылка ): API набора данных включает методы для предварительной выборки, поэтому нет необходимости добавлять здесь очередь, и вы можете сохранить другие преимущества наборов данных (например, повторная инициализация и т. Д.). Передача output_buffer_size=100 * FLAGS.batch_size вызову dataset.map() , а затем dataset.batch(FLAGS.batch_size) запустит вашу функцию preprocess_image параллельно и должна прилично повысить производительность.

    dataset = tf.contrib.data.Dataset.list_files('{}/*/*.jpg'.format(FLAGS.dataset_dir))
    dataset = dataset.map(preprocess_image, num_threads=FLAGS.num_threads,
                        output_buffer_size=100*FLAGS.batch_size)
    dataset = datsaet.batch(FLAGS.batch_size)
    iterator = dataset.make_one_shot_iterator()
    filenames, images = iterator.get_next()
    

    Обратите внимание, что в TF 1.4 будет метод Dataset.prefetch() который упрощает добавление предварительной выборки в любой точке конвейера, а не только после map() . (Вы можете попробовать это, загрузив текущую ночную сборку.)

    В ответ на конкретный вопрос @kratzert о реализации, классы Dataset и Iterator не используют предыдущие очереди производителей / потребителей TensorFlow (например, tf.FIFOQueue или tf.RandomShuffleQueue ), но они включают более простые (и более эффективные) реализации основных идей. Например, Dataset.prefetch() запустит фоновый поток для заполнения упорядоченного буфера, который действует как tf.FIFOQueue , так что этапы нижнего конвейера не должны блокироваться. Однако реализация prefetch() намного проще, потому что ей не нужно поддерживать столько различных одновременных операций, сколько tf.FIFOQueue .

  • @vvekic ( ссылка ): мне было бы любопытно увидеть ваш код до и после попытки использования Dataset API, и, возможно, вы могли бы продолжить, открыв проблему, описывающую узкое место производительности. По сравнению с кормлением или конвейером на основе очередей (не StagingArea ) новый API должен быть более эффективным, и мне было бы любопытно узнать, какие части нет!

    В настоящее время вы правы, что функциональность StagingArea не включена в Dataset API, и для максимальной производительности при рабочих нагрузках графического процессора вам потребуется добавить промежуточную область вручную. Однако мы активно работаем над реализацией наборов данных, которые могут охватывать устройства (некоторые из незавершенных работ см. В 19a55725af8102d72d4e081c5139f0e4bd5a4bb7), и одним из первых вариантов использования этого является поддержка предварительной выборки в память графического процессора.

  • @tengerye ( ссылка ): для динамической передачи данных в набор данных я предлагаю вам попробовать метод Dataset.from_generator() который мы добавляем в TF 1.4 (и который уже доступен в ночных сборках). Я ответил на вопрос @albertz о переполнении стека об этом здесь . (Поддержка распределенных трубопроводов будет зависеть от перекрестного устройства поддержки Dataset , что я говорил в предыдущем ответе, и мы будем реализация , что скоро.) Я думаю , что это также будет работать для @rasmusbergpalm «s запроса , потому что вы можете создавать параллельные генераторы , и «ы @tillahoffmann запроса и @sirfz» s запрос , а также. Однако этот API очень новый, поэтому, если у вас есть какие-либо отзывы, дайте нам знать!

  • @jasonkriss ( ссылка ) Мы реализовали так называемые «питаемые» итераторы, которые позволяют переключать входные данные для одного графа между несколькими итераторами (например, один для обучения и один для тестирования). В руководстве программиста есть более подробная информация о том, как использовать эту функцию.

  • @guillaumekln ( ссылка ) Если вы хотите группировать последовательности разной длины, вы можете использовать преобразование Dataset.group_by_window() . Посмотрите, как это используется в коде модели NMT для примера.

Еще раз спасибо всем за ваш постоянный интерес к этой части TensorFlow!

Была ли эта страница полезной?
0 / 5 - 0 рейтинги