代码之家  ›  专栏  ›  技术社区  ›  Eric Auld

为什么TensorFlow的'tf.data'包会减慢我的代码速度?

  •  16
  • Eric Auld  · 技术社区  · 7 年前

    我只是在学习使用TensorFlow的 tf.data API,我发现它大大降低了我的代码速度,以每个时代的时间来衡量。我想这和它应该做的恰恰相反。我写了一个简单的线性回归程序来测试它。

    Tl;博士 :拥有100000个培训数据, tf.数据 如果你使用的是全批量训练,那么每个历元的时间会减少大约10倍。更糟的是如果你用的是小批量的。500个训练数据的情况正好相反。

    我的问题是: 怎么回事?我的实现有缺陷吗?我读过的其他资料 tf.数据 速度提高了大约30%。

    import tensorflow as tf 
    import numpy as np
    import timeit
    
    import os
    os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
    tf.logging.set_verbosity(tf.logging.ERROR)
    
    n_epochs = 10
    input_dimensions_list = [10]
    
    def function_to_approximate(x):
        return np.dot(x, random_covector).astype(np.float32) + np.float32(.01) * np.random.randn(1,1).astype(np.float32)
    
    def regress_without_tfData(n_epochs, input_dimension, training_inputs, training_labels):
        tf.reset_default_graph()
        weights = tf.get_variable("weights", initializer=np.random.randn(input_dimension, 1).astype(np.float32))
    
        X = tf.placeholder(tf.float32, shape=(None, input_dimension), name='X')
        Y = tf.placeholder(tf.float32, shape=(None, 1), name='Y')
        prediction = tf.matmul(X,weights)
        loss = tf.reduce_mean(tf.square(tf.subtract(prediction, Y)))
        loss_op = tf.train.AdamOptimizer(.01).minimize(loss)
    
        init = tf.global_variables_initializer()
    
        with tf.Session() as sess:
            sess.run(init)
            for _ in range(n_epochs):
                sess.run(loss_op, feed_dict={X: training_inputs, Y:training_labels})
    
    def regress_with_tfData(n_epochs, input_dimension, training_inputs, training_labels, batch_size):
        tf.reset_default_graph()
        weights = tf.get_variable("weights", initializer=np.random.randn(input_dimension, 1).astype(np.float32))
    
        X,Y = data_set.make_one_shot_iterator().get_next()
    
        prediction = tf.matmul(X, weights)
        loss = tf.reduce_mean(tf.square(tf.subtract(prediction, Y)))
        loss_op = tf.train.AdamOptimizer(.01).minimize(loss)
    
        init = tf.global_variables_initializer()
    
        with tf.Session() as sess:
            sess.run(init)
            while True:
                try: 
                    sess.run(loss_op)
                except tf.errors.OutOfRangeError:
                    break
    
    for input_dimension in input_dimensions_list:
        for data_size in [500, 100000]:
    
            training_inputs = np.random.randn(data_size, input_dimension).astype(np.float32)
            random_covector = np.random.randint(-5, 5, size=(input_dimension, 1))
            training_labels = function_to_approximate(training_inputs)
    
            print("Not using tf.data, with data size "
            "{}, input dimension {} and training with "
            "a full batch, it took an average of "
            "{} seconds to run {} epochs.\n".
                format(
                    data_size,
                    input_dimension,
                    timeit.timeit(
                        lambda: regress_without_tfData(
                            n_epochs, input_dimension, 
                            training_inputs, training_labels
                        ), 
                        number=3
                    ),
                    n_epochs))
    
    for input_dimension in input_dimensions_list:
        for data_size, batch_size in [(500, 50), (500, 500), (100000, 50), (100000, 100000)]:
    
            training_inputs = np.random.randn(data_size, input_dimension).astype(np.float32)
            random_covector = np.random.randint(-5, 5, size=(input_dimension, 1))
            training_labels = function_to_approximate(training_inputs)
    
            data_set = tf.data.Dataset.from_tensor_slices((training_inputs, training_labels))
            data_set = data_set.repeat(n_epochs)
            data_set = data_set.batch(batch_size)
    
            print("Using tf.data, with data size "
            "{}, and input dimension {}, and training with "
            "batch size {}, it took an average of {} seconds "
            "to run {} epochs.\n".
                format(
                    data_size,
                    input_dimension,
                    batch_size,
                    timeit.timeit(
                        lambda: regress_with_tfData(
                            n_epochs, input_dimension, 
                            training_inputs, training_labels, 
                            batch_size
                        ),
                        number=3
                    )/3,
                    n_epochs
                ))
    

    这对我来说输出:

    不使用tf.data,数据大小为500,输入维度10和训练 如果是整批,平均需要0.2024338289998134秒 跑10个时代。

    不使用tf.data,数据大小为100000,输入维度10和 整批培训,平均0.2431719040000644 再过10秒就到了。

    使用tf.data,数据大小为500,输入维度10,以及 批量为50的培训,平均花费0.09512088866661846 再过10秒就到了。

    使用tf.data,数据大小为500,输入维度10,以及 批量为500的培训,平均花费了 0.07286913600000844秒,运行10个阶段。

    使用tf.data,数据大小为100000,输入维度10,以及 批量训练50人,平均4.421892363666605人 再过10秒就到了。

    使用tf.data,数据大小为100000,输入维度10,以及 批量培训100000次,平均花费 2.2555197536667038秒,运行10个阶段。

    编辑: 修正了Fred Guth指出的一个重要问题。不过,这对结果没有太大影响。

    4 回复  |  直到 7 年前
        1
  •  5
  •   P-Gn    7 年前

    那是因为你在拿苹果和香蕉作比较。

    一方面,当使用占位符时,您将按原样提供一个整体张量。另一方面,当使用 Dataset ,您正在将张量切片为单个样本。这是非常不同的。

    等效于提供一个单声道占位符张量 数据集 管道是通过使用 tf.data.Dataset.from_tensors . 当我使用 from_tensors 在您的示例中,我得到的计算时间与占位符相似(实际上更小)。

    如果要比较更复杂的管道,请使用 from_tensor_slices ,应该与占位符进行公平比较。例如,洗牌你的数据。在切片上添加一些预处理。毫无疑问,您将看到性能的提高,使人们转向这个管道。

        2
  •  7
  •   Clement Viricel    7 年前

    我想测试数据集API,它似乎非常方便处理数据。我用CPU、GPU和multi-GPU的方式对这个API进行了大量的测试,测试了不同类型数据的大小NN。

    首先,在我看来你的代码是好的。但我需要指出的是,你的神经网络只是一个简单的层。

    现在,DataSet API不适合于你的NN类型,但是对于更复杂的NN来说。为什么?我在下面解释了几个原因(建立在我理解数据集API的过程中)。

    首先,一方面数据集API 每批处理数据 另一方面 数据经过预处理 . 因此,如果它适合您的RAM,您可以通过预处理数据来节省时间。这里你的数据只是为了“简单”。如果你想测试我说的话,试着找到一个真正的大数据集来处理。不过,可以使用 prefetching 数据。你可以看看这个 tutorial 这很好地解释了为什么用预取处理数据是好的。

    其次,在我对用于多GPU训练的数据集API的探索中,我发现据我所知 对于小型神经网络,旧的预处理方法比数据集API快 . 您可以通过创建一个简单的可堆叠RNN来验证这一点,该RNN接受输入中的序列。你可以尝试不同大小的堆栈(我已经测试了1、2、10和20)。您将看到,使用dataset API,在1-GPU或4-GPU上,对于小的RNN堆栈(1、2和5),时间没有区别。

    总而言之, 数据集API适用于具有无法预处理的数据的神经网络 . 根据您的任务,预处理数据可能更方便,例如,如果您想调整NN以改进它。我同意dataset API对于批处理、填充和大量数据的洗牌都很酷,但它也不适合多GPU训练。

        3
  •  6
  •   Fred Guth    7 年前

    第一:

    您正在不必要地重新创建数据集。

    data_set = tf.data.Dataset.from_tensor_slices((training_inputs, training_labels))

    在循环之前创建数据集并更改 regress_with_tfData 输入签名以使用数据集而不是 training_inputs training_labels .

    第二:

    这里的问题是,大小为50甚至500的小批量太小,无法补偿td.数据构建延迟的成本。你应该增加小批量的大小。有趣的是,你用了一小批10万号的,但是可能太大了(我不确定,我想需要更多的测试)。

    有几件事你可以试试:

    1)将小批量大小增加到10000左右,看看是否有改进 2)将管道更改为使用迭代器,例如:

        data_set = tf.data.Dataset.from_tensor_slices((training_inputs, training_labels))
        data_set = data_set.repeat(n_epochs)
        data_set = data_set.batch(batch_size)
        iterator = data_set.make_one_shot_iterator()
        ....
        next_element = iterator.get_next()
    
        4
  •  5
  •   Burton2000    7 年前

    一个可能缺少的是预取。在数据管道的末尾添加1的预取,如下所示:

    data_set = tf.data.Dataset.from_tensor_slices((training_inputs, training_labels))
    data_set = data_set.repeat(n_epochs)
    data_set = data_set.batch(batch_size).prefetch(1)
    

    在数据集管道的末尾添加1的预取意味着在进行培训时尝试获取1批数据。这样你就不会在批处理准备好的时候等待,它应该在每一个序列迭代完成后就准备好了。

    推荐文章