Tensorflow: [增强] 重新设计 TensorFlow 的输入管道

创建于 2017-02-28  ·  134评论  ·  资料来源: tensorflow/tensorflow

[ TL;DR:我们正在为 TensorFlow 设计一个新的输入管道 API,我们希望收集您对这个问题的功能请求。]

我们注意到,开始使用 TensorFlow 的最大挑战之一是如何将您自己的数据加载到您的程序中。 虽然 TensorFlow 有多种方法可用于构建复杂的输入管道(例如tf.train.string_input_producer()tf.train.batch()等),但它们是为特定用例设计的(处理一组静态文件重复),并且这些方法的平均用户体验并不好。 例如:

  • 一旦到达管道的末端,它就会关闭,并且您永远无法在同一个会话中再次使用它。 这需要用户使用不自然的变通方法——通过控制流或多个会话——在处理整个 epoch 后获取信号,或者在同一程序中处理两个数据集(例如训练和验证数据)之间切换。

    • 有关处理多个时期的功能请求,请参阅 #2514 和 #4535。

    • 有关在同一程序中处理不同数据集的示例,请参阅 #7902 和许多 Stack Overflow 问题。

  • 当前的管道使用 TensorFlow 队列和多个 Python 线程,这会导致性能不佳(队列中的锁争用和 Python GIL)和难以理解的异常 ( tf.errors.OutOfRangeError )。

    • 有关输入管道性能的讨论,请参阅 #6845。

    • 有关令人困惑的错误示例,请参阅 #7525 和更多堆栈溢出问题

  • 如果您忘记调用tf.train.start_queue_runners(sess) ,管道的行为就会很糟糕:事实上,它们会无限期地挂起并使用户程序死锁。

我们决定从头开始,重新设计输入管道 API。 现有的方法将保留到 TF 2.0(至少),但我们计划添加一组新的方法来加载和操作数据集。 我们仍在准备详细设计,我们计划很快分享,但我们预计会有两个新的 API:

  • Dataset表示数据元素的集合。 每个元素可以是一个或多个张量的元组(例如图像及其标签)。 我们将提供从张量创建数据集和从另一个数据集导出它们的方法(例如,通过切片其元素、重复其元素、混洗其元素、批处理其元素、将函数映射到其元素上等)。
  • 一个Iterator可以从创建Dataset 。 迭代器表示数据集中的当前位置,并公开可以运行以获取下一个元素的操作(如tf.QueueBase.dequeue() )。 初始化迭代器将有显式操作,以便在处理完数据集中的所有元素后可以重用它。

类似的模式出现在许多不同的设置中,包括Java 的 Stream APIScala 的集合(以及 Spark 的 RDD)和.NET 的 Language Integrated Query

我们提前宣布此计划是因为我们希望收集有关您(作为 TensorFlow 用户)希望在输入管道 API 中看到哪些功能的反馈。 我们还遗漏了哪些痛点? 您错过了其他系统的哪些功能? 你还有什么建议?

我们期待您的回音!

feature

最有用的评论

就我个人而言,我非常喜欢将数据输入图中的feed_dict方法。 它是迄今为止最灵活的,使调试方式更容易,并使代码更简单。 因此,我最大的愿望是使该方法的性能更高。 现在,这种方法一直让我的 GPU 处于饥饿状态,这是一种耻辱,因为大多数其他 DL 框架(甚至那些基于计算图的框架)都设法提高了它的性能。 我认为在后台进行的复制/处理比必要的要多。

所有134条评论

我们的用例之一的必备条件是通过回调函数临时创建数据元素(它动态创建张量,例如使用 py_func() 或通过一些其他方式)。

更具体地说,我们目前有一个用例,我们使用两个队列; 一个外部队列,使用 string_input_producer(带混洗),其中每个字符串表示/指向一个“数据集”,然后通过从每个“数据集”生成可变数量的样本来生成内部队列。 每个时期生成的样本数量和样本数量不同(可能取决于过去的训练行为)。 实际上,我们甚至不再使用纪元的命名法,因为相同的数据从未见过两次,并且上述生成/采样超出了通常的数据增强。

长话短说:在一个稍微不同寻常的用例中,我们遇到了您上面提到的几乎所有问题,而且我们的解决方法并不完美。 我们非常高兴看到一种非常灵活的机制,在这种机制中支持这种情况,并且数据生成不必硬塞进像纪元、有限重复队列等强制概念中(尽管它们可以通过以下方式建模)它的原语)。
我不确定计划中的数据集/迭代器 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 次:训练 k iter/epoch,验证模型,重复]。 像你说的队列有明显的问题。 我没有提供解决方案的一个小问题是,虽然调度(验证前要训练多长时间)可能是通过模型类的某种方法完成的,我希望与数据集无关,但讨论是否有意义iter 或 epoch 的术语由数据集决定——破坏了一些独立性。

我在对自己的班级进行头脑风暴时记下的其他一些想法:

  • 数据集类(而不是模型)应该是将 batch_size 传递给它的类。 在拟合期间和数据集/队列创建期间要求将 batch_size 作为参数是很尴尬的,理想情况下,计算图没有包含 batch_size。
  • “详细”数据集类应该跟踪它自己的统计信息。 它应该维护自己的计数器(张量)来跟踪迭代、样本和时代。 在大多数用例中,我想在恢复模型参数的同时恢复这些。
  • 最重要的是,我们需要解决出队开销。 我见过几十种情况(在分析器中;iirc MEMCpyWhatever)真的很慢。 这主要是 GPU 从 CPU 获取数据的问题。
  • 如果仍然有一种方法可以获得来自多线程或多处理 python
    session.run(enqueue_op, feed_dict=$some_numpy_batch_input)在哪里可以从 python 异步馈送队列。
  • 拥有GPU 常驻队列也太棒了。

好点子。 一件事是,目前队列操作是在计算图中“烘焙”的,因此很难随时随地修改任何内容。 在不考虑使用控制流或其他黑客的情况下,更高的抽象可以使它更容易。

