别再用占位符了,在TensorFlow中使用Dataset API构建数据输入的pipeline

点击上方“AI公园”,关注公众号,选择加“星标“或“置顶”


作者:Francesco Zuppichini

编译:ronghuaiyang

导读

你在写TensorFlow代码的时候还在用占位符吗,别再用了,用Dataset吧。

内置的输入管道。永远不要再使用 feed-dict

你应该知道, feed-dict是传递信息给TensorFlow的最慢的方法,必须避免。将数据输入模型的正确方法是使用输入管道,以确保GPU永远不会等待新东西的到来。

幸运的是,TensorFlow有一个内置的API,名为Dataset,以便更容易地完成这项任务。在本教程中,我们将看到如何创建输入管道,以及如何高效地将数据输入模型。

本文将解释数据集的基本机制,包括最常见的用例。

你可以在这里找到所有代码:

https://github.com/FrancescoSaverioZuppichini/Tensorflow-Dataset-Tutorial/blob/master/dataset_tutorial.ipynb

通用的概述

为了使用数据集,我们需要三个步骤:

  • 导入数据。从一些数据创建数据集实例

  • 创建一个迭代器。通过使用创建的数据集创建迭代器实例来遍历数据集

  • 消费数据。通过使用创建的迭代器,我们可以从数据集中获取元素来提供给模型

导入数据

我们首先需要把一些数据放入数据集中

来自numpy

这是常见的情况,我们有一个numpy数组,我们想把它传递给tensorflow。

# create a random vector of shape (100,2)	
x = np.random.sample((100,2))	
# make a dataset from a numpy array	
dataset = tf.data.Dataset.from_tensor_slices(x)

我们还可以传递多个numpy数组,一个典型的例子是,我们将一些数据划分为特征和标签

features, labels = (np.random.sample((100,2)), np.random.sample((100,1)))	
dataset = tf.data.Dataset.from_tensor_slices((features,labels))
来自tensors

当然,我们可以用一些张量初始化我们的数据集

# using a tensor	
dataset = tf.data.Dataset.from_tensor_slices(tf.random_uniform([100, 2]))
来自占位符

当我们想要动态地改变数据集中的数据时,这是很有用的,我们将在后面看到如何做。

x = tf.placeholder(tf.float32, shape=[None,2])	
dataset = tf.data.Dataset.from_tensor_slices(x)
来自生成器

我们也可以从生成器初始化数据集,当我们有一个不同元素长度的数组(比如一个序列)的时候:

# from generator	
sequence = np.array([[[1]],[[2],[3]],[[3],[4],[5]]])	
def generator():	
    for el in sequence:	
        yield el	
dataset = tf.data.Dataset().batch(1).from_generator(generator,	
                                           output_types= tf.int64, 	
                                           output_shapes=(tf.TensorShape([None, 1])))	
iter = dataset.make_initializable_iterator()	
el = iter.get_next()	
with tf.Session() as sess:	
    sess.run(iter.initializer)	
    print(sess.run(el))	
    print(sess.run(el))	
    print(sess.run(el))

Ouputs:

[[1]]	
[[2]	
 [3]]	
[[3]	
 [4]	
 [5]]

在本例中,还需要指定用于创建正确张量的数据的类型和形状。

来自csv文件

你可以直接将csv文件读入数据集中。例如,我有一个csv文件,其中包含推文和对应的情绪。

640?wx_fmt=png

tweets.csv

我现在可以通过调用 tf.contrib.data.make_csv_dataset轻松地从它创建 Dataset。注意,迭代器将创建一个字典,其中键作为列名,值作为张量,并具有正确的行值。

# load a csv	
CSV_PATH = './tweets.csv'	
dataset = tf.contrib.data.make_csv_dataset(CSV_PATH, batch_size=32)	
iter = dataset.make_one_shot_iterator()	
next = iter.get_next()	
print(next) # next is a dict with key=columns names and value=column data	
inputs, labels = next['text'], next['sentiment']	
with  tf.Session() as sess:	
    sess.run([inputs, labels])

next 的值为:

