我只是在学习使用TensorFlow的tf.data
API,并且发现它使我的代码变慢了很多,以每个时期的时间来衡量。我想这与应该做的相反。我编写了一个简单的线性回归程序进行测试。
Tl; Dr :有了100,000个训练数据,tf.data
可以将每个时期的时间减慢大约十倍,如果您使用的是批量训练。如果使用较小的批次,更糟。对于500个训练数据,情况恰恰相反。
我的问题:怎么回事?我的实现有缺陷吗?我读过的其他资料显示tf.data
的速度提高了约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.20243382899980134秒 运行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指出的一个重要问题。不过,对结果影响不大。
答案 0 :(得分:7)
我想测试数据集API,这似乎对处理数据非常方便。我用大量数据类型的小型和大型NN对CPU,GPU和多GPU方式的API进行了大量测试。
第一件事,在我看来您的代码还可以。但我需要指出的是,您的NN只是一个简单的层。
现在,数据集API不适合您的NN类型,但是更复杂。为什么呢出于以下原因,我在下面进行了解释(这是出于对理解数据集API的追求)。
首先,一方面数据集API 每批处理数据,而另一方面数据已预处理。因此,如果适合您的RAM,则可以通过预处理数据来节省时间。在这里,您的数据只是“简单”的。如果要测试我在说什么,请尝试查找要处理的非常大的数据集。不过,可以使用prefetching数据来调整数据集API。您可以看看这个tutorial,它很好地解释了为什么使用预取来处理数据会很好。
第二,在寻求用于多GPU训练的数据集API时,我发现,旧的预处理方法比小型神经网络的数据集API快。您可以通过创建一个简单的可堆叠RNN进行验证,该RNN在输入中采用一个序列。您可以尝试不同大小的堆栈(我已经测试了1,2,10和20)。您将看到,使用数据集API在1-GPU或4-GPU上,时间对于小型RNN堆栈(1、2和5)没有区别。
总而言之,数据集API适用于包含无法进行预处理的数据的神经网络。根据您的任务,预处理数据可能会更方便,例如,如果您想调整NN以改善数据。我同意数据集API对于批处理,填充和填充大量数据都很方便,但是它也不适合用于多GPU训练。
答案 1 :(得分:6)
第一:
您不必要地重新创建数据集。
library(dplyr)
library(magrittr)
> z <- x %>% mutate(match = ifelse(( (lead(X1)==X1) & (lead(X2)==X2)),"YES","NO"))
> z %>% mutate(X3 = replace(X3, match=="YES", "f"))
X1 X2 X3 X4 X5 X6 match
1 1 a f c 3 d YES
2 1 a 2 c 3 g NO
3 1 f 4 g 5 h <NA>
在循环之前创建数据集,并更改data_set = tf.data.Dataset.from_tensor_slices((training_inputs, training_labels))
输入签名以使用数据集而不是regress_with_tfData
和training_inputs
。
第二:
这里的问题是大小为50甚至500的微型批处理太小,无法补偿td.data构建延迟的成本。您应该增加小批量的大小。有趣的是,您使用的最小批量为100000,但是它可能太大了(我不确定,我认为它需要更多测试)。
您可以尝试以下几种方法:
1)将小批量的大小增加到10000左右,看看是否有所改善 2)更改管道以使用迭代器,例如:
training_labels
答案 2 :(得分:5)
您可能缺少的一件事是预取。在数据管道的末尾添加预取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批数据。这样,您就不会在批处理准备期间等待,每次火车迭代完成后都应该准备就绪。
答案 3 :(得分:3)
那是因为您正在将苹果与香蕉进行比较。
一方面,使用占位符时,您将按原样提供整体张量。另一方面,使用Dataset
时,您将张量切成单个样本。这是非常不同的。
使用Dataset
相当于为tf.data.Dataset.from_tensors
管道提供单占位符张量。 在您的示例中使用from_tensors
时,我得到的计算时间与占位符相似(实际上更短)。
如果要使用from_tensor_slices
比较更复杂的管道,则应与占位符进行公平比较。例如,将您的数据混洗。在切片上添加一些预处理。毫无疑问,您会看到使人们切换到该管道的性能提升。
答案 4 :(得分:0)
接受的答案不再有效,因为 TF 行为已经改变。每个文档:
<块引用>from_tensors 生成仅包含单个元素的数据集。至 将输入张量切成多个元素,使用 from_tensor_slices 相反。
这意味着你不能批量处理
X = np.arange(10)
data = tf.data.Dataset.from_tensors( X )
data = data.batch(2)
for t in data.as_numpy_iterator():
print(t)
# only one row, whereas expected 5 !!!
文档推荐from_tensor_slices
。但是与 numpy 切片相比,这有相当多的开销。慢切片是一个悬而未决的问题 https://github.com/tensorflow/tensorflow/issues/39750
本质上,TF 中的切片速度很慢,并且会影响输入边界或轻型模型,例如小型网络(回归、word2vec)。