Tensorflow基础0:文件的读取与存储

版权声明:未经本人同意不得转载! https://blog.csdn.net/yanpenggong/article/details/84680497

文件读取流程

学习目标

  • 目标
    • 说明TensorFlow文件读取的流程
  • 应用

有四种获取数据到TensorFlow程序的方法:

  1. tf.dataAPI:轻松构建复杂的输入管道。(优选方法,在新版本当中)
  2. QueueRunner:基于队列的输入管道从TensorFlow图形开头的文件中读取数据。
  3. Feeding:运行每一步时,Python代码提供数据。
  4. 预加载数据:TensorFlow图中的常量或变量包含所有数据(对于小数据集)。

1、文件读取流程

  • 第一阶段将生成文件名来读取它们并将它们排入文件名队列。
  • 第二阶段对于文件名的队列,进行出队列实例,并且实行内容的解码
  • 第三阶段重新入新的队列,这将是新的样本队列。

注:这些操作需要启动运行这些排队操作的线程,以便我们的训练循环可以将队列中的内容入队出队操作。

1.1 第一阶段

我们称之为构造文件队列,将需要读取的文件装入到一个固定的队列当中

  • tf.train.string_input_producer(string_tensor,shuffle=True)
    • string_tensor:含有文件名+路径的1阶张量
    • num_epochs:过几遍数据,默认无限过数据
    • return 文件队列

1.2、第二阶段

这里需要从队列当中读取文件内容,并且进行解码操作。关于读取内容会有一定的规则

1.2.1 读取文件内容

TensorFlow默认每次只读取一个样本,具体到文本文件读取一行、二进制文件读取指定字节数(最好一个样本)、图片文件默认读取一张图片、TFRecords默认读取一个example

  • tf.TextLineReader:
    • 阅读文本文件逗号分隔值(CSV)格式,默认按行读取
    • return:读取器实例
  • tf.WholeFileReader:用于读取图片文件
  • tf.TFRecordReader:
    • 读取TFRecords文件
  • tf.FixedLengthRecordReader:二进制文件
    • 要读取每个记录是固定数量字节的二进制文件
    • record_bytes:整型,指定每次读取(一个样本)的字节数
    • return:读取器实例

1、他们有共同的读取方法:read(file_queue):从队列中指定数量内容返回一个Tensors元组(key文件名字,value默认的内容(一个样本))

2、由于默认只会读取一个样本,所以通常想要进行批处理。使用tf.train.batch或tf.train.shuffle_batch进行多样本获取,便于训练时候指定每批次多个样本的训练

1.2.2 内容解码

对于读取不通的文件类型,内容需要解码操作,解码成统一的Tensor格式

  • tf.decode_csv:解码文本文件内容
  • tf.decode_raw:解码二进制文件内容
    • 与tf.FixedLengthRecordReader搭配使用,二进制读取为uint8格式
  • tf.image.decode_jpeg(contents)
    • 将JPEG编码的图像解码为uint8张量
    • return:uint8张量,3-D形状[height, width, channels]
  • tf.image.decode_png(contents)
    • 将PNG编码的图像解码为uint8张量
    • return:张量类型,3-D形状[height, width, channels]

解码阶段,默认所有的内容都解码成tf.uint8格式,如果需要后续的类型处理继续处理

1.3 第三阶段

在解码之后,我们可以直接获取默认的一个样本内容了,但是如果想要获取多个样本,这个时候需要结合管道的末尾进行批处理

  • tf.train.batch(tensors,batch_size,num_threads = 1,capacity = 32,name=None)
    • 读取指定大小(个数)的张量
    • tensors:可以是包含张量的列表,批处理的内容放到列表当中
    • batch_size:从队列中读取的批处理大小
    • num_threads:进入队列的线程数
    • capacity:整数,队列中元素的最大数量
    • return:tensors
  • tf.train.shuffle_batch

2、线程操作

