TensorflowIO操作

线程和队列

在使用TensorFlow进行异步计算时,队列是一种强大的机制。

为了感受一下队列,让我们来看一个简单的例子。我们先创建一个“先入先出”的队列(FIFOQueue),并将其内部所有元素初始化为零。然后,我们构建一个TensorFlow图,它从队列前端取走一个元素,加上1之后,放回队列的后端。慢慢地,队列的元素的值就会增加。

TensorFlow提供了两个类来帮助多线程的实现:tf.Coordinator和 tf.QueueRunner。Coordinator类可以用来同时停止多个工作线程并且向那个在等待所有工作线程终止的程序报告异常,QueueRunner类用来协调多个工作线程同时将多个张量推入同一个队列中。

队列概述

队列,如FIFOQueue和RandomShuffleQueue,在TensorFlow的张量异步计算时都非常重要。

例如,一个典型的输入结构:是使用一个RandomShuffleQueue来作为模型训练的输入:

  • 多个线程准备训练样本,并且把这些样本推入队列。
  • 一个训练线程执行一个训练操作

同步执行队列

# 创建一个队列
Q = tf.FIFOQueue(3, dtypes=tf.float32)

# 数据进队列
init = Q.enqueue_many(([0.1, 0.2, 0.3],))

# 定义操作,op,出队列,+1,进队列,注意返回的都是op
out_q = Q.dequeue()
data = out_q + 1
en_q = Q.enqueue(data)


with tf.Session() as sess:

    # 初始化队列,是数据进入
    sess.run(init)

    # 执行两次入队加1
    for i in range(2):
        sess.run(en_q)

    # 循环取队列
    for i in range(3):
        print(sess.run(Q.dequeue()))

 

分析:当数据量很大时,入队操作从硬盘中读取数据,放入内存中, 主线程需要等待入队操作完成,才能进行训练。会话里可以运行多个 线程,实现异步读取.

 

tf.QueueRunner

QueueRunner类会创建一组线程, 这些线程可以重复的执行Enquene操作, 他们使用同一个Coordinator来处理线程同步终止。此外,一个QueueRunner会运行一个closer thread,当Coordinator收到异常报告时,这个closer thread会自动关闭队列。

您可以使用一个queue runner,来实现上述结构。 首先建立一个TensorFlow图表,这个图表使用队列来输入样本。增加处理样本并将样本推入队列中的操作。增加training操作来移除队列中的样本。

分析:这时候有一个问题就是,入队自顾自的去执行,在需要的出 队操作完成之后,程序没法结束。需要一个实现线程间的同步,终 止其他线程。

tf.Coordinator

Coordinator类用来帮助多个线程协同工作,多个线程同步终止。 其主要方法有:

  • should_stop():如果线程应该停止则返回True。
  • request_stop(): 请求该线程停止。
  • join():等待被指定的线程终止。

首先创建一个Coordinator对象,然后建立一些使用Coordinator对象的线程。这些线程通常一直循环运行,一直到should_stop()返回True时停止。 任何线程都可以决定计算什么时候应该停止。它只需要调用request_stop(),同时其他线程的should_stop()将会返回True,然后都停下来。

异步执行队列:

#主线程,不断的去取数据,开启其它线程来进行增加计数,入队
#主线程结束了,队列线程没有结束,就会抛出异常
#主线程没有结束,需要将队列线程关闭,防止主线程等待

Q = tf.FIFOQueue(1000,dtypes=tf.float32)

# 定义操作
var = tf.Variable(0.0)
increment_op = tf.assign_add(var,tf.constant(1.0))
en_op = Q.enqueue(increment_op)

# 创建一个队列管理器,指定线程数,执行队列的操作
qr = tf.train.QueueRunner(Q,enqueue_ops=[increment_op,en_op]*3)

with tf.Session() as sess:
    tf.global_variables_initializer().run()

    # 生成一个线程协调器
    coord = tf.train.Coordinator()

    # 启动线程执行操作
    threads_list = qr.create_threads(sess,coord=coord,start=True)

    print(len(threads_list),"----------")
    # 主线程去取数据
    for i in range(20):
        print(sess.run(Q.dequeue()))

    # 请求其它线程终止
    coord.request_stop()

    # 关闭线程
    coord.join(threads_list)

文件读取

先看下文件读取以及读取数据处理成张量结果的过程:

 

一般数据文件格式有文本、excel和图片数据。那么TensorFlow都有对应的解析函数,除了这几种。还有TensorFlow指定的文件格式。