{'sentiment': <tf.Tensor 'IteratorGetNext_15:0' shape=(?,) dtype=int32>, 'text': <tf.Tensor 'IteratorGetNext_15:1' shape=(?,) dtype=string>}

创建迭代器

我们已经看到了如何创建数据集,但是如何取回数据呢?我们必须使用“迭代器”,这将使我们能够遍历数据集并检索数据的实际值。有四种类型的迭代器。

  • One shot. 它可以迭代一次数据集,你不能向它提供任何值。

  • Initializable 你可以动态更改调用它的 initializer操作,并使用 feed_dict传递新数据。它基本上是一个可以装满东西的桶。

  • Reinitializable 它可以从不同的 Dataset.初始化。当你有一个需要额外变换的训练数据集时,比如说shuffle和测试集,这非常有用。这就像使用塔式起重机来选择不同的容器。

  • Feedable 使用迭代器进行选择。按照前面的示例,它就像塔式起重机一样,选择要使用哪个塔式起重机来选择要取哪个容器。在我看来是没有用的。

One shot迭代器

这是最简单的迭代器。使用第一个例子

x = np.random.sample((100,2))	
# make a dataset from a numpy array	
dataset = tf.data.Dataset.from_tensor_slices(x)	
# create the iterator	
iter = dataset.make_one_shot_iterator()

然后需要调用 get_next() 来获得包含数据的张量

...	
# create the iterator	
iter = dataset.make_one_shot_iterator()	
el = iter.get_next()

我们可以运行 el 来查看它的值

with tf.Session() as sess:	
    print(sess.run(el)) # output: [ 0.42116176  0.40666069]
Initializable迭代器

如果我们想要构建一个动态数据集,我们可以在运行时更改数据源,我们可以创建一个带有占位符的数据集。然后,我们可以使用常见的 feed-dict机制初始化占位符。这是通过一个initializable迭代器完成的。使用上一节的示例3

# using a placeholder	
x = tf.placeholder(tf.float32, shape=[None,2])	
dataset = tf.data.Dataset.from_tensor_slices(x)	
data = np.random.sample((100,2))	
iter = dataset.make_initializable_iterator() # create the iterator	
el = iter.get_next()	
with tf.Session() as sess:	
    # feed the placeholder with data	
    sess.run(iter.initializer, feed_dict={ x: data }) 	
    print(sess.run(el)) # output [ 0.52374458  0.71968478]

这次我们调用 make_initializable_iterator。然后,在 sess范围内,我们运行 initializer操作,以便传递我们的数据,在本例中是一个随机的numpy数组。

假设现在我们有一个训练集和一个测试集,一个真实的常见场景:

train_data = (np.random.sample((100,2)), np.random.sample((100,1)))	
test_data = (np.array([[1,2]]), np.array([[0]]))

然后我们想要训练模型,然后在测试数据集中评估它,这可以通过训练后再次初始化迭代器来完成

# initializable iterator to switch between dataset	
EPOCHS = 10	
x, y = tf.placeholder(tf.float32, shape=[None,2]), tf.placeholder(tf.float32, shape=[None,1])	
dataset = tf.data.Dataset.from_tensor_slices((x, y))	
train_data = (np.random.sample((100,2)), np.random.sample((100,1)))	
test_data = (np.array([[1,2]]), np.array([[0]]))	
iter = dataset.make_initializable_iterator()	
features, labels = iter.get_next()	
with tf.Session() as sess:	
#     initialise iterator with train data	
    sess.run(iter.initializer, feed_dict={ x: train_data[0], y: train_data[1]})	
    for _ in range(EPOCHS):	
        sess.run([features, labels])	
#     switch to test data	
    sess.run(iter.initializer, feed_dict={ x: test_data[0], y: test_data[1]})	
    print(sess.run([features, labels]))
Reinitializable迭代器

这个概念与以前类似,我们希望在数据之间进行动态切换。但是,我们不是将新数据提供给相同的数据集,而是切换数据集。和以前一样,我们希望有一个训练数据集和一个测试数据集

# making fake data using numpy	
train_data = (np.random.sample((100,2)), np.random.sample((100,1)))	
test_data = (np.random.sample((10,2)), np.random.sample((10,1)))

我们可以创建两个数据集

