「导语」 TFRecord 是 TensorFlow 生态中的一个重要组件,它是一种二进制序列的存储格式,使用该格式可以使输入数据的读取和处理更为高效,从而提升整体训练流程的速度,另外,它还具有极高的灵活性,可以为复杂特征数据的构建与解析提供便利。本文将对 TFRecord 数据文件的生成与读取流程进行详细地介绍,并提供相应的示例代码作为参考。
TFRecord 格式简介
TFRecord
是 TensorFlow
生态中一个重要的组件,其本质上是一种文件格式,用于存储二进制序列的内容。 TFRecord
文件由序列化后的 protobuf
数据构成,可以直接供 TensorFlow
程序读取并用于模型训练。
对于文本格式的数据文件而言,其存储和读取的开销都比较大,相比之下, TFRecord
格式的数据文件占用的磁盘空间会更少,而且可以更高效地进行数据读取,因此将数据使用 TFRecord
格式存储能够在一定程度上提升数据处理的效率。
另外,文本格式的数据更适合处理定长且维度单一的
特征数据,对于变长以及多维度的
特征数据的处理会比较麻烦,而 TFRecord
格式的数据则没有这一限制,这给数据读取与处理带来了极大的灵活性。
TFRecord ProtoBuf
在生成或者读取 TFRecord
文件之前,我们需要对与 TFRecord
格式相关的 protobuf
协议有一定的了解,它们是构成 TFRecord
文件的关键部分。
Example
Example
指一个数据输入样本,它由一系列的特征组成。其 protobuf
格式如下所示:
message Example { |
上面的 Features
即指特征组合,其 protobuf
格式如下所示:
message Features { |
这里 Features
使用 map
结构来进行存储,其中 map
的 key
表示特征的名称,为 string
类型, value
表示具体的特征值,为 Feature
类型。
另外,可以看到 Example
只是封装了一下 Features
的结构,其实质与 Features
是等同的。
Feature
Feature
指具体的特征值,其 protobuf
格式如下所示:
message Feature { |
从 Feature
的定义可以看出,它可以接收三种格式的数据,分别为:
BytesList
格式,可以表示string
或byte
类型的数据。FloatList
格式,可以表示float
和double
类型的数据。Int64List
格式,可以表示bool
类型、enum
类型、int32
类型、uint32
类型、int64
类型以及uint64
等多种类型的数据。
以上三种格式,基本囊括了所有常见的数据输入类型,在生成和解析 TFRecord
数据时,可以根据具体的数据类型来使用相应的 Feature
结构。
Python 实现
在将上述 protobuf
文件转为语言相关的数据结构后,即可使用这些结构来构建特征数据并进行序列化了。下面以 python
语言为例,来介绍这些数据结构的具体使用方法。(注:在 TensorFlow
的 python
安装包里已经包含了 Example
以及 Feature
等相关的数据结构,我们可以直接使用而无需再用 protoc
工具生成了。)
首先,我们需要构建 Feature
对象。因为 Feature
结构支持 3
种格式的数据,所以这里使用 3
个函数来分别生成不同类型的 Feature
对象。示例代码如下所示:
import tensorflow as tf |
其中 _bytes_feature
、 _float_feature
和 _int64_feature
函数分别用来生成 BytesList
、 FloatList
和 Int64List
格式的 Feature
对象,这里假设这 3
个函数的参数 value
均为单个值。
由于 tf.train.*List
函数接收的参数均为列表 (list
) 或数组 (array
),所以上面的代码中才会加上 []
符号来表示列表,如果输入的参数 value
已经为列表或数组则无需此操作。另外还需注意传递给 3
个函数的参数 value
的基础类型要与具体的 *List
相匹配,否则会报错。
在生成了 Feature
对象后,可以调用其 SerializeToString
方法对其进行序列化,从而生成序列化后的字符串。另外还可以使用 tf.train.Feature.FromString
方法来将序列化后的数据还原为 Feature
对象。
接着,我们就可以构建 Example
对象并对其进行序列化了。假设我们有 4
种 Feature
,它们分别为 boolean
类型的特征、 integer
类型的特征、 string
类型的特征和 float
类型的特征,我们首先将这 4
种特征通过上面 3
个函数编码后返回相应的 Feature
对象,然后构建一个 Feature Map
字典并生成 Features
对象,最后使用 Features
对象生成 Example
对象并进行序列化。将上述流程统一到一个函数 serialize_example
中,其示例代码如下所示:
def serialize_example(feature0, feature1, feature2, feature3): |
同样地,可以调用 Example
对象的 SerializeToString
来将其序列化为字符串,调用 tf.train.Example.FromString
方法来将序列化后的 Example
对象还原。
TFRecord 文件生成
假设我们有 4
种类型的 Feature
,如上一节所述,并假设它们的原始数据 (numpy
) 生成方式如下述代码所示:
# The number of observations in the dataset. |
现在我们要使用这 4
种 Feature
来生成一个包含 10,000
个数据样本的 TFRecord
文件,可以使用以下几种方式进行生成。
使用 tf.data 生成
首先使用 tf.data.Dataset.from_tensor_slices
函数来生成一个包含原始数据类型的 dateset
,代码如下所示:
features_dataset = tf.data.Dataset.from_tensor_slices( |
接着我们使用上一节定义的 serialize_example
函数来生成一个包含序列化字符串类型的 dataset
,代码如下所示:
def generator(): |
最后我们将序列化后的 dataset
写入 TFRecord
文件中,代码如下所示:
filename = 'train.tfrecord' |
注意这里的 writer
使用的是 tf.data.experimental.TFRecordWriter
对象,它专用于将序列化的 dataset
对象写入到 TFRecord
文件中,要与后面介绍的 tf.io.TFRecordWriter
对象区分开来。
使用 tf.io 生成
首先将每个数据样本都转为 tf.train.Example
对象并序列化,然后再将其写入 TFRecord
文件中,这里同样使用上面介绍过的 serialize_example
函数来进行序列化,代码如下所示:
# Write the `tf.train.Example` observations to the file. |
这里的 writer
使用的是 tf.io.TFRecordWriter
对象,它直接将序列化的字符串写入到 TFRecord
文件中。
一般情况下,这种生成 TFRecord
文件的方式在 python
中是最常使用的,在实际使用中可以根据具体情况进行选择。
使用 MapReduce 生成
在数据处理环节,我们可能会使用 MapReduce
进行一些预处理操作,同时我们也希望可以直接借助 MapReduce
任务来生成多个 TFRecord
数据文件以供分布式训练使用,为了满足这一需求, TensorFlow
生态提供了一个扩展库 tensorflow-hadoop
,它包含了 TFRecord
格式的 MapReduce InputFormat
和 OutputFormat
实现。利用这个扩展库,我们就可以直接使用 MapReduce
任务来生成和读取 TFRecord
文件了。部分示例代码如下所示:
// Main function. |
需要注意的是,为了匹配正在使用的 hadoop
版本,你可能需要修改 tensorflow-hadoop
源码中的 pom.xml
文件,将 hadoop.version
设置为你正在使用的 hadoop
版本并使用 maven
工具重新编译该项目,然后将生成的 jar
包引入到 MapReduce
项目中,避免因版本不匹配而报错。
另外,为了使 MapReduce
项目能正常编译,你还需引入 org.tensorflow:proto
库以及 com.google.protobuf:protobuf-java
库,可以从 maven
官方仓库搜索这 2
个库的最新版本并加入到 gradle
或 maven
项目的配置文件中,然后再进行项目编译即可。
使用 TFRecorder 生成
由于在生成 TFRecord
文件时往往需要编写大量的复杂代码,为了优化代码的复杂度, TensorFlow
官方开源了 TensorFlow Recorder
项目(即 TFRecorder
)来更为便捷地生成 TFRecord
文件。
TFRecorder
允许用户从 Pandas dataframe
或 CSV
直接生成 TFRecords
文件,而无需编写任何复杂的代码。其对于图像数据的处理尤其方便,在 TFRecorder
之前,要大规模生成 TFRecord
格式的图像数据,必须编写一个数据流水线来从存储中加载图像并将结果序列化为 TFRecord
格式,而现在只需几行代码即可生成基于图像的 TFRecord
文件。示例代码如下所示:
import pandas as pd |
更多 TFRecorder
的用法请参考其官方文档。
TFRecord 文件读取
TensorFlow
提供了专用于读取 TFRecord
文件的 API
接口 tf.data.TFRecordDataset
,该接口可以将 TFRecord
文件中的内容读取到 dataset
中。代码如下所示:
# Read TFRecord file to dataset. |
此时 dataset
中存储的是序列化格式的字符串,如果要将其解析为真实的值,还需要进一步操作。
还原为 Example
我们可以将 raw_dataset
中的每个元素都还原为 tf.train.Example
,一般在小范围对 TFRecord
数据进行检查时使用,示例代码如下所示:
for raw_record in raw_dataset.take(1): |
模型训练使用
为了在模型训练时使用该 dataset
,我们需要将 raw_dataset
中的每个元素都解析为 FeatureMap
,以匹配 Keras
模型的输入与输出。代码如下所示:
def _parse_function(example_proto): |
这里使用了 tf.io.parse_example
函数来将序列化后字符串解析为指定的数据类型,我们需要提前准备好 feature_description
字典, 该字典定义了 feature
的名称、长度(定长/变长)、数据类型以及默认值,以供在解析时使用。最终我们通过调用 raw_dataset
的 map
方法来将该解析函数应用到 dataset
中的每个元素。
另外,我们也可以使用 tf.io.parse_single_example
函数来进行解析,但要注意它与 tf.io.parse_example
的区别,前者适合解析单个序列化的元素,而后者适用于一个 batch
的解析。在TensorFlow 数据输入的最佳实践
一文中曾介绍过 dataset
的向量化 map
操作,即对 dataset
先应用 batch
转换然后再应用 map
转换以提升效率,因此这里推荐使用后者作为序列化数据的解析函数。
最终我们可以将 parsed_dataset
应用于模型的训练中。示例代码如下所示:
model.fit(parsed_dataset) |
注: label
数据需要与 features
数据放在同一个 dataset
中, TensorFlow
会根据模型输入和输出 Tensor
的 name
去 dataset
中获取相应的数据来进行训练。
参考资料
- TFRecord and tf.train.Example
- TFRecord 相关 ProtoBuf 文件地址
- 创建 TFRecords 的救星 — TensorFlow Recorder 现已开源!
- TFRecorder Github 地址
- Hadoop MapReduce InputFormat/OutputFormat for TFRecords