tensorflow数据篇(三)——使用tf.data建立数据通道

使用tf.data建立数据通道

 

动 机

在机器学习项目中构建输入管道总是漫长而痛苦的,并且比构建实际模型需要更多的时间。在本教程中,我们将学习如何使用TensorFlow的数据集模块tf.data为图像和文本构建有效的管道。

 

目 标

  • 学习如何使用tf.data并练习
  • 建立高效的加载图像和处理图像的通道
  • 建立高效的文本处理通道,包括如何建立词库

 

内容目录

一、tf.data概述

  • 使用Text Example介绍tf.data
  • 迭代和转换
  • 为什么使用可初始化的迭代器
  • 代码示例地址

二、建立图像数据通道

三、建立文本数据通道

  • 文件格式
  • 数据合并
  • 创建词汇表
  • 创建填充批次
  • 计算句子大小
  • 高级用法-提取字符

四、最佳实践

  • 打乱和重复
  • 使用多线程实现并行化
  • 预取数据
  • 操作顺序

 

 

一、tf.data概述

  官方资源

 使用Text Example介绍tf.data

新建file.txt文件,包含语句

I use Tensorflow
You use PyTorch
Both are great

 使用tf.dataAPI读取文件:

dataset = tf.data.TextLineDataset('file.txt')

迭代和转换

dataset是Tensorflow的图节点,其包含着读取文件的指令。如果我们想要读取文件,我们需要初始化图并在会话中评估这个节点。尽管这个听起来很复杂,实际上恰恰相反。现在甚至数据集对象也是图的一部分,因此你不需要担心如何将数输入模型。

我们需要增加一些额外的代码,来完成工作。首先,我们创建一个基于整个数据集的迭代器对象。

iterator = dataset.make_one_shot_iterator()
next_element = iterator.get_next()

The one_shot_iterator method creates an iterator that will be able to iterate once over the dataset. In other words, once we reach the end of the dataset, it will stop yielding elements and raise an Exception.

现在,next_element 是图的节点。每一次执行,它将包含着迭代器的下一个元素。执行如下

with tf.Session() as sess:
    for i in range(3):
        print(sess.run(next_element))

既然已经了解tf.data API的基本原理,下面介绍一些先进的技巧。

例如

import tensorflow as tf
print(tf.__version__)
dataset = tf.data.TextLineDataset('file.txt')
#the value in dataet :‘I use Tensorflow’->['I', 'use', 'Tensorflow']
#'You use PyTorch'->['You', 'use', 'PyTorch']
#基于分隔符分割输入的每个元素
#map函数映射输入函数到整个数据集
#values为SparseTensor属性,表示取值
dataset = dataset.map(lambda string: tf.string_split([string]).values)

#将数据集中连续的元素以batch_size为单位集合成批次
dataset = dataset.batch(2)
#预取数据,即它总是使得一个批次的数据准备被加载。
dataset = dataset.prefetch(1)
#创建基于整个数据集的迭代器
iterator = dataset.make_one_shot_iterator()
#使用get_next()方法取出元素,每次执行,next_element保存迭代器的下一个元素
next_element = iterator.get_next()

with tf.Session() as sess:
    print(sess.run(next_element))

为什么使用初始化迭代

通过初始化节点,相当于重新加载数据(make_one_shot仅执行一个epoch),我们可以选择从头开始训练。这对于我们执行多次epoch操作,极为有利。

dataset = tf.data.TextLineDataset('file.txt')
iterator = dataset.make_initializable_iterator()
next_element = iterator.get_next()
init_op = iterator.initializer

with tf.Session() as sess:
  #初始化迭代器
  sess.run(init_op)
  print(sess.run(next_element))
  print(sess.run(next_element))
  #移动迭代器到最初
  sess.run(init_op)
  print(sess.run(next_element))

 

二、建立图像数据通道

假设我们已经有了一个包含所有JPEG图像名称的列表和一个与之对应的标签列表。

通道建立步骤如下:

  1. 从文件名和标签的切片创建数据集
  2. 使用长度等于数据集大小的buffer size,打乱数据集。这确保了良好的改组。
  3. 从图像文件名中解析像素值。使用多线程提升预处理的速度
  4. (可选操作)图像数据扩增。使用多线程提升预处理的速度。
  5. 批量处理图片
  6. 预取一个批次以确保批处理可以随时使用

tf.data.Dataset的方法的输入为其内部的数据。

dataset = tf.data.Dataset.from_tensor_slices((filenames, labels))
dataset = dataset.shuffle(len(filenames))
dataset = dataset.map(parse_function, num_parallel_calls=4)
dataset = dataset.map(train_preprocess, num_parallel_calls=4)
dataset = dataset.batch(batch_size)
dataset = dataset.prefetch(1)

