http://wiki.jikexueyuan.com/project/tensorflow-zh/how_tos/reading_data.html#QueueRunner
http://wiki.jikexueyuan.com/project/tensorflow-zh/how_tos/threading_and_queues.html
https://haosdent.gitbooks.io/tensorflow-document/content/how_tos/
QueueRunner
对象来预取 简单来说:使用上面列出的许多tf.train
函数添加QueueRunner
到你的数据流图中。在你运行任何训练步骤之前,需要调用tf.train.start_queue_runners
函数,否则数据流图将一直挂起。tf.train.start_queue_runners
这个函数将会启动输入管道的线程,填充样本到队列中,以便出队操作可以从队列中拿到样本。这种情况下最好配合使用一个tf.train.Coordinator
,这样可以在发生错误的情况下正确地关闭这些线程。如果你对训练迭代数做了限制,那么需要使用一个训练迭代数计数器,并且需要被初始化。推荐的代码模板如下:
# Create the graph, etc.
init_op = tf.initialize_all_variables()
# Create a session for running operations in the Graph.
sess = tf.Session()
# Initialize the variables (like the epoch counter).
sess.run(init_op)
# Start input enqueue threads.
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=sess, coord=coord)
try:
while not coord.should_stop():
# Run training steps or whatever
sess.run(train_op)
except tf.errors.OutOfRangeError:
print 'Done training -- epoch limit reached'
finally:
# When done, ask the threads to stop.
coord.request_stop()
# Wait for threads to finish.
coord.join(threads)
sess.close()
首先,我们先创建数据流图,这个数据流图由一些流水线的阶段组成,阶段间用队列连接在一起。第一阶段将生成文件名,我们读取这些文件名并且把他们排到文件名队列中。第二阶段从文件中读取数据(使用Reader
),产生样本,而且把样本放在一个样本队列中。根据你的设置,实际上也可以拷贝第二阶段的样本,使得他们相互独立,这样就可以从多个文件中并行读取。在第二阶段的最后是一个排队操作,就是入队到队列中去,在下一阶段出队。因为我们是要开始运行这些入队操作的线程,所以我们的训练循环会使得样本队列中的样本不断地出队。
在tf.train
中要创建这些队列和执行入队操作,就要添加tf.train.QueueRunner
到一个使用tf.train.add_queue_runner
函数的数据流图中。每个QueueRunner
负责一个阶段,处理那些需要在线程中运行的入队操作的列表。一旦数据流图构造成功,tf.train.start_queue_runners
函数就会要求数据流图中每个QueueRunner
去开始它的线程运行入队操作。
如果一切顺利的话,你现在可以执行你的训练步骤,同时队列也会被后台线程来填充。如果您设置了最大训练迭代数,在某些时候,样本出队的操作可能会得到一个tf.OutOfRangeError
的错误。这其实是TensorFlow的“文件结束”(EOF) ———— 这就意味着已经达到了最大训练迭代数,已经没有更多可用的样本了。
最后一个因素是Coordinator
。这是负责在收到任何关闭信号的时候,让所有的线程都知道。最常用的是在发生异常时这种情况就会呈现出来,比如说其中一个线程在运行某些操作时出现错误(或一个普通的Python异常)。
想要了解更多的关于threading, queues, QueueRunners, and Coordinators的内容可以看这里.
想象一下,你有一个模型并且设置了最大训练迭代数。这意味着,生成文件的那个线程将只会在产生OutOfRange
错误之前运行许多次。该QueueRunner
会捕获该错误,并且关闭文件名的队列,最后退出线程。关闭队列做了两件事情:
OutOfRange
错误)。它们不会防止等待更多的元素被添加到队列中,因为上面的一点已经保证了这种情况不会发生。关键是,当在文件名队列被关闭时候,有可能还有许多文件名在该队列中,这样下一阶段的流水线(包括reader和其它预处理)还可以继续运行一段时间。 一旦文件名队列空了之后,如果后面的流水线还要尝试从文件名队列中取出一个文件名(例如,从一个已经处理完文件的reader中),这将会触发OutOfRange
错误。在这种情况下,即使你可能有一个QueueRunner关联着多个线程。如果这不是在QueueRunner中的最后那个线程,OutOfRange
错误仅仅只会使得一个线程退出。这使得其他那些正处理自己的最后一个文件的线程继续运行,直至他们完成为止。 (但如果假设你使用的是tf.train.Coordinator
,其他类型的错误将导致所有线程停止)。一旦所有的reader线程触发OutOfRange
错误,然后才是下一个队列,再是样本队列被关闭。
同样,样本队列中会有一些已经入队的元素,所以样本训练将一直持续直到样本队列中再没有样本为止。如果样本队列是一个RandomShuffleQueue
,因为你使用了shuffle_batch
或者shuffle_batch_join
,所以通常不会出现以往那种队列中的元素会比min_after_dequeue
定义的更少的情况。 然而,一旦该队列被关闭,min_after_dequeue
设置的限定值将失效,最终队列将为空。在这一点来说,当实际训练线程尝试从样本队列中取出数据时,将会触发OutOfRange
错误,然后训练线程会退出。一旦所有的培训线程完成,tf.train.Coordinator.join
会返回,你就可以正常退出了。
举个例子,有形式为[x, y, z]
的样本,我们可以生成一批形式为[batch, x, y, z]
的样本。 如果你想滤除这个记录(或许不需要这样的设置),那么可以设置batch的大小为0;但如果你需要每个记录产生多个样本,那么batch的值可以大于1。 然后很简单,只需调用批处理函数(比如:shuffle_batch
or shuffle_batch_join
)去设置enqueue_many=True
就可以实现。