# create two datasets, one for training and one for test	
train_dataset = tf.data.Dataset.from_tensor_slices(train_data)	
test_dataset = tf.data.Dataset.from_tensor_slices(test_data)

有个技巧,我们创建一个通用迭代器

# create a iterator of the correct shape and type	
iter = tf.data.Iterator.from_structure(train_dataset.output_types,	
                                           train_dataset.output_shapes)

然后是两个初始化操作:

# create the initialisation operations	
train_init_op = iter.make_initializer(train_dataset)	
test_init_op = iter.make_initializer(test_dataset)

我们得到下一个元素

features, labels = iter.get_next()

现在,我们可以使用会话直接运行两个初始化操作。把所有这些放在一起,我们得到:

# Reinitializable iterator to switch between Datasets	
EPOCHS = 10	
# making fake data using numpy	
train_data = (np.random.sample((100,2)), np.random.sample((100,1)))	
test_data = (np.random.sample((10,2)), np.random.sample((10,1)))	
# create two datasets, one for training and one for test	
train_dataset = tf.data.Dataset.from_tensor_slices(train_data)	
test_dataset = tf.data.Dataset.from_tensor_slices(test_data)	
# create a iterator of the correct shape and type	
iter = tf.data.Iterator.from_structure(train_dataset.output_types,	
                                           train_dataset.output_shapes)	
features, labels = iter.get_next()	
# create the initialisation operations	
train_init_op = iter.make_initializer(train_dataset)	
test_init_op = iter.make_initializer(test_dataset)	
with tf.Session() as sess:	
    sess.run(train_init_op) # switch to train dataset	
    for _ in range(EPOCHS):	
        sess.run([features, labels])	
    sess.run(test_init_op) # switch to val dataset	
    print(sess.run([features, labels]))
Feedable迭代器

这与 reinitializable 迭代器非常相似,但是它不是在数据集之间切换,而是在迭代器之间切换。在我们创建两个数据集之后

train_dataset = tf.data.Dataset.from_tensor_slices((x,y))	
test_dataset = tf.data.Dataset.from_tensor_slices((x,y))

一个用于训练,一个用于测试。然后,我们可以创建我们的迭代器,在本例中,我们使用 initializable迭代器,但也可以使用 one shot迭代器

train_iterator = train_dataset.make_initializable_iterator()	
test_iterator = test_dataset.make_initializable_iterator()

现在,我们需要定义一个 handle,这将是一个可以动态更改的占位符。

handle = tf.placeholder(tf.string, shape=[])

然后,与前面类似,我们使用数据集的形状定义一个通用迭代器

iter = tf.data.Iterator.from_string_handle(	
    handle, train_dataset.output_types, train_dataset.output_shapes)

然后,我们得到下一个元素

next_elements = iter.get_next()

为了在迭代器之间切换,我们只需要调用 next_elemenents操作,并在feed_dict中传递正确的 handle。例如,要从训练集中得到一个元素:

sess.run(next_elements, feed_dict = {handle: train_handle})

如果您正在使用 initializable 迭代器,就像我们正在做的一样,请记住在开始之前初始化它们

sess.run(train_iterator.initializer, feed_dict={ x: train_data[0], y: train_data[1]})	
    sess.run(test_iterator.initializer, feed_dict={ x: test_data[0], y: test_data[1]})

把所有这些放在一起,我们得到:

# feedable iterator to switch between iterators	
EPOCHS = 10	
# making fake data using numpy	
train_data = (np.random.sample((100,2)), np.random.sample((100,1)))	
test_data = (np.random.sample((10,2)), np.random.sample((10,1)))	
# create placeholder	
x, y = tf.placeholder(tf.float32, shape=[None,2]), tf.placeholder(tf.float32, shape=[None,1])	
# create two datasets, one for training and one for test	
train_dataset = tf.data.Dataset.from_tensor_slices((x,y))	
test_dataset = tf.data.Dataset.from_tensor_slices((x,y))	
# create the iterators from the dataset	
train_iterator = train_dataset.make_initializable_iterator()	
test_iterator = test_dataset.make_initializable_iterator()	
# same as in the doc https://www.tensorflow.org/programmers_guide/datasets#creating_an_iterator	
handle = tf.placeholder(tf.string, shape=[])	
iter = tf.data.Iterator.from_string_handle(	
    handle, train_dataset.output_types, train_dataset.output_shapes)	