以上的创建这些队列和排队操作称之为tf.train.QueueRunner。每个QueueRunner都负责一个阶段,并拥有需要在线程中运行的排队操作列表。一旦图形被构建, tf.train.start_queue_runners 函数就会要求图中的每个QueueRunner启动它的运行排队操作的线程。(这些操作需要在会话中开启)

  • tf.train.start_queue_runners(sess=None,coord=None)
    • 收集所有图中的队列线程,并启动线程
    • sess:所在的会话中
    • coord:线程协调器
    • return:返回所有线程
  • tf.train.Coordinator()
    • 线程协调员,实现一个简单的机制来协调一组线程的终止
    • request_stop():请求停止
    • should_stop():询问是否结束
    • join(threads=None, stop_grace_period_secs=120):回收线程
    • return:线程协调员实例

3、图像基本知识

对于图像文件,我们怎么进行转换成机器学习能够理解的数据。之前我们讲过文本怎么处理成数字信息。对于图片来讲,组成图片的最基本单位是像素,所以我们获取的是每张图片的像素值。接触的图片有两种,一种是黑白图片,另一种是彩色图片。

3.1 图片三要素

组成一张图片特征值是所有的像素值,有这么几个要素。图片长度、图片宽度、图片通道数。什么是图片的通道数呢,描述一个像素点,如果是灰度,那么只需要一个数值来描述它,就是单通道。如果一个像素点,有RGB三种颜色来描述它,就是三通道。那所以

  • 灰度图:单通道
  • 彩色图片:三通道

假设一张彩色图片的长200,宽200,通道数为3,那么总的像素数量为200 * 200 * 3

3.2 张量形状

读取图片之后,怎么用张量形状来表示呢。一张图片就是一个3D张量,[height, width, channel],height就表示高,width表示宽,channel表示通道数。我们会经常遇到3D和4D的表示

  • 单个图片:[height, width, channel]
  • 多个图片:[batch,height, width, channel],batch表示批数量

3.3 图片特征值处理

在进行图片识别的时候,每个图片样本的特征数量要保持相同。所以需要将所有图片张量大小统一转换。另一方面如果图片的像素量太大,也可以通过这种方式适当减少像素的数量,减少训练的计算开销

  • tf.image.resize_images(images, size)
    • 缩小放大图片
    • images:4-D形状[batch, height, width, channels]或3-D形状的张量[height, width, channels]的图片数据
    • size:1-D int32张量:new_height, new_width,图像的新尺寸
    • 返回4-D格式或者3-D格式图片

3.4 数据格式

  • 存储:uint8(节约空间)
  • 矩阵计算:float32(提高精度)

4、CIFAR10二进制数据集介绍

在这里插入图片描述

https://www.cs.toronto.edu/~kriz/cifar.html 
  • 二进制版本数据文件

二进制版本

二进制版本包含文件data_batch_1.bin,data_batch_2.bin,…,data_batch_5.bin以及test_batch.bin

。这些文件中的每一个格式如下,数据中每个样本包含了特征值和目标值:

<1×标签> <3072×像素> 
... 
<1×标签> <3072×像素>

第一个字节是第一个图像的标签,它是一个0-9范围内的数字。接下来的3072个字节是图像像素的值。前1024个字节是红色通道值,下1024个绿色,最后1024个蓝色。值以行优先顺序存储,因此前32个字节是图像第一行的红色通道值。 每个文件都包含10000个这样的3073字节的“行”图像,但没有任何分隔行的限制。因此每个文件应该完全是30730000字节长。

5、TFRecords

5.1 什么是TFRecords文件

TFRecords其实是一种二进制文件,虽然它不如其他格式好理解,但是它能更好的利用内存,更方便复制和移动,并且不需要单独的标签文件。

TFRecords文件包含了tf.train.Example 协议内存块(protocol buffer)(协议内存块包含了字段 Features)。可以获取你的数据, 将数据填入到Example协议内存块(protocol buffer),将协议内存块序列化为一个字符串, 并且通过tf.python_io.TFRecordWriter 写入到TFRecords文件。

  • 文件格式 *.tfrecords

