TensorFlow输入数据的方法有四种:
tf.data API:可以很容易的构建一个复杂的输入通道(pipeline)(首选数据输入方式)(Eager模式必须使用该API来构建输入通道)
在 tf.data 之前,一般使用 QueueRunner,但 QueueRunner 基于 Python 的多线程及队列等,效率不够高,所以 Google发布了tf.data,其基于C++的多线程及队列,彻底提高了效率。所以不建议使用 QueueRunner 了,取而代之,使用 tf.data 模块吧:简单、高效。
在TensorFlow 1.3中,Dataset API是放在contrib包中的:tf.contrib.data.Dataset
而在TensorFlow 1.4中,Dataset API已经从contrib包中移除,变成了核心API的一员:tf.data.Dataset
Dataset API 中的类图:
TextLineDataset
TFRecordDataset
FixedLengthRecordDataset
Iterator
Dataset可以看作是相同类型“元素”的有序列表。在实际使用时,单个“元素”可以是向量,也可以是字符串、图片,甚至是tuple或者dict。
先以最简单的,Dataset的每一个元素是一个数字为例:
import tensorflow as tf
import numpy as np
dataset = tf.data.Dataset.from_tensor_slices(np.array([1.0, 2.0, 3.0, 4.0, 5.0]))
如何将这个dataset中的元素取出呢?方法是从Dataset中示例化一个Iterator,然后对Iterator进行迭代。
iterator = dataset.make_one_shot_iterator()
one_element = iterator.get_next()
with tf.Session() as sess:
for i in range(5):
print(sess.run(one_element))
对应的输出结果应该就是从1.0到5.0。语句iterator = dataset.make_one_shot_iterator()从dataset中实例化了一个Iterator,这个Iterator是一个“one shot iterator”,即只能从头到尾读取一次。one_element = iterator.get_next()表示从iterator里取出一个元素。由于这是非Eager模式,所以one_element只是一个Tensor,并不是一个实际的值。调用sess.run(one_element)后,才能真正地取出一个值。
在Eager模式中,创建Iterator的方式有所不同。是通过tfe.Iterator(dataset)的形式直接创建Iterator并迭代。迭代时可以直接取出值,不需要使用sess.run():
import tensorflow.contrib.eager as tfe
tfe.enable_eager_execution()
dataset = tf.data.Dataset.from_tensor_slices(np.array([1.0, 2.0, 3.0, 4.0, 5.0]))
for one_element in tfe.Iterator(dataset):
print(one_element)
其实,tf.data.Dataset.from_tensor_slices的功能不止如此,它的真正作用是切分传入Tensor的第一个维度,生成相应的dataset。
dataset = tf.data.Dataset.from_tensor_slices(np.random.uniform(size=(5, 2)))
传入的数值是一个矩阵,它的形状为(5, 2),tf.data.Dataset.from_tensor_slices就会切分它形状上的第一个维度,最后生成的dataset中一个含有5个元素,每个元素的形状是(2, ),即每个元素是矩阵的一行。
利用tf.data.Dataset.from_tensor_slices创建每个元素是一个tuple的dataset也是可以的:
dataset = tf.data.Dataset.from_tensor_slices(
(np.array([1.0, 2.0, 3.0, 4.0, 5.0]), np.random.uniform(size=(5, 2)))
)
Dataset支持一类特殊的操作:Transformation。一个Dataset通过Transformation变成一个新的Dataset。通常我们可以通过Transformation完成数据变换,打乱,组成batch,生成epoch等一系列操作。
常用的Transformation有:
map
batch
shuffle
repeat
(1)map
map接收一个函数,Dataset中的每个元素都会被当作这个函数的输入,并将函数返回值作为新的Dataset,如我们可以对dataset中每个元素的值加1:
dataset = tf.data.Dataset.from_tensor_slices(np.array([1.0, 2.0, 3.0, 4.0, 5.0]))
dataset = dataset.map(lambda x: x + 1) # 2.0, 3.0, 4.0, 5.0, 6.0
(2)batch
batch就是将多个元素组合成batch,如下面的程序将dataset中的每个元素组成了大小为32的batch:
dataset = dataset.batch(32)
(3)shuffle
shuffle的功能为打乱dataset中的元素,它有一个参数buffersize,表示打乱时使用的buffer的大小:
dataset = dataset.shuffle(buffer_size=10000)
(4)repeat
repeat的功能就是将整个序列重复多次,主要用来处理机器学习中的epoch,假设原先的数据是一个epoch,使用repeat(5)就可以将之变成5个epoch:
dataset = dataset.repeat(5)
如果直接调用repeat()的话,生成的序列就会无限重复下去,没有结束,因此也不会抛出tf.errors.OutOfRangeError异常:
dataset = dataset.repeat()
读入磁盘图片与对应label:
# 函数的功能时将filename对应的图片文件读进来,并缩放到统一的大小
def _parse_function(filename, label):
image_string = tf.read_file(filename)
image_decoded = tf.image.decode_image(image_string)
image_resized = tf.image.resize_images(image_decoded, [28, 28])
return image_resized, label
# 图片文件的列表
filenames = tf.constant(["/var/data/image1.jpg", "/var/data/image2.jpg", ...])
# label[i]就是图片filenames[i]的label
labels = tf.constant([0, 37, ...])
# 此时dataset中的一个元素是(filename, label)
dataset = tf.data.Dataset.from_tensor_slices((filenames, labels))
# 此时dataset中的一个元素是(image_resized, label)
dataset = dataset.map(_parse_function)
# 此时dataset中的一个元素是(image_resized_batch, label_batch)
dataset = dataset.shuffle(buffersize=1000).batch(32).repeat(10)
Dataset的其它创建方法
tf.data.TextLineDataset():这个函数的输入是一个文件的列表,输出是一个dataset。dataset中的每一个元素就对应了文件中的一行。可以使用这个函数来读入CSV文件。
tf.data.FixedLengthRecordDataset():这个函数的输入是一个文件的列表和一个record_bytes,之后dataset的每一个元素就是文件中固定字节数record_bytes的内容。通常用来读取以二进制形式保存的文件,如CIFAR10数据集就是这种形式。
tf.data.TFRecordDataset():顾名思义,这个函数是用来读TFRecord文件的,dataset中的每一个元素就是一个TFExample。
更多类型的Iterator
在非Eager模式下,最简单的创建Iterator的方法就是通过dataset.make_one_shot_iterator()来创建一个one shot iterator。除了这种one shot iterator外,还有三个更复杂的Iterator,即:
initializable iterator
reinitializable iterator
feedable iterator
在使用tf.data.Dataset.from_tensor_slices(array)时,实际上发生的事情是将array作为一个tf.constants保存到了计算图中。当array很大时,会导致计算图变得很大,给传输、保存带来不便。这时,我们可以用一个placeholder取代这里的array,并使用initializable iterator,只在需要时将array传进去,这样就可以避免把大数组保存在图里,示例代码为(来自官方例程):
# 从硬盘中读入两个Numpy数组
with np.load("/var/data/training_data.npy") as data:
features = data["features"]
labels = data["labels"]
features_placeholder = tf.placeholder(features.dtype, features.shape)
labels_placeholder = tf.placeholder(labels.dtype, labels.shape)
dataset = tf.data.Dataset.from_tensor_slices((features_placeholder, labels_placeholder))
iterator = dataset.make_initializable_iterator()
sess.run(iterator.initializer, feed_dict={features_placeholder: features,
labels_placeholder: labels})
Feeding:使用Python代码提供数据,然后将数据feeding到计算图中。
注意:Feeding是数据输入效率最低的方式,应该只用于小数据集和调试(debugging)
TensorFlow的Feeding机制允许我们将数据输入计算图中的任何一个Tensor。因此可以用Python来处理数据,然后直接将处理好的数据feed到计算图中 。虽然你可以用feed data替换任何Tensor的值(包括variables和constants),但最好的使用方法是使用一个tf.placeholder节点(专门用于feed数据)。它不用初始化,也不包含数据。一个placeholder没有被feed数据,则会报错。
QueueRunner:基于队列的输入通道(在计算图计算前从队列中读取数据)
注意:这一部分介绍了基于队列(Queue)API构建输入通道(pipelines),这一方法完全可以使用 tf.data API来替代。
一个基于queue的从文件中读取records的通道(pipline)一般有以下几个步骤:
文件名列表(The list of filenames)
----文件名打乱(可选)(Optional filename shuffling)
----epoch限制(可选)(Optional epoch limit)
文件名队列(Filename queue)
与文件格式匹配的Reader(A Reader for the file format)
decoder(A decoder for a record read by the reader)
----预处理(可选)(Optional preprocessing)
Example队列(Example queue)
3.1 Filenames, shuffling, and epoch limits
对于文件名列表,有很多方法:
1. 使用一个constant string Tensor(比如:["file0", "file1"])或者[("file%d" %i) for i in range(2)];
2. 使用 tf.train.match_filenames_once 函数;
3. 使用 tf.gfile.Glob(path_pattern)。
将文件名列表传给 tf.train.string_input_producer 函数。string_input_producer 创建一个 FIFO 队列来保存(holding)文件名,以供Reader使用。
string_input_producer 可以对文件名进行shuffle(可选)、设置一个最大迭代 epochs 数。在每个epoch,一个queue runner将整个文件名列表添加到queue,如果shuffle=True,则添加时进行shuffle。This procedure provides a uniform sampling of files, so that examples are not under- or over- sampled relative to each other。
queue runner线程独立于reader线程,所以enqueuing和shuffle不会阻碍reader
3.2 File formats
要选择与输入文件的格式匹配的reader,并且要将文件名队列传递给reader的 read 方法。read 方法输出一个 key identifying the file and record(在调试过程中非常有用,如果你有一些奇怪的 record)
3.2.1 CSV file
为了读取逗号分隔符分割的text文件(csv),要使用一个 tf.TextLineReader 和一个 tf.decode_csv。
read 方法每执行一次,会从文件中读取一行。然后 decode_csv 将读取的内容解析成一个Tensor列表。参数 record_defaults 决定解析产生的Tensor的类型,另外,如果输入中有缺失值,则用record_defaults 指定的默认值来填充。
#直接输入文件名
filename_queue = tf.train.string_input_producer(["file0.csv", "file1.csv"])
#新建一个reader
reader = tf.TextLineReader()
#读取文件流中的结果
key, value = reader.read(filename_queue)
# Default values, in case of empty columns. Also specifies the type of the
# decoded result.
#默认的输出结果
record_defaults = [[1], [1], [1], [1], [1]]
#读取value,并用default value 填充结果
col1, col2, col3, col4, col5 = tf.decode_csv(
value, record_defaults=record_defaults)
#特征
features = tf.stack([col1, col2, col3, col4])
#新建计算图
with tf.Session() as sess:
# Start populating the filename queue.
#这个函数是什么
coord = tf.train.Coordinator()
#tf 的多线程
threads = tf.train.start_queue_runners(coord=coord)
#训练集和验证集
for i in range(1200):
# Retrieve a single instance:
example, label = sess.run([features, col5])
coord.request_stop()
coord.join(threads)
在使用run或者eval 执行 read 方法前,你必须调用 tf.train.start_queue_runners 去填充 queue。否则,read 方法将会堵塞(等待 filenames queue 中 enqueue 文件名)。
代码示例:
文件准备
$ 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
单个Reader,单个样本
import tensorflow as tf
# 生成一个先入先出队列和一个QueueRunner
filenames = ['A.csv', 'B.csv', 'C.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']])
# 运行Graph
with tf.Session() as sess:
coord = tf.train.Coordinator() #创建一个协调器,管理线程
threads = tf.train.start_queue_runners(coord=coord) #启动QueueRunner, 此时文件名队列已经进队。
for i in range(10):
print example.eval() #取样本的时候,一个Reader先从文件名队列中取出文件名,
#读出数据,Decoder解析后进入样本队列。
coord.request_stop()
coord.join(threads)
单个Reader,多个样本
import tensorflow as tf
filenames = ['A.csv', 'B.csv', 'C.csv']
## filenames = tf.train.match_filenames_once('.\data\*.csv')
filename_queue = tf.train.string_input_producer(filenames, shuffle=False)
reader = tf.TextLineReader()
key, value = reader.read(filename_queue)
example, label = tf.decode_csv(value, record_defaults=[['null'], ['null']])
# 使用tf.train.batch()会多加了一个样本队列和一个QueueRunner。Decoder解后数据会进入这个队列,再批量出队。
# 虽然这里只有一个Reader,但可以设置多线程,相应增加线程数会提高读取速度,但并不是线程越多越好。
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)
多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)
record_defaults = [['null'], ['null']]
example_list = [tf.decode_csv(value, record_defaults=record_defaults)
for _ in range(2)] # Reader设置为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)
迭代控制
filenames = ['A.csv', 'B.csv', 'C.csv']
filename_queue = tf.train.string_input_producer(filenames, shuffle=False, num_epochs=3)
# num_epoch: 设置迭代数
reader = tf.TextLineReader()
key, value = reader.read(filename_queue)
record_defaults = [['null'], ['null']]
example_list = [tf.decode_csv(value, record_defaults=record_defaults)
for _ in range(2)]
example_batch, label_batch = tf.train.batch_join(
example_list, batch_size=5)
init_local_op = tf.initialize_local_variables()
with tf.Session() as sess:
sess.run(init_local_op) # 初始化本地变量
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(coord=coord)
try:
while not coord.should_stop():
print example_batch.eval()
except tf.errors.OutOfRangeError:
print('Epochs Complete!')
finally:
coord.request_stop()
coord.join(threads)
coord.request_stop()
coord.join(threads)
3.2.2 Fixed length records
为了读取二进制文件(二进制文件中,每一个record都占固定bytes),需要使用一个 tf.FixedLengthRecordReader 和 tf.decode_raw。decode_raw 将 reader 读取的 string 解析成一个uint8 tensor。
例如,二进制格式的CIFAR-10数据集中的每一个record都占固定bytes:label占1 bytes,然后后面的image数据占3072 bytes。当你有一个unit8 tensor时,通过切片便可以得到各部分并reformat成需要的格式。对于CIFAR-10数据集的reading和decoding,可以参照:tensorflow_models/tutorials/image/cifar10/cifar10_input.py或这个教程。
3.2.3 Standard TensorFlow format
另一个方法是将数据集转换为一个支持的格式。这个方法使得数据集和网络的混合和匹配变得简单(make it easier to mix and match data sets and network architectures)。TensorFlow中推荐的格式是 TFRecords文件,TFRecords中包含 tf.train.Example protocol buffers (在这个协议下,特征是一个字段).
你写一小段程序来获取数据,然后将数据填入一个Example protocol buffer,并将这个 protocol buffer 序列化(serializes)为一个string,然后用 tf.python_io.TFRcordWriter 将这个string写入到一个TFRecords文件中。例如,tensorflow/examples/how_tos/reading_data/convert_to_records.py 将MNIST数据集转化为TFRecord格式。
读取TFRecord文件的推荐方式是使用 tf.data.TFRecordDataset,像这个例子一样:
dataset = tf.data.TFRecordDataset(filename)
dataset = dataset.repeat(num_epochs)
# map takes a python function and applies it to every sample
dataset = dataset.map(decode)
为了完成相同的任务,基于queue的输入通道需要下面的代码(使用的decode和上一段代码一样):
filename_queue = tf.train.string_input_producer([filename], num_epochs=num_epochs)
reader = tf.TFRecordReader()
_, serialized_example = reader.read(filename_queue)
image,label = decode(serialized_example)
3.3 Preprocessing
然后你可以对examples进行你想要的预处理(preprocessing)。预处理是独立的(不依赖于模型参数)。常见的预处理有:数据的标准化(normalization of your data)、挑选一个随机的切片,添加噪声(noise)或者畸变(distortions)等。具体的例子见:tensorflow_models/tutorials/image/cifar10/cifar10_input.py
3.4 Batching
在pipeline的末端,我们通过调用 tf.train.shuffle_batch 来创建两个queue,一个将example batch起来 for training、evaluation、inference;另一个来shuffle examples的顺序。
def read_my_file_format(filename_queue):
reader = tf.SomeReader()
key, record_string = reader.read(filename_queue)
example, label = tf.some_decoder(record_string)
processed_example = some_processing(example)
return processed_example, label
def input_pipeline(filenames, batch_size, num_epochs=None):
filename_queue = tf.train.string_input_producer(
filenames, num_epochs=num_epochs, shuffle=True)
example, label = read_my_file_format(filename_queue)
# min_after_dequeue defines how big a buffer we will randomly sample
# from -- bigger means better shuffling but slower start up and more
# memory used.
# capacity must be larger than min_after_dequeue and the amount larger
# determines the maximum we will prefetch. Recommendation:
# min_after_dequeue + (num_threads + a small safety margin) * batch_size
min_after_dequeue = 10000
capacity = min_after_dequeue + 3 * batch_size
example_batch, label_batch = tf.train.shuffle_batch(
[example, label], batch_size=batch_size, capacity=capacity,
min_after_dequeue=min_after_dequeue)
return example_batch, label_batch
如果你需要更多的并行或者打乱不同文件中example,使用多个reader,然后使用 tf.train.shuffle_batch_join将多个reader读取的内容整合到一起。(If you need more parallelism or shuffling of examples between files, use multiple reader instances using the tf.train.shuffle_batch_join)
def read_my_file_format(filename_queue):
reader = tf.SomeReader()
key, record_string = reader.read(filename_queue)
example, label = tf.some_decoder(record_string)
processed_example = some_processing(example)
return processed_example, label
def input_pipeline(filenames, batch_size, read_threads, num_epochs=None):
filename_queue = tf.train.string_input_producer(
filenames, num_epochs=num_epochs, shuffle=True)
example_list = [read_my_file_format(filename_queue)
for _ in range(read_threads)]
min_after_dequeue = 10000
capacity = min_after_dequeue + 3 * batch_size
example_batch, label_batch = tf.train.shuffle_batch_join(
example_list, batch_size=batch_size, capacity=capacity,
min_after_dequeue=min_after_dequeue)
return example_batch, label_batch
所有的reader共享一个filename queue。这种方式保证了不同的reader在同一个epoch,读取不同的文件,直到所有的文件的已经读取完,然后在下一个epoch,重新从所有的文件读取(You still only use a single filename queue that is shared by all the readers. That way we ensure that the different readers use different files from the same epoch until all the files from the epoch have been started. (It is also usually sufficient to have a single thread filling the filename queue.))。
另一个可选的方法是去通过调用 tf.train.shuffle_batch 使用单个的reader,但是将参数 num_threads 参数设置为大于1的值。这将使得在同一时间只能从一个文件读取内容(但是比 1 线程快),而不是同时从N个文件中读取。这可能很重要:
如果你的num_threads参数值比文件的数量多,那么很有可能:有两个threads会一前一后从同一个文件中读取相同的example。这是不好的,应该避免。
或者,如果并行地读取N个文件,可能或导致大量的磁盘搜索(意思是,多个文件存在于磁盘的不同位置,而磁头只能有一个位置,所以会增加磁盘负担)
那么需要多少个线程呢?tf.train.shuffle_batch*函数会给计算图添加一个summary来记录 example queue 的使用情况。如果你有足够的reading threads,这个summary将会总大于0。你可以用TensorBoard来查看训练过程中的summaries
3.5 Creating threads to prefetch using QueueRunner objects
使用QueueRunner对象来创建threads来prefetch数据
说明:tf.train里的很多函数会添加tf.train.QueueRunner对象到你的graph。这些对象需要你在训练或者推理前,调用tf.train.start_queue_runners,否则数据无法读取到图中。调用tf.train.start_queue_runners会运行输入pipeline需要的线程,这些线程将example enqueue到队列中,然后dequeue操作才能成功。这最好和tf.train.Coordinator配合着用,当有错误时,它会完全关闭掉开启的threads。如果你在创建pipline时设置了迭代epoch数限制,将会创建一个epoch counter的局部变量(需要初始化)。下面是推荐的代码使用模板:
# Create the graph, etc.
init_op = tf.global_variables_initializer()
# 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()
这里的代码是怎么工作的?
首先,我们创建整个图。它的input pipeline将有几个阶段,这些阶段通过Queue连在一起。第一个阶段将会产生要读取的文件的文件名,并将文件名enqueue到filename queue。第二个阶段使用一个Reader来dequeue文件名并读取,产生example,并将example enqueue到一个example queue。根据你的设置,你可能有很多第二阶段(并行),所以你可以从并行地读取多个文件。最后一个阶段是一个enqueue操作,将example enqueue成一个queue,然后等待下一步操作。我们想要开启多个线程运行着些enqueue操作,所以我们的训练loop能够从example queue中dequeue examples。
tf.train里的辅助函数(创建了这些queue、enqueuing操作)会调用tf.train.add_queue——runner添加一个tf.train.QueueRunner到图中。每一个QueueRunner负责一个阶段。一旦图构建好,tf.train.start_queue_runners函数会开始图中每一个QueueRunner的入队操作。
如果一切进行顺利,你现在可以运行训练step(后台线程会填满queue)。如果你设置了epoch限制,在达到固定的epoch时,在进行dequeuing会得到tf.errors.OutOfRangeError。这个错误等价于EOF(end of file),意味着已经达到了固定的epochs。
最后一部分是tf.train.Coordinator。它主要负责通知所有的线程是否应该停止。在大多数情况下,这通常是因为遇到了一个异常(exception)。例如,某一个线程在运行某些操作时出错了(或者python的异常)。
关于threading、queues、QueueRunners、Coordinators的更多细节见这里
3.6 Filtering records or producing multiple examples per record
一个example的shape是 [x,y,z],一个batch的example的shape为 [batch, x, y, z]。如果你想去过滤掉这个record,你可以把 batch size 设置为 0;如果你想让每一个record产生多个example,你可以把batch size设置为大于1。然后,在调用调用batching函数(shuffle_batch或shuffle_batch_join)时,设置enqueue_many=True。
3.7 Sparse input data
queues在SparseTensors的情况下不能很好的工作。如果你使用SparseTensors,你必须在batching后用tf.sparse_example来decode string records(而不是在batching前使用tf.parse_single_example来decode)
Preloaded data:用一个constant常量将数据集加载到计算图中(主要用于小数据集)
这仅仅适用于小数据集,小数据集可以被整体加载到内存。预加载数据集主要有两种方法:
将数据集存储成一个constant
将数据集存储在一个variable中,一旦初始化或者assign to后,便不再改变。
使用一个constant更简单,但是需要更多的内存(因为所有的常量都储存在计算图中,而计算图可能需要进行多次复制)。
training_data = ...
training_labels = ...
with tf.Session():
input_data = tf.constant(training_data)
input_labels = tf.constant(training_labels)
...
为了使用一个varibale,在图构建好后,你需要去初始化它。
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_initializer, trainable=False, collections=[])
input_labels = tf.Variable(label_initializer, trainable=False, collections=[])
...
sess.run(input_data.initializer,
feed_dict={data_initializer: training_data})
sess.run(input_labels.initializer,
feed_dict={label_initializer: training_labels})
设置trainable=False将使variable不加入GraphKeys.TRAINABLE_VARIABLES容器,所以我们不用在训练过程中更新它。设置collections=[]将会使variable不加入GraphKeys.GLOBAL_VARIABLES容器(这个容器主要用于保存和恢复checkpoints)。
无论哪种方式,tf.train.slice_input_producer都能够用来产生一个slice。这在整个epoch上shuffle了example,所以batching时,进一步的shuffling不再需要。所以不再使用shuffle_batch函数,而使用tf.train.batch函数。为了使用多个预处理线程,设置num_threads参数大于1。
MNIST数据集上使用constant来preload数据的实例见tensorflow/examples/how_tos/reading_data/fully_connected_preloaded.py;使用variable来preload数据的例子见tensorflow/examples/how_tos/reading_data/fully_connected_preloaded_var.py,你可以通过 fully_connected_feed和 fully_connected_feed版本来对比两种方式。
5. Multiple input pipelines
一般,你想要去在一个数据集上训练,而在另一个数据集上评估模型。实现这个想法的一种方式是:以两个进程,建两个独立的图和session:
训练进程读取训练数据,并且周期性地将模型的所有训练好的变量保存到checkpoint文件中。
评估进程从checkpoint文件中恢复得到一个inference模型,这个模型读取评估数据。
在estimators里和CIFAR-10模型示例里,采用就是上面的方法。该方法主要有两个好处:
你的评估是在一个训练好的模型的快照上进行的。
在训练完成或中断后,你也可以进行评估。
你可以在同一个进程中同一个图中进行训练和评估,并且训练和评估共享训练好的参数和层。关于共享变量,详见这里。
为了支持单个图方法(single-graph approach),tf.data也提供了高级的iterator类型,它将允许用户去在不重新构建graph和session的情况下,改变输入pipeline。
TFRecords
TFRecords其实是一种二进制文件,虽然它不如其他格式好理解,但是它能更好的利用内存,更方便复制和移动,并且不需要单独的标签文件(等会儿就知道为什么了)… …总而言之,这样的文件格式好处多多,所以让我们用起来吧。
TFRecords文件包含了tf.train.Example 协议内存块(protocol buffer)(协议内存块包含了字段 Features)。我们可以写一段代码获取你的数据, 将数据填入到Example协议内存块(protocol buffer),将协议内存块序列化为一个字符串, 并且通过tf.python_io.TFRecordWriter 写入到TFRecords文件。
从TFRecords文件中读取数据, 可以使用tf.TFRecordReader的tf.parse_single_example解析器。这个操作可以将Example协议内存块(protocol buffer)解析为张量。
生成TFRecords文件
我们使用tf.train.Example来定义我们要填入的数据格式,然后使用tf.python_io.TFRecordWriter来写入。
import os
import tensorflow as tf
from PIL import Image
cwd = os.getcwd()
writer = tf.python_io.TFRecordWriter("train.tfrecords")
for index, name in enumerate(classes):
class_path = cwd + name + "/"
for img_name in os.listdir(class_path):
img_path = class_path + img_name
img = Image.open(img_path)
img = img.resize((224, 224))
img_raw = img.tobytes()
example = tf.train.Example(features=tf.train.Features(feature={
"label": tf.train.Feature(int64_list=tf.train.Int64List(value=[index])),
'img_raw': tf.train.Feature(bytes_list=tf.train.BytesList(value=[img_raw]))
}))
writer.write(example.SerializeToString()) #序列化为字符串
writer.close()
就这样,我们把相关的信息都存到了一个文件中,所以前面才说不用单独的label文件。而且读取也很方便。
for serialized_example in tf.python_io.tf_record_iterator("train.tfrecords"):
example = tf.train.Example()
example.ParseFromString(serialized_example)
image = example.features.feature['image'].bytes_list.value
label = example.features.feature['label'].int64_list.value
# 可以做一些预处理之类的
print image, label
一旦生成了TFRecords文件,为了高效地读取数据,TF中使用队列(queue)读取数据。
def read_and_decode(filename):
#根据文件名生成一个队列
filename_queue = tf.train.string_input_producer([filename])
reader = tf.TFRecordReader()
_, serialized_example = reader.read(filename_queue) #返回文件名和文件
features = tf.parse_single_example(serialized_example,
features={
'label': tf.FixedLenFeature([], tf.int64),
'img_raw' : tf.FixedLenFeature([], tf.string),
})
img = tf.decode_raw(features['img_raw'], tf.uint8)
img = tf.reshape(img, [224, 224, 3])
img = tf.cast(img, tf.float32) * (1. / 255) - 0.5
label = tf.cast(features['label'], tf.int32)
return img , label
之后我们可以在训练的时候这样使用
img, label = read_and_decode("train.tfrecords")
#使用shuffle_batch可以随机打乱输入
img_batch, label_batch = tf.train.shuffle_batch([img, label],
batch_size=30, capacity=2000,
min_after_dequeue=1000)
init = tf.initialize_all_variables()
with tf.Session() as sess:
sess.run(init)
threads = tf.train.start_queue_runners(sess=sess)
for i in range(3):
val, l= sess.run([img_batch, label_batch])
#我们也可以根据需要对val, l进行处理
#l = to_categorical(l, 12)
print(val.shape, l)
return img, label
eg1:
制作TFRECORD文件
import os
import tensorflow as tf
from PIL import Image #注意Image,后面会用到
import matplotlib.pyplot as plt
import numpy as np
cwd='D:\Python\data\dog\\'
classes={'husky','chihuahua'} #人为 设定 2 类
writer= tf.python_io.TFRecordWriter("dog_train.tfrecords") #要生成的文件
for index,name in enumerate(classes):
class_path=cwd+name+'\\'
for img_name in os.listdir(class_path):
img_path=class_path+img_name #每一个图片的地址
img=Image.open(img_path)
img= img.resize((128,128))
img_raw=img.tobytes()#将图片转化为二进制格式
example = tf.train.Example(features=tf.train.Features(feature={
"label": tf.train.Feature(int64_list=tf.train.Int64List(value=[index])),
'img_raw': tf.train.Feature(bytes_list=tf.train.BytesList(value=[img_raw]))
})) #example对象对label和image数据进行封装
writer.write(example.SerializeToString()) #序列化为字符串
writer.close()
读取TFRECORD文件
def read_and_decode(filename): # 读入dog_train.tfrecords
filename_queue = tf.train.string_input_producer([filename])#生成一个queue队列
reader = tf.TFRecordReader()
_, serialized_example = reader.read(filename_queue)#返回文件名和文件
features = tf.parse_single_example(serialized_example,
features={
'label': tf.FixedLenFeature([], tf.int64),
'img_raw' : tf.FixedLenFeature([], tf.string),
})#将image数据和label取出来
img = tf.decode_raw(features['img_raw'], tf.uint8)
img = tf.reshape(img, [128, 128, 3]) #reshape为128*128的3通道图片
img = tf.cast(img, tf.float32) * (1. / 255) - 0.5 #在流中抛出img张量
label = tf.cast(features['label'], tf.int32) #在流中抛出label张量
return img, label
参考链接:
https://blog.youkuaiyun.com/u014061630/article/details/80712635
https://blog.youkuaiyun.com/kwame211/article/details/78579035
https://blog.youkuaiyun.com/hnxyxiaomeng/article/details/78405632
https://blog.youkuaiyun.com/hujiameihuxu/article/details/79944366