next_elements = iter.get_next()	
with tf.Session() as sess:	
    train_handle = sess.run(train_iterator.string_handle())	
    test_handle = sess.run(test_iterator.string_handle())	
    # initialise iterators. 	
    sess.run(train_iterator.initializer, feed_dict={ x: train_data[0], y: train_data[1]})	
    sess.run(test_iterator.initializer, feed_dict={ x: test_data[0], y: test_data[1]})	
    for _ in range(EPOCHS):	
        x,y = sess.run(next_elements, feed_dict = {handle: train_handle})	
        print(x, y)	
    x,y = sess.run(next_elements, feed_dict = {handle: test_handle})	
    print(x,y)

消费数据

在前面的示例中,我们使用会话打印数据集中 next 元素的值。

...	
next_el = iter.get_next()	
...	
print(sess.run(next_el)) # will output the current element

为了将数据传递给模型,我们必须传递由 get_next()生成的张量

在下面的代码片段中,我们有一个包含两个numpy数组的数据集,使用了第一部分中的相同示例。注意,我们需要在另一个numpy数组中封装 .random.sample ,以添加一个维度,组成我们需要的批数据。

# using two numpy arrays	
features, labels = (np.array([np.random.sample((100,2))]), 	
                    np.array([np.random.sample((100,1))]))	
dataset = tf.data.Dataset.from_tensor_slices((features,labels)).repeat().batch(BATCH_SIZE)

然后像往常一样,我们创建一个迭代器

iter = dataset.make_one_shot_iterator()	
x, y = iter.get_next()

我们做了一个模型,一个简单的神经网络

# make a simple model	
net = tf.layers.dense(x, 8) # pass the first value from iter.get_next() as input	
net = tf.layers.dense(net, 8)	
prediction = tf.layers.dense(net, 1)	
loss = tf.losses.mean_squared_error(prediction, y) # pass the second value from iter.get_net() as label	
train_op = tf.train.AdamOptimizer().minimize(loss)

我们直接使用 iter.get_next()中的张量作为第一层的输入,并作为loss函数的标签。包装在一起:

EPOCHS = 10	
BATCH_SIZE = 16	
# using two numpy arrays	
features, labels = (np.array([np.random.sample((100,2))]), 	
                    np.array([np.random.sample((100,1))]))	
dataset = tf.data.Dataset.from_tensor_slices((features,labels)).repeat().batch(BATCH_SIZE)	
iter = dataset.make_one_shot_iterator()	
x, y = iter.get_next()	
# make a simple model	
net = tf.layers.dense(x, 8, activation=tf.tanh) # pass the first value from iter.get_next() as input	
net = tf.layers.dense(net, 8, activation=tf.tanh)	
prediction = tf.layers.dense(net, 1, activation=tf.tanh)	
loss = tf.losses.mean_squared_error(prediction, y) # pass the second value from iter.get_net() as label	
train_op = tf.train.AdamOptimizer().minimize(loss)	
with tf.Session() as sess:	
    sess.run(tf.global_variables_initializer())	
    for i in range(EPOCHS):	
        _, loss_value = sess.run([train_op, loss])	
        print("Iter: {}, Loss: {:.4f}".format(i, loss_value))

Output:

Iter: 0, Loss: 0.1328 	
Iter: 1, Loss: 0.1312 	
Iter: 2, Loss: 0.1296 	
Iter: 3, Loss: 0.1281 	
Iter: 4, Loss: 0.1267 	
Iter: 5, Loss: 0.1254 	
Iter: 6, Loss: 0.1242 	
Iter: 7, Loss: 0.1231 	
Iter: 8, Loss: 0.1220 	
Iter: 9, Loss: 0.1210

有用的东西

Batch

通常,批处理数据是一件令人头痛的事情,使用 DatasetAPI,我们可以使用 batch(BATCH_SIZE) 方法,该方法自动用提供的大小批处理数据集。默认值为1。在下面的示例中,我们使用批处理大小为4

