【深度学习 走进tensorflow2.0】TensorFlow 2.0 常用模块tf.data

无意中发现了一个巨牛的人工智能教程,忍不住分享一下给大家。教程不仅是零基础,通俗易懂,而且非常风趣幽默,像看小说一样!觉得太牛了,所以分享给大家。点这里可以跳转到教程。人工智能教程

背景:
很多时候,我们希望使用自己的数据集来训练模型。然而,面对一堆格式不一的原始数据文件,将其预处理并读入程序的过程往往十分繁琐,甚至比模型的设计还要耗费精力。为此,TensorFlow 提供了 tf.data 这一模块,包括了一套灵活的数据集构建 API,能够帮助我们快速、高效地构建数据输入的流水线,尤其适用于数据量巨大的场景。

1、小数据集对象的建立:

tf.data 的核心是 tf.data.Dataset 类,提供了对数据集的高层封装。tf.data.Dataset 由一系列的可迭代访问的元素(element)组成,每个元素包含一个或多个张量。比如说,对于一个由图像组成的数据集,每个元素可以是一个形状为 长×宽×通道数 的图片张量,也可以是由图片张量和图片标签张量组成的元组(Tuple)。
最基础的建立 tf.data.Dataset 的方法是使用 tf.data.Dataset.from_tensor_slices() ,适用于数据量较小(能够整个装进内存)的情况。具体而言,如果我们的数据集中的所有元素通过张量的第 0 维,拼接成一个大的张量(例如,前节的 MNIST 数据集的训练集即为一个 [60000, 28, 28, 1] 的张量,表示了 60000 张 28*28 的单通道灰度图像),那么我们提供一个这样的张量或者第 0 维大小相同的多个张量作为输入,即可按张量的第 0 维展开来构建数据集,数据集的元素数量为张量第 0 位的大小。具体示例如下:

import os

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'

import tensorflow as tf
import numpy as np

# X = tf.constant([2013, 2014, 2015, 2016, 2017])
# Y = tf.constant([12000, 14000, 15000, 16500, 17500])


# 也可以使用NumPy数组,效果相同
X = np.array([2013, 2014, 2015, 2016, 2017])
Y = np.array([12000, 14000, 15000, 16500, 17500])


dataset = tf.data.Dataset.from_tensor_slices((X, Y))
for x, y in dataset:
   print(x.numpy(), y.numpy())

输出:

2013 12000
2014 14000
2015 15000
2016 16500
2017 17500

用tf.data.Dataset.from_tensor_slices((train_data, train_label)) 导入数据集:


(train_data, train_label), (test_data, test_lable) = tf.keras.datasets.mnist.load_data()
train_data = np.expand_dims(train_data.astype(np.float32) / 255.0, axis=-1)      # [60000, 28, 28, 1]
mnist_dataset = tf.data.Dataset.from_tensor_slices((train_data, train_label))

print(mnist_dataset)

2、大数据集的建立:
对于特别巨大而无法完整载入内存的数据集,我们可以先将数据集处理为 TFRecord 格式,然后使用 tf.data.TFRocrdDataset() 进行载入。我们会在后面的连载文章中介绍 TFRecord 格式的详细使用方式,或者你也可以参考 下文 以了解详情:
https://tensorflow.google.cn/tutorials/load_data/tfrecord

3、数据集对象的预处理

tf.data.Dataset 类为我们提供了多种数据集预处理方法。最常用的如:
Dataset.map(f) :对数据集中的每个元素应用函数 f ,得到一个新的数据集(这部分往往结合 tf.io 进行读写和解码文件, tf.image 进行图像处理);
Dataset.shuffle(buffer_size) :将数据集打乱(设定一个固定大小的缓冲区(Buffer),取出前 buffer_size 个元素放入,并从缓冲区中随机采样,采样后的数据用后续数据替换);
Dataset.batch(batch_size) :将数据集分成批次,即对每 batch_size 个元素,使用 tf.stack() 在第 0 维合并,成为一个元素。
Dataset.prefetch() :预取出数据集中的若干个元素(可提升训练流程并行效率)。
除此以外,还有 Dataset.repeat() (重复数据集的元素)、 Dataset.reduce() (与 Map 相对的聚合操作)、 Dataset.take ()(截取数据集中的前若干个元素)等,可参考 API 文档 进一步了解。
https://tensorflow.google.cn/versions/r2.0/api_docs/python/tf/data/Dataset

一个例子:
使用 Dataset.map() 将所有图片旋转 90 度:

import matplotlib.pyplot as plt 
(train_data, train_label), (test_data, test_lable) = tf.keras.datasets.mnist.load_data()
train_data = np.expand_dims(train_data.astype(np.float32) / 255.0, axis=-1)      # [60000, 28, 28, 1]
mnist_dataset = tf.data.Dataset.from_tensor_slices((train_data, train_label))

def rot90(image, label):
   image = tf.image.rot90(image)
   return image, label

mnist_dataset = mnist_dataset.map(rot90)

for image, label in mnist_dataset:
   plt.title(label.numpy())
   plt.imshow(image.numpy()[:, :, 0])
   plt.show()

使用 Dataset.batch() 将数据集划分批次,每个批次的大小为 4:

mnist_dataset = mnist_dataset.batch(4)

使用 Dataset.shuffle() 将数据打散后再设置批次,缓存大小设置为 10000:

mnist_dataset = mnist_dataset.shuffle(buffer_size=10000).batch(4)

注意:
Dataset.shuffle() 时缓冲区大小 buffer_size 的设置
tf.data.Dataset 作为一个针对大规模数据设计的迭代器,本身无法方便地获得自身元素的数量或随机访问元素。因此,为了高效且较为充分地打散数据集,需要一些特定的方法。Dataset.shuffle() 采取了以下方法:
设定一个固定大小为 buffer_size 的缓冲区(Buffer);
初始化时,取出数据集中的前 buffer_size 个元素放入缓冲区;
每次需要从数据集中取元素时,即从缓冲区中随机采样一个元素并取出,然后从后续的元素中取出一个放回到之前被取出的位置,以维持缓冲区的大小。

因此,缓冲区的大小需要根据数据集的特性和数据排列顺序特点来进行合理的设置。比如:
当 buffer_size 设置为 1 时,其实等价于没有进行任何打散;
当数据集的标签顺序分布极为不均匀(例如二元分类时数据集前 N 个的标签为 0,后 N 个的标签为 1)时,较小的缓冲区大小会使得训练时取出的 Batch 数据很可能全为同一标签,从而影响训练效果。一般而言,数据集的顺序分布若较为随机,则缓冲区的大小可较小,否则则需要设置较大的缓冲区。

4、数据集元素的获取与使用

构建好数据并预处理后,我们需要从其中迭代获取数据以用于训练。tf.data.Dataset 是一个 Python 的可迭代对象,因此可以使用 For 循环迭代获取数据。

Keras 支持使用 tf.data.Dataset 直接作为输入。当调用 tf.keras.Model 的 fit() 和 evaluate() 方法时,可以将参数中的输入数据 x 指定为一个元素格式为 (输入数据, 标签数据) 的 Dataset ,并忽略掉参数中的标签数据 y 。例如,对于上述的 MNIST 数据集,常规的 Keras 训练方式是:

model.fit(x=train_data, y=train_label, epochs=num_epochs, batch_size=batch_size)

使用 tf.data.Dataset 后,我们可以直接传入 Dataset :

model.fit(mnist_dataset, epochs=num_epochs)

由于已经通过 Dataset.batch() 方法划分了数据集的批次,所以这里也无需提供批次的大小。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章