对于我的很多用例,我的输入数据要么是 1. 不在文件系统上,要么是 2. 需要 TensorFlow 中不可用的复杂预处理。 对于这两种情况,现有的输入管道都无济于事,所以我使用了一个带有 enqueue/feed_dict 的输入线程 + 一个带有很多 dequeue 的训练线程。

假设在大多数情况下,您不需要使用模型本身来生成数据(尽管有时并非如此)。 然后我非常喜欢看到的一个解决方案是能够从不同的进程接收(类似于出队)张量。 (如#4836)
好处是:

  1. 可以使用任何工具/语言从任何来源生成数据,只要它们最终使用特定的消息协议发送即可。
  2. (理论上)在训练过程中不需要额外的 python 线程。
  3. 如果消息协议支持 pub/sub,那么 (1) 多个训练会话可以订阅和重用相同的输入数据,这在尝试新模型时非常有用。 (2)如果预处理对于单个CPU来说太重,可以从不同的机器生成数据。

这些是我一直在使用的私人系统中真正错过的功能。
一个缺点是 IPC/socket 的带宽比 RAM 小,但通常不是瓶颈。
我知道这个功能可能离我们太远了,但我希望新的设计可以允许这种可能的未来功能。

@mrry一个“数据集”可以由大约 500-30,000 个动态生成的样本之间的任何内容组成。 目前,我们不会在每个数据集的末尾执行特定操作,即所有内容都放入相同的(大)随机洗牌队列,以在数据集之间混合样本。 但我也可以想象集合分离可能有帮助的情况。

请支持直接读取hdf5文件。

就我个人而言,我非常喜欢将数据输入图中的feed_dict方法。 它是迄今为止最灵活的,使调试方式更容易,并使代码更简单。 因此,我最大的愿望是使该方法的性能更高。 现在,这种方法一直让我的 GPU 处于饥饿状态,这是一种耻辱,因为大多数其他 DL 框架(甚至那些基于计算图的框架)都设法提高了它的性能。 我认为在后台进行的复制/处理比必要的要多。

我很高兴看到这个倡议。 输入管道绝对是
学习曲线中最陡峭的部分。

我想要:

  • 一个统一的 API 来管理内存中 ( feed_dict ) 数据集和大型数据集,以便相同的代码规模和您的模型只需要与一个 API 对话。 虽然我还没有使用它,但我喜欢我在输入管道文档中读到的内容。
  • 更多迭代器! 他们都是伟大的。 异步迭代器会更好(参见PEP492 )。 实现__len__迭代器非常适合进度报告。
  • 多处理而不是线程
  • 请不要使用Dataset类,因为恕我直言,“数据集”概念定义不明确。 原帖中描述的Dataset类已经存在于 Python 中:它是一个元组列表。 什么是“数据集”? 训练/有效/测试数据的集合还是简单的数据集合? 它只是一个文件吗? 目录? 发电机? 每个数据项(输入/目标)是一对吗? 总是这样吗? 字典是文本数据集的一部分吗?
    数据容器的选择受到许多限制因素的驱动,具体取决于其大小和执行环境。 我宁愿拥有一组丰富的容器,而不是Dataset容器,在内存/时间复杂性方面提供不同的权衡。 此外,我希望拥有一组丰富的迭代器、拆分器、加载器、转储器、切片器、中继器、服务器、生成器,以实际处理来自各种来源的数据。
  • epoch 概念也没有明确的语义。 根据我的经验,它最好由epoch = global_step / steps_per_epochsteps_per_epoch = dataset_size / batch_size

在这里,我尝试将 TF 输入管道中用于大型数据集的一些例程转换为小型内存数据集。 以下是我希望在 TensorFlow 中看到的一些示例:

这些例程展示了在索引列表上使用简单的迭代器可以走多远。

+1 对 feed_dict 之类的东西。 这是通过与外部世界交互(训练机械臂、Atari 游戏、 Universe )来学习的唯一方法。

可以通过避免复制来提高效率。 像 PyTorch,它的 Tensor 与底层的 numpy 数组共享内存缓冲区

我不了解 TF 以及这里的其他人,所以请持怀疑态度接受我的评论:

  • 使用tf.py_func我能够解决大部分与输入相关的问题,例如以符号方式加载 .mat 文件。 我目前正在努力解决的问题是将tf.train.batch与选择输入来源的能力相结合,以便在同一符号变量中包含训练/验证数据。 第8168章

    我知道这些函数最初是为简单的用例而考虑的,但是如果能够更好地控制管道而不需要管理 _everything_(例如使用tf.QueueBase.from_list但被迫提供队列和管理线程的负担)会很好手动)。

  • 我不确定 TensorFlow 是否在幕后优化了出队操作,但如果没有,我认为我们可以从并行出队操作中受益匪浅,该操作在处理先前数据的同时将数据(即下一批)装入 GPU 内存(即当前批次)。

  • 我认为feed_dict类的解决方案对于将大块数据传递给训练函数(例如一批图像)来说并不是最佳选择,因为它们基本上是执行图中的暂停以强制 TF 与 _python 的运行时交互_ . 图内解决方案听起来更好,带有指导图执行的指针,例如feed_dict={is_training = True}表示输入应该来自训练管道,模型应该设置 batchnorm 和 dropout(等)到训练模式等。这样,TF 可以更好地优化/并行执行,并且所有解决方案都可以扩展。

  • 创建批次的标准函数显然没有提供索引来指示我们正在处理哪个批次。 例如, slice_input_producer接收要生成的纪元数,但如果不计算我们已经评估了多少个样本,似乎无法知道一个样本的纪元。

现在有两种非常不同的方式将数据导入 Tensorflow:feed_dict 和 queues。 队列非常棒,除非您没有办法在本机操作数据——例如,如果您想加载 .wav 文件,请将其分成几部分,然后将其转换为频谱图。 在这一点上,您必须编写 C++ 操作(可行,但上下文切换 + 它使管道非常不灵活)或弹回 Python 领域(较慢,但非常容易和灵活)。

速度和灵活性之间的最佳折衷似乎是创建一个 TF 队列,然后创建一堆 Python 线程来为其提供数据。 这允许您在 Python 中进行灵活的数据处理(在 CPU 上大致并行化,除了 GIL 问题)同时保持一定的速度优势。

