原文地址:http://blog.csdn.net/lujiandong1/article/details/53376134
TensorFlow是一种符号编程框架(与theano类似),先构建数据流图再输入数据进行模型训练。Tensorflow支持很多种样例输入的方式。最容易的是使用placeholder,但这需要手动传递numpy.array类型的数据。第二种方法就是使用二进制文件和输入队列的组合形式。这种方式不仅节省了代码量,避免了进行data augmentation和读文件操作,可以处理不同类型的数据, 而且也不再需要人为地划分开“预处理”和“模型计算”。在使用TensorFlow进行异步计算时,队列是一种强大的机制。
正如TensorFlow中的其他组件一样,队列就是TensorFlow图中的节点。这是一种有状态的节点,就像变量一样:其他节点可以修改它的内容。具体来说,其他节点可以把新元素插入到队列后端(rear),也可以把队列前端(front)的元素删除。队列,如FIFOQueue和RandomShuffleQueue(A queue implementation that dequeues elements in a random order.)等对象,说明有时候我们读取的训练样本希望是有序的,如果希望是有序的读入训练样本,主要是训练RNN的时候需要,那么就是要用FIFOQueue,如果是无序地读入训练样本,那么就是使用RandomShuffleQueue,每次随机弹出一个样本。
在TensorFlow的tensor异步计算时都非常重要。例如,一个典型的输入结构是使用一个RandomShuffleQueue来作为模型训练的输入,多个线程准备训练样本,并且把这些样本压入队列,一个训练线程执行一个训练操作,此操作会从队列中移除最小批次的样本(mini-batches),这种结构具有许多优点。
TensorFlow的Session对象是可以支持多线程的,因此多个线程可以很方便地使用同一个会话(Session)并且并行地执行操作。然而,在Python程序实现这样的并行运算却并不容易。所有线程都必须能被同步终止,异常必须能被正确捕获并报告,会话终止的时候, 队列必须能被正确地关闭。所幸TensorFlow提供了两个类来帮助多线程的实现:tf.Coordinator和 tf.QueueRunner。从设计上这两个类必须被一起使用。Coordinator类可以用来同时停止多个工作线程并且向那个在等待所有工作线程终止的程序报告异常。QueueRunner类用来协调多个工作线程同时将多个tensor压入同一个队列中。
===================================================================================
下面,我们通过代码和流程图来解释tf使用队列读取数据的流程:
解释:在上图中,首先由一个单线程把文件名堆入队列,两个Reader同时从队列中取文件名并读取数据,Decoder将读出的数据解码后堆入样本队列,最后单个或批量取出样本(图中没有展示样本出列)
总结:搭建数据读取图需要5个步骤:
1、首先,将我们的数据转成相应的文件放入磁盘
2、把文件名堆入队列
3、从队列中取文件名并读取数据
4、Decoder将读出的数据解码
5、解码后堆入样本队列,最后单个或批量取出样本
=========================================================================================
下面,就使用代码一步步来解释上述的过程:
1、构造训练数据,使用tf.python_io.TFRecordWriter创建一个专门存储tensorflow数据的writer,扩展名为’.tfrecord’,最后存储的文件是序列化的文件
[python] view plain copy
-- coding: utf-8 --
import tensorflow as tf
import numpy
def write_binary():
writer = tf.python_io.TFRecordWriter(‘/home/jdlu/data.tfrecord’)
#创建example
for i in range(0, 100):
a = 0.618 + i
b = [2016 + i, 2017+i]
c = numpy.array([[0, 1, 2],[3, 4, 5]]) + i
c = c.astype(numpy.uint8)
c_raw = c.tostring() #转化成字符串
#每个example的feature成员变量是一个dict,存储一个样本的不同部分(例如图像像素+类标)
example = tf.train.Example(
features=tf.train.Features(
feature={
‘a’: tf.train.Feature(
float_list=tf.train.FloatList(value=[a])
),
'b': tf.train.Feature(
int64_list=tf.train.Int64List(value=b)
),
'c': tf.train.Feature(
bytes_list=tf.train.BytesList(value=[c_raw])
)
}
)
)
#序列化
serialized = example.SerializeToString()
#写入文件
writer.write(serialized)
writer.close()
2、把文件名堆入队列,从队列中取文件名并读取数据,Decoder将读出的数据解码 这三个步骤都放在一个函数里面,刚才的writer不同,这个reader是符号化的,只有在sess中run才会执行
[python] view plain copy
定义一个函数,创建从”文件中读一个样本”的操作
def read_single_sample(filename):
#创建文件队列,不限读取的数量
filename_queue = tf.train.string_input_producer([filename], num_epochs=None)
# create a reader from file queue
reader = tf.TFRecordReader()
#reader从文件队列中读入一个序列化的样本
_, serialized_example = reader.read(filename_queue)
# get feature from serialized example
#解析符号化的样本
features = tf.parse_single_example(
serialized_example,
features={
'a': tf.FixedLenFeature([], tf.float32),
'b': tf.FixedLenFeature([2], tf.int64),
'c': tf.FixedLenFeature([], tf.string)
}
)
a = features['a']
b = features['b']
c_raw = features['c']
c = tf.decode_raw(c_raw, tf.uint8)
c = tf.reshape(c, [2, 3])
return a, b, c
3、解码后堆入样本队列,最后单个或批量取出样本,是使用tf.train.shuffle_batch来实现的
[python] view plain copy
—–main function—–
if 0:
write_binary()
else:
# create tensor
a, b, c = read_single_sample(‘/home/jdlu/data.tfrecord’)
#shuffle_batch才能实现[a,b,c]的同步,也即特征和label的同步,不然可能输入的特征和label不匹配
#比如只有这样使用,才能使image和label一一对应,每次提取一个image和对应的label
#shuffle_batch返回的值就是RandomShuffleQueue.dequeue_many()的结果
#Shuffle_batch构建了一个RandomShuffleQueue,并不断地把单个的[a,b,c],送入队列中
a_batch, b_batch, c_batch = tf.train.shuffle_batch([a, b, c], batch_size=2, capacity=200, min_after_dequeue=100, num_threads=2)
# sess
sess = tf.Session()
init = tf.initialize_all_variables()
sess.run(init)
tf.train.start_queue_runners(sess=sess)
#print sess.run(a),sess.run(b),sess.run(c)
#print sess.run(a),sess.run(b),sess.run(c)
#print sess.run(a),sess.run(b),sess.run(c)
for step in range(3):
a_val, b_val, c_val = sess.run([a_batch, b_batch, c_batch])
print(a_val, b_val, c_val)
注意:必须使用tf.train.shuffle_batch,不然提取出来的样本会出现特征和label不对应的情况,比如说我们文件中的样本是(a1,b1,c1),(a2,b2,c2),不使用tf.train.shuffle_batch,可能提取出来的样本是(a1,b1,c2),也即出现特征和label不对应的情况。
对tf.train.shuffle_batch功能的解释:
读取batch数据需要使用新的队列queues和QueueRunners(大致流程图如下)。Shuffle_batch构建了一个RandomShuffleQueue,并不断地把单个的[a,b,c](a,b,c是一一对应的)送入队列中,这个入队操作是通过QueueRunners启动另外的线程来完成的。这个RandomShuffleQueue会顺序地压样例到队列中,直到队列中的样例个数达到了batch_size+min_after_dequeue个。它然后从队列中选择batch_size个随机的元素进行返回。事实上,shuffle_batch返回的值就是RandomShuffleQueue.dequeue_many()的结果。有了这个batches变量,就可以开始训练机器学习模型了。总结,tf.train.shuffle_batch的功能就是将我们解码完的样本放到一个随机队列中,然后在我们需要的时候弹出一个batch_size的样本。
==================================================================================
可想而知,在你运行任何训练步骤之前,我们要告知tensorflow去启动这些线程,否则这些队列会因为等待数据入队而被堵塞,导致数据流图将一直处于挂起状态。我们可以调用tf.train.start_queue_runners(sess=sess)来启动所有的QueueRunners。这个调用并不是符号化的操作,它会启动输入管道的线程,填充样本到队列中,以便出队操作可以从队列中拿到样本。另外,必须要先运行初始化操作再创建这些线程。如果这些队列未被初始化,tensorflow会抛出错误。