疑问: 在达到最大训练迭代数的时候如何清理关闭线程?
想象一下,你有一个模型并且设置了最大训练迭代数。这意味着,生成文件的那个线程将只会在产生OutOfRange
错误之前运行许多次。该QueueRunner
会捕获该错误,并且关闭文件名的队列,最后退出线程。关闭队列做了两件事情:
艾伯特(http://www.aibbt.com/)国内第一家人工智能门户
- 如果还试着对文件名队列执行入队操作时将发生错误。任何线程不应该尝试去这样做,但是当队列因为其他错误而关闭时,这就会有用了。
- 任何当前或将来出队操作要么成功(如果队列中还有足够的元素)或立即失败(发生
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
就可以实现。
稀疏输入数据
SparseTensors这种数据类型使用队列来处理不是太好。如果要使用SparseTensors你就必须在批处理之后使用tf.parse_example
去解析字符串记录 (而不是在批处理之前使用 tf.parse_single_example
) 。
预取数据
这仅用于可以完全加载到存储器中的小的数据集。有两种方法:
- 存储在常数中。
- 存储在变量中,初始化后,永远不要改变它的值。
使用常数更简单一些,但是会使用更多的内存(因为常数会内联的存储在数据流图数据结构中,这个结构体可能会被复制几次)。
training_data = ...
training_labels = ...
with tf.Session():
input_data = tf.constant(training_data)
input_labels = tf.constant(training_labels)
...
要改为使用变量的方式,您就需要在数据流图建立后初始化这个变量。
training_data = ...
training_labels = ...
with tf.Session() as sess:
data_initializer = tf.placeholder(dtype=training_data.dtype,
shape=training_data.shape)
label_initializer = tf.placeholder(dtype=training_labels.dtype,
shape=training_labels.shape)
input_data = tf.Variable(data_initalizer, trainable=False, collections=[])
input_labels = tf.Variable(label_initalizer, trainable=False, collections=[])
...
sess.run(input_data.initializer,
feed_dict={data_initializer: training_data})
sess.run(input_labels.initializer,
feed_dict={label_initializer: training_lables})
设定trainable=False
可以防止该变量被数据流图的 GraphKeys.TRAINABLE_VARIABLES
收集, 这样我们就不会在训练的时候尝试更新它的值; 设定 collections=[]
可以防止GraphKeys.VARIABLES
收集后做为保存和恢复的中断点。
无论哪种方式,tf.train.slice_input_producer function
函数可以被用来每次产生一个切片。这样就会让样本在整个迭代中被打乱,所以在使用批处理的时候不需要再次打乱样本。所以我们不使用shuffle_batch
函数,取而代之的是纯tf.train.batch
函数。 如果要使用多个线程进行预处理,需要将num_threads
参数设置为大于1的数字。
在tensorflow/g3doc/how_tos/reading_data/fully_connected_preloaded.py
中可以找到一个MNIST例子,使用常数来预加载。 另外使用变量来预加载的例子在tensorflow/g3doc/how_tos/reading_data/fully_connected_preloaded_var.py
,你可以用上面 fully_connected_feed
和 fully_connected_reader
的描述来进行比较。
多输入管道
通常你会在一个数据集上面训练,然后在另外一个数据集上做评估计算(或称为 "eval")。 这样做的一种方法是,实际上包含两个独立的进程:
- 训练过程中读取输入数据,并定期将所有的训练的变量写入还原点文件)。
- 在计算过程中恢复还原点文件到一个推理模型中,读取有效的输入数据。
这两个进程在下面的例子中已经完成了:the example CIFAR-10 model,有以下几个好处:
- eval被当做训练后变量的一个简单映射。
- 你甚至可以在训练完成和退出后执行eval。
您可以在同一个进程的相同的数据流图中有训练和eval,并分享他们的训练后的变量。参考the shared variables tutorial.