TensorFlow的数据pipeline系列:使用dataset结合Example和SequenceExample协议的解析方法比较(四)

前言:本文详细介绍tf.train.Example和tf.train.SequenceExample的区别,前面的几篇文章参见:

tensorflow的Data Pipeline系列教程(一)——Dataset类的属性即常用方法

TensorFlow的数据pipeline系列:Datasets+TFRecord的数据导入(二)

TensorFlow之tfrecords文件详细教程

TensorFlow的数据pipeline系列:tf.train.Example和tf.train.SequenceExample协议的比较(三)

一、如何查看tfrecord文件所包含的信息

1.1 tfrecords文件的简单预览——以系列文章(三)中得到的example.tfrecord文件为例子

我们可以简单的查看一下我们所保存的tfrecords文件是否符合我们的预期,我们希望预览一下每一组样本中包含的特征信息,我们可以这么做,在tensorflow1.x和tensorflow2.x中有不同的实现。

(1)tensorflow1.x的实现

我们可以使用tf.train.Example.FromString()进行简单的查看,代码如下:

import tensorflow as tf
 
#确认tfrecord的内容
ex=next(tf.python_io.tf_record_iterator('titanic_train.tfrecords'))
print(tf.train.Example.FromString(ex))

(2)tensorflow2.x的实现——通过dataset直接完成

dataset = tf.data.TFRecordDataset("example.tfrecord")  # 将record文件加载成dataset
print(dataset.element_spec)  # 查看dataset的“元素element”信息,得到TensorSpec(shape=(), dtype=tf.string, name=None)

    
for raw_record in dataset.take(1):  # 从dataset中取一个样本进行查看即可
    example = tf.train.Example()
    example.ParseFromString(raw_record.numpy())
    print(example)

得到的结果如下:

features {
  feature {
    key: "X"
    value {
      float_list {
        value: 1.0
        value: 2.0
        value: 3.0
        value: 4.0
      }
    }
  }
  feature {
    key: "Y"
    value {
      int64_list {
        value: 1
      }
    }
  }
}

从上面的解析中可以看见,前面的tfrecord文件中的数据结构清楚的展示了出来。

二、tfrecord文件的解析

2.1 解析的思想

实际上经过保存的tfrecord文件已经是一个dataset了,他跟我们的列表没有任何区别,所以可以很方便的使用dataset直接进行加载,但是需要注意的,由于tfrecord的每一个样本example,也就是每一个element都是经过序列化的,我们像常规的数据迭代只能得到序列化的样本,即serielized_example,所以数据就需要从serielized_example中进行解析。

解析的实质本质上依然是迭代dataset元素,然后对每一个元素应用map函数,,map函数的参数是一个解析函数,这个解析函数需要我们自己进行编写,总而言之,tfrecord文件的解析最核心的地方在于解析函数的编写

下面依然以上面的example.tfrecord文件为例子来说明:

2.2 直接上代码——以上面的example.tfrecord文件为例子

(1)一次性只解析一条样本——tf.io.parse_single_example

定义的解析函数如下:

def parse_tfrecords(serialized_example):
    # 定义解析的规则,需要注意的是,这里需要与数据保存是后定义的规则一致
    features = {
            'X':tf.io.FixedLenFeature([4], dtype=tf.float32),
            'Y': tf.io.FixedLenFeature([1], dtype=tf.int64) 
            }
    
    # 一次仅仅解析一条样本example
    example = tf.io.parse_single_example(serialized_example, features=features)
    
    return example['X'], example['Y']

下面获取整个数据

tfrecords_dataset = tf.data.TFRecordDataset("example.tfrecord")
tfrecords_dataset = tfrecords_dataset.map(parse_tfrecords)  # 其实就是解析没一个样本
for feature, label in tfrecords_dataset:
    print(feature, label)
    print("----------------------------------------")

'''运行结果如下:
tf.Tensor([1. 2. 3. 4.], shape=(4,), dtype=float32) tf.Tensor([1], shape=(1,), dtype=int64)
----------------------------------------
tf.Tensor([5. 6. 7. 8.], shape=(4,), dtype=float32) tf.Tensor([2], shape=(1,), dtype=int64)
----------------------------------------
tf.Tensor([11. 12. 13. 14.], shape=(4,), dtype=float32) tf.Tensor([3], shape=(1,), dtype=int64)
----------------------------------------
tf.Tensor([20. 21. 22. 23.], shape=(4,), dtype=float32) tf.Tensor([4], shape=(1,), dtype=int64)
----------------------------------------
(python3.7.4) facepro@huaxin:/media/huaxin/tcl3/facepro/hand-gesture-recognition/jester-data-prepr
'''