如果你只是形式化呢? 接口将是:push_data、end_of_data(用于表示一个时期的结束)和一个为模型提供数据的 dequeue_batch 函数。 那么您的代码就可以在 Python 中加载数据并将其并行地塞入队列中,而模型与所有这些完全分开。

我们应该使 feed_dict 更快(可能通过不复制提到的@yaroslavvb 的numpy.arrays ),但这与此更改是正交的。 无论我们对其进行了多少优化,feed_dict 永远不会是将数据提供给训练作业的最快方式。

feed_dict具体可能不是必需的。 更准确地说,我们需要支持以在线方式完成学习的管道,并且训练数据由响应 TensorFlow 网络动作的系统生成(学习 Atari 模拟器、机器人模拟器、机器人与现实世界交互等) . 这对于 OpenAI 的大多数应用程序来说是必需的,这里有一个例子——https: //github.com/openai/universe-starter-agent

最快的选择是创建一个 TensorFlow op 来维护状态,将动作作为输入,并生成训练数据。 然后添加一个占位符来指定操作。

不过,我的猜测是,您正在寻找可以完全用 Python 完成的事情。 两者之间可能有一些中间点。

我不确定这个概念是否已经提出,但我至少会用我自己的术语来解决这个问题。

在处理 RL 问题和训练重放缓冲区时,我找不到一种简单的方法来使用队列来加速通过 feed_dict 提供的样本。 此外,当随机创建一个样本集时,当我希望它们留在缓冲区中时,这些样本似乎被消耗了。

我希望做的是(可能通过 feed_dict 或文件)一个带有新样本的队列,一旦超过缓冲区的大小,最旧的样本就会从缓冲区中删除。 所以“样本年龄”的一些概念会很好。 我确信使用循环缓冲区可以修复多个样本,但“年龄”可能也很有趣,可能作为元组的一部分传递,但在 RL 情况下,只是添加样本的序列可能涵盖年龄 (FIFO)。

同样,我可能还不清楚如何使用队列,但是能够从这个样本缓冲区中随机抽取一个小批量而不是删除样本,因此可以收集一组新样本(可能与先前采样的示例)会很好。

我可能不明白 TF 数据输入管道 API 旨在解决的分布式设置。 是否有可能像 Pytorch 那样有一个简单的 API 设计:只有三个简单的类。 我可以在 5 分钟内掌握 pytorch 的数据集 API,它对于所有流行的学术数据集来说已经足够了。 http://pytorch.org/docs/data.html

很高兴看到解决 TF 数据集 API 中的痛点的新努力。 期待一个简单/漂亮/灵活的 API,引入最少的类/概念。 谢谢。

@lming是的,这里的前两条评论涵盖了这一点:通过制作使用py_func的 Dataset 实现,它等同于 PyTorch 实现。

我在上面第二个@lming的情绪。

当前数据加载方案的最大问题是它非常复杂并且涉及很多新概念。

我们不觉得自己用 Python 编写多线程数据加载器非常困难,而且通常我们不觉得很难确保我们的数据加载和预处理运行得足够快,以至于它实际上不会成为训练的瓶颈。

我们陷入困境的地方在于,为了最佳地遵循建议,我们最终陷入了尴尬的境地,其中之一是:

  • 使用feed_dict并遭受任何相关的性能影响
  • 从一个单独的线程馈送并处理一些一次性队列样板(除了在我们尝试时这根本没有加快速度)
  • 使用 TF 原语重新实现我们的数据加载和转换管道,可能使用py_func ,但仍然使用 TF API 来管理队列运行器

Python 线程 API 并不完美,但一般来说,当我们在 NumPy 或其他任何东西中执行大部分非 GIL 任务时,TF 队列 API 似乎更像是一种负担而不是帮助。

为我们提供的几个具体用例:

  • 我们的模型之一是本地化模型。 我们使用 scikit-image AffineTransform对象为这个模型应用裁剪和调整大小操作,因为这些对象让我们可以轻松地将模型输出转换回原始输入坐标空间。 用完全基于张量的 API 来解决这个问题似乎很棘手。
  • 我们有来自大量不平衡段的数据,在训练中我们使用一些自定义的分层抽样逻辑来确保我们均匀地呈现每个段的示例。 对于我们来说,在 Python 中为每个时期从我们的原始数据集中生成一个新的绘图相对简单,但是在上面提出的 API 中,我们必须弄清楚如何将这种行为实现为Iterator ,这似乎不那么简单。

我们主要处理时间序列数据,并且更愿意在训练每个独特的模型输入架构之前不必批量预处理整个数据集。 事实上,一个输入架构变体的预处理数据集大小很容易比未处理的文件集大小大一个数量级。

我们已经能够使用当前的队列系统、TF ops、batch_join/等来处理这个问题,以启用多个实时预处理线程和跨文件示例混合。 我不得不说它对于输入架构的超参数调整非常好和灵活,我喜欢整个管道都存在于图中,提供数据以响应 sess.run(train_op) 调用,并且可以从一个常见的检查点恢复该模型。

如果您打算弃用当前的队列范例,我想知道DatasetIterator将实现相同的灵活性。 对于我的用例,似乎Dataset可以表示时间序列的集合,而Iterator行为类似于 python 迭代器/生成器,并且可以处理任何预处理以形成批量示例?

特性请求:队列的控制机制,特别是结合TFRecordReader的/TextFileReader的read()方法,自动出队。 原因:挂起队列的自动预评估。

MXNet IIterator比Java的API流,Scala的集合(因此斯巴克的RDDS)和.NET的语言集成查询更相关的例子。 该设计支持灵活组合输入管道的各种组件,例如ImageRecordIterImageNormalizeIterBatchLoaderPrefetcherIterImageAugmenter

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 :

Feed dict 开销本质上是执行额外的 memcpy(Python->TensorFlow CPU->TensorFlow GPU)与使用像队列这样的本机操作(TensorFlow CPU->TensorFlow GPU)的成本。 所以如果这个memcpy很小,应该可以忽略不计。

