【问题标题】:Using feed_dict is more than 5x faster than using dataset API?使用 feed_dict 比使用数据集 API 快 5 倍以上?
【发布时间】:2018-11-19 17:43:30
【问题描述】:

我创建了一个 TFRecord 格式的数据集用于测试。每个条目包含 200 列,命名为 C1 - C199,每一个都是一个字符串列表,还有一个 label 列来表示标签。创建数据的代码可以在这里找到:https://github.com/codescv/tf-dist/blob/8bb3c44f55939fc66b3727a730c57887113e899c/src/gen_data.py#L25

然后我使用线性模型来训练数据。第一种方法如下所示:

dataset = tf.data.TFRecordDataset(data_file)
dataset = dataset.prefetch(buffer_size=batch_size*10)
dataset = dataset.map(parse_tfrecord, num_parallel_calls=5)
dataset = dataset.repeat(num_epochs)
dataset = dataset.batch(batch_size)

features, labels = dataset.make_one_shot_iterator().get_next()    
logits = tf.feature_column.linear_model(features=features, feature_columns=columns, cols_to_vars=cols_to_vars)
train_op = ...

with tf.Session() as sess:
    sess.run(train_op)

完整的代码可以在这里找到:https://github.com/codescv/tf-dist/blob/master/src/lr_single.py

当我运行上面的代码时,我得到 0.85 步/秒(批量大小为 1024)。

在第二种方法中,我手动从 Dataset 中获取批次到 python,然后将它们提供给占位符,如下所示:

example = tf.placeholder(dtype=tf.string, shape=[None])
features = tf.parse_example(example, features=tf.feature_column.make_parse_example_spec(columns+[tf.feature_column.numeric_column('label', dtype=tf.float32, default_value=0)]))
labels = features.pop('label')
train_op = ...

dataset = tf.data.TFRecordDataset(data_file).repeat().batch(batch_size)
next_batch = dataset.make_one_shot_iterator().get_next()

with tf.Session() as sess:
    data_batch = sess.run(next_batch)
    sess.run(train_op, feed_dict={example: data_batch})

完整代码可以在这里找到:https://github.com/codescv/tf-dist/blob/master/src/lr_single_feed.py

当我运行上面的代码时,我得到 5 步/秒。这比第一种方法快 5 倍。这是我不明白的,因为理论上第二个应该会因为数据批次的额外序列化/反序列化而变慢。

谢谢!

【问题讨论】:

    标签: tensorflow tensorflow-datasets


    【解决方案1】:

    目前(从 TensorFlow 1.9 开始)在使用 tf.data 映射和批处理具有大量特征而每个特征中的数据量很少的张量时存在性能问题。问题有两个原因:

    1. dataset.map(parse_tfrecord, ...) 转换将执行 O(batch_size * num_columns) 小操作来创建批处理。相比之下,将 tf.placeholder() 提供给 tf.parse_example() 将执行 O(1) 次操作来创建相同的批次。

    2. 使用dataset.batch() 批处理许多tf.SparseTensor 对象比直接创建与tf.parse_example() 的输出相同的tf.SparseTensor 慢得多。

    这两个问题的改进工作正在进行中,并且应该可以在 TensorFlow 的未来版本中使用。同时,您可以通过切换dataset.map()dataset.batch() 的顺序并重写dataset.map() 以处理字符串向量来提高基于tf.data 的管道的性能,例如基于馈送的版本:

    dataset = tf.data.TFRecordDataset(data_file)
    dataset = dataset.prefetch(buffer_size=batch_size*10)
    dataset = dataset.repeat(num_epochs)
    
    # Batch first to create a vector of strings as input to the map(). 
    dataset = dataset.batch(batch_size)
    
    def parse_tfrecord_batch(record_batch):
      features = tf.parse_example(
          record_batch,
          features=tf.feature_column.make_parse_example_spec(
              columns + [
                  tf.feature_column.numeric_column(
                      'label', dtype=tf.float32, default_value=0)]))
      labels = features.pop('label')
      return features, labels
    
    # NOTE: Parallelism might not be as useful, because the individual map function now does
    # more work per invocation, but you might want to experiment with this.
    dataset = dataset.map(parse_tfrecord_batch)
    
    # Add a prefetch at the end to pipeline execution.
    dataset = dataset.prefetch(1)
    
    features, labels = dataset.make_one_shot_iterator().get_next()    
    # ...
    

    编辑 (2018/6/18):回答 cmets 提出的问题:

    1. 为什么是dataset.map(parse_tfrecord, ...) O(batch_size * num_columns),而不是 O(batch_size)?如果解析需要枚举列,为什么 parse_example 不取 O(num_columns)?

    当您将 TensorFlow 代码包装在 Dataset.map()(或其他函数转换)中时,每个输出的恒定数量的额外操作将添加到函数的“返回”值和(在 tf.SparseTensor 值的情况下)“convert "将它们转换为标准格式。当您直接将tf.parse_example() 的输出传递给模型的输入时,不会添加这些操作。虽然它们是非常小的操作,但执行如此多的操作可能会成为瓶颈。 (从技术上讲,解析确实需要 O(batch_size * num_columns) 时间,但解析中涉及的常量比执行操作要小得多。)

    1. 为什么要在管道末尾添加预取?

    当您对性能感兴趣时,这几乎总是最好的做法,它应该会提高您管道的整体性能。有关最佳实践的更多信息,请参阅performance guide for tf.data

    【讨论】:

    • 感谢您的回答!我刚刚确认切换地图和批处理确实有效。我仍然不太确定两件事:1. 为什么 dataset.map(parse_tfrecord, ...) O(batch_size * num_columns) 而不是 O(batch_size) ?如果解析需要列的枚举,为什么 parse_example 不采用 o(num_columns) ? 2. 为什么要在流水线的末尾添加预取?
    • 好问题!答案对于评论来说有点太长了,所以我将它们添加到上面的主要答案中。
    • @mrry 这仍然是 TF 1.14 的问题吗?
    猜你喜欢
    • 2012-08-16
    • 1970-01-01
    • 2015-01-18
    • 1970-01-01
    • 2012-04-17
    • 2015-04-22
    • 2019-12-10
    • 2020-04-05
    • 1970-01-01
    相关资源
    最近更新 更多