(2)一次性解析多条样本——tf.io.parse_exsample()

直接看代码:

def parse_tfrecords(serialized_example):
    # 定义解析的规则,需要注意的是,这里需要与数据保存是后定义的规则一致
    features = {
            'X':tf.io.FixedLenFeature([4], dtype=tf.float32),
            'Y': tf.io.FixedLenFeature([1], dtype=tf.int64) 
            }
    
    # 一次解析多个样本example,仅仅是这里的一句话不同而已
    example = tf.io.parse_example(serialized_example, features=features)
    
    return example['X'], example['Y']

解析的结果如下:

tfrecords_dataset = tf.data.TFRecordDataset("example.tfrecord")

# 一次解析多条样本可以使用batch方法,实际上就是一次解析一个batch的样本数据
tfrecords_dataset = tfrecords_dataset.repeat(3).batch(5) # 重复数据集
     
tfrecords_dataset = tfrecords_dataset.map(parse_tfrecords)  # 其实就是解析没一个样本
    
for feature, label in tfrecords_dataset:
    print(feature, label)
    print("----------------------------------------")

'''运行结果为:
tf.Tensor(
[[ 1.  2.  3.  4.]
 [ 5.  6.  7.  8.]
 [11. 12. 13. 14.]
 [20. 21. 22. 23.]
 [ 1.  2.  3.  4.]], shape=(5, 4), dtype=float32) tf.Tensor(
[[1]
 [2]
 [3]
 [4]
 [1]], shape=(5, 1), dtype=int64)
----------------------------------------
tf.Tensor(
[[ 5.  6.  7.  8.]
 [11. 12. 13. 14.]
 [20. 21. 22. 23.]
 [ 1.  2.  3.  4.]
 [ 5.  6.  7.  8.]], shape=(5, 4), dtype=float32) tf.Tensor(
[[2]
 [3]
 [4]
 [1]
 [2]], shape=(5, 1), dtype=int64)
----------------------------------------
tf.Tensor(
[[11. 12. 13. 14.]
 [20. 21. 22. 23.]], shape=(2, 4), dtype=float32) tf.Tensor(
[[3]
 [4]], shape=(2, 1), dtype=int64)
----------------------------------------
'''

总结:

从结果上来看,共四组样本,重复3次,一共12组样本,每一次解析一个batch=5的,最后一次只剩两组样本,从结果来看完全正确。

2.3 tensorflow2.x中的四种解析方式

tf.io.parse_example(serialized, features, example_names=None, name=None)

tf.io.parse_single_example(serialized, features, example_names=None, name=None)

# 下面是针对序列所组成的example而言的
tf.io.parse_sequence_example(serialized, context_features=None, sequence_features=None, example_names=None,name=None)

tf.io.parse_single_sequence_example(serialized, context_features=None, sequence_features=None, example_name=None,name=None)

共性总结:

  • 参数一:serialized,实际上就是表示的是一个序列化的样本,即dataset中的一个序列化的元素element;
  • 参数二:features,实际上是一个解析字典,这个字典是最核心的,定义了序列化的数据解析的规则,否则解析会失败

对于sequence example而言

  • 参数一:serialized,同上面
  • 参数二:context_features,实际上是针对序列中的定长特征而言的,一般如标签label
  • 参数三:sequence_features,实际上针对的是不定长feature而言的,这两个的具体含义可以参考前面一篇文章

另外,带有single的一次只能解析一条样本,即解析一个元素,这就不能对dataset进行batch操作,

不带有single的可以一次性解析多条样本,可以对dataset进行batch操作,一次性结息一个batch的样本

2.4 tfrecord四种解析方式的返回值

虽然在tfrecord上面的解析是大致相同的,但是每一个解析函数的返回值是不一样的,其中对于example的单个解析与批量解析代码几乎一样,只在于一个可以使用batch,一个不能使用,他们均只返回一个字典,如下:

example_dict = tf.io.parse_example(serialized, features, example_names=None, name=None)

example_dict = tf.io.parse_single_example(serialized, features, example_names=None, name=None)

注意:

返回的是一个dict字典,表示的是每一个Feature的key与value的映射,只需要按需求取得value即可。

但是对于sequence_example的单个解析和批量解析返回值是不一样的,一个返回两个字典,另一个返回三个字典,参见下面:

context_example_dict, sequence_example_dict  = tf.io.parse_single_sequence_example(serialized_example,context_features = context_features,sequence_features = sequence_features)