这是有道理的,这就是我的假设。

我认为这使得反对feed_dict的建议有点夸大其词——实际上,问题似乎更像是导致 GPU 饥饿的低效数据馈送,而不是使用feed_dict本身。

如果我错了, @yaroslavvb 会纠正我,但这并不完全正确。 除非您还没有使用 Pythons Queue 库或类似的东西实现一些输入管道,否则将额外的时间将数据从磁盘加载到内存中并最终对它们进行预处理。

对于图像,尤其是更大的批量大小,这可能需要很长时间。 在这里,您可以使用 TF 输入队列真正加快速度,因为当您在 GPU 上训练/评估网络时,它们会将图像加载到 CPU 上的磁盘(+ 预处理)中。 计算完成后,您可以直接在 GPU 上复制数据时抓取下一批,而无需等待原生 Python 将新数据加载到内存中。

@kratzert ,这正是@taion 的意思

这个问题似乎更像是低效的数据馈送,导致 GPU 挨饿

异步预处理很难做好,因此大多数用户从tf.train为他们做这件事中受益。

是吗? 我绝不想捍卫 TFs 输入队列,但是当我阅读@yaroslavvb的帖子时,他指出额外的时间(仅)来自在本机 Python 和 TF(+ GPU)之间传递内存。 我唯一想补充的是,如果您不能将所有训练数据存储在内存中,那么还将内存从磁盘加载到内存中会增加一个训练周期的时间。 我在@taion的帖子中找不到任何这样的声明,但可能是我的误解,因为我不是英语母语者。

而且我知道,在很多情况下异步预处理是不可能的,但对于这种情况(图像分类 CNN 的简单训练)TFs 输入队列有很大帮助。

我唯一想补充的是,如果您不能将所有训练数据存储在内存中,那么还将内存从磁盘加载到内存中会增加一个训练周期的时间。

这也适用于tf.train 。 无论您是通过 Python 还是 TensorFlow 的执行引擎,数据都不会在没有从磁盘读取的情况下出现在图中。

无论如何,我发现通过joblib.cache.Memory和 feed_dict 的 memapped NumPy 数组性能非常好(在整个训练过程中 GPU 负载超过 90%),尽管有额外的副本。

啊好吧,所以也许是误会。 以为我让自己更清楚了。

我知道如果我使用 TF,数据不会神奇地出现在内存中。 但是 TF 可以很容易地将图像加载 + 预处理显式地放置在 CPU 上,并将图形计算放置在 GPU 上,并且两者都是并行完成的。 因此,当 GPU 计算某些数据的操作时,CPU 已经在将下一个数据加载到内存中。 由于这是并行完成的,我们通过从磁盘加载数据量有效地减少了计算时间(因为这通常比通过网络图向前+向后传递所需的时间更少)。 但是,是的,这仅适用于 CPU + GPU,如果仅使用 CPU,则无效。

编辑:我可能唯一喜欢 TFs 输入队列的地方是我可以在 Tensorboard 中观察队列(和批量生产者)的状态。 其余的我花了很长时间让它们运行我想要的所有预处理以及在同一运行中进行测试和验证的队列。

我想说,除了输入管道的陡峭学习曲线也可以通过文档克服,关键的缺失点是:

  1. 一种对数据进行自定义预处理的体面方法,无论是否基于队列,能够预见所有可能的数据输入需求的想法注定要失败。
  2. 一种在创建图形后更改图形输入管道的简单且既定的方法,因为它是最典型的使用模式。 当前的input_map提供了这样的功能,但它相当笨拙。 也有记录(当涉及到import_meta_graph )。
  3. 一种控制和监视时代的方法 - 目前它们被隐藏得很深,即使是简单的检查也无法访问。

我会支持@nicolasdespres的“无数据集”承诺,主要是因为一体式捆绑包不灵活,没有未来证明,而且 - 与 TF 提供小型、稳定、明确定义和可组装的范式不一致用于构建自定义模型的块。 拥有一些捆绑包,应该欢迎预定义的“easy-starter”包装器。

我认为 TF 并不真的需要再次尝试统一数据预处理以将其直接放入图中。 如果需要自定义的东西和动态生成/修改数据,事情会变得更糟。 通常,这些修改不是前向模型的一部分是有充分理由的:这些操作不需要任何反向传播。 因此,它们应该只是松散耦合。

所以理想的输入管道(没有反向传播的一切)应该非常简单和苗条:它应该由一个队列操作组成,该操作从某个源(套接字)接收数据(张量列表)。 #8728 与专业人士一起朝着这个方向迈出了很好的一步:

  • 您可以使用任何库进行预处理(opencv、nltk、...)
  • 预取是完全并行的,可以在任何/多台机器上完成
  • 发送者代码可以放在任何地方——甚至直接放在游戏引擎或渲染引擎中
  • 数据生成可以用任何编程语言完成(无需自定义操作)

我不知道,如果你真的需要别的东西,我不明白为什么你真的需要tf.image.random_*

我不认为这里的建议是完全摆脱队列,是吗?

数据集风格的抽象在这个领域很常见,而且非常有用。 较高级别抽象的存在并不排除较低级别的 API 也存在。

事实上,对于这些更高级别的抽象,越早越好——阅读已发表的 TF 研究代码的最大挫折之一是绝大多数代码库使用自己的特殊层库,而不是例如tf.layerstf.contrib.layers ,而这些库都是不同的,这使得共享工作变得更加困难。

我经常尝试在非常大的输入(可能 >1GB minibatch)上使用 TensorFlow,对每个 minibatch 进行相对较轻的计算。 这些输入位于磁盘或内存中的 HDF5 文件或 Numpy 数组中,因此我通常使用feed_dict馈送,可能会异步进入队列。 当使用多个 GPU 运行时,由于从 feed_dict 到 CPU 张量的 memcpy,TensorFlow 甚至无法使 GPU 的 PCI-e 带宽饱和。

正如@yaroslavvb 所提到的, feed_dict memcpy(在单个 CPU 内核上?)可能是一个巨大的性能瓶颈,我希望在 TensorFlow 输入处理的任何重构中解决这个问题。

