本文所有内容均不是原创,仅是看到好的资源,转载到这里,方便自己查找,所有来源都会标注清楚。
https://www.cnblogs.com/demian/p/8005407.html
如果你打开了这篇博客,推荐去上面链接读原文,原文写的更好。
Tf中的队列tensorflow的session对象可以支持多线程,多线程可以方便地使用同一个Session并执行,然而,Python中的并行运算并不容易。所有的线程必须能被同部终止,异常也必须能被正确捕获并报告,会话终止时,队列必须能够被正确关闭。
Tensorflow提供了两个类来实现多线程:tf.Coordinator和tf.QueueRunner
tf.Coordinator类用于同时停止多个线程并向等待所有线程终止的程序报告异常
tf.QueueRunner类用于协调多个线程同时将多个张量推入同一队列中。存储操作队列
队列tf.FIFOQueue(capacity, dtypes)
Capacity:制定队列中元素数量的上限
Dtypes:Dtype对象的列表,dtypes的长度必须等于每个队列元素中张量的数量。
注意:tf.initialize_all_variables()被弃用,采用tf.global_variables_initializer()
Tensorflow读取数据的基本机制:将读入数据和计算分别放在两个线程中,读取线程源源不断地将文件系统中的数据读入到一个内存队列中,负责计算的则是另一个线程,计算需要数据时,直接从内存队列中取就可以了。Tensorflow使用文件名队列+内存队列双队列的形式读入文件,可以很好地管理epoch。
对于文件名队列,使用tf.train.string_input_producer生成,tf.train.string_input_producer格式如下
filename_queue = tf.train.string_input_producer([tfRecord_path]) tf.train.string_input_producer( string_tensor, num_epochs=None, shuffle=True, seed=None, capacity=32, shared_name=None, name=None, cancel_op=None)
该函数会生成一个先入先出的队列,文件阅读器会使用它来读取数据。
参数说明:
string_tensor: TFRecord 文件名列表
num_epochs: 循环读取的轮数(可选)
shuffle:布尔值(可选),如果为 True,则在每轮随机打乱读取顺序
seed:随机读取时设置的种子(可选)
capacity:设置队列容量shared_name:(可选) 如果设置,该队列将在多个会话中以给定名称共享。所有具有此队列的设备都可以通过 shared_name 访问它。在分布式设置中使用这种方法意味着每个名称只能被访问此操作的其中一个会话看到。
name:操作的名称(可选)
cancel_op:取消队列(None)
使用tf.train.string_input_producer创建文件名队列后,整个系统其实还是处于“停滞状态”的,也就是说,我们文件名并没有真正被加入到队列中,此时如果我们开始计算,因为内存队列中什么也没有,计算单元就会一直等待,导致整个系统被阻塞。使用tf.train.start_queue_runners之后,才会启动填充队列的线程,这时系统就不再“停滞”。此后计算单元就可以拿到数据并进行计算,整个程序也就跑起来了。
Queue:tf队列和缓存机制的实现;
类型:
tf.FIFOQueue先进先出队列
tf.RandomShuffleQueue随机出列队列
tf.PaddingFIFOQueue以固定长度批量出列队列
tf.PriorityQueue带优先级出列队列
操作:
入列enqueue返回计算图中的一个Operation节点
出列dequeue返回一个Tensor值
QueueRunner:tf中对操作Queue的线程的封装;
Tensorflow计算主要使用CPU/GPU和内存,而数据读取涉及磁盘操作,速度远低于计算,因此通常会使用多个线程读取数据,然后使用一个线程计算数据,QueueRunner则是管理这些读写队列的线程。
代码:
import tensorflow as tf
import sys
q = tf.FIFOQueue(10, "float")
counter = tf.Variable(0.0) #计数器
# 给计数器加一
increment_op = tf.assign_add(counter, 1.0)
# 将计数器加入队列
enqueue_op = q.enqueue(counter)
# 创建QueueRunner
# 用多个线程向队列添加数据
# 这里实际创建了4个线程,两个增加计数,两个执行入队
qr = tf.train.QueueRunner(q, enqueue_ops=[increment_op, enqueue_op] * 2)
# 主线程
sess = tf.InteractiveSession()
tf.global_variables_initializer().run()
# 启动入队线程
qr.create_threads(sess, start=True)
for i in range(20):
print (sess.run(q.dequeue()))
上述代码解释:增加计数的进程会不停的后台运行,执行入队的进程会先执行10次(因为队列长度只有10),然后主线程开始消费数据,当一部分数据消费被后,入队的进程又会开始执行。最终主线程消费完20个数据后停止,但其他线程继续运行,程序不会结束。
Coordinator:tf中用来协调线程运行的工具,保存线程组运行状态的协调器对象。
要么用create_threads创建并启动线程,要么tf.train.start_queue_runners(sess = sess)启动线程,两者效果一样。
Create_threads代码如下:
import tensorflow as tf
# 1000个4维输入向量,每个数取值为1-10之间的随机数
data = 10 * np.random.randn(1000, 4) + 1
# 1000个随机的目标值,值为0或1
target = np.random.randint(0, 2, size=1000)
# 创建Queue,队列中每一项包含一个输入数据和相应的目标值
queue = tf.FIFOQueue(capacity=50, dtypes=[tf.float32, tf.int32], shapes=[[4], []])
# 批量入列数据(这是一个Operation)
enqueue_op = queue.enqueue_many([data, target])
# 出列数据(这是一个Tensor定义)
data_sample, label_sample = queue.dequeue()
# 创建包含4个线程的QueueRunner
qr = tf.train.QueueRunner(queue, [enqueue_op] * 4)
with tf.Session() as sess:
# 创建Coordinator
coord = tf.train.Coordinator()
# 启动QueueRunner管理的线程
enqueue_threads = qr.create_threads(sess, coord=coord, start=True)
# 主线程,消费100个数据
for step in range(100):
if coord.should_stop():
break
data_batch, label_batch = sess.run([data_sample, label_sample])
# 主线程计算完成,停止所有采集数据的进程
coord.request_stop()
coord.join(enqueue_threads)
start_queue_runners代码如下:
import tensorflow as tf
# 同时打开多个文件,显示创建Queue,同时隐含了QueueRunner的创建
filename_queue = tf.train.string_input_producer(["data1.csv","data2.csv"])
reader = tf.TextLineReader(skip_header_lines=1)
# Tensorflow的Reader对象可以直接接受一个Queue作为输入
key, value = reader.read(filename_queue)
with tf.Session() as sess:
coord = tf.train.Coordinator()
# 启动计算图中所有的队列线程
threads = tf.train.start_queue_runners(coord=coord)
# 主线程,消费100个数据
for _ in range(100):
features, labels = sess.run([data_batch, label_batch])
# 主线程计算完成,停止所有采集数据的进程
coord.request_stop()
coord.join(threads)
2)reader = tf.TFRecordReader() #新建一个 reader
3)_, serialized_example = reader.read(filename_queue)
把读出的每个样本保存在 serialized_example 中进行解序列化,标签和文本的键名应该和制作 tfrecords 的键名相同。
4)context_parsed, sequence_parsed = tf.parse_single_sequence_example(
serialized=serialized_example,
context_features=context_features,
sequence_features=sequence_features
)
函数格式:parse_single_sequence_example (
serialized ,
context_features = None ,
sequence_features = None ,
example_names = None ,
name = None
)
函数功能:可以将serialized单个序列化成SequenceExample,解析为字典的元组 tf.train.Example 协议内存块(protocol buffer)解析为张量。
参数说明:serialized: 一个标量字符串张量
features: 一个字典映射功能键 FixedLenFeature 或 VarLenFeature值,也就是在协议内存块中储存的
name:操作的名称(可选)
example_names: 标量字符串联的名称(可选)
4)labels = context_parsed['label']
text = sequence_parsed['text']
5)input_tensors = [labels, text]
6)
针对固定长度的序列:
tf.train.shuffle_batch( tensors,batch_size, capacity, min_after_dequeue, num_threads=1, seed=None, enqueue_many=False, shapes=None, allow_smaller_final_batch=False, shared_name=None, name=None)
函数功能:随机读取一个 batch 的数据。
参数说明:
tensors: 待乱序处理的列表中的样本(图像和标签)
batch_size: 从队列中提取的新批量大小
capacity:队列中元素的最大数量
min_after_dequeue: 出队后队列中的最小数量元素,用于确保元素的混合级别
num_threads: 排列 tensors 的线程数
seed:用于队列内的随机洗牌
enqueue_many: tensor 中的每个张量是否是一个例子
shapes: 每个示例的形状
allow_smaller_final_batch: (可选)布尔值,如果为 True,则在队列中剩余数量不足时允许最终批次更小。
shared_name:(可选)如果设置,该队列将在多个会话中以给定名称共享。
name:操作的名称(可选)
7)tf.ConfigProto()配置tf.Session的运算方式,比如gpu运算或者cpu运算
参数:
log_device_placement= True,打印出tf使用的操作
inter_op_parallelism_threads = 0,设置线程一个操作内部并行运算的线程数,0表示以最优线程数处理
intra_op_parallelism_threads = 0,设置多个操作并行运算的线程数
allow_soft_placement = True,当运行设备不满足要求时,自动分配gpu或者cpu
8)tf.train.batch([example, label], batch_size=batch_size, capacity=capacity):[example, label]表示样本和样本标签,这个可以是一个样本和一个样本标签,batch_size是返回的一个batch样本集的样本个数。capacity是队列中的容量。这主要是按顺序组合成一个batch.
遗留的问题:针对变长序列的shuffle_batch