context_example_dict, sequence_example_dict, other_dict  = tf.io.parse_sequence_example(serialized_example,context_features = context_features,sequence_features = sequence_features)

注意:

前两个字典是一样的含义

  • context_example_dict :表示的是对于固定大小的context_feature的key与value的映射
  • sequence_example_dict :表示的是对于不固定大小的sequence_feature的key与value的映射

另外,批量解析的第三个字典

  •  final dict contains the lengths of any dense feature_list features.

 

三、解析函数的编写

我们实际上不一定非要编写解析函数,由于序列化元素的解析是通过dataset.map(func)来完成的,我们完全看可以在解析的时候通过lambda表达式来完成,但是由于我们一般需要保存成tfrecord文件的话都是比较复杂的数据,通过lambda表达式自然不好解析,因此我们最好是编写一个解析函数,解析函数的一般模板如下:

3.1 解析函数四不走原则

遵  循解析函数四步走  原则

(1)对于规则的example而言

def parse_tfrecords(serialized_example):
    # 第一步:需要有一个参数,这个参数实际上代表的意思就是每一次从dataset中取出的一个或者是一个batch的序列化的样本

    # 第二步:定义解析的规则,需要注意的是,这里需要与数据保存是后定义的规则一致
    features = {
            'X':tf.io.FixedLenFeature([4], dtype=tf.float32),
            'Y': tf.io.FixedLenFeature([1], dtype=tf.int64) 
            }
    
    # 第三步:解析一条或者是一个batch的样本
    example = tf.io.parse_example(serialized_example, features=features)
    
    # 第四步:返回解析之后的样本数据
    return example['X'], example['Y']

(2)对于不规则的sequence_example而言

依然是遵循解析的四步走原则,唯一不同的是,,我需要定义两个解析规则,分别包含

context_feature={}

sequence_feature={}

 

3.2 四步走的核心——定义解析字典(数据的两种解析方式)

核心就是定义一个字典,这个字典的键key要与写入tfrecord是的key保持一致,然后定义一个value存储从中取得的值,有两种存储方式,一是定长存储,另一种是变长存储。

(1)定长特征解析:tf.io.FixedLenFeature

即如下形式:

features = {
            'key1': tf.io.FixedLenFeature([4], dtype=tf.float32),
            'key2': tf.io.FixedLenFeature([1], dtype=tf.int64) 
            }

函数原型如下:

tf.io.FixedLenFeature(shape, dtype, default_value)
  • shape:即解析的这个value的形状,一般保持原始形状即可,,但是同也可以作为reshape,改变原来的形状

可当reshape来用,如vector的shape从(3,)改动成了(1,3)。注:如果写入的feature使用了.tostring() 其shape就是()

  • dtype:即原来数据的数据格式,必须是tf.float32, tf.int64, tf.string中的一种。
  • default_value:feature值缺失时所指定的值。

(2)不定长特征解析:tf.io.VarLenFeature(dtype)

一般格式如下:

features = {
            'key1': tf.io.VarLenFeature(dtype=dtype('float32')),
            'key2': tf.io.VarLenFeature(dtype=dtype('float32'))
}

函数原型如下:

tf.io.VarLenFeature(dtype)

它只有一个参数,即数据的类型,同样需要为必须是tf.float32, tf.int64, tf.string中的一种,

由于本身是存储不定长数据的,就没有shape这一个概念了。

特别注意:由于变长特征没有指定shape,但得到的tensor是SparseTensor。

 

3.3 解析结果的变换处理

根据前面的说明,每一次通过tf.io.parse_xxx()解析出来的样本依然是一个字典类型,

example = tf.io.parse_example(serialized_example, features=features)

即得到的example也是一个字典,其中每个key是对应feature的名字,value是相应的feature解析值。

如果使用了下面两种情况,则还需要对这些值进行转变。其他情况则不用。

(1)第一:将数据转化成了string,即使用了.tostring()

string类型:tf.decode_raw(parsed_feature, type) 来解码,注:这里type必须要和当初.tostring()化前的一致。如tensor转变前是tf.uint8,这里就需是tf.uint8;转变前是tf.float32,则tf.float32

(2)第二:即使用变长解析得到的sparse_tensor
VarLen解析:由于得到的是SparseTensor,所以视情况需要用

tf.sparse_tensor_to_dense(SparseTensor)来转变成DenseTensor——tensorflow1.x

tft.sparse.to_dense(sparseTensor)——tensorflow2.x

 

3.4 解析字典的编写依据

(1)依据一