@jhseu您提到您考虑删除feed_dict副本作为与此问题正交的副本。 您是否知道在删除副本方面是否存在任何问题或正在进行的工作(至少在某些情况下,例如具有良好步幅的行主 Numpy 数组)?

@eamartin已经出现了一些自三月初通过@alextp加快feed_dict的变化; 当内存与 16 字节对齐时,我认为我们与 numpy 共享缓冲区,因此每晚发布对您来说可能会更快。

不幸的是,16 字节对齐问题来自 Eigen,它要求内存地址的开头与 16 字节对齐。 我不确定为什么没有编写 Eigen 来处理未对齐的第一个和最后一个“数据包”,所以这无关紧要。 :/

numpy 是否可以与 tensorflow 变量共享缓冲区
当它们从会话运行中返回时?

我意识到这可能会引发各种可变性和状态
问题,但这些应该可以通过设置 WRITABLE 标志来避免
将 numpy 数组返回为 false。

2017 年 4 月 29 日凌晨 1:37,“Vijay Vasudevan”通知@ github.com 写道:

@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/commits?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对于未来,请继续关注,因为我们正在朝这个方向制作原型。

我的主要要求是,无论您构建新的输入管道系统,它都应该与图形的其余部分完全分开。 我正在尝试为我们的设备深度学习模型迁移到 tensorflow; 但是输入管道与计算图的其余部分如此紧密地绑定在一起,以至于它就像执行手术以对其进行推理。

示例:我使用 TFRecords 和输入队列进行训练; 我得到了我的重量/模型。 我想通过运行我的预测操作来执行推理; 但是因为输入队列运行器等是之前图表的一部分; 我坚持执行推理的机制。

请参阅此处的问题: http :

我喜欢 tf 记录和队列运行器,因为我已经习惯了; 问题是与图表的紧密结合....

这就是开发 tf.estimator.Estimator 之类的工具的原因,以允许
更容易分离训练和推理之间的关注点,并允许
用于交换输入管道。 你能用 Estimator 来写你的模型吗?

2017 年 5 月 1 日星期一下午 2:54,David Crook通知@github.com
写道:

我的主要要求是无论您如何构建新的输入管道系统
它应该与图表的其余部分完全分开。 我是
尝试为我们的深度学习模型迁移到 tensorflow
设备; 但是输入管道与其余部分如此紧密地绑定在一起
计算图,它就像执行手术以对其进行推理。

示例:我使用 TFRecords 和输入队列进行训练; 我得到了我的
重量/型号。 我想通过运行我的预测来执行推理
手术; 但是因为输入队列运行器等是图形的一部分
在那之前; 我坚持执行推理的机制。

请参阅此处的问题: http :
推理

我喜欢 tf 记录和队列运行器,因为我已经习惯了; 这
问题是与图形的紧密结合....


你收到这个是因为你被提到了。
直接回复本邮件,在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 ,但这一直(至少在我有限的经验中)是一个主要困难。 这里的教程页面主要只是建议使用单独的进程,但这可能会很痛苦,特别是如果我想根据验证数据提前停止。 如果我可以创建某种输入方法(队列,数据集,等等),我可以在训练和验证输入之间干净地交换,那会更好(同样, feed_dict对此非常有用,但如果它会总是更慢,如果有一个性能更好的替代方案会很好)。

@neighthan :是的,这也是@mrry计划好的:)

@neighthan使用当前的构造,我们构建了一个SwitchableDataSet ,它允许您在运行时在训练、验证和测试之间切换。

@kdavis-mozilla 听起来很有趣,但链接似乎已失效。 有其他参考吗?

@neighthan抱歉,已修复。

很有前途! 这是我对当前 API 的最大问题之一。
也就是说,看看 SwitchableDataSet,似乎实现新类型的数据馈送用例将主要通过实现特定于用例的类来完成。 新的编程模型是否还会有一个 API 来实现 SwitchableDataSet 所提供的功能,以及从更通用的低级原语中实现的功能? 我只是想知道用户会想出哪些东西(wrt 数据生成和使用)否则需要(特定的)添加到 API 中……

我是否正确假设https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/learn/python/learn/dataframe是新输入管道计划的一部分? 是否有任何代码使用它,即使该代码没有记录? 我想看看它的使用示例。

我还假设数据帧和转换旨在与估算器紧密集成? 目前,我没有看到估算器如何(很好地)与数据框和转换相匹配。 我看到你有办法从数据帧为估计器生成 feed_fns,但它似乎更像是另一种方法的适配器,而不是管道设计的一部分。

我知道这一切都非常新并且正在开发中。 我真的很喜欢我所看到的! 保持良好的工作。

@vonclites那是为了与 Pandas DataFrame/Series 交互,而不是新的 Dataset 构造。

@jimfleming似乎比这更笼统。 有一些方法可以从 csv 文件、dicts、numpy 数组、TFRecords 以及 Pandas 创建 TensorFlowDataFrames。 它遵循Pandas的命名法,但也类似于

我同意它相当笼统,它可能是未来的基础
工作,但这已经有一段时间了,大多数文件
几个月没更新了。
在周六,2017年5月13日,在上午09点46分vonclites [email protected]写道:

@jimfleming https://github.com/jimfleming好像更一般
比起那个来说。 有一些方法可以从 csv 文件创建 TensorFlowDataFrames,
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下关于 master 的第一个文档: https :

这一切看起来非常惊人!

的确很好。 以下迭代器会很棒:

任何支持可查找文件格式的计划,可以从任意偏移量开始读取?

以下是我们需要它的原因:
假设有一个很大的文本格式的训练数据集,我们需要将其转换为 tfrecord 格式。 然后我们开始了一个 map-reduce 工作,把它转换成 10 个 tfrecord 文件,让 10 个工人读取它们,完美! 然后我们想要更快地运行它,我们将工人数量更改为 20、30、40,...如果我们可以在不重新生成训练数据的情况下做到这一点,那就太好了。

解决方案:
首先,我们需要将偏移量和长度与文件名一起传递给 Dataset 的构造函数。 只有文件名是不够的。