5.2 Example结构解析

tf.train.Example 协议内存块(protocol buffer)(协议内存块包含了字段 Features),Features包含了一个Feature字段,Features中包含要写入的数据、并指明数据类型。这是一个样本的结构,批数据需要循环存入这样的结构

 example = tf.train.Example(features=tf.train.Features(feature={
                "image": tf.train.Feature(bytes_list=tf.train.BytesList(value=[image])),
                "label": tf.train.Feature(int64_list=tf.train.Int64List(value=[label])),
            }))
  • tf.train.Example(features=None)
    • 写入tfrecords文件
    • features:tf.train.Features类型的特征实例
    • return:example格式协议块
  • tf.train.Features(feature=None)
    • 构建每个样本的信息键值对
    • feature:字典数据,key为要保存的名字
    • value为tf.train.Feature实例
    • return:Features类型
  • tf.train.Feature(options)
    • options:例如
      • bytes_list=tf.train. BytesList(value=[Bytes])
      • int64_list=tf.train. Int64List(value=[Value])
    • 支持存入的类型如下
    • tf.train.Int64List(value=[Value])
    • tf.train.BytesList(value=[Bytes])
    • tf.train.FloatList(value=[value])

这种结构是不是很好的解决了数据和标签(训练的类别标签)或者其他属性数据存储在同一个文件中

完整Demo

# -*- coding=utf-8 -*-
import os
# os.environ["TF_CPP_MIN_LOG_LEVEL"]='1' # 这是默认的显示等级,显示所有信息  
os.environ["TF_CPP_MIN_LOG_LEVEL"]='2' # 只显示 warning 和 Error   
# os.environ["TF_CPP_MIN_LOG_LEVEL"]='3' # 只显示 Error

import tensorflow as tf


def picread(file_list):
    """
    读取狗图片数据到张量
    :param file_list:路径+文件名到列表
    :return:
    """
    # 1. 构建文件队列
    file_queue = tf.train.string_input_producer(file_list)

    # 2. 利用图片读取器去读取文件队列内容
    reader = tf.WholeFileReader()

    # 3. 默认一次一张图片,没有形状
    key, value = reader.read(file_queue)
    print(value)

    # 4. 对图片数据进行解码
    # string --> unit8
    # () ---> (?, ?, ?)
    image = tf.image.decode_jpeg(value)
    print(image)

    # 5. 图片的形状固定、大小处理
    # 把图像大小固定统一大小(算法训练要求样本的特征值数量一样)
    # 固定【200, 200]
    image_resize = tf.image.resize_images(image, size=[200, 200])
    print(image_resize)

    # 6. 设置图片形状
    image_resize.set_shape([200, 200, 3])

    # 7. 进行批处理
    # 3D --> 4D
    # tf.train.batch(tensors,batch_size,num_threads = 1,capacity = 32,name=None)
    #   - 读取指定大小(个数)的张量
    #   - tensors:可以是包含张量的列表,批处理的内容放到列表当中
    #   - batch_size:从队列中读取的批处理大小
    #   - num_threads:进入队列的线程数
    #   - capacity:整数,队列中元素的最大数量
    #   return:tensors
    image_batch = tf.train.batch([image_resize], batch_size=10, num_threads=1, capacity=8)
    print(image_batch)

    return image_batch