# BATCHING	
BATCH_SIZE = 4	
x = np.random.sample((100,2))	
# make a dataset from a numpy array	
dataset = tf.data.Dataset.from_tensor_slices(x).batch(BATCH_SIZE)	
iter = dataset.make_one_shot_iterator()	
el = iter.get_next()	
with tf.Session() as sess:	
    print(sess.run(el)) 

Output:

[[ 0.65686128  0.99373963]	
 [ 0.69690451  0.32446826]	
 [ 0.57148422  0.68688242]	
 [ 0.20335116  0.82473219]]
Repeat

使用 .repeat() 可以指定希望迭代数据集的次数。如果没有传递参数,它将永远循环,通常最好是永远循环,并直接用标准循环控制周期的数量。

Shuffle

我们可以使用 shuffle()方法来打乱数据集,该方法在每个epoch开始的时候默认打乱数据集。

请记住:打乱数据集是非常重要的,以避免过度拟合

我们还可以设置参数 buffer_size,这是一个固定大小的缓冲区,下一个元素将从其中均匀的选择。例子:

# BATCHING	
BATCH_SIZE = 4	
x = np.array([[1],[2],[3],[4]])	
# make a dataset from a numpy array	
dataset = tf.data.Dataset.from_tensor_slices(x)	
dataset = dataset.shuffle(buffer_size=100)	
dataset = dataset.batch(BATCH_SIZE)	
iter = dataset.make_one_shot_iterator()	
el = iter.get_next()	
with tf.Session() as sess:	
    print(sess.run(el))

第一次运行输出:

[[4]	
 [2]	
 [3]	
 [1]]

第二次运行输出:

[[3]	
 [1]	
 [2]	
 [4]]

是的,数据被打乱了。如果需要,还可以设置 seed 参数。

Map

你可以使用 map方法将自定义函数应用于数据集的每个成员。在下面的例子中,我们将每个元素乘以2:

# MAP	
x = np.array([[1],[2],[3],[4]])	
# make a dataset from a numpy array	
dataset = tf.data.Dataset.from_tensor_slices(x)	
dataset = dataset.map(lambda x: x*2)	
iter = dataset.make_one_shot_iterator()	
el = iter.get_next()	
with tf.Session() as sess:	
#     this will run forever	
        for _ in range(len(x)):	
            print(sess.run(el))

Output:

[2]	
[4]	
[6]	
[8]

完整的例子

Initializable iterator

在下面的示例中,我们使用批处理来训练一个简单的模型,并使用Initializable iterator在训练和测试数据集之间切换

# Wrapping all together -> Switch between train and test set using Initializable iterator	
EPOCHS = 10	
# create a placeholder to dynamically switch between batch sizes	
batch_size = tf.placeholder(tf.int64)	
x, y = tf.placeholder(tf.float32, shape=[None,2]), tf.placeholder(tf.float32, shape=[None,1])	
dataset = tf.data.Dataset.from_tensor_slices((x, y)).batch(batch_size).repeat()	
# using two numpy arrays	
train_data = (np.random.sample((100,2)), np.random.sample((100,1)))	
test_data = (np.random.sample((20,2)), np.random.sample((20,1)))	
iter = dataset.make_initializable_iterator()	
features, labels = iter.get_next()	
# make a simple model	
net = tf.layers.dense(features, 8, activation=tf.tanh) # pass the first value from iter.get_next() as input	
net = tf.layers.dense(net, 8, activation=tf.tanh)	
prediction = tf.layers.dense(net, 1, activation=tf.tanh)	
loss = tf.losses.mean_squared_error(prediction, labels) # pass the second value from iter.get_net() as label	
train_op = tf.train.AdamOptimizer().minimize(loss)	
with tf.Session() as sess:	
    sess.run(tf.global_variables_initializer())	
    # initialise iterator with train data	
    sess.run(iter.initializer, feed_dict={ x: train_data[0], y: train_data[1], batch_size: BATCH_SIZE})	
    print('Training...')	
    for i in range(EPOCHS):	
        tot_loss = 0	
        for _ in range(n_batches):	
            _, loss_value = sess.run([train_op, loss])	
            tot_loss += loss_value	
        print("Iter: {}, Loss: {:.4f}".format(i, tot_loss / n_batches))	
    # initialise iterator with test data	
    sess.run(iter.initializer, feed_dict={ x: test_data[0], y: test_data[1], batch_size: test_data[0].shape[0]})	
    print('Test Loss: {:4f}'.format(sess.run(loss)))