其次,它本身的文件格式必须是可查找的(也就是可拆分的)。 它应该是以下之一:

  1. 文本格式
  2. 带填充的阻塞二进制格式。
  3. 有一个索引文件。

这个新 API 比旧 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')

不确定我是否应该提出功能请求或错误报告?

既然新的数据集 API存在,高性能基准测试示例建议是否仍然适用?

例如,在基准测试中RecordInput被拆分为 minibatches ,在我发现这个之前,我试图通过 #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))) 。 你也可以用Dataset.flat_map()tf.cond()伪造chain() ,但这会很丑:)。

  • @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())保护训练子图Iterator.move_next()Iterator.get_current() (例如 C++ IEnumerator协议),但这会引入额外的sess.run()调用,并使其更难在线程之间共享迭代器。

    我考虑过的一种可能性是创建一个包装器,将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实现中。

    特别是, DatasetIterator实现的一个当前限制是整个管道在单个设备上运行,而基准测试中更明确的代码能够将处理拆分到多个 CPU和 GPU 设备。 为了获得最佳性能,在需要数据之前集成诸如StagingArea类的优化以将数据预取到 GPU 非常重要,我们正在研究一种更透明的方法。 现在,您可以以与基准代码类似的方式使用StagingArea.put()操作手动流水线化Iterator.get_next()操作的输出。

这是太棒了! 我现在正在使用它并喜欢它。 @vrv @mrry是否能够在已经存在的训练数据集和验证数据集之间进行交换,还是仍然存在? 我看到可重新初始化的迭代器使我们能够对多个数据集使用相同的迭代器,但是每次运行 init 操作时,它基本上都是在该数据集上重新开始。 我目前正在使用新的DatasetIterator 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() 的方法。 感谢您的解决方法。 :-)

想让你知道我从队列迁移到数据集,这很棒。 绝对是朝着正确的方向前进。 目前缺少一些东西,我不得不解决:

  • 除了元组和列表之外,还能够使用 Tensor 结构的字典(这在解析具有命名特征并为您提供字典的示例时非常自然)
  • 支持稀疏张量。 tf.batch支持 SparseTensor 并且 SparseTensor 的自动批处理会让我的生活变得更轻松

此外,我有这个想法,您可以在数据集中实现随机测试/训练拆分功能。 这也可以使事情变得更容易。 就像是

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

保持良好的工作!

@lhlmgr我理解该示例的方式是每次在训练和验证之间切换时都需要重新初始化迭代器。 这不是世界末日,但对于我们正在洗牌小批量的大型数据集,我们需要一个合理的buffer_size ,这意味着每次初始化都很慢。 我发现tf.cond方法与两个单独的数据集/迭代器在这种情况下工作得更好/更快。 这样我们就可以定期运行验证数据而不会失去我们在训练集中的位置。

我真的很喜欢新的数据集/迭代器 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()

我想要的是op0op1以相同的顺序输出元素(因为它们共享shuffle步骤),但具有不同的功能( my_func0 / my_func1 ) 应用。 也就是说,我想创建共享一些处理的输入管道,然后在某些时候发散以进行额外的处理。

@drasmuss 的建议对于需要增强标签和图像的分割任务非常有用。 例如,图像可以非常合理地使用双线性插值,但插值标签值是不行的,因为 0 和 2 的标签像素边界不应该插值到完全不同的标签 1。

我刚刚开始阅读新的 API,但我想分享我在旧输入队列中遇到的两个问题,同时将 MonitoredSession 与 SessionRunHooks 一起使用。
我们还使用了两个单独的队列,一个将输入 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 并添加一个日志钩子并告诉它记录“准确度”张量时,它尝试评估第一个会话运行调用是徒劳的,因为钩子已将该张量添加到获取列表中,但是由于队列仍然是空的,所以还没有办法评估准确性。
问题在于程序只是停止并等待某个进程开始填充队列,但没有,所以它什么都不做,但也没有抛出异常或发出任何警告,这使得理解发生了什么有点困难。
这个问题最终很容易解决,只需执行一个入队操作作为with MonitoredSession(...) as sess:块中的第一个 sess.run() 调用,但如果输入队列可以预先填充一些初始值,那就太好了创建时,因此不会出现此问题。

除此之外,我们使用两个不同的输入管道来训练和验证数据,我们通过tf.QueueBase.from_list实现的开关交替连接到我们图的网络部分。
现在在with ... as sess:块中,您可以轻松实现一个 if 块,根据global_step % interval == 0选择哪个管道,但这意味着复制粘贴相同的代码(sess.run() 和 add_summary() 调用)次您使用的不同输入管道的数量(例如 train、validation_1、validation_2、...)
以某种方式使用 Hooks 将其直接集成到 MonitoredSession 会很好(即每 10 步使用validation_1 输入管道创建和保存摘要,每 25 步......)

  1. 任何能帮助我分析输入管道中是否存在瓶颈以及瓶颈在哪里的东西都会很棒。 例如,监控输入管道缓冲区中示例数量的方法会很有帮助。 或者 .map() 操作的每个线程处理的项目数 基本上,类似于队列如何为它们持有的图像数量创建摘要。
  2. 我认为其他人已经提到了类似的东西,但是一种从流数据源创建数据集的方法。 提供与https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/estimator/inputs/queues/feeding_functions.py 中的生成器提要功能类似的功能的东西(但如果源代码是数据可能来自任意来源,也许使用某种发布者/订阅者模型?)

感谢您的辛勤工作。 我正在挖掘新的 API。

在过去的几天里,我玩了很多新的 Input API,我认为DatasetIterator类极大地提高了代码的清晰度和可读性(与旧的输入相比)队列)。 在一个会话中,例如在训练和验证数据集之间切换也很容易。

但我也有一些问题和建议。
也许首先要问一个问题: Dataset类是基于队列实现的吗? 因为从这里的帖子中,如果不是,我不清楚。 在 Tensorboard 中,新 API 没有添加关于任何队列状态的附加信息(当前排队的对象数量)。 同时观察我的 CPU/GPU 资源/工作负载,我可以看到 GPU 工作负载经常下降到零(我猜在批次之间)。