由于存储tfrecord文件的时候就已经定义了存储规则,key的名称是什么,value的形状是什么,只需要按照这个规则再编写解析字典即可

(2)依据二

如果我本身不是数据的保存着,不也不知道tfrecord里面到地方了一些什么数据,我可以采用本文第一章里面的方法,先拿出来一个样本,查看他的概要信息,然后根据信息编写解析字典。

 

四、SequenceExample的数据解析——参照上一篇文章的sequence_example.tfrecord

直接上代码了

def parse_tfrecords(serialized_example):
    # 定义解析的规则,需要注意的是,这里需要与数据保存是后定义的规则一致
    context_features = {
            'Y': tf.io.FixedLenFeature([1], dtype=tf.int64) 
            }
    
    sequence_features = {
            'X': tf.io.VarLenFeature(dtype=tf.float32) 
            }
    
    # 一次仅仅解析一条样本example,返回解析的两个字点
    context_example, sequence_example = tf.io.parse_single_sequence_example(serialized_example,context_features = context_features,sequence_features = sequence_features)
    
    # 由于sequence数据是变长的,得到的是稀疏数据,即这里的sequence_example['X']是稀疏矩阵
    # 需要将稀疏矩阵转化成周密矩阵,在tf2.x版本如下
    # 具体参见sparse tensor到dense tensor的转化
    dense_feature = tf.sparse.to_dense(sequence_example['X'])
    
    return  dense_feature,context_example['Y']

    

if __name__ == "__main__":
    # generate_tfrecords()
    
    tfrecords_dataset = tf.data.TFRecordDataset("sequence_example.tfrecord")
  
    tfrecords_dataset = tfrecords_dataset.map(parse_tfrecords)  # 其实就是解析没一个样本
    
    for feature, label in tfrecords_dataset:
        print(feature.numpy(), label, sep="       ")
        print("----------------------------------------")
    

运行结果如下:

tf.Tensor([[1.]], shape=(1, 1), dtype=float32)       tf.Tensor([1], shape=(1,), dtype=int64)
----------------------------------------
tf.Tensor(
[[2.]
 [2.]], shape=(2, 1), dtype=float32)       tf.Tensor([2], shape=(1,), dtype=int64)
----------------------------------------
tf.Tensor(
[[3.]
 [3.]
 [3.]], shape=(3, 1), dtype=float32)       tf.Tensor([3], shape=(1,), dtype=int64)
----------------------------------------
tf.Tensor(
[[4.]
 [4.]
 [4.]
 [4.]], shape=(4, 1), dtype=float32)       tf.Tensor([4], shape=(1,), dtype=int64)
----------------------------------------
tf.Tensor(
[[5.]
 [5.]
 [5.]
 [5.]
 [5.]], shape=(5, 1), dtype=float32)       tf.Tensor([5], shape=(1,), dtype=int64)
----------------------------------------
tf.Tensor([[1.]], shape=(1, 1), dtype=float32)       tf.Tensor([1], shape=(1,), dtype=int64)
----------------------------------------
tf.Tensor(
[[2.]
 [2.]
 [3.]], shape=(3, 1), dtype=float32)       tf.Tensor([2], shape=(1,), dtype=int64)
----------------------------------------
tf.Tensor(
[[4.]
 [4.]
 [4.]
 [4.]], shape=(4, 1), dtype=float32)       tf.Tensor([3], shape=(1,), dtype=int64)
----------------------------------------
tf.Tensor(
[[6.]
 [7.]
 [8.]], shape=(3, 1), dtype=float32)       tf.Tensor([4], shape=(1,), dtype=int64)
----------------------------------------

这与前面一篇文章的数据是完全吻合的,说明解析正确了 

4.2 批量解析——参见前一篇文章的sequence_example.tfrecord

如下底代码:

def parse_tfrecords(serialized_example):
    # 定义解析的规则,需要注意的是,这里需要与数据保存是后定义的规则一致
    context_features = {
            'Y': tf.io.FixedLenFeature([1], dtype=tf.int64) 
            }
    
    sequence_features = {
            'X': tf.io.VarLenFeature(dtype=tf.float32) 
            }
    
    # 一次仅仅解析一条样本example,返回解析的  “三个”  字点,这里需要尤其注意,否则会出错
    context_example, sequence_example , _ = tf.io.parse_sequence_example(serialized_example,context_features = context_features,sequence_features = sequence_features)
    
    # 由于sequence数据是变长的,得到的是稀疏数据,即这里的sequence_example['X']是稀疏矩阵
    # 需要将稀疏矩阵转化成周密矩阵,在tf2.x版本如下
    # 具体参见sparse tensor到dense tensor的转化
    dense_feature = tf.sparse.to_dense(sequence_example['X'])
    
    return  dense_feature,context_example['Y']

    

