1. Tensorflow高效流水线Pipeline
1. Tensorflow高效流水线Pipeline
2. Tensorflow的数据处理中的Dataset和Iterator
3. Tensorflow生成TFRecord
4. Tensorflow的Estimator实践原理
1. 前言
GPU和TPU可以显著缩短执行单个训练步所需的时间。实现最高性能需要高效的输入流水线,以在当前时间步完成之前为下一步提供数据。tf.data API可以帮助我们构建灵活高效的输入流水线。本文档介绍了 tf.data API的功能,以及在各种模型和加速器上构建高性能TensorFlow输入流水线的最佳做法。
2. Pipeline Structure输入流水线结构
我们可以将典型的 TensorFlow 训练输入流水线视为 ETL 流程:
- Extract:从永久性存储(可以是 HDD 或 SSD 等本地存储或 GCS 或 HDFS 等远程存储)读取数据。
- Transform:使用CPU核心解析数据并对其执行预处理操作,例如图像解压缩、数据增强转换(例如随机裁剪、翻转和颜色失真)、重排和批处理。
- Load:将转换后的数据加载到执行机器学习模型的加速器设备(例如,GPU 或 TPU)上。
这种模式可高效利用 CPU,同时预留加速器来完成对模型进行训练的繁重工作。此外,将输入流水线视为 ETL 流程可提供便于应用性能优化的结构。
使用 tf.estimator.Estimator API 时,前两个阶段(提取和转换)是在 input_fn(传递给 tf.estimator.Estimator.train)中捕获的。代码可能如以下(简单序列)实现所示:
def parse_fn(example):
"Parse TFExample records and perform simple data augmentation."
example_fmt = {
"image": tf.FixedLengthFeature((), tf.string, ""),
"label": tf.FixedLengthFeature((), tf.int64, -1)
}
parsed = tf.parse_single_example(example, example_fmt)
image = tf.image.decode_image(parsed["image"])
image = _augment_helper(image) # augments image using slice, reshape, resize_bilinear
return image, parsed["label"]
def input_fn():
files = tf.data.Dataset.list_files("/path/to/dataset/train-*.tfrecord")
dataset = files.interleave(tf.data.TFRecordDataset)
dataset = dataset.shuffle(buffer_size=FLAGS.shuffle_buffer_size)
dataset = dataset.map(map_func=parse_fn)
dataset = dataset.batch(batch_size=FLAGS.batch_size)
return dataset
2.1 最佳Pipeline步骤
在这里先给出最佳做法,如果同学们只想知道怎么做,直接参考这里就可以啦。
- 使用 prefetch 转换可将提供方和使用方的工作重叠。我们特别建议将 prefetch(n)(其中 n 是单步训练使用的元素数/批次数)添加到输入流水线的末尾,以便将在 CPU 上执行的转换与在加速器上执行的训练重叠。
- 通过设置 num_parallel_calls 参数并行处理 map 转换。建议您将其值设为可用 CPU 核心的数量。
- 如果您使用 batch 转换将预处理元素组合到一个批次中,建议您使用 map_and_batch 混合转换;特别是在您使用的批次较大时。
- 如果您要处理远程存储的数据并/或需要反序列化,建议您使用 parallel_interleave 转换来重叠从不同文件读取(和反序列化)数据的操作。
- 向量化传递给 map 转换的低开销用户定义函数,以分摊与调度和执行相应函数相关的开销。
- 如果内存可以容纳您的数据,请使用 cache 转换在第一个周期中将数据缓存在内存中,以便后续周期可以避免与读取、解析和转换该数据相关的开销。
- 如果预处理操作会增加数据大小,建议您首先应用 interleave、prefetch 和 shuffle(如果可以)以减少内存使用量。
- 建议您在应用 repeat 转换之前先应用 shuffle 转换,最好使用 shuffle_and_repeat 混合转换。
下面的内容是针对每一点优化的原理。
3. 优化性能
由于新型计算设备(例如 GPU 和 TPU)可以不断提高神经网络的训练速度,因此,CPU 处理很容易成为瓶颈。tf.data API 为用户提供构建块来设计可高效利用 CPU 的输入流水线,并优化 ETL 流程的每个步骤。
3.1 prefetch预取数据
要执行训练步骤,您必须首先提取并转换训练数据,然后将其提供给在加速器上运行的模型。但是,在一个简单的同步实现中,当 CPU 正在准备数据时,加速器处于空闲状态。相反,当加速器正在训练模型时,CPU 处于空闲状态。因此,训练步的用时是 CPU 预处理时间和加速器训练时间的总和。
流水线将训练步骤的预处理和模型执行过程重叠到一起。当加速器正在执行第 N 个训练步时,CPU 正在准备第 N+1 步的数据。这样做不仅可以最大限度地缩短训练的单步用时(而不是总用时),而且可以缩短提取和转换数据所需的时间。
如果不使用流水线,CPU 和 GPU/TPU 在大部分时间都处于空闲状态:
使用流水线可显著减少空闲时间:
tf.data API 通过 tf.data.Dataset.prefetch 转换提供了一种软件流水线机制,该机制可用于将生成数据的时间和使用数据的时间分离开。具体而言,该转换使用后台线程和内部缓冲区,以便在请求元素之前从输入数据集中预取这些元素。因此,为了实现上图所示的流水线效果,您可以将 prefetch() 作为最终转换添加到数据集流水线中(如果单步训练使用 n 个元素,则添加 prefetch(n))。
要将此项更改应用于我们正在运行的示例,请将:
dataset = dataset.batch(batch_size=FLAGS.batch_size)
return dataset
更改为:
dataset = dataset.batch(batch_size=FLAGS.batch_size)
dataset = dataset.prefetch(buffer_size=FLAGS.prefetch_buffer_size)
return dataset
3.2 map并行处理数据转换
准备批次数据时,可能需要预处理输入元素。为此,tf.data API 提供了 tf.data.Dataset.map 转换,以将用户定义的函数(例如,正在运行的示例的 parse_fn)应用于输入数据集的每个元素。由于输入元素彼此独立,因此可以跨多个 CPU 核心并行执行预处理。为实现这一点,map 转换提供了 num_parallel_calls 参数来指定并行处理级别。例如,下图说明了将 num_parallel_calls=2 设置为 map 转换的效果:
并行后,由于数据预处理的时间缩短,整体的时间也减少了。如何为 num_parallel_calls 参数选择最佳值取决于硬件、训练数据的特征(例如其大小和形状)、映射函数的成本以及同时在 CPU 上进行的其他处理;一个简单的启发法是设为可用 CPU 核心的数量。例如,如果执行以上示例的机器有 4 个核心,则设置 num_parallel_calls=4 会更高效。另一方面,将 num_parallel_calls 设置为远大于可用 CPU 数量的值可能会导致调度效率低下,进而减慢速度。
要将此项更改应用于我们正在运行的示例,请将:
dataset = dataset.map(map_func=parse_fn)
更改为:
dataset = dataset.map(map_func=parse_fn, num_parallel_calls=FLAGS.num_parallel_calls)
此外,如果批次大小为数百或数千,那么并行处理批次创建过程还可能给流水线带来更大的优势。为此,tf.data API 提供了 tf.contrib.data.map_and_batch 转换,它可以有效地将映射和批次转换“混合”在一起。
要将此项更改应用于我们正在运行的示例,请将:
dataset = dataset.map(map_func=parse_fn, num_parallel_calls=FLAGS.num_parallel_calls)
dataset = dataset.batch(batch_size=FLAGS.batch_size)
更改为:
dataset = dataset.apply(tf.contrib.data.map_and_batch(
map_func=parse_fn, batch_size=FLAGS.batch_size))
3.3 并行处理远程数据提取
在实际设置中,输入数据可能会远程存储(例如,GCS 或 HDFS),这是因为输入数据不适合本地存储,或因为训练是分布式训练,因此在每台机器上复制输入数据没有意义。非常适合在本地读取数据的数据集流水线在远程读取数据时可能会遇到 I/O 瓶颈,这是因为本地存储和远程存储之间存在以下差异:
- 首字节时间:与本地存储相比,从远程存储读取文件的首字节所用时间可能要多出几个数量级。
- 读取吞吐量:虽然远程存储通常可提供较大的聚合带宽,但读取单个文件可能只能利用此带宽的一小部分。
此外,将原始字节读入内存中后,可能还需要对数据进行反序列化或解密(例如,protobuf),这会带来额外的开销。无论数据是在本地还是远程存储,都存在这种开销,但如果未有效预取数据,则在远程存储的情况下可能更糟。
为了降低各种数据提取开销的影响,tf.data API 提供了 tf.contrib.data.parallel_interleave 转换。使用此转换可以并行执行其他数据集(例如数据文件读取器)并交错这些数据集的内容。可以通过 cycle_length 参数指定要重叠的数据集的数量。
下图说明了为 parallel_interleave 转换提供 cycle_length=2 的效果:
要将此项更改应用于我们正在运行的示例,请将:
dataset = files.interleave(tf.data.TFRecordDataset)
更改为:
dataset = files.apply(tf.contrib.data.parallel_interleave(
tf.data.TFRecordDataset, cycle_length=FLAGS.num_parallel_readers))
由于负载或网络事件,远程存储系统的吞吐量可能会随时间而变化。鉴于这种差异,parallel_interleave 转换可以选择使用预取(如需了解详情,请参阅 tf.contrib.data.parallel_interleave)。
默认情况下,parallel_interleave 转换可提供元素的确定性排序以帮助实现可再现性。作为预取的替代方案(在某些情况下可能效率低下),parallel_interleave 转换还提供了一个可提升性能但无法保证排序的选项。特别是,如果 sloppy 参数设为 true,则该转换可在系统请求下一个元素时暂时跳过其元素不可用的文件,从而放弃该转换的确定性排序。
4. 性能考虑因素
tf.data API 围绕可组合转换而设计,旨在为用户提供灵活性。虽然这些转换中有很多都是可以交替的,但某些转换的顺序会对性能产生影响。
4.1 map映射和batch批次
调用传递给 map 转换的用户定义函数具有与调度和执行用户定义函数相关的开销。通常,与函数执行的计算量相比,这种开销很小。但是,如果 map 几乎不起作用,那么这种开销可能会占总成本的很大一部分。在这种情况下,建议向量化用户定义的函数(即,让该函数一次对一批输入进行操作),并在 map 转换之前先应用 batch 转换。
或者直接更改为如下代码:
dataset = dataset.apply(tf.contrib.data.map_and_batch(
map_func=parse_fn, batch_size=FLAGS.batch_size))
4.2 map映射和cache缓存
tf.data.Dataset.cache 转换可以在内存或本地存储中缓存数据集。如果传递给 map 转换的用户定义函数代价很高,则只要内存或本地存储仍可以容纳生成的数据集,就可以在映射转换后应用缓存转换。如果用户定义的函数会增加存储数据集所需的空间,并超出缓存容量,请考虑在训练作业之前预处理数据以减少资源消耗量。
4.3 map映射和interleave交错/prefetch预取/shuffle重排
许多转换(包括map interleave、prefetch 和 shuffle)都维持一个内部元素缓冲区。如果传递给 map 转换的用户定义函数改变了元素的大小,那么映射转换的顺序和缓冲元素的转换会影响内存使用量。通常,我们建议选择可以减少内存占用的顺序,除非为了提高性能而需要采用不同的顺序(例如,为了混合映射和批次转换)。
4.4 repeat重复和shuffle重排
tf.data.Dataset.repeat 转换会将输入数据重复有限(或无限)次;每次数据重复通常称为一个周期。tf.data.Dataset.shuffle 转换会随机化数据集样本的顺序。
如果在 shuffle 转换之前应用 repeat 转换,则系统会对周期边界进行模糊处理。也就是说,某些元素可以在其他元素出现之前重复出现。另一方面,如果在重复转换之前应用 shuffle 转换,那么在每个周期开始时性能可能会降低,因为需要初始化 shuffle 转换的内部状态。换言之,前者(repeat 在 shuffle 之前)可提供更好的性能,而后者(repeat 在 shuffle 之前)可提供更强的排序保证。
如果可能,建议您使用 tf.contrib.data.shuffle_and_repeat 混合转换,这样可以达到两全其美的效果(良好的性能和强大的排序保证)。否则,我们建议在repeat重复之前进行shuffle重排。
1. Tensorflow高效流水线Pipeline的更多相关文章
- jenkins的流水线pipeline+项目实验php
声明:实验环境使用Jenkins的应用与搭建的环境 新建一个流水线 pipeline脚本语法架构 node('slave节点名'){ def 变量 #def可以进行变量声明 stage('阶段名A') ...
- Tensorflow高效读取数据的方法
最新上传的mcnn中有完整的数据读写示例,可以参考. 关于Tensorflow读取数据,官网给出了三种方法: 供给数据(Feeding): 在TensorFlow程序运行的每一步, 让Python代码 ...
- Jenkins流水线(pipeline)实战之:从部署到体验
关于Jenkins流水线(pipeline) Jenkins 流水线 (pipeline) 是一套插件,让Jenkins可以实现持续交付管道的落地和实施. 关于blueocean Blue Ocean ...
- Hadoop架构: 流水线(PipeLine)
该系列总览: Hadoop3.1.1架构体系——设计原理阐述与Client源码图文详解 : 总览 流水线(PipeLine),简单地理解就是客户端向DataNode传输数据(Packet)和接收Dat ...
- 吴裕雄 python 机器学习——数据预处理流水线Pipeline模型
from sklearn.svm import LinearSVC from sklearn.pipeline import Pipeline from sklearn import neighbor ...
- 8.Jenkins进阶之流水线pipeline基础使用实践(1)
目录一览: 0x01 基础实践 (1) Maven 构建之 Pipeline Script (2) Maven 构建之 Pipeline Script from SCM (3) Jenkins pi ...
- TensorFlow高效读取数据的方法——TFRecord的学习
关于TensorFlow读取数据,官网给出了三种方法: 供给数据(Feeding):在TensorFlow程序运行的每一步,让python代码来供给数据. 从文件读取数据:在TensorFlow图的起 ...
- Redis附加功能之Redis流水线pipeline
流水线功能的目的:通过减少客户端与服务器之间的通信次数来提高程序的执行效率. 一.通信 在一般情况下, 用户每执行一个 Redis 命令,客户端与服务器都需要进行一次通信:客户端会将命令请求发送给服务 ...
- Tensorflow高效读取数据
关于Tensorflow读取数据,官网给出了三种方法: 供给数据(Feeding): 在TensorFlow程序运行的每一步, 让Python代码来供给数据. 从文件读取数据: 在TensorFlow ...
随机推荐
- hihocode #1032 : 最长回文子串【manacher】模板题
题目链接:https://vjudge.net/problem/HihoCoder-1032 manacher算法详解:https://blog.csdn.net/dyx404514/article/ ...
- go语言学习-结构体
结构体 go语言中的结构体,是一种复合类型,有一组属性构成,这些属性被称为字段.结构体也是值类型,可以使用new来创建. 定义: type name struct { field1 type1 fie ...
- IE8 margin:0 auto 不能居中显示的问题
ie8下面margin:0 auto;不能居中的解决方案,ie8兼容性代码 今天写了个div,用margin:0 auto:来定义他的属性,让他居中,结果,竟然无效. 一开始以为是css里的代码冲突了 ...
- China Operating System 电脑操作系统 2016全球互联网排名
韩梦飞沙 韩亚飞 313134555@qq.com yue31313 han_meng_fei_sha 电脑操作系统 China Operating System 2016全球互联网排名 == ...
- web开发必备的浏览器常识
浏览器内核: 1.使用Trident内核的浏览器:IE.Maxthon.TT.The World等: 2.使用Gecko内核的浏览器:Netcape6及以上版本.FireFox.MozillaSuit ...
- Linux.Centos安装scp反复失败的解决方式
现象 在A服务器用scp命令从B服务器拉文件时, 报找不到scp指令的错误 在A服务器运行 scp --help 发现是有指令的 在B服务器上 常规方式, 安装openssh-clients yum ...
- Reactor 3 学习笔记(1)
Reactor 3 与之前学习的RxJava是同一类(反应式编程)框架,基本概念大致差不多,简单记录一下: Reactor 3 利用了java 8中的CompletableFuture.Stream. ...
- java程序中实现打开 某个指定浏览器
package com.test; import java.lang.reflect.Method; //实现打开浏览器并跳到指定网址的类 public class BareBonesBrowserL ...
- 如何正确地使用android中的progressdialog
网上有很多关于progressdialog的用法的介绍,下面这个是最具代表性的: http://sd8089730.iteye.com/blog/1441610 其核心代码: Handler hand ...
- VC在线程中操作界面
http://blog.csdn.net/tingsking18/article/details/4399199 多线程是我们在编程中经常遇到的问题,线程执行完后往往要把执行的结果传给主线程,但是MF ...