然后是一个建议:
我认为dataset.shuffle()可以改进,如果改组不仅仅在内存中的 _n_ ( = buffer_size) 样本上进行,而是在整个输入列表上进行。 例如:我将图像和标签的路径存储在文本文件中。 如果文本文件中尚未进行改组,您通常可以在同一类中依次出现数千行。 如果我现在只使用dataset.shuffle()很容易发生(取决于 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'

因此,由于缓冲区中的 3 个示例之间只有混洗,因此第一个样本(批次相同)都将只有一个类别的样本。 因此,除非文件名列表中尚未进行改组,否则您在训练任何网络时都会遇到麻烦。 如果可用的图像数据集很大,增加 buffer_size 通常不是解决方案。
我看到的另一个问题是,就像目前实施的改组一样,不可能对整个数据集进行真正的改组。 我发现的唯一解决方法是在创建数据集之前对我从文本文件中读取的文件列表进行预混洗。 但是一旦创建了数据集,就只能在buffer_size 的范围内进行shuffle。

@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由(随机地)遍历各元素创建d1dataset1 ,并为每个这些元件的功能func样品(可变) 来自d1的子图像 [和子标签] 的数量(例如每个 256x256),取自d1各个区域。 例如,在一个实例中, func可能会返回 142 个新的 {image, label} 对,这些对将被添加到dataset2 。 在另一个例子中,它可能返回 389 个新对等。每次生成的元素数量是可变的,并且取决于元素d1的属性。

@kmhofmann我认为您可以使用Dataset.flat_map()将一个示例映射到多个示例。 在您的 flatmap 函数中,为每个输入示例创建一个新的 Dataset 对象,其中包含一个或多个示例。

@EdeMeijer那可能是 - 真的很难说,因为文档非常稀疏,没有示例。 (似乎有两个地方包含一些单独的文档:在GitHubtensorflow.org 上
我注意到的一件事是map()中缺少来自num_threadsoutput_buffer_size flat_map() ; 这是否意味着不可能进行并行处理? 或者这是一个TODO? 很难同时界定功能和特性集...

我的建议基于您链接的 github 页面上的一个 flat_map 代码示例。 在那里,单个字符串张量进来(文件名),整个数据集在 map 函数中发出,所以这看起来很清楚。 我猜map()的并行处理是一个待办事项,我相信他们会喜欢 PR :)

啊,谢谢,我错过了,因为它是关于文本处理的示例。 尽管如此,文档中的函数描述似乎有点稀疏,由Maps map_func across this dataset and flattens the result

input_fn 必须只返回特征和标签。 在训练过程中可以使用额外的参数来定制给定输入的损失呢?

另一个功能请求:如果有一个iterator.peek()运算符会很棒,它会返回当前的迭代器值(如iterator.get_next() ),但不会推进迭代器。 这将使协调模型的多个元素变得更容易,这些元素都想在推进一步之前从迭代器中读取。

嗨,首先感谢这个 API,我非常热衷于使用它。 我主要有兴趣使用它在同一过程中在训练和验证数据集之间切换。

但是,我很困惑如何在这种新范式中做到这一点。 例如,我看不到在数据集中间“获取迭代器”的方法。 作为示例,这里有一段代码演示了我想要做什么。 在一个 epoch 中每隔几步,我想运行一次验证操作,但此代码的输出显示迭代器永远不会在任一数据集中的项目 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。 tf.train.shuffle_batch(...) 中的参数“allow_smaller_final_batch”在我想确保所有批次都可以被 GPU 数量整除时很有用。 (我在多个 gpu 上进行数据并行化,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 开发带外数据平面,但这仍然是大量正在进行的工作。 该设计类似于 Spark 中的ExternalShuffleService ,使用内存存储(如 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 看到“?” 因为形状是因为此时您正在查看张量的“静态”形状,该形状并不总是定义的(图形事先不知道将有多少个示例)。 但是, tf.shape()评估张量的动态实时形状,所以我认为这应该可行。

无论如何,我尝试创建一个最小的示例,但是当我的过滤器排除任何元素时,我遇到了内部内核错误,因此对于初学者,我打开了https://github.com/tensorflow/tensorflow/issues/10725。 然而,我确实发现,显然,我们应该使用 Tensorflow ops 而不是标准比较,因为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我也遇到了让 GPU 达到 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

然而,在shuffle 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']

在获得file_0之前获得 3 file_1

repeat之前调用shuffle是否有任何顾虑?

只是想在这里添加一些东西,我为多任务学习实现了一个基于多进程的数据馈送管道。 它可以达到平均。 GPU 利用率 >90%,四核 CPU 利用率 >95%。 不太容易发生内存泄漏,特别适合几天的训练。 并不是说它是完美的,但至少在我的情况下比当前的 TF 队列 API 工作得更好。 如果有人感兴趣: https :

@ppwwyyxx已经在

@PatWie感谢您指出这一点! 我只是快速检查了@ppwwyyxx repo 真的很棒! 再次感谢

拥有 GPU 常驻队列会很棒。

拥有 GPU 常驻队列会很棒。

@xieqihuiPG查看StagingAreaMapStagingArea

将不胜感激:

  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这是

  • 我目前看不到“解压缩”数据集的方法。 假设我们有一个可训练的模型,它同时具有train / fit方法和infer / predict方法。 让我们把投入的(潜在的)嵌套结构的类型,我们的模型I和培训投入,这是只有在需要的时候训练(例如,监管标签)的类型, TI 。 在这种情况下,我们希望train方法接受具有(I, TI)类型元素的数据集(即ITI )和predict方法接受具有I(I, TI)类型元素的数据集(在这种情况下,它将忽略标签)。 我们还希望模型只有一个底层图,支持所有这些类型的输入。 我可以看到这样做的方式是让底层模型构造两个迭代器(一个元素类型I ,一个元素类型TI )并根据提供的数据集初始化它们。 但是,如果有人向train方法提供了包含(I, TI)类型元素的数据集,则无法解压缩此数据集并初始化两个迭代器。 一个人必须使用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)

这些都不令人满意,很高兴看到能够从带有缓冲队列的python 迭代器构造Dataset s 或修复 #4535(将自动修复 #2514)。

期待听到我们是否只是没有正确使用数据集 API。

我认为队列已经足够好了。 不过,我希望看到两件事有所改进:

除了使用占位符和管理线程之外,一种从原生 python 输入数据的更简单方法。

也许一个类InputQueue(delegate, fn, n_filler_threads)需要一个 tensorflow 队列委托和一个 python 函数fnfn返回一个(可能是嵌套的) np.array 或列表的元组。 InputQueue 开始n_filler_threads调用fn并将它们放在delegate 。 线程是守护进程,因此在主进程关闭时关闭。

无论如何,这只是我的想法。 由于 tensorflow 的静态要求,这可能比这要困难得多。 也许您只需要在创建delegate时提供大小。

我现在正在使用新的 api Dataset 。 但是还是发现了如何动态的给Dataset喂数据的问题。 此处此处@albertz 有两个类似的问题。

如您所见,现实世界的问题不仅仅是输入一系列图像或文本。 因此,如果您能让我在时间方式方面自由地提供数据,我将不胜感激。

我可以想象两个选项。 一种是通过feed_dict高效的分布式阅读。 虽然慢,但是多处理,就是机器问题了。 另一种是_wrap_一些成熟且被广泛接受的实现。

使用占位符作为队列的输入,模型从队列中读取输入,然后使用会话运行线程将输入(可能由 hadoop mapreduce 产生)提供给队列。 使用暂存区,您甚至可以隐藏所有预处理和输入时间。

我正在尝试测试文档中的示例

但似乎这个调用只传递了 1 个参数给函数:

数据集 = dataset.map(_parse_function)

相反,该函数是用两个参数定义的

@eaplatanios zip/unzip 的一个相关 PR 是https://github.com/tensorflow/tensorflow/issues/10837

@mrry您是否使用 python3 测试过文档的这一

@bhack我一直无法使它使用多个参数作为返回给 py_func 的函数的回报。 我正在使用 python3 并没有尝试使用 python2。
你的问题类似吗?

@bhack谢谢,我会尽快尝试的,我正在使用一种我并不引以为
文档中的修复已有一个月的历史,在 v1.3 发布之前,tensorflow.org 网站在发布新版本时不会更新? 官方文档没有修复

@AMairesse我建议你在https://github.com/tensorflow/tensorflow/issues/11786 中通知这个

需要更多的图像处理算子,比如map_coordinates ,所以我们可以只使用 tensorflow 构建图像增强管道

并且 Dataset 没有稳定地将 map 函数中定义的 init 变量定义为https://github.com/tensorflow/tensorflow/issues/12648

我想重新提出@kratzert之前提出的一个与性能相关的问题,这个问题似乎已经失去了焦点。 使用新数据集 API 的性能提升可以忽略不计。

@ppwwyyxx表示队列和 StagingArea 仍然可以与数据集 API 一起使用,但我仍然没有看到这样的工作示例。 我们有吗?

如果仍然必须包含队列、data_flow_ops 或 StagingArea 复杂性,新 API 的用途是什么?

@vvekic ,在我惊恐地意识到推理循环中的 0.8s/step 之后,我对队列和数据集 API 进行了一些试验,0.2s 是数据获取(GPU 利用率为 0%),如果HDD 同时被其他东西使用。
我的管道如下所示:

  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链接):Dataset API 包含预取方法,因此不需要在此处添加队列,您可以保留 Datasets 的其他优点(如重新初始化等)。 将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关于实现的具体问题的回应DatasetIterator类不使用 TensorFlow 以前的生产者/消费者队列(例如tf.FIFOQueuetf.RandomShuffleQueue ),但它们确实包括核心思想的更简单(和更有效)的实现。 例如, Dataset.prefetch()将启动一个后台线程来填充一个有序的缓冲区,它的作用类似于tf.FIFOQueue ,因此下游管道阶段不需要阻塞。 然而, prefetch()实现要简单得多,因为它不需要像tf.FIFOQueue那样支持那么多不同的并发操作。

  • @vvekic链接):我很想在尝试 Dataset API 之前和之后查看您的代码,也许您可​​以通过打开一个描述性能瓶颈的问题来跟进。 与馈送或(非StagingArea )基于队列的管道相比,新的 API 应该更高效,我很想知道哪些部分不是!

    目前,数据集 API 中不包含StagingArea功能是正确的,为了在 GPU 工作负载中获得峰值性能,您需要手动添加一个暂存区。 但是,我们正在积极致力于实现可以跨设备的数据集(有关正在进行的一些工作,请参阅 19a55725af8102d72d4e081c5139f0e4bd5a4bb7),并且其第一个用例之一是支持预取到 GPU 内存中。

  • @tengerye链接):对于动态地将数据输入数据集,我建议您尝试我们添加到 TF 1.4 的Dataset.from_generator()方法(并且已经在每晚构建中可用)。 我在这里回答了@albertz的 Stack Overflow 问题。 (支持分布式管道将取决于我在上一个答案中提到的跨设备数据集支持,我们将很快实现。)我认为这也适用于@rasmusbergpalm请求,因为您可以创建并发生成器,以及@tillahoffmann请求@sirfz请求。 不过这个 API 很新,所以如果您有任何反馈,请告诉我们!

  • @jasonkriss链接)我们已经实现了一种称为“可馈送”迭代器的东西,它允许您在多个迭代器(例如一个用于训练,一个用于测试)之间切换单个图的输入。 程序员指南提供有关如何使用此功能的

  • @guillaumekln (链接) 如果要批处理不同长度的序列,可以使用Dataset.group_by_window()转换。 看一下如何在 NMT 模型代码中使用它作为示例。

再次感谢大家对 TensorFlow 的这一部分的持续关注!

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

infojunkie picture infojunkie  ·  101评论

aebk2015 picture aebk2015  ·  144评论

ymfa picture ymfa  ·  145评论

konnerthg picture konnerthg  ·  100评论

keon picture keon  ·  246评论