注意,我们为批大小使用占位符,以便训练之后动态切换它

Output

Training...	
Iter: 0, Loss: 0.2977	
Iter: 1, Loss: 0.2152	
Iter: 2, Loss: 0.1787	
Iter: 3, Loss: 0.1597	
Iter: 4, Loss: 0.1277	
Iter: 5, Loss: 0.1334	
Iter: 6, Loss: 0.1000	
Iter: 7, Loss: 0.1154	
Iter: 8, Loss: 0.0989	
Iter: 9, Loss: 0.0948	
Test Loss: 0.082150
Reinitializable Iterator

在下面的示例中,我们使用批处理来训练一个简单的模型,并使用Reinitializable Iterator在训练和测试数据集之间切换

# Wrapping all together -> Switch between train and test set using Reinitializable iterator	
EPOCHS = 10	
# create a placeholder to dynamically switch between batch sizes	
batch_size = tf.placeholder(tf.int64)	
x, y = tf.placeholder(tf.float32, shape=[None,2]), tf.placeholder(tf.float32, shape=[None,1])	
train_dataset = tf.data.Dataset.from_tensor_slices((x,y)).batch(batch_size).repeat()	
test_dataset = tf.data.Dataset.from_tensor_slices((x,y)).batch(batch_size) # always batch even if you want to one shot it	
# using two numpy arrays	
train_data = (np.random.sample((100,2)), np.random.sample((100,1)))	
test_data = (np.random.sample((20,2)), np.random.sample((20,1)))	
# create a iterator of the correct shape and type	
iter = tf.data.Iterator.from_structure(train_dataset.output_types,	
                                           train_dataset.output_shapes)	
features, labels = iter.get_next()	
# create the initialisation operations	
train_init_op = iter.make_initializer(train_dataset)	
test_init_op = iter.make_initializer(test_dataset)	
# make a simple model	
net = tf.layers.dense(features, 8, activation=tf.tanh) # pass the first value from iter.get_next() as input	
net = tf.layers.dense(net, 8, activation=tf.tanh)	
prediction = tf.layers.dense(net, 1, activation=tf.tanh)	
loss = tf.losses.mean_squared_error(prediction, labels) # pass the second value from iter.get_net() as label	
train_op = tf.train.AdamOptimizer().minimize(loss)	
with tf.Session() as sess:	
    sess.run(tf.global_variables_initializer())	
    # initialise iterator with train data	
    sess.run(train_init_op, feed_dict = {x : train_data[0], y: train_data[1], batch_size: 16})	
    print('Training...')	
    for i in range(EPOCHS):	
        tot_loss = 0	
        for _ in range(n_batches):	
            _, loss_value = sess.run([train_op, loss])	
            tot_loss += loss_value	
        print("Iter: {}, Loss: {:.4f}".format(i, tot_loss / n_batches))	
    # initialise iterator with test data	
    sess.run(test_init_op, feed_dict = {x : test_data[0], y: test_data[1], batch_size:len(test_data[0])})	
    print('Test Loss: {:4f}'.format(sess.run(loss)))

其他的资源

TensorFlow Dataset教程: https://www.tensorflow.org/programmers_guide/datasets

Dataset文档:https://www.tensorflow.org/api_docs/python/tf/data/Dataset

总结

Dataset API为我们提供了一种快速而健壮的方法来创建优化的输入管道,来训练、评估和测试我们的模型。在本文中,我们已经看到了可以使用它们进行的大多数常见操作。

640?wx_fmt=png— END—

英文原文:https://towardsdatascience.com/how-to-use-dataset-in-tensorflow-c758ef9e4428

640?wx_fmt=jpeg

请长按或扫描二维码关注本公众号

喜欢的话,请给我个好看吧640?wx_fmt=gif


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值