class Cifarread(object):
    """
    读取cifar10类别的二进制文件
    """

    def __init__(self):
        # 每个图像样本的属性
        self.height = 32
        self.width = 32
        self.channel = 3

        # bytes
        # 标签字节
        self.label_bytes = 1
        # 特征值字节
        self.image_bytes = self.height * self.width * self.channel
        # 总的字节数
        self.all_bytes = self.label_bytes + self.image_bytes

    def bytes_read(self, file_list):
        """
        读取二进制解码张量
        :param file_list: 路径+文件名到列表
        :return:
        """
        # 1. 构造文件队列
        file_queue = tf.train.string_input_producer(file_list)

        # 2. 使用tf.FixedLengthRecordReader(bytes)读取
        # 默认必须指定读取一个样本
        reader = tf.FixedLengthRecordReader(self.all_bytes)

        # 读取队列
        _, value = reader.read(file_queue)

        # 3. 解码操作
        # (?, )  (3.73, )  =  label(1, ) + feature(3072, )
        label_image = tf.decode_raw(value, tf.uint8)
        # 为了训练方便,一般会把特征值和目标值分开处理
        print(label_image)

        # 使用tf.slice进行切片,tf.cast更改所要的类型
        label = tf.cast(tf.slice(label_image, [0], [self.label_bytes]), tf.int32)  # tf.cast:将原来的unit8 改为 int32类型
        image = tf.slice(label_image, [self.label_bytes], [self.image_bytes])

        print(label, image)

        # 处理类型和图片数据的形状
        # 图片形状[32, 32, 3]
        # reshape (3072, )  --> [channel, height, width]
        # transpose [channel, height, width] --> [height, width, channel]
        depth_major = tf.reshape(image, [self.channel, self.height, self.width])
        print(depth_major)
        image_reshape = tf.transpose(depth_major, [1, 2, 0])
        print(image_reshape)

        # 4. 批处理
        # 3D --> 4D
        # tf.train.batch(tensors,batch_size,num_threads = 1,capacity = 32,name=None)
        #   - 读取指定大小(个数)的张量
        #   - tensors:可以是包含张量的列表,批处理的内容放到列表当中
        #   - batch_size:从队列中读取的批处理大小
        #   - num_threads:进入队列的线程数
        #   - capacity:整数,队列中元素的最大数量
        #   return:tensors
        image_batch, label_batch = tf.train.batch([image_reshape, label], batch_size=10, num_threads=1, capacity=10)

        return image_batch, label_batch

    def write_to_tfrecords(self, image_batch, label_batch):
        """
        将数据写入TFRecords文件
        :param image_batch: 特征值
        :param label_batch: 目标值
        :return:
        """
        # 构造TFRecords存储器
        writer = tf.python_io.TFRecordWriter("./temp/cifar.tfrecords")

        #  循环将每个样本构造成一个example,然后序列化写入
        for i in range(10):
            # 取出相应对第i个样本的特征值和目标值
            # 写入的是具体的张量的值,不是OP的名字
            # [10, 32, 32, 3]  --> [32, 32, 3]
            image = image_batch[i].eval().tostring()  # 将其转化为Bytes类型
            #[10, 1]
            label = int(label_batch[i].eval()[0])

            # 每个样本对example
            example = tf.train.Example(features=tf.train.Features(feature={
                "image": tf.train.Feature(bytes_list=tf.train.BytesList(value=[image])),
                "label": tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))
            }))

            # 写入第i个样本的example
            writer.write(example.SerializeToString())
        writer.close()
        return None

    def read_tfrecords(self):
        """
        读取tfrecords的数据
        :return: None
        """
        # 1、构造文件队列
        file_queue = tf.train.string_input_producer(["./temp/cifar.tfrecords"])

        # 2、构造tfrecords读取器,读取队列
        reader = tf.TFRecordReader()

        # 默认也是只读取一个样本
        key, values = reader.read(file_queue)

        # tfrecords
        # 多了解析example的一个步骤
        feature = tf.parse_single_example(values, features={
            "image": tf.FixedLenFeature([], tf.string),
            "label": tf.FixedLenFeature([], tf.int64)
        })

        # 取出feature里面的特征值和目标值
        # 通过键值对获取
        image = feature["image"]

        label = feature["label"]

        # 3、解码操作
        # 对于image是一个bytes类型,所以需要decode_raw去解码成uint8张量
        # 对于Label:本身是一个int类型,不需要去解码
        image = tf.decode_raw(image, tf.uint8)

        print(image, label)

        # # 从原来的[32,32,3]的bytes形式直接变成[32,32,3]
        # 不存在一开始我们的读取RGB的问题
        # 处理image的形状和类型
        image_reshape = tf.reshape(image, [self.height, self.width, self.channel])

        # 处理label的形状和类型
        label_cast = tf.cast(label, tf.int32)

        print(image_reshape, label_cast)

        # 4、批处理操作
        image_batch, label_batch = tf.train.batch([image_reshape, label_cast], batch_size=10, num_threads=1, capacity=10)

        print(image_batch, label_batch)
        return image_batch, label_batch