if __name__ == "__main__":
    # generate_tfrecords()
        
    tfrecords_dataset = tf.data.TFRecordDataset("sequence_example.tfrecord")
    tfrecords_dataset = tfrecords_dataset.repeat(3).batch(3) # 批量读取
    
    tfrecords_dataset = tfrecords_dataset.map(parse_tfrecords)  # 其实就是解析没一个样本
    
    for feature, label in tfrecords_dataset:
        print(feature,label, sep="       ")
        
        print("---------------------------------------------------------")

运行结果如下:

 

tf.Tensor(
[[[1.]
  [0.]
  [0.]]

 [[2.]
  [2.]
  [0.]]

 [[3.]
  [3.]
  [3.]]], shape=(3, 3, 1), dtype=float32)       tf.Tensor(
[[1]
 [2]
 [3]], shape=(3, 1), dtype=int64)
--------------------------------------------------------------------------------------
tf.Tensor(
[[[4.]
  [4.]
  [4.]
  [4.]
  [0.]]

 [[5.]
  [5.]
  [5.]
  [5.]
  [5.]]

 [[1.]
  [0.]
  [0.]
  [0.]
  [0.]]], shape=(3, 5, 1), dtype=float32)       tf.Tensor(
[[4]
 [5]
 [1]], shape=(3, 1), dtype=int64)
--------------------------------------------------------------------------------------
tf.Tensor(
[[[2.]
  [2.]
  [3.]
  [0.]]

 [[4.]
  [4.]
  [4.]
  [4.]]

 [[6.]
  [7.]
  [8.]
  [0.]]], shape=(3, 4, 1), dtype=float32)       tf.Tensor(
[[2]
 [3]
 [4]], shape=(3, 1), dtype=int64)
--------------------------------------------------------------------------------------
tf.Tensor(
[[[1.]
  [0.]
  [0.]]

 [[2.]
  [2.]
  [0.]]

 [[3.]
  [3.]
  [3.]]], shape=(3, 3, 1), dtype=float32)       tf.Tensor(
[[1]
 [2]
 [3]], shape=(3, 1), dtype=int64)
--------------------------------------------------------------------------------------
tf.Tensor(
[[[4.]
  [4.]
  [4.]
  [4.]
  [0.]]

 [[5.]
  [5.]
  [5.]
  [5.]
  [5.]]

 [[1.]
  [0.]
  [0.]
  [0.]
  [0.]]], shape=(3, 5, 1), dtype=float32)       tf.Tensor(
[[4]
 [5]
 [1]], shape=(3, 1), dtype=int64)
--------------------------------------------------------------------------------------
tf.Tensor(
[[[2.]
  [2.]
  [3.]
  [0.]]

 [[4.]
  [4.]
  [4.]
  [4.]]

 [[6.]
  [7.]
  [8.]
  [0.]]], shape=(3, 4, 1), dtype=float32)       tf.Tensor(
[[2]
 [3]
 [4]], shape=(3, 1), dtype=int64)
--------------------------------------------------------------------------------------
tf.Tensor(
[[[1.]
  [0.]
  [0.]]

 [[2.]
  [2.]
  [0.]]

 [[3.]
  [3.]
  [3.]]], shape=(3, 3, 1), dtype=float32)       tf.Tensor(
[[1]
 [2]
 [3]], shape=(3, 1), dtype=int64)
--------------------------------------------------------------------------------------
tf.Tensor(
[[[4.]
  [4.]
  [4.]
  [4.]
  [0.]]

 [[5.]
  [5.]
  [5.]
  [5.]
  [5.]]

 [[1.]
  [0.]
  [0.]
  [0.]
  [0.]]], shape=(3, 5, 1), dtype=float32)       tf.Tensor(
[[4]
 [5]
 [1]], shape=(3, 1), dtype=int64)
--------------------------------------------------------------------------------------
tf.Tensor(
[[[2.]
  [2.]
  [3.]
  [0.]]

 [[4.]
  [4.]
  [4.]
  [4.]]

 [[6.]
  [7.]
  [8.]
  [0.]]], shape=(3, 4, 1), dtype=float32)       tf.Tensor(
[[2]
 [3]
 [4]], shape=(3, 1), dtype=int64)
--------------------------------------------------------------------------------------

有前面的数据可知,解析结果完全正确。 

 

 

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值