这一部分主要参考tensorflow的官方教程:tf.data.datasets, 上一篇tensorflow数据读取是基于多线程数据抓取的方式,维护多队列(文件队列,example队列),是比较偏底层的。可能现在tensorflow开始慢慢走了上封装之路,datasets的出现隐藏了底层的实现。(还好不像python~~~~~~~~匿了)。
因为本人是做计算机视觉方向的,所以这里就从计算机视觉的需求出发。主要考虑从TFRecords中读取数据。
整个小目录可能会更好理解:
tensorflow的口号:tf.data API可以让你以简单可复用的方式构建复杂的Input Pipeline。例如:一个图片模型的Pipeline可能会聚合在一个分布式文件系统中的多个文件,对每个图片进行随机扰动(random perturbations),接着将随机选中的图片合并到一个training batch中。tf.data API可以很方便地以不同的数据格式处理大量的数据,以及处理复杂的转换。
Dataset API引入了两个新的抽象类到Tensorflow中:
这部分描述了创建不同Dataset和Iterator对象的机制,以及如何使用它们来抽取数据。
Step1: 定义Source
要想启动一个input pipeline,你必须定义一个source。这里官方主要给了两类方法:
Step2:消费数据
对于消费数据,datasets封装得比较好,只留出了几个不同类型的接口。但都是属于迭代指针类型一样,iterator对象。它提供了一次可以访问dataset中的一个元素(例如:通过调用Dataset.make_one_shot_iterator())。tf.data.Iterator提供了两个操作:
之后,建议直接看代码吧。
一个dataset由element组成,它们每个都具有相同的结构。一个元素包含了一或多个tf.Tensor对象,称为“components“。每个component都具有一个tf.DType:它表示在tensor中的元素的类型;以及一个tf.TensorShape:它表示每个元素的静态shape。Dataset.output_types 和 Dataset.output_shapes 属性允许你观察到一个dataset元素的每个component内省的types和shapes。这些属性的这种嵌套式结构(nested structure),映射到一个元素(它可以是单个tensor、一个tensors的tuple、一个tensors的嵌套式tuple)的结构上。例如:
dataset1 = tf.data.Dataset.from_tensor_slices(tf.random_uniform([4, 10]))
print(dataset1.output_types) # ==> "tf.float32"
print(dataset1.output_shapes) # ==> "(10,)"
dataset2 = tf.data.Dataset.from_tensor_slices(
(tf.random_uniform([4]),
tf.random_uniform([4, 100], maxval=100, dtype=tf.int32)))
print(dataset2.output_types) # ==> "(tf.float32, tf.int32)"
print(dataset2.output_shapes) # ==> "((), (100,))"
dataset3 = tf.data.Dataset.zip((dataset1, dataset2))
print(dataset3.output_types) # ==> (tf.float32, (tf.float32, tf.int32))
print(dataset3.output_shapes) # ==> "(10, ((), (100,)))"
为一个元素(element)的每个component给定names很方便,例如,如果它们表示一个训练样本的不同features。除了tuples,你可以使用collections.namedtuple,或者一个将strings映射为关于tensors的字典来表示一个Dataset的单个元素。
dataset = tf.data.Dataset.from_tensor_slices(
{"a": tf.random_uniform([4]),
"b": tf.random_uniform([4, 100], maxval=100, dtype=tf.int32)})
print(dataset.output_types) # ==> "{'a': tf.float32, 'b': tf.int32}"
print(dataset.output_shapes) # ==> "{'a': (), 'b': (100,)}"
Dataset的转换(transformations)支持任何结构的datasets。当使用Dataset.map(),Dataset.flat_map(),以及Dataset.filter()转换时,它们会对每个element应用一个function,元素结构决定了函数的参数:
dataset1 = dataset1.map(lambda x: ...)
dataset2 = dataset2.flat_map(lambda x, y: ...)
# Note: Argument destructuring is not available in Python 3.
dataset3 = dataset3.filter(lambda x, (y, z): ...)
一旦你已经构建了一个Dataset来表示你的输入数据,下一步是创建一个Iterator来访问dataset的elements。Dataset API当前支持四种iterator,复杂度依次递增:
feedable
case1: one-shot iterator
one-shot iterator是最简单的iterator,它只支持在一个dataset上迭代一次的操作,不需要显式初始化。One-shot iterators可以处理几乎所有的己存在的基于队列的input pipeline支持的情况,但它们不支持参数化(parameterization)。使用Dataset.range()示例如下:
dataset = tf.data.Dataset.range(100)
iterator = dataset.make_one_shot_iterator()
next_element = iterator.get_next()
for i in range(100):
value = sess.run(next_element)
assert i == value
case2: initializable iterator
initializable iterator在使用它之前需要你返回一个显 式的iterator.initializer操作。虽然有些不便,但它允许你可以对dataset的定义进行参数化(parameterize),使用一或多个tf.placeholder() tensors:它们可以当你初始化iterator时被feed进去。继续Dataset.range() 的示例:
max_value = tf.placeholder(tf.int64, shape=[])
dataset = tf.data.Dataset.range(max_value)
iterator = dataset.make_initializable_iterator()
next_element = iterator.get_next()
# Initialize an iterator over a dataset with 10 elements.
sess.run(iterator.initializer, feed_dict={max_value: 10})
for i in range(10):
value = sess.run(next_element)
assert i == value
# Initialize the same iterator over a dataset with 100 elements.
sess.run(iterator.initializer, feed_dict={max_value: 100})
for i in range(100):
value = sess.run(next_element)
assert i == value
case3: reinitializable iterator
reinitializable iterator可以从多个不同的Dataset对象处初始化。例如,你可能有一个training input pipeline(它对输入图片做随机扰动来提高泛化能力);以及一个validation input pipeline(它会在未修改过的数据上进行预测的评估)。这些pipeline通常使用不同的Dataset对象,但它们具有相同的结构(例如:对每个component相同的types和shapes)
# Define training and validation datasets with the same structure.
training_dataset = tf.data.Dataset.range(100).map(
lambda x: x + tf.random_uniform([], -10, 10, tf.int64))
validation_dataset = tf.data.Dataset.range(50)
# A reinitializable iterator is defined by its structure. We could use the
# `output_types` and `output_shapes` properties of either `training_dataset`
# or `validation_dataset` here, because they are compatible.
iterator = tf.data.Iterator.from_structure(training_dataset.output_types,
training_dataset.output_shapes)
next_element = iterator.get_next()
training_init_op = iterator.make_initializer(training_dataset)
validation_init_op = iterator.make_initializer(validation_dataset)
# Run 20 epochs in which the training dataset is traversed, followed by the
# validation dataset.
for _ in range(20):
# Initialize an iterator over the training dataset.
sess.run(training_init_op)
for _ in range(100):
sess.run(next_element)
# Initialize an iterator over the validation dataset.
sess.run(validation_init_op)
for _ in range(50):
sess.run(next_element)
case4: feedable iterator
feedable iterator可以与tf.placeholder一起使用,通过熟悉的feed_dict机制,来选择在每次调用tf.Session.run所使用的Iterator,。它提供了与reinitializable iterator相同的功能,但当你在iterators间相互切换时,它不需要你去初始化iterator。例如:使用上述相同的training和validation样本,你可以使用tf.data.Iterator.from_string_handle来定义一个feedable iterator,并允许你在两个datasets间切换:
# Define training and validation datasets with the same structure.
training_dataset = tf.data.Dataset.range(100).map(
lambda x: x + tf.random_uniform([], -10, 10, tf.int64)).repeat()
validation_dataset = tf.data.Dataset.range(50)
# A feedable iterator is defined by a handle placeholder and its structure. We
# could use the `output_types` and `output_shapes` properties of either
# `training_dataset` or `validation_dataset` here, because they have
# identical structure.
handle = tf.placeholder(tf.string, shape=[])
iterator = tf.data.Iterator.from_string_handle(
handle, training_dataset.output_types, training_dataset.output_shapes)
next_element = iterator.get_next()
# You can use feedable iterators with a variety of different kinds of iterator
# (such as one-shot and initializable iterators).
training_iterator = training_dataset.make_one_shot_iterator()
validation_iterator = validation_dataset.make_initializable_iterator()
# The `Iterator.string_handle()` method returns a tensor that can be evaluated
# and used to feed the `handle` placeholder.
training_handle = sess.run(training_iterator.string_handle())
validation_handle = sess.run(validation_iterator.string_handle())
# Loop forever, alternating between training and validation.
while True:
# Run 200 steps using the training dataset. Note that the training dataset is
# infinite, and we resume from where we left off in the previous `while` loop
# iteration.
for _ in range(200):
sess.run(next_element, feed_dict={handle: training_handle})
# Run one pass over the validation dataset.
sess.run(validation_iterator.initializer)
for _ in range(50):
sess.run(next_element, feed_dict={handle: validation_handle})
Iterator.get_next()方法会返回一或多个tf.Tensor对象,对应于一个iterator的下一个element。每次这些tensors被评测时,它们会在底层的dataset中获得下一个element的value。(注意:类似于Tensorflow中其它的有状态对象,调用Iterator.get_next() 不会立即让iterator前移。相反的,你必须使用Tensorflow表达式所返回的tf.Tensor对象,传递该表达式的结果给tf.Session.run(),来获取下一个elements,并让iterator前移)
如果iterator达到了dataset的结尾,执行Iterator.get_next() 操作会抛出一个tf.errors.OutOfRangeError。在这之后,iterator会以一个不可用的状态存在,如果你想进一步使用必须重新初始化它。
dataset = tf.data.Dataset.range(5)
iterator = dataset.make_initializable_iterator()
next_element = iterator.get_next()
# Typically `result` will be the output of a model, or an optimizer's
# training operation.
result = tf.add(next_element, next_element)
sess.run(iterator.initializer)
print(sess.run(result)) # ==> "0"
print(sess.run(result)) # ==> "2"
print(sess.run(result)) # ==> "4"
print(sess.run(result)) # ==> "6"
print(sess.run(result)) # ==> "8"
try:
sess.run(result)
except tf.errors.OutOfRangeError:
print("End of dataset") # ==> "End of dataset"
一种常用的模式是,将”training loop”封装到一个try-except块中:
sess.run(iterator.initializer)
while True:
try:
sess.run(result)
except tf.errors.OutOfRangeError:
break
如果dataset的每个元素都具有一个嵌套的结构,Iterator.get_next()的返回值将会是以相同嵌套结构存在的一或多个tf.Tensor对象:
dataset1 = tf.data.Dataset.from_tensor_slices(tf.random_uniform([4, 10]))
dataset2 = tf.data.Dataset.from_tensor_slices((tf.random_uniform([4]), tf.random_uniform([4, 100])))
dataset3 = tf.data.Dataset.zip((dataset1, dataset2))
iterator = dataset3.make_initializable_iterator()
sess.run(iterator.initializer)
next1, (next2, next3) = iterator.get_next()
注意,对next1, next2, or next3的任意一个进行评估都会为所有components进行iterator。一个iterator的一种常见consumer将包含在单个表达式中的所有components。
Dataset API支持多种文件格式,因此你可以处理超过内存大小的大数据集。例如,TFRecord文件格式是一种简单的面向记录的二进制格式,许多TensorFlow应用都用它来做训练数据。tf.data.TFRecordDataset类允许你在一或多个TFRecord文件的内容上进行流化,将它们作为input pipeline的一部分:
# Creates a dataset that reads all of the examples from two files.
filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]
dataset = tf.data.TFRecordDataset(filenames)
妥了,就这样。
不得不说,Datasets.map是个好东西。可以通过它,设计出你想要的任何形式的数据。
根据你的打包TFRecords方式,先进行解析操作。用map就行。
# Transforms a scalar string `example_proto` into a pair of a scalar string and
# a scalar integer, representing an image and its label, respectively.
def _parse_function(example_proto):
features = {"image": tf.FixedLenFeature((), tf.string, default_value=""),
"label": tf.FixedLenFeature((), tf.int32, default_value=0)}
parsed_features = tf.parse_single_example(example_proto, features)
return parsed_features["image"], parsed_features["label"]
# Creates a dataset that reads all of the examples from two files, and extracts
# the image and label features.
filenames = ["/var/data/file1.tfrecord", "/var/data/file2.tfrecord"]
dataset = tf.data.TFRecordDataset(filenames)
dataset = dataset.map(_parse_function)
根据压缩的方式,定义decode方式,这一步往往和解析是写在一起的。然后map一下。
# Reads an image from a file, decodes it into a dense tensor, and resizes it
# to a fixed shape.
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
# A vector of filenames.
filenames = tf.constant(["/var/data/image1.jpg", "/var/data/image2.jpg", ...])
# `labels[i]` is the label for the image in `filenames[i].
labels = tf.constant([0, 37, ...])
dataset = tf.data.Dataset.from_tensor_slices((filenames, labels))
dataset = dataset.map(_parse_function)
一般的情况下,图片数据还是需要归一化的。
def normalize(image, label):
"""Convert `image` from [0, 255] -> [-0.5, 0.5] floats."""
image = tf.cast(image, tf.float32) * (1. / 255) - 0.5
return image, label
batching的最简单方式是,将数据集上n个连续的elements进行stack成单个elements。Dataset.batch() 转换可以精准地做到这一点,它使用与tf.stack() 操作相同的constraints,应用在元素的每个component上:例如,对于每个元素i,所有元素必须具有一个相同shape的tensor:
inc_dataset = tf.data.Dataset.range(100)
dec_dataset = tf.data.Dataset.range(0, -100, -1)
dataset = tf.data.Dataset.zip((inc_dataset, dec_dataset))
batched_dataset = dataset.batch(4)
iterator = batched_dataset.make_one_shot_iterator()
next_element = iterator.get_next()
print(sess.run(next_element)) # ==> ([0, 1, 2, 3], [ 0, -1, -2, -3])
print(sess.run(next_element)) # ==> ([4, 5, 6, 7], [-4, -5, -6, -7])
print(sess.run(next_element)) # ==> ([8, 9, 10, 11], [-8, -9, -10, -11])
上面的方法需要相同的size。然而,许多模型(比如:序列模型)的输入数据的size多种多样(例如:序列具有不同的长度)为了处理这种情况,Dataset.padded_batch() 转换允许你将不同shape的tensors进行batch,通过指定一或多个dimensions,在其上进行pad。
dataset = tf.data.Dataset.range(100)
dataset = dataset.map(lambda x: tf.fill([tf.cast(x, tf.int32)], x))
dataset = dataset.padded_batch(4, padded_shapes=[None])
iterator = dataset.make_one_shot_iterator()
next_element = iterator.get_next()
print(sess.run(next_element)) # ==> [[0, 0, 0], [1, 0, 0], [2, 2, 0], [3, 3, 3]]
print(sess.run(next_element)) # ==> [[4, 4, 4, 4, 0, 0, 0],
# [5, 5, 5, 5, 5, 0, 0],
# [6, 6, 6, 6, 6, 6, 0],
# [7, 7, 7, 7, 7, 7, 7]]
Dataset.padded_batch() 转换允许你为每个component的每个dimension设置不同的padding,它可以是可变的长度(在样本上指定None即可)或恒定长度。你可以对padding值(缺省为0.0)进行override。
这里我们用mnist作为例子。
写入tfrecords文件的方法很多,可能会出现很多不可思议的类型错误。
#!/usr/bin/env python
# coding=utf-8
import tensorflow as tf
import logging
import os
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
logging.basicConfig(level=logging.INFO)
def _int64_feature(value):
return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))
def _bytes_feature(value):
return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))
def convert_tfrecords():
# 注意读取的类型,dtype
mnist = input_data.read_data_sets("./data", dtype=tf.uint8,reshape=True)
# convert train data to tfrecords
data_train = mnist.train
images = data_train.images # list
labels = data_train.labels
num_examples = data_train.num_examples
assert images.shape[0] == num_examples
num_examples, feature_length = images.shape
# tfrecords output path
#os.rmdir('./data/')
#os.mkdir('./data')
outpath = './data/mnist_train.tfrecords'
print "begin writing data into tfrecords....."
writer = tf.python_io.TFRecordWriter(outpath)
for indx in xrange(num_examples):
image_raw = images[indx].tostring()
logging.info('images-{} write in '.format(indx))
# build a example proto for an image example
example = tf.train.Example(features = tf.train.Features(feature = {
'image_raw': _bytes_feature(image_raw),
'label': _int64_feature(int(labels[indx]))
}))
# write in tfrecords
writer.write(example.SerializeToString())
print "writing over !"
writer.close()
return
convert_tfrecords()
def _parse_data(example_proto):
features = { "image_raw": tf.FixedLenFeature((), tf.string, default_value=""),
"label": tf.FixedLenFeature((),tf.int64,default_value=0)}
parsed_features = tf.parse_single_example(example_proto, features)
img = parsed_features["image_raw"]
img = tf.decode_raw(img, tf.uint8)
img = tf.cast(img,tf.float32) * (1./255.) - 0.5
label = parsed_features["label"]
return img,label
# just repeat 一下就ok
dataset = dataset.repeat(10)
Dataset.shuffle() 转换会与tf.RandomShuffleQueue使用相同的算法对输入数据集进行随机shuffle:它会维持一个固定大小的buffer,并从该buffer中随机均匀地选择下一个元素:
dataset = dataset.shuffle(buffer_size=10000)
我这里就没shuffling了。凑合看吧~
filenames = ['./data/mnist_train.tfrecords']
dataset = tf.data.TFRecordDataset(filenames)
dataset = dataset.map(_parse_data)
dataset = dataset.repeat(10)
dataset = dataset.batch(32)
iterator = dataset.make_one_shot_iterator()
next_element = iterator.get_next()
sess = tf.Session()
for i in range(10):
img,label = sess.run(next_element)
print img.shape,label
sess.close()