目录
(1)Placeholder
Placeholder的方式比较简单,在设计计算图时把输入设置为占位符Placeholder,在执行计算时通过feed_dict来传入数据,数据的预处理在计算图外面进行
sess.run([accuracy, pred], feed_dict={image: batch_image, label: batch_label, keep_prob: 1})
这种方式数据的预处理需要占用较大的开销,数据量大时会影响计算速度
(2)队列的形式,建立从文件到tensor的映射
使用文件名队列+内存队列的形式,把数据导入和计算图的执行放到不同的线程中执行,从而减少IO开销,加快计算速度
读取步骤如下:
1. 用 tf.train.string_input_producer建立文件名队列
filename_queue = tf.train.string_input_producer(filenames, num_epochs, shuffle)
文件名队列的三个重要参数:文件名列表、epoch数、是否乱序,若乱序,会在一个epoch内部打乱顺序
2. 用tf.reader读取数据
内存队列不需要手动建立,只需使用reader对象从文件名队列中读取数据就可以了。
reader主要有tf.WholeFileReader()和 tf.FixedLengthRecordReader两种,前者直接读取整个文件(如读取图像文件),后者每次读取二进制文件的一部分(如读取二进制保存的文件)
reader = tf.FixedLengthRecordReader(record_bytes = result._record_bytes)
result.key, value = reader.read(filename_queue)
3. 调用tf.train.start_queue_runners启动队列
必须要执行这一步,否则没有填充数据,计算图会一直处于阻塞等待状态
4. 调用sess.run读取数据
with tf.Session() as sess:
filename_list = ['A.jpg', 'B.jpg', 'C.jpg']
filename_queue = tf.train.string_input_producer(filename_list, num_epochs=5, shuffle=False)
reader = tf.WholeFileReader() #tf.FixedLengthRecordReader(record_bytes = num_bytes)
key, value = reader.read(filename_queue)
tf.local_variables_initializer().run() #因为有num_epochs=5,需进行初始化
threads = tf.train.start_queue_runners(sess = sess)
i = 0
while i < 15:
i += 1
image_data = sess.run(value)
文件名队列读取完毕后,若读取操作未停止,会抛出OutOfRangeError异常
(3)tf 原生 Dataset API
参考文献:TensorFlow全新的数据读取方式:Dataset API入门教程
Dataset API是从tensorflow1.3开始引入的模块,专门用于构建输入的pipeline,结合了Placeholder和队列的优点
tensorflow1.3放在tf.contrib.data.Dataset中,1.4开始成为了核心API,放在tf.data.Dataset中
读取步骤如下:
1. 创建Dataset
2. 从Dataset中实例化一个Iterator
3. 对Iterator进行迭代,读取数据
dataset = tf.data.Dataset.from_tensor_slices(np.array([1.0, 2.0, 3.0, 4.0, 5.0]))
iterator = dataset.make_one_shot_iterator()
one_element = iterator.get_next()
with tf.Session() as sess:
try:
while True:
print(sess.run(one_element))
except tf.errors.OutOfRangeError:
print("end!")
其中,tf.data.Dataset.from_tensor_slices()的作用是切分传入的tensor的第一个维度,生成dataset。除了可以是简单的一维列表,还可以是更复杂的数据结构,如矩阵、元组、词典等形式
矩阵:如下面的(5,2)二维矩阵,得到的是包含5个元素,每个元素形状为(2,)的dataset
dataset = tf.data.Dataset.from_tensor_slices(np.random.uniform(size=(5, 2)))
# [0.52155373 0.53907886]
# [0.36116747 0.43672128]
# [0.3303004 0.46345623]
# [0.33523273 0.36780843]
# [0.8326401 0.08976421]
元组:与矩阵类似,将对元组第一个维度进行切分
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))))
# ('1.0', array([0.36135805, 0.56215008]))
# ('2.0', array([0.52311487, 0.89666151]))
# ('3.0', array([0.99675441, 0.63204821]))
# ('4.0', array([0.68753578, 0.64301258]))
# ('5.0', array([0.9371197 , 0.13179488]))
词典:如下面的词典数据,得到的dataset的一个元素形式类似于 {"a": 1.0, "b": [0.9, 0.1]}
dict = { "a": np.array([1.0, 2.0, 3.0, 4.0, 5.0]), "b": np.random.uniform(size=(5, 2)) }
dataset = tf.data.Dataset.from_tensor_slices(dict)
# {'a': '1.0', 'b': array([0.51324604, 0.61600024])}
# {'a': '2.0', 'b': array([0.44094485, 0.27298109])}
# {'a': '3.0', 'b': array([0.91821223, 0.70311565])}
# {'a': '4.0', 'b': array([0.02771158, 0.31863663])}
# {'a': '5.0', 'b': array([0.38443944, 0.10002596])}
4. 对数据进行Trainsformation处理
对数据进行Transformation处理主要有:map、batch、shuffle、repeat
map:接受一个函数作为输入,用以对每个元素进行加工
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
batch:把数据打包成batch
dataset = dataset.batch(32)
shuffle:在每一个epoch内部进行乱序
dataset = dataset.shuffle(buffer_size=10000)
repeat:对数据进行重复,即生成多个epoch
dataset = dataset.repeat(5) #如果不输入参数,将会无限重复
示例代码:
# 函数的功能时将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中的一个元素是(image_resized_batch, label_batch),image_resized_batch的形状为(32, 28, 28, 3),而label_batch的形状为(32, )
Dataset的其他创建方法:
除了tf.data.from_tensor_slices之外,Dataset还有以下几种创建方式:
- tf.data.TextLineDataset():这个函数的输入是一个文件的列表,输出是一个dataset。dataset中的每一个元素就对应了文件中的一行。可以使用这个函数来读入CSV文件。
- tf.data.FixedLengthRecordDataset():这个函数的输入是一个文件的列表和一个record_bytes,之后dataset的每一个元素就是文件中固定字节数record_bytes的内容。通常用来读取以二进制形式保存的文件,如CIFAR10数据集就是这种形式。
- tf.data.TFRecordDataset():顾名思义,这个函数是用来读TFRecord文件的,dataset中的每一个元素就是一个TFExample。
Iterator的其他创建方式:
除了dataset.make_one_shot_iterator之外,Iterator还有以下几种创建方式:
- initializable iterator
可以将placeholder代入Iterator中(必须要在使用前通过sess.run()来初始化),这种方式有两种用途:
1、通过参数快速定义新的Iterator,
limit = tf.placeholder(dtype=tf.int32, shape=[])
dataset = tf.data.Dataset.from_tensor_slices(tf.range(start=0, limit=limit))
iterator = dataset.make_initializable_iterator()
next_element = iterator.get_next()
with tf.Session() as sess:
sess.run(iterator.initializer, feed_dict={limit: 10})
for i in range(10):
value = sess.run(next_element)
assert i == value
2、避免直接将大的数据一次性全部保存到计算图中
# 从硬盘中读入两个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})
- reinitializable iterator(较少使用)
- feedable iterator(较少使用)
(4)slim.dataset
数据文件为flowers_train_00000-of-00005.tfrecord到flowers_train_00004-of-00005.tfrecord。tfrecord的格式如下
tf.train.Example(features=tf.train.Features(feature={
'image/encoded': bytes_feature(image_data),
'image/format': bytes_feature(image_format),
'image/class/label': int64_feature(class_id),
'image/height': int64_feature(height),
'image/width': int64_feature(width),
}))
读取过程如下:
- 定义一个decoder,主要定义以下两个部分
1. 把example反序列化回原来的格式
2. 封装成需要的输出格式
- 定义dataset,输入文件路径等参数
- 定义provider,确定文件读取的相关参数
- 通过provider.get得到数据,进行预处理
- 把需要获取的最终数据传入slim.prefetch_queue,在prefetch_queue中deueue,得到真正的数据
# 第一步
# 将example反序列化成存储之前的格式。由tf完成
keys_to_features = {
'image/encoded': tf.FixedLenFeature((), tf.string, default_value=''),
'image/format': tf.FixedLenFeature((), tf.string, default_value='png'),
'image/class/label': tf.FixedLenFeature(
[], tf.int64, default_value=tf.zeros([], dtype=tf.int64)),
}
# 第一步
# 将反序列化的数据组装成更高级的格式。由slim完成
items_to_handlers = {
'image': slim.tfexample_decoder.Image('image/encoded','image/format'),
'label': slim.tfexample_decoder.Tensor('image/class/label'),
}
# 解码器,进行解码
decoder = slim.tfexample_decoder.TFExampleDecoder(
keys_to_features, items_to_handlers)
# dataset对象定义了数据集的文件位置,解码方式等元信息
dataset = slim.dataset.Dataset(
data_sources=file_pattern,
reader=tf.TFRecordReader,
decoder=decoder,
num_samples=SPLITS_TO_SIZES[split_name],#训练数据的总数
items_to_descriptions=_ITEMS_TO_DESCRIPTIONS,
num_classes=_NUM_CLASSES,
labels_to_names=labels_to_names #字典形式,格式为:id:class_call,
)
# provider对象根据dataset信息读取数据
provider = slim.dataset_data_provider.DatasetDataProvider(
dataset,
num_readers=FLAGS.num_readers,
common_queue_capacity=20 * FLAGS.batch_size,
common_queue_min=10 * FLAGS.batch_size)
# 获取数据,获取到的数据是单个数据,还需要对数据进行预处理,组合数据
[image, label] = provider.get(['image', 'label'])
# 图像预处理
image = image_preprocessing_fn(image, train_image_size, train_image_size)
images, labels = tf.train.batch(
[image, label],
batch_size=FLAGS.batch_size,
num_threads=FLAGS.num_preprocessing_threads,
capacity=5 * FLAGS.batch_size)
labels = slim.one_hot_encoding(
labels, dataset.num_classes - FLAGS.labels_offset)
batch_queue = slim.prefetch_queue.prefetch_queue(
[images, labels], capacity=2 * deploy_config.num_clones)
# 组好后的数据
images, labels = batch_queue.dequeue()