if __name__ == '__main__':
    # 例1:图像数据读取
    filename = os.listdir("../data/dog/")
    file_list = [os.path.join("../data/dog/", file) for file in filename]
    # 例 2:二进制数据读取
    filename_01 = os.listdir("../data/cifar10/cifar-10-batches-bin/")
    file_list_01 = [os.path.join("../data/cifar10/cifar-10-batches-bin/", file) for file in filename_01 if file[-3:] == "bin"]

    # print(file_list)

    # 0. 传入数据
    # 例1:
    image_batch = picread(file_list)
    # 例2:
    cr = Cifarread()
    image_batch_01, label_batch_01 = cr.bytes_read(file_list_01)
    # 例3:读取TFRecords的结果
    image_batch_02, label_batch_02 = cr.read_tfrecords()

    with tf.Session() as sess:
        # 8.创建线程回收的协调器
        # coord = tf.train.Coordinator()
        # 线程协调员, 实现一个简单的机制来协调一组线程的终止
        # coord.request_stop():请求停止
        # coord.should_stop():询问是否结束
        # coord.join(threads=None, stop_grace_period_secs=120):回收线程
        # return:线程协调员实例
        coord = tf.train.Coordinator()

        # 9. 需要手动开启子线程去进行批量处理读取到队列操作
        # tf.train.start_queue_runners(sess=None, coord=None)
        # 收集所有图中的队列线程,并启动线程
        #   - sess: 所在的会话中
        #   - coord:线程协调器
        # return:返回所有线程
        threads = tf.train.start_queue_runners(sess=sess, coord=coord)

        # ⚠️打印内容、运行:需要开启子线程去运行,子线程就把数据读取到队列,主线程取出数据去训练
        print("image:", sess.run(image_batch))
        print("Bytes:", sess.run([image_batch_01, label_batch_01]))
        print(".tfrecords:", sess.run([image_batch_02, label_batch_02]))

        # 写入文件
        # cr.write_to_tfrecords(image_batch_01, label_batch_01)

        # 10.回收线程
        coord.request_stop()
        coord.join(threads)

    with tf.Session() as sess:
        # 8.创建线程回收的协调器
        # coord = tf.train.Coordinator()
        # 线程协调员, 实现一个简单的机制来协调一组线程的终止
        # coord.request_stop():请求停止
        # coord.should_stop():询问是否结束
        # coord.join(threads=None, stop_grace_period_secs=120):回收线程
        # return:线程协调员实例
        coord = tf.train.Coordinator()

        # 9. 需要手动开启子线程去进行批量处理读取到队列操作
        # tf.train.start_queue_runners(sess=None, coord=None)
        # 收集所有图中的队列线程,并启动线程
        #   - sess: 所在的会话中
        #   - coord:线程协调器
        # return:返回所有线程
        threads = tf.train.start_queue_runners(sess=sess, coord=coord)

        # ⚠️打印内容、运行:需要开启子线程去运行,子线程就把数据读取到队列,主线程取出数据去训练
        print("image:", sess.run(image_batch))
        print("Bytes:", sess.run([image_batch_01, label_batch_01]))
        # print(".tfrecords:", sess.run([image_batch_02, label_batch_02]))

        # 写入文件
        cr.write_to_tfrecords(image_batch_01, label_batch_01)
        print("news_writer")

        # 10.回收线程
        coord.request_stop()
        coord.join(threads)

猜你喜欢

转载自blog.csdn.net/yanpenggong/article/details/84680497