• 揭秘关于TFRcord的五脏六腑


    揭秘关于TFRcord的五脏六腑

    前言:本篇文章将演示如何创建、解析和使用tf.Example消息,以及如何在.tfrecord文件之间对tf.Example消息进行序列化、写入和读取。

    教程讲解使用的都是结构化数据,文章最后还会演示如果将图片写成.tfrecord文件,这在同个数据集用于不同模型情景之下非常有用。

    官网文档是从讲原理,然后再展现示例。我觉得这种方式很容易劝退小白,因为原理晦涩难懂。这里先展示示例,然后再肢解示例,先总体再细分的思想

    希望能让读者更容易理解和接受

    一、如何将标量输入值变成协议消息

    这里先不解释什么是协议消息,先看示例,后解释

    # todo 导入相应工具包
    import tensorflow as tf
    
    import numpy as np
    import IPython.display as display
    
    # todo 为了讲解,模拟生成一些数据
    # 这里准备生成10000个元素
    n_observations = int(1e4)
    
    # 随机生成10000个True和False布尔值
    feature0 = np.random.choice([False, True], n_observations)
    
    # 随机生成10000个0-5的整数
    feature1 = np.random.randint(0, 5, n_observations)
    
    # 随机生成10000个值是以下字符串的字符串
    strings = np.array([b'cat', b'dog', b'chicken', b'horse', b'goat'])
    feature2 = strings[feature1]
    
    # 随机生成10000个01正太分布数据,数据是浮点型
    feature3 = np.random.randn(n_observations)
    
    # todo 下面分别演示将字符串类型标量、浮点型标量、整数型标量转换成协议消息
    # todo 这些方法可以复制过去直接使用
    def _bytes_feature(value):
      """输入string / byte类型数据,返回bytes_list类型的协议消息"""
      if isinstance(value, type(tf.constant(0))):
        value = value.numpy() # BytesList won't unpack a string from an EagerTensor.
      return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))  # 先记住这个写法,这个写法是TF官网推荐的写法
    
    def _float_feature(value):
      """输入float / double类型数据,返回float_list类型的协议消息"""
      return tf.train.Feature(float_list=tf.train.FloatList(value=[value]))
    
    def _int64_feature(value):
      """输入bool / enum / int / uint.类型数据,返回int64_list类型的协议消息"""
      return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))
    
    # todo 通过调用上面的方法可以做到将相应类型的标量转换成协议消息了
    print(_bytes_feature(b'test_string'))  # 字符串
    print(_bytes_feature(u'test_bytes'.encode('utf-8')))  # 二进制bytes
    
    print(_float_feature(np.exp(1)))  # 浮点型,自然数e
    
    print(_int64_feature(True))  # 布尔型
    print(_int64_feature(1))  # 整型
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    结果如下。结果展示的就是协议消息的“样子”,协议消息就是长这样,协议消息对应的值就是value里面的值

    bytes_list {
      value: "test_string"
    }
    
    bytes_list {
      value: "test_bytes"
    }
    
    float_list {
      value: 2.7182817459106445
    }
    
    int64_list {
      value: 1
    }
    
    int64_list {
      value: 1
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    二、如何将协议消息变成二进制字符串

    接着上面代码的延续,先不解释什么是二进制字符串,读者先阅读一遍代码的注释

    # todo 将浮点型标量转成协议消息
    feature = _float_feature(np.exp(1))
    
    # todo 使用方法SerializeToString(),将协议消息变成二进制字符串
    string_010 = feature.SerializeToString()
    
    print(string_010)  # 结果是:   b'\x12\x06\n\x04T\xf8-@'
    
    """
    b'\x12\x06\n\x04T\xf8-@'
    就是二进制字符串的样子
    你可以简单把它理解成字符串
    """
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    三、如何将协议消息变成字典协议消息

    字典型的协议消息如下,读者先看一遍代码的注释,再进行理解一遍

    # todo 构建不同类型的协议消息
    bytes_fea = _bytes_feature(b'test_string')  # 字符串协议消息
    
    float_fea = _float_feature(np.exp(1))  # 浮点型协议消息
    
    int64_fea = _int64_feature(1)  # 整数型协议消息
    
    # todo 将协议消息变成字典型协议消息
    dict_fea = {
        'featrue0':bytes_fea,
        'featrue1':float_fea,
        'featrue2':int64_fea,
    }
    """
    说明:
    dict_fea就是字典型协议消息,其中字典的key可以顺便自己命名,而字典的value就是协议消息
    """
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    字典型协议消息就是字典的value就是协议消息的字典

    四、如何将字典协议消息变成二进制字符串

    做法就是将字典协议消息转成特征消息,因为特征消息是协议消息的一种,所以可以将特征消息根据方法SerializeToString()变成二进制字符串

    # todo 构建不同类型的协议消息
    bytes_fea = _bytes_feature(b'test_string')  # 字符串协议消息
    
    float_fea = _float_feature(np.exp(1))  # 浮点型协议消息
    
    int64_fea = _int64_feature(1)  # 整数型协议消息
    
    # todo 将协议消息变成字典型协议消息
    dict_fea = {
        'featrue0':bytes_fea,
        'featrue1':float_fea,
        'featrue2':int64_fea,
    }
    
    # todo 将协议消息变成特征消息,代码就是这么写的,读者根据官网推荐这些写法这么写即可
    example_proto = tf.train.Example(features=tf.train.Features(feature=dict_fea))
    
    # todo 将特征消息转成二进制字符串
    bytes_string = example_proto.SerializeToString()
    print(bytes_string)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    结果如下。显示的就是字典协议消息的二进制字符串

    b'\nF\n\x1b\n\x08featrue0\x12\x0f\n\r\n\x0btest_string\n\x14\n\x08featrue1\x12\x08\x12\x06\n\x04T\xf8-@\n\x11\n\x08featrue2\x12\x05\x1a\x03\n\x01\x01'
    
    • 1

    五、关于TFRecord文件各种疑问

    • 什么是TFRecord文件

      TFRecord 格式是一种用于存储二进制字符串记录序列的简单格式。

    • 为什么要用TFRecord文件

      通过tfrecord文件建立的数据管道Dataset对象读数的性能更好

    • TFRecord格式详细信息

      TFRecord 文件包含一系列记录。该文件只能按顺序读取。

      每条记录包含一个字节字符串(用于数据有效负载),外加数据长度,以及用于完整性检查的 CRC32C(使用 Castagnoli 多项式的 32 位 CRC)哈希值。

      每条记录会存储为以下格式:

      uint64 length uint32 masked_crc32_of_length byte   data[length] uint32 masked_crc32_of_data
      
      • 1

      将记录连接起来以生成文件。此处对 CRC 进行了说明,且 CRC 的掩码为:

      masked_crc = ((crc >> 15) | (crc << 17)) + 0xa282ead8ul
      
      • 1

      注:不需要在 TFRecord 文件中使用 tf.Exampletf.Example 只是将字典序列化为字节字符串的一种方法。文本行、编码的图像数据,或序列化的张量(使用 tf.io.serialize_tensor,或在加载时使用 tf.io.parse_tensor)。有关更多选项,请参阅 tf.io 模块。

    六、TFRecord文件如何存储数据

    TFRecord文件存储的是二进制字符串,二进制字符串由协议消息或者是字典协议消息生成,协议消息又由标量或者是向量生成。

    所以TFRecord存储的二进制字符串相当于存储了真实的数据

    之所以要通过协议消息、字典协议消息来存储这些数据,是因为这样子可以提高数据的复用率和使用效率

    七、将numpy数据存储成TFRecord格式文件

    读者可以直接阅读代码的注释.从上往下阅读即可,不需要觉得很难

    # todo 导入相应工具包
    import tensorflow as tf
    
    import numpy as np
    import IPython.display as display
    
    # todo 为了讲解,模拟生成一些数据
    # 这里准备生成10000个元素
    n_observations = int(1e4)
    
    # 随机生成10000个True和False布尔值
    feature0 = np.random.choice([False, True], n_observations)
    
    # 随机生成10000个0-5的整数
    feature1 = np.random.randint(0, 5, n_observations)
    
    # 随机生成10000个值是以下字符串的字符串
    strings = np.array([b'cat', b'dog', b'chicken', b'horse', b'goat'])
    feature2 = strings[feature1]
    
    # 随机生成10000个01正太分布数据,数据是浮点型
    feature3 = np.random.randn(n_observations)
    
    # todo 下面分别演示将字符串类型标量、浮点型标量、整数型标量转换成协议消息
    # todo 这些方法可以复制过去直接使用
    def _bytes_feature(value):
      """输入string / byte类型数据,返回bytes_list类型的协议消息"""
      if isinstance(value, type(tf.constant(0))):
        value = value.numpy() # BytesList won't unpack a string from an EagerTensor.
      return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))  # 先记住这个写法,这个写法是TF官网推荐的写法
    
    def _float_feature(value):
      """输入float / double类型数据,返回float_list类型的协议消息"""
      return tf.train.Feature(float_list=tf.train.FloatList(value=[value]))
    
    def _int64_feature(value):
      """输入bool / enum / int / uint.类型数据,返回int64_list类型的协议消息"""
      return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))
    
    
    def serialize_example(feature0, feature1, feature2, feature3):
      """
      输入标量,转成字典协议消息,转成特征消息,转成二进制字符串
      """
      # 构建字典协议消息
      feature = {
          'feature0': _int64_feature(feature0),
          'feature1': _int64_feature(feature1),
          'feature2': _bytes_feature(feature2),
          'feature3': _float_feature(feature3),
      }
    
      # Create a Features message using tf.train.Example.
    
      example_proto = tf.train.Example(features=tf.train.Features(feature=feature))
      return example_proto.SerializeToString()
    
    # todo 将二进制字符串写进tfrecord文件中
    filename = './test.tfrecord'
    with tf.io.TFRecordWriter(filename) as writer:  # 构建一个写入对象的上下文
      for i in range(n_observations):  # 循环写入
        example = serialize_example(feature0[i], feature1[i], feature2[i], feature3[i])
        writer.write(example)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    <ipython-input-62-e109c8c4da87>:38: DeprecationWarning: In future, it will be an error for 'np.bool_' scalars to be interpreted as an index
      return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))
    
    • 1
    • 2

    从上面可以看出来,上面的代码需要循环写入数据,速度比较慢,下面介绍一种速度更加快速的写入方法

    八、通过Dataset将numpy数据存储成TFRecord格式文件

    过程代码解释了原理,并且这些代码都可以进行复用,请读者认真消化

    # todo 导入相应工具包
    import tensorflow as tf
    
    import numpy as np
    import IPython.display as display
    
    # todo 为了讲解,模拟生成一些数据
    # 这里准备生成10000个元素
    n_observations = int(1e4)
    
    # 随机生成10000个True和False布尔值
    feature0 = np.random.choice([False, True], n_observations)
    
    # 随机生成10000个0-5的整数
    feature1 = np.random.randint(0, 5, n_observations)
    
    # 随机生成10000个值是以下字符串的字符串
    strings = np.array([b'cat', b'dog', b'chicken', b'horse', b'goat'])
    feature2 = strings[feature1]
    
    # 随机生成10000个01正太分布数据,数据是浮点型
    feature3 = np.random.randn(n_observations)
    
    # todo 下面分别演示将字符串类型标量、浮点型标量、整数型标量转换成协议消息
    # todo 这些方法可以复制过去直接使用
    def _bytes_feature(value):
      """输入string / byte类型数据,返回bytes_list类型的协议消息"""
      if isinstance(value, type(tf.constant(0))):
        value = value.numpy() # BytesList won't unpack a string from an EagerTensor.
      return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))  # 先记住这个写法,这个写法是TF官网推荐的写法
    
    def _float_feature(value):
      """输入float / double类型数据,返回float_list类型的协议消息"""
      return tf.train.Feature(float_list=tf.train.FloatList(value=[value]))
    
    def _int64_feature(value):
      """输入bool / enum / int / uint.类型数据,返回int64_list类型的协议消息"""
      return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))
    
    
    def serialize_example(feature0, feature1, feature2, feature3):
      """
      输入标量,转成字典协议消息,转成特征消息,转成二进制字符串
      """
      # 构建字典协议消息
      feature = {
          'feature0': _int64_feature(feature0),
          'feature1': _int64_feature(feature1),
          'feature2': _bytes_feature(feature2),
          'feature3': _float_feature(feature3),
      }
    
      # Create a Features message using tf.train.Example.
    
      example_proto = tf.train.Example(features=tf.train.Features(feature=feature))
      return example_proto.SerializeToString()
    
    # todo 构建Dataset对象
    features_dataset = tf.data.Dataset.from_tensor_slices((feature0, feature1, feature2, feature3))
    
    def tf_serialize_example(f0,f1,f2,f3):
      tf_string = tf.py_function(  # py_function函数将python函数转成可以通过tf计算图进行运算的函数,提升速度的关键就是在此
        serialize_example,
        (f0,f1,f2,f3),  # 传到函数serialize_example的参数
        tf.string)      # 指定函数serialize_example返回值的类型
      return tf.reshape(tf_string, ()) # 返回结果
    
    # todo 构建一个生成器
    def generator():
      for features in features_dataset:
        yield serialize_example(*features)
    
    if True:
        # todo 通过map函数将函数tf_serialize_example作用到features_dataset对象中的每个元素中,生成一个新的Dataset对象
        serialized_features_dataset = features_dataset.map(tf_serialize_example)
    else:
    	# todo 也可以直接通过生成器构建需要的Dataset对象
    	serialized_features_dataset = tf.data.Dataset.from_generator(
        generator, # 可调用的生成器函数
        output_types=tf.string, # 输出数据的类型
        output_shapes=()   # 输出数据的形状
        )
        
    # todo 通过Dataset对象,将数据存储成TFRecord格式文件
    filename = 'test.tfrecord'
    writer = tf.data.experimental.TFRecordWriter(filename)
    writer.write(serialized_features_dataset)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    九、通过Dataset对象读取TFRecord格式文件

    过程代码解释了原理,并且这些代码都可以进行复用,请读者认真消化。定义特征描述,这个非常重要,主要是定义了特征描述的形状和类型

    
    # 将TFRecord文件读成Dataset对象
    filenames = [filename]
    raw_dataset = tf.data.TFRecordDataset(filenames)
    
    # todo 定义特征描述,这个非常重要,格式如下,主要是定义了特征描述的形状和类型
    feature_description = {
        'feature0': tf.io.FixedLenFeature([], tf.int64, default_value=0),
        'feature1': tf.io.FixedLenFeature([], tf.int64, default_value=0),
        'feature2': tf.io.FixedLenFeature([], tf.string, default_value=''),
        'feature3': tf.io.FixedLenFeature([], tf.float32, default_value=0.0),
    }
    
    def _parse_function(example_proto):
      # 将二进制字符串转成实际的存储数据
      return tf.io.parse_single_example(example_proto, feature_description)
    
    # 运用map函数将函数_parse_function作用到Dataset对象的每个元素里面
    parsed_dataset = raw_dataset.map(_parse_function)
    
    # parsed_dataset对象的每个元素就是真实的存储数据
    for parsed_record in parsed_dataset.take(10):
      print(repr(parsed_record))
      break
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    结果如下

    {'feature0': <tf.Tensor: shape=(), dtype=int64, numpy=1>, 'feature1': <tf.Tensor: shape=(), dtype=int64, numpy=2>, 'feature2': <tf.Tensor: shape=(), dtype=string, numpy=b'chicken'>, 'feature3': <tf.Tensor: shape=(), dtype=float32, numpy=-0.10460473>}
    
    • 1

    10、欢迎关注,下期讲解使用TFRecord文件读取和写入图像数据

  • 相关阅读:
    达梦SQL语法兼容笔记
    【Python基础篇016】异常处理的超详细讲解
    node.js学习之http模块笔记
    基于springboot+vue的戒毒所人员管理系统毕业设计源码251514
    分享Web端即时通讯的发展与WebSocket的实践
    Qt超时机制设计
    SpringCloud学习笔记(上):服务注册与发现:Eureka、Zookeeper、Consul+负载均衡服务调用:Ribbon
    Springboot应用在k8s集群中解耦配置项
    RK3568 + YT 9215交换机芯片,MAC TO MAC 调试记录
    C++面试 -操作系统-架构能力:系统网络性能评估与优化
  • 原文地址:https://blog.csdn.net/my_name_is_learn/article/details/127127491