tf.data.Dataset.map() 和 tf.data.Dataset.apply() 的区别

32
4个回答

44

不同之处在于map会独立地对Dataset的每个元素执行一个函数,而apply会一次性对整个Dataset执行一个函数(例如在文档中给出的group_by_window)。

apply的参数是一个接受一个Dataset并返回一个Dataset的函数,而map的参数是一个接受一个元素并返回一个转换后元素的函数。


我在想,新的apply函数的文档在哪里可以找到。现在我发现这些函数在tf.contrib.data中,而Dataset API已经移动到了tf.data中,这正是我一直在寻找的地方。 - GPhilo
1
恐怕我仍然不理解实际应用中的区别。map 用于转换数据集中的值,而 apply 则是在数据集本身上操作... 所以 apply 可以替代 map 吗? - GPhilo
1
apply is used when you need to consider several elements at once. For example, if you want to create a dataset with averages of five consecutive elements in your dataset, then you couldn't do that with map - Sunreef
1
我明白了!好的,现在有意义了。所以,如果我理解正确的话,apply可以完全替代map(因为如果它可以访问所有项目,它也可以像map一样逐个访问它们),但编写apply函数不像编写操作值本身的函数那么直接,因此出于实际原因我们仍然使用map。 这有意义吗? - GPhilo
1
是的,我是这样理解的。如果你想用 apply 调用替换 map,那么在 apply 内部的函数仍然需要执行相当于 map 的操作。 - Sunreef

21

Sunreef的答案是完全正确的。你可能仍然在想为什么我们引入了Dataset.apply(),我想提供一些背景。

tf.data API有一组核心转换,比如Dataset.map()Dataset.filter(),通常对各种数据集都很有用,不太可能改变,并且作为tf.data.Dataset对象上的方法实现。特别是,它们受到与TensorFlow中其他核心API相同的向后兼容性保证

然而,核心方法略显狭隘。我们还想自由地尝试新的转换,然后再将其添加到核心中,并允许其他库开发人员创建自己的可重复使用的转换。因此,在TensorFlow 1.4中,我们将一组自定义转换拆分到了tf.contrib.data中。这些自定义转换包括一些具有非常特定功能的转换(如tf.contrib.data.sloppy_interleave()),以及一些API仍在不断变化中的转换(如tf.contrib.data.group_by_window())。最初,我们将这些自定义转换实现为从DatasetDataset的函数,这对于管道的语法流程有不良影响。例如:

dataset = tf.data.TFRecordDataset(...).map(...)

# Method chaining breaks when we apply a custom transformation.
dataset = custom_transformation(dataset, x, y, z)

dataset = dataset.shuffle(...).repeat(...).batch(...)

由于这似乎是一种常见的模式,我们添加了 Dataset.apply() 作为将核心和自定义转换链接在单个管道中的方法:

dataset = (tf.data.TFRecordDataset(...)
           .map(...)
           .apply(custom_transformation(x, y, z))
           .shuffle(...)
           .repeat(...)
           .batch(...))

在整个计划中,这只是一个细小的特性,但希望它有助于使tf.data程序更易于阅读,并使该库更易于扩展。


谢谢!这正是我希望理解的内容,也是我完全掌握新接口所缺失的部分。我会将@Sunreef的答案标记为“答案”,但实际上这两个答案互相补充。 - GPhilo
@mrry,map和apply是如何执行的?它们是在每个批次中执行并迭代器的下一个元素上应用,还是在图表初始化后一次性应用于整个数据集?我之所以问这个问题是因为如果在每个批次上应用而不是一次性应用于整个数据集,像ImageNet这样的数据集加载可能需要更长时间。 - Eliethesaiyan
@Eliethesaiyan 在 tf.data 中,大多数转换都是无状态和流式的,这意味着它们可以立即返回结果,并在处理可能不适合内存的大型数据集时消耗很少的内存。这意味着,如果您在这些转换后有一个 Dataset.repeat(),它们将在每次通过数据时执行。如果要避免这种情况,请在要执行一次的转换后添加 Dataset.cache() - mrry
1
@mrry,你能提供一个 custom_transformation 的 "hello world 实现" 吗? - Marsellus Wallace

4
我没有足够的声望来发表评论,但我想指出与@sunreef自己的帖子上的评论相反,您实际上可以使用map将其应用于数据集中的多个元素。
根据文档,map作为参数接受以下内容:
map_func:将张量的嵌套结构(其形状和类型由self.output_shapes和self.output_types定义)映射到另一个张量的嵌套结构的函数。
输出形状由数据集定义,并且可以通过使用api函数(如batch)进行修改。因此,例如,您可以仅使用dataset.batch和.map进行批量归一化。
dataset = dataset ...
dataset.batch(batch_size)
dataset.map(normalize_fn)

似乎apply()的主要作用是在整个数据集上进行转换。

您的用例确实可以通过批处理和映射的组合来构建,但是映射仍然在其调用的数据集的单个值上操作(在这种情况下,当然是批次)。Apply更通用,因为它允许例如具有可变长度批次、重新排序、分组等。@Sunreef的group_by_window示例无法使用map()复制(请注意,这不是需要在整个数据集上处理的转换,因为窗口可以逐步构建)。 - GPhilo
2
我同意 - 再次强调,这更多是针对那些不熟悉数据集API的新手而言的评论,因为他们可能不熟悉“元素”实际上是一个动态概念,取决于您如何定义数据集。一旦理解了它,就很直观,但我也用了一点时间来理解它。 - zephyrus

0

简单来说,apply()transformation_func 参数是 Datasetmap()map_func 参数是 element


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接