参考原文:1.https://blog.csdn.net/chengshuhao1991/article/details/78644966
2.https://blog.csdn.net/u014061630/article/details/80712635
最近在学习Tensorflow文档。在对QueueRunner的学习过程中顺便了解了一下Tensorflow中常见的数据读取方式。
在处理数据的过程中,由于现在硬件性能的极大提升,数值计算的性能可以通过加强硬件性能来提升,因此数据读取即IO往往会成为系统运行性能的瓶颈。在Tensorflow框架中提供了三种数据读取的方式:
- Preloaded data:预加载数据。constant...
- Feeding:由占位符代替数据,运行时填入数据。placeholder,feed_dict...
- Reading from file:从文件中直接读取
1.Preloaded data 预加载数据
constant...
数据直接添加进Graph中,由Graph传入session中运行。
import tensorflow as tf
# 设计graph
x = tf.constant([1,2,3], name='x')
y = tf.constant([2,3,4], name='y')
z = tf.add(x,y, name='z')
# 打开一个session,计算z
with tf.Session() as sess:
print(sess.run(z))
在设计Graph时,x和y就已经被定义成了两个有具体值的constant,在计算z的时候则直接取Graph中的x和y的值。
2.Feeding 由占位符代替数据,运行时填入数据
placeholder,feed_dict...
在Graph中仅仅是加入占位符placeholder,可以理解为外部数据进入Graph的一个接口,或者说是Buff,由feed_dict将数据填入placeholder即Buff中,从而实现将外部的数据加入Graph中从而完成计算
import tensorflow as tf
# 设计graph,用占位符代替
x = tf.placeholder(tf.int16)
y = tf.placeholder(tf.int16)
z = tf.add(x,y, name='z')
# 打开一个session
with tf.Session() as sess:
#创建数据
xs = [1,2,3]
ys = [2,3,4]
# 运行session,用feed_dict来将创建的数据传递进占位符
print(sess.run(z, feed_dict={x: xs, y: ys}))
在这里x, y只是占位符,没有具体的值,运行时将使用sess.run()中的feed_dict参数,将Python产生的数据喂给后端,并计算z。
3.Reading from file 从文件中直接读取
前两种方法很方便,但是在遇到大型数据时会很吃力,即使是Feeding,中间环节的增加也是不小的开销。比如数据类型转换等等。最好的解决方法就是在Graph中定义好文件读取的方法,让TF自己去从文件中读取数据,并解码成可使用的样本集。
也就是说,直接在Graph中添加文件读取的相关操作,之后直接在运行Graph的时候实现文件的读取。相比于Feeding方式,就省去了将数据喂入占位符这个Buff的开销,而是直接从指定的文件中获取数据。因为如果是Feeding方式,要先将数据加入placeholder中,再在Graph中,从placeholder处获取数据执行相关的操作。而相比之下,直接从文件中获取数据的方式,就省去了先把数据加入占位符placeholder中这个步骤,而是直接在Graph中,从文件中获取数据并利用数据执行相应的操作。
这种从文件中直接读取数据的方式需要借助Queue才能较好地解决IO瓶颈的问题。
Queue有三个特点:
- producer-consumer pattern(生产消费模式)
- 独立于主线程执行
- 异步IO:reader.read(queue),tf.train.batch()
首先由一个单线程将文件名加入文件名队列,两个Reader同时从文件名队列中取出文件名并且读取数据。Decoder将读出的数据解码后加入样本队列,最后单个或者批量地取出样本。
从文件名队列中取出文件名并读取数据再加入样本队列 这一系列的过程是由Tensorflow自动进行完成的,并不需要手动完成。
Reader是对文件名队列进行的操作。这也就是Tensorflow直接读取文件获取数据的方式的特殊之处:在使用Reader将文件数据读入内存之前,会先创建一个文件名队列。
这里通过三段代码逐步实现上图的数据流。不使用随机出队列方式,让结果更清晰。
3.1.文件准备
$ echo -e "Alpha1,A1\nAlpha2,A2\nAlpha3,A3" > A.csv
$ echo -e "Bee1,B1\nBee2,B2\nBee3,B3" > B.csv
$ echo -e "Sea1,C1\nSea2,C2\nSea3,C3" > C.csv
$ cat A.csv
Alpha1,A1
Alpha2,A2
Alpha3,A3
3.2.1.单个Reader,单个样本
import tensorflow as tf
# filenames为文件名列表,使用tf.train.string_input_producer函数将文件名列表
# 转化为输入文件名队列,该队列为先进先出队列
filenames = ['A.csv', 'B.csv', 'C.csv']
filename_queue = tf.train.string_input_producer(filenames, shuffle=False)
# 定义Reader 。一个Reader
reader = tf.TextLineReader()
key, value = reader.read(filename_queue)
# 定义Decoder
example, label = tf.decode_csv(value, record_defaults=[['null'], ['null']])
# 运行Graph
with tf.Session() as sess:
coord = tf.train.Coordinator() #创建一个协调器,管理线程
# 启动QueueRunner, 此时文件名队列已经进队。
threads = tf.train.start_queue_runners(sess=sess, coord=coord)
for i in range(10):
# 取样本的时候,一个Reader先从文件名队列中取出文件名,读出数据,
# Decoder解析后进入样本队列。
print example.eval()
coord.request_stop()
coord.join(threads)
3.2.2.单个Reader,多个样本
import tensorflow as tf
filenames = ['A.csv', 'B.csv', 'C.csv']
# 可使用tf.train.match_filenames_once函数,检索符合正则表达式的相应名称文件
# 并返回得到一个相关文件名的列表
# filenames = tf.train.match_filenames_once('.\data\*.csv')
filename_queue = tf.train.string_input_producer(filenames, shuffle=False)
# 一个Reader
reader = tf.TextLineReader()
key, value = reader.read(filename_queue)
# 定义Decoder
example, label = tf.decode_csv(value, record_defaults=[['null'], ['null']])
# Decoder解码后数据会进入样本队列,再批量出队。
# 虽然这里只有一个Reader,但可以设置多线程,
# 相应增加线程数会提高读取速度,但并不是线程越多越好。
# 在这里体现多个样本:batch
example_batch, label_batch = tf.train.batch(
[example, label], batch_size=5)
with tf.Session() as sess:
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(coord=coord)
for i in range(10):
print example_batch.eval()
coord.request_stop()
coord.join(threads)
3.2.3.多个Reader,多个样本
import tensorflow as tf
filenames = ['A.csv', 'B.csv', 'C.csv']
filename_queue = tf.train.string_input_producer(filenames, shuffle=False)
reader = tf.TextLineReader()
key, value = reader.read(filename_queue)
# Reader设置为2。通过解码后样本列表内元素个数为2来体现。
# 因为一个Reader读取的是一个样本。
# 所以两个Reader对应的就是一个样本列表中的两个元素
# 每个样本列表的元素的格式为[example, label]
example_list = [tf.decode_csv(value, record_defaults = [['null'], ['null']] )
for _ in range(2)]
# 使用tf.train.batch_join(),可以使用多个reader,并行读取数据。
# 每个Reader使用一个线程。
example_batch, label_batch = tf.train.batch_join(
example_list, batch_size=5)
with tf.Session() as sess:
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(coord=coord)
for i in range(10):
print example_batch.eval()
coord.request_stop()
coord.join(threads)
注意,这里定义的是操作的方式,并不是定义操作的过程。也就是说,虽然定义的是多个Reader进行读取,但是实际的读取过程是由Tensorflow完成的,并不是在计算图中定义的。
tf.train.batch与tf.train.shuffle_batch函数用于单个Reader,但是可以多线程读取。而tf.train.batch_join'和tf.train.shuffle_batch_join函数可以设置多个Reader读取,而每个Reader使用一个线程。如果处理器是双核的。关于两种方法的效率,单个Reader时,2个线程就达到了速度的极限。多Reader时,2个Reader就达到了极限。并不是线程越多越快,甚至更多的线程反而会使效率下降。