数据载入过慢?这里有一份TensorFlow加速指南

时间:2022-05-05
本文章向大家介绍数据载入过慢?这里有一份TensorFlow加速指南,主要内容包括概述、导入数据、使用Tensors、使用Placeholder、使用generator、创建迭代器、可初始化迭代器、可重初始化迭代器、可馈送迭代器、消耗数据、更多内容、Repeat操作、Shuffle操作、Map操作、结论、相关链接、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。
王小新 编译自 Towards Data Science 量子位 出品 | 公众号 QbitAI

机器学习算法烂熟于心,网络结构顺手拈来,但是如果数据集载入时耗费大量时间,那整个训练时间就会大大增加。

这个问题可能困扰着很多使用大型数据集训练的炼丹师们。最近,Francesco Zuppichini在medium上的一篇文章,从使用Dataset函数的三个步骤讲起,介绍了相应的解决方法。

以下内容译自他的文章。

看完这篇文章后,千万不要再用默认的输入函数feed-dict了。

本文以TensorFlow 1.5为标准函数库。根据以往经验,在TensorFlow中,feed-dict函数可能是最慢的一种数据载入方法,尽量少用。把数据输入到模型的最佳方法是使用输入流水线(input pipeline),来确保GPU无须等待新数据输入。

幸好,TensorFlow有一个内置接口,叫做Dataset。这个接口是为了更容易地实现数据输入,在1.3版本已被提出。这份教程将会介绍如何使用它来创建输入流水线,高效率地将数据输入到模型中。

本文还会解释Dataset接口的基本原理,包括最常见的一些用法。

所有代码可从这个网址获取:

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

概述

使用Dataset接口,有以下三个步骤:

1. 导入数据,从某些数据创建一个数据集实例;

2. 创建迭代器iterator,即使用已有的数据集来创建一个迭代器实例,对数据集进行迭代;

3. 消耗数据,即使用所创建的迭代器,从数据集中取出元素输入到模型。

导入数据

首先,我们需要把数据导入到数据集中,有以下几种方式。

使用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

当然,我们可以用Tensor来初始化数据集:

# using a tensor
dataset = tf.data.Dataset.from_tensor_slices(tf.random_uniform([100, 2]))

使用Placeholder

当我们需要多次修改Dataset函数中的数据时,这个方法是最合适的,稍后会详细介绍。

x = tf.placeholder(tf.float32, shape=[None,2])
dataset = tf.data.Dataset.from_tensor_slices(x)

使用generator

我们也可以使用生成器generator来初始化Dataset,在处理长度不同的元素(如序列)时,这种方法很有用:

sequence = np.array([[1],[2,3],[3,4]])
def generator():
    for el in sequence:
        yield el
dataset = tf.data.Dataset().from_generator(generator,
                                           output_types=tf.float32, 
                                           output_shapes=[tf.float32])

在这种情况下,你还需要指定输入数据的类型和形状,来得到合适的Tensor。

创建迭代器

上面已经介绍了如何创建一个数据集,但是如何拿出里面的数据呢?这里要使用迭代器Iterator,来遍历整个数据集并取出数据的实际值,有以下四种类型。

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]

可初始化迭代器

如果要构建一个动态数据集,在运行时要更改其中的源数据,则应该选择占位符placeholder来创建数据集,然后使用经典的feed-dict方法来初始化占位符。这些可以用一个可初始化迭代器来完成,利用上节“使用Placeholder”部分的示例:

# 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函数来传递数据,这里先以随机数组为例。

到这里,我们已经构建好训练集和测试集:

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])

可重初始化迭代器

这个概念与上个类似,要在数据之间进行动态切换。但是,上面是将新数据输入到同一个数据集中,这里是在数据集之间切换。同样地,我们要构建一个训练数据集和一个测试数据集:

# 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)

这里是关键,要构建一个通用型Iterator:

# 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()

现在,可以使用构建的Session来直接运行这两个初始化操作,把这些程序组合起来:

# 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]))

可馈送迭代器

在我看来,这些方法可能效果不好,它们基本上不是在数据集之间切换,而是在迭代器之间切换。你可以分别用make_one_shot_iterator函数和make_initializable_iterator函数来创建两个迭代器。

消耗数据

在前面例子中,我们使用过Session来输出数据集中元素next的值:

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

为了将数据传递给模型,我们只要传递get_next函数生成的张量。

在下面代码段中,有一个包含两个numpy数组的数据集,这里用了第一节的例子。请注意,我们要用.random.sample函数来包装另一个numpy数组以满足数据批量化的维度要求:

# 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函数输出的张量作为第一层的输入和损失函数的标签,整理后得到:

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))

输出为:

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

更多内容

批处理

通常来说,批处理数据是一件麻烦的事。但是可以用Dataset函数中的批处理方法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))

输出:

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

Repeat操作

使用repeat函数,可指定数据集中的迭代次数。若没有参数传递,它会一直循环。通常在持续循环后直接用一个标准循环来控制epoch大小。

Shuffle操作

我们可使用shuffle函数来打乱数据集,该函数默认在每个epoch打乱数据集。

打乱数据集,这个操作是非常重要的,可以减弱过拟合效应。

我们也可以设置参数buffer_size,这是shuffle函数的一个内置参数,下个元素将在这个缓冲区中统一选择。下面举例:

# 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方法将自定义函数应用到数据集的每个元素中。在下面示例中,我们把每个元素都乘二:

# 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))

输出:

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

结论

本文介绍的Dataset API给我们提供了一种快速且稳定的方法来创建最佳的输入流水线,以更好地训练、评估和测试网络模型。这篇文章介绍了这个API中的大部分常见操作。更多代码可参见本文对应的jupyter-notebook。

相关链接

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

本文对应jupyter notebook: https://github.com/FrancescoSaverioZuppichini/Tensorflow-Dataset-Tutorial/blob/master/dataset_tutorial.ipynb

TensorFlow dataset官方教程: https://www.tensorflow.org/programmers_guide/datasets

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