parse_function功能如下:

  1. 读取图像文件内容
  2. 使用JPEG图像格式解码
  3. 转化为0到1的浮点值
  4. 修改尺寸到(64, 64)
def parse_function(filename, label):
    image_string = tf.read_file(filename)
    
    #Don't use tf.image.decode_image, or the output shape will be undefined.
    image = tf.image.decode_jepg(image_string, channels)
    
    #This will convert to float values in [0, 1]
    image = tf.image.convert_image_dtype(image, tf.float32)

    image = tf.image.resize_images(image, [64, 64])
    return resized_image, label

函数 train_preprocess(optionally)可用于执行数据扩增。

  • 以1/2的概率水平翻转图像
  • 应用随机亮度和饱和度
def train_preprocess(image, label):
  image  tf.image.random_flip_left_right(image)
  
  image = tf.image.random_brightness(image, max_delta=32)
  image = tf.image.random_saturation(image, lowe=0.5, upper=1.5)
  
  #Make sure the image is still in [0, 1]
  image = tf.clip_by_value(image, 0.0, 1.0)
  return image, label

三、建立文本数据输入通道

 

四、最佳实践

参考https://www.tensorflow.org/guide/performance/overview#input_pipeline

一般情况下,将所有的数据处理通道放在cpu上,以此保证gpu仅用于训练深度神经网络模型。

wtih tf.device('/cpu:0'):
    dataset =  ....

打乱和重复

当在一个数据集上训练时,我们常需要重复循环多个epochs并打乱重组。

一个特别需要注意的地方时,当重组时候,保证buffer_size参数需要足够的大,一般为整个数据集大小。此参数值越大,它在初始时候将花费更多的时间加载数据。然而一个较小的buffer_size可能对于训练产生糟糕的影响。参考解释

最好避免这种错误的方法是提前将数据集分为train/dev/test并打乱。参考解释

另一个建议点是,最好在数据通道建立初始就打乱和重复。例如,假设数据集输入是一个文件名列表,如果直接打乱,之后tf.data.Dataset.shuffle()将只包含文件名,这只占用很小的内存资源。

When choosing the ordering between shuffle and repeat, you may consider two options:

  • shuffle then repeat: we shuffle the dataset in a certain way, and repeat this shuffling for multiple epochs (ex: [1, 3, 2, 1, 3, 2] for 2 epochs with 3 elements in the dataset)
  • repeat then shuffle: we repeat the dataset for multiple epochs and then shuffle (ex: [1, 2, 1, 3, 3, 2] for 2 epochs with 3 elements in the dataset)

The second method provides a better shuffling, but you might wait multiple epochs without seeing an example. The first method makes sure that you always see every element in the dataset at each epoch. You can also use tf.contrib.data.shuffle_and_repeat() to perform shuffle and repeat.

使用多线程并行化

tf.data模块运行时,使用多线程进行数据通道处理,从而实现并行,这种操作几乎是透明的。我们只需要添加一个num_parallel_calls参数到每一个dataset.map()call中,

num_threads = 4
dataset = dataset.map(parse_function, num_parallel_calls=num_threads)

如果使用值tf.data.experimental.AUTOTUNE,则根据可用的CPU动态设置并行调用的数量。

预取数据

当GPU执行在当前批次执行前向或者后向传播时,我们希望CPU处理下一个批次的数据,以便于数据批次能够迅速被GPU使用。我们希望GPU被完全、时刻用于训练。我们称这种机制为消费者/生产者重叠,消费者是GPU,生产者是CPU。

使用tf.data,你可以轻易的做到只一点,只需要在通道末尾调用dataset.prefetch(1)。这将总是预取一个批次的数据,并且保证总有一个数据准备好被消耗。

dataset = dataset.batch(64)
dataset = dataset.prefetch(1)

特殊情况下,预取超过一个批次的数据会更有用。例如,当预处理时间较长,预取10个批次的数据将会优于取10次。

更具体的例子,加入10%的批次需要10s计算,90%需要1s计算。如何GPU花费2s在一个batch上训练,通过预取多个批次,我们永远不需要等待那些稀缺的花费时间更长的批次。

 

操作顺序

总结,不同转换数据的方式执行顺序如下:

  1. 创建实例
  2. 重组(较大的buffer_size)
  3. 重复
  4. 数据预处理、数据扩增,使用多线程等
  5. 批次化
  6. 预取数据

更多https://towardsdatascience.com/how-to-use-dataset-in-tensorflow-c758ef9e4428

  • 9
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值