标准TensorFlow格式

TensorFlow还提供了一种内置文件格式TFRecord,二进制数据和训练类别标签数据存储在同一文件。模型训练前图像等文本信息转换为TFRecord格式。TFRecord文件是protobuf格式。数据不压缩,可快速加载到内存。TFRecords文件包含 tf.train.Example protobuf,需要将Example填充到协议缓冲区,将协议缓冲区序列化为字符串,然后使用该文件将该字符串写入TFRecords文件。在图像操作我们会介绍整个过程以及详细参数。

数据读取实现

文件队列生成函数

  • tf.train.string_input_producer(string_tensor, num_epochs=None, shuffle=True, seed=None, capacity=32, name=None)

产生指定文件张量

文件阅读器类

  • class tf.TextLineReader

阅读文本文件逗号分隔值(CSV)格式

  • tf.FixedLengthRecordReader

要读取每个记录是固定数量字节的二进制文件

  • tf.TFRecordReader

读取TfRecords文件

解码

由于从文件中读取的是字符串,需要函数去解析这些字符串到张量

  • tf.decode_csv(records,record_defaults,field_delim = None,name = None)将CSV转换为张量,与tf.TextLineReader搭配使用

  • tf.decode_raw(bytes,out_type,little_endian = None,name = None) 将字节转换为一个数字向量表示,字节为一字符串类型的张量,与函数tf.FixedLengthRecordReader搭配使用

生成文件队列

将文件名列表交给tf.train.string_input_producer函数。string_input_producer来生成一个先入先出的队列,文件阅读器会需要它们来取数据。string_input_producer提供的可配置参数来设置文件名乱序和最大的训练迭代数,QueueRunner会为每次迭代(epoch)将所有的文件名加入文件名队列中,如果shuffle=True的话,会对文件名进行乱序处理。一过程是比较均匀的,因此它可以产生均衡的文件名队列。

这个QueueRunner工作线程是独立于文件阅读器的线程,因此乱序和将文件名推入到文件名队列这些过程不会阻塞文件阅读器运行。根据你的文件格式,选择对应的文件阅读器,然后将文件名队列提供给阅读器的 read 方法。阅读器的read方法会输出一个键来表征输入的文件和其中纪录(对于调试非常有用),同时得到一个字符串标量,这个字符串标量可以被一个或多个解析器,或者转换操作将其解码为张量并且构造成为样本。

# 读取CSV格式文件
# 1、构建文件队列

# 2、构建读取器,读取内容

# 3、解码内容

# 4、现读取一个内容,如果有需要,就批处理内容
import tensorflow as tf
import os
def readcsv_decode(filelist):
    """
    读取并解析文件内容
    :param filelist: 文件列表
    :return: None
    """

    # 把文件目录和文件名合并
    flist = [os.path.join("./csvdata/",file) for file in filelist]

    # 构建文件队列
    file_queue = tf.train.string_input_producer(flist,shuffle=False)

    # 构建阅读器,读取文件内容
    reader = tf.TextLineReader()

    key,value = reader.read(file_queue)

    record_defaults = [["null"],["null"]] # [[0],[0],[0],[0]]

    # 解码内容,按行解析,返回的是每行的列数据
    example,label = tf.decode_csv(value,record_defaults=record_defaults)

    # 通过tf.train.batch来批处理数据
    example_batch,label_batch = tf.train.batch([example,label],batch_size=9,num_threads=1,capacity=9)


    with tf.Session() as sess:

        # 线程协调员
        coord = tf.train.Coordinator()

        # 启动工作线程
        threads = tf.train.start_queue_runners(sess,coord=coord)

        # 这种方法不可取
        # for i in range(9):
        #     print(sess.run([example,label]))

        # 打印批处理的数据
        print(sess.run([example_batch,label_batch]))


        coord.request_stop()

        coord.join(threads)

    return None


if __name__=="__main__":
    filename_list = os.listdir("./csvdata")
    readcsv_decode(filename_list)

每次read的执行都会从文件中读取一行内容,注意,(这与后面的图片和TfRecords读取不一样),decode_csv操作会解析这一行内容并将其转为张量列表。如果输入的参数有缺失,record_default参数可以根据张量的类型来设置默认值。在调用run或者eval去执行read之前,你必须调用tf.train.start_queue_runners来将文件名填充到队列。否则read操作会被阻塞到文件名队列中有值为止。

猜你喜欢

转载自blog.csdn.net/qq_35654080/article/details/88865056