近年来,深度学习在解决模式识别中广泛的问题方面发挥了核心作用。在优步先进技术集团(Uber Advanced Technologies Group, ATG),我们使用深度学习来解决自动驾驶领域的各种问题,因为其中许多是模式识别问题。我们的许多模型需要从许多传感器(包括相机、激光雷达和雷达)获得数十tb的训练数据。
雷竞技是骗人的优步ATG的研究人员和工程师正积极推动自动驾驶技术跨越多个问题领域,如感知、预测和规划。为了支持这些努力,我们的团队正在开发数据集存储解决方案,使研究人员更容易获得数据,使他们能够专注于模型实验。雷竞技是骗人的
在这篇文章中,我们将描述Petastorm,一个在Uber ATG开发的开源数据访问库。该库支持直接从多个tb的数据集中对深度学习模型进行单机或分布式训练和评估Apache拼花格式。petstorm支持流行的基于python的机器学习(ML)框架,例如Tensorflow,Pytorch,PySpark.它也可以从纯Python代码中使用。
一个深度学习集群设置
即使在现代硬件上,训练最先进的模型也需要时间,而且在许多情况下,将训练负载分配到多台机器上是非常必要的。典型的深度学习集群执行以下步骤:
- 一台或多台机器从集中式或本地数据集中读取样本。
- 各个机器评估损失函数的值,并计算其相对于模型参数的梯度。这一步通常使用GPU卡。
- 模型系数通过组合估计的梯度更新,通常由多台机器以分布式方式计算(例如,使用我们的开源软件)HorovodLibrary,一个深度学习框架)。
如下面的图1所示:
考虑到gpu的开销,启用gpu的集群的良好性能利用率是必不可少的。一个经过良好调优的数据访问层可以确保用于训练的数据始终对gpu可用,这样gpu就不会处于空闲状态。在这种设置中,数据直接从存储流向各个节点。
简化模型体系结构研究雷竞技是骗人的
准备一个包含来自多个数据源的正确同步数据的多tb数据集通常是一项容易出错的任务。我们希望为研究人员提供一个单一的雷竞技是骗人的数据集,使他们能够从事广泛的任务,而不必为每个问题创建一个新的数据集。
为此,需要遵循以下原则:
- 数据集包含研究人员可能需要的数据的超集,因此他们可以为特定的实验挑选列和行的子集。雷竞技是骗人的要想大规模实现这一目标,需要谨慎选择技术。
- 存储在数据集中的传感器数据经过最低限度的预处理。我们鼓励研究人员实时执行所雷竞技是骗人的有预处理,作为培训/评估程序的一部分。在许多情况下,这可以通过使用其他未充分利用的cpu实现。
深度学习应用程序中数据集存储的常见行业模式通常分为两类:多文件数据集和记录流数据集。在接下来的部分中,我们将更详细地描述它们。
多文件数据集
在这种情况下,每个张量/图像/标签集存储在一个单独的文件中(例如,PNG、JPEG、NPZ和CSV)。整个数据集存储为一个或多个文件系统目录,每个目录包含许多文件。文件的数量可能达到数百万(例如,ImageNet拥有120万个文件)。如果以这种格式存储,Uber ATG使用的数据集将有超过1亿个文件。
这种方法使用户可以随机访问数据集中任意行中的任意列。然而,到文件系统的多次往返开销很大。很难大规模实现,特别是使用现代分布式文件系统,例如HDFS而且S3(这些系统通常为快速读取大块数据而优化)。
记录流数据集
或者,将行集分组到一个或多个文件中。例如,Tensorflow使用一个protobuf文件(TFRecord).其他流行的格式包括HDF5而且Python pickle文件.
这种方法适用于HDFS和S3文件系统。然而,查询特定列需要通过网络传输所有字段,然后丢弃未使用的数据。查询单行还需要自定义索引实现。
在评估了多个选项后,我们决定利用Apache Parquet存储格式,它可以缓解这两种方法的一些缺点:
- 促进大型连续读取(HDFS/ s3友好)
- 支持对单个列的快速访问
- 在某些情况下允许更快的行查询
- 可以很好地与Apache Spark集成,作为现成的查询/操作框架
柱状存储和Apache Parquet
列数据存储按列顺序而不是按行顺序组织表数据。例如,自动驾驶汽车传感器记录的数据表可能是这样的:
| 行 | 相机# 1 | 相机# 2 | 激光雷达 | 标签 |
| 1 | < camera1-1 > | < camera2-1 > | < 1 >激光雷达 | < 1 >标签 |
| 2 | < camera1-2 > | < camera2-2 > | < 2 >激光雷达 | < 2 >标签 |
| 3. | < camera1-3 > | < camera2-3 > | < 3 >激光雷达 | < 3 >标签 |
行存储和柱存储的区别如下:
| 行存储 | 列存储 | ||
| 第1行 | < camera1-1 > | < camera1-1 > | |
| < camera2-1 > | < camera1-2 > | ||
| < 1 >激光雷达 | < camera1-3 > | ||
| < 1 >标签 | < camera2-1 > | ||
| 第二行 | < camera1-2 > | < camera2-2 > | |
| < camera2-2 > | < camera2-3 > | ||
| < 2 >激光雷达 | < 1 >激光雷达 | ||
| < 2 >标签 | < 2 >激光雷达 | ||
| 第三行 | < camera1-3 > | < 3 >激光雷达 | |
| < camera2-3 > | < 1 >标签 | ||
| < 3 >激光雷达 | < 2 >标签 | ||
| < 3 >标签 | < 3 >标签 |
按列顺序存储数据允许用户只加载列的一个子集,从而减少通过网络传输的数据量。在自动驾驶车辆的丰富传感器数据的情况下,好处可能是显著的:如果你的实验只使用单个相机的图像,那么考虑从同一行存储的10张高分辨率图像中只加载一张图像。
Apache Parquet是近年来流行的柱状存储格式。它得到了Apache Spark、Apache Arrow和其他开源项目的良好支持,并且它拥有简化模型体系结构研究所需的属性。雷竞技是骗人的
Tensorflow和Pytorch是深度学习社区常用的框架。这些框架本身并不支持Parquet存储访问,因此我们构建了petstorm来弥补这一差距。
引入Petastorm
通常,数据集是通过连接来自多个数据源的记录而生成的。这个数据集是由Apache Spark的Python接口PySpark生成的,稍后会被ML训练过程使用。petstorm提供了一个简单的函数,它用petstorm特定的元数据增强了标准的Parquet存储,从而使其与petstorm兼容。
使用petstorm,使用数据就像从HDFS或文件系统路径创建一个读取器对象并对其进行迭代一样简单。petstorm使用PyArrow库读取Parquet文件。这个过程的高层概述如下图2所示:
生成数据集
要用petstorm生成数据集,用户首先需要定义一个数据模式,称为un缺血。这是用户唯一需要定义模式的时候,因为petstorm将它转换为所有支持的框架格式,如PySpark、Tensorflow和纯Python。
un缺血的实例被序列化为Parquet存储元数据中的自定义字段,因此一个到数据集的路径足以读取它。
下面的例子展示了un缺血实例是如何创建的。必需的字段属性包括:字段名、数据类型(由NumPy数据类型表示)、多维数组形状、用于数据编码/解码的编解码器以及字段是否为空。
HelloWorldSchema = un缺血('HelloWorldSchema', [un缺血afield ('id', np.int32, (), ScalarCodec(IntegerType()), False), un缺血afield ('image1', np。uint8, (128, 256,3) CompressedImageCodec('png'), False), unshadowaffield ('array_4d', np. nuethaffield)。uint8, (None, 128, 30, None), NdarrayCodec(), False),])
我们使用PySpark来编写petstorm数据集。下面的示例展示了如何使用我们的库创建一个1,000行数据集。
rows_count = 10 with materialize_dataset(spark, output_url, HelloWorldSchema, rowgroup_size_mb): rows_rdd = sc.parallelize(range(rows_count))\ .map(row_generator)\ .map(lambda x: dict_to_spark_row(HelloWorldSchema, x)) spark。creatatedataframe (rows_rdd, HelloWorldSchema.as_spark_schema()) \ .write \ .parquet('file:// tmp/hello_world_dataset')
- materialize_dataset上下文管理器在开始时执行必要的配置,并在结束时写出特定于petstorm的元数据。输出URL可以指向HDFS或文件系统位置。
- Rowgroup_size_mb以兆字节为单位定义Parquet行组的目标大小。
- row_generator是一个返回匹配HelloWorldSchema.
- Dict_to_spark_row根据HelloWorldSchema并将字典转换为apyspark。行对象。
读取数据集
接下来,我们将概述如何从普通的Python代码以及两个常用的机器学习框架Tensorflow和Pytorch中读取数据集。
Python
Reader实例可以直接从Python代码访问petstorm数据集。Reader实现了迭代器接口,因此浏览示例非常简单:
使用Reader('file:// tmp/hello_world_dataset')作为Reader: # Pure python for sample in Reader: print(sample.id) plt.imshow(sample.image1)
Tensorflow
下面的例子展示了如何将数据集流到Tensorflow中。例子是一个命名元组,键自动从un缺血派生,值为tf.tensor对象:
用Reader('file:// tmp/hello_world_dataset')作为Reader:张量= tf_tenors (Reader) with tf.Session()作为sess: sample = sess.run(张量)print(sample.id) plt.imshow(sample.image1)
在不久的将来,用户将能够使用tf.data.Dataset接口。
Pytorch
petstorm数据集可以通过一个适配器类合并到Pytorch中,petastorm.pytorch.DataLoader,如下:
with DataLoader(Reader('file:// tmp/hello_world_dataset')) as train_loader: sample = next(iter(train_loader)) print(sample['id']) plt.plot(sample['image1'])
使用Spark分析数据集
使用Spark本身支持的Parquet数据格式,可以使用广泛的Spark工具来分析和操作数据集。下面的例子展示了如何将petstorm数据集作为Spark RDD对象读取:
rdd = dataset_as_rdd('file:// tmp/hello_world_dataset', spark, [HelloWorldSchema. rdd = dataset_as_rdd('file:// tmp/hello_world_dataset', spark, [HelloWorldSchema.]打印(rdd.first().id)
标准的PySpark工具可以用来处理petstorm数据集。注意,数据没有被解码,只有具有相应的Parquet格式的原生表示(例如标量)的字段值是有意义的:
#从paraframe文件中创建一个dataframe对象dataframe = spark.read.parquet(dataset_url) #显示一个模式dataframe. printschema() #统计所有dataframe. Count() #显示一个单列dataframe.select('id').show()
SQL可以用来查询petstorm数据集:
Number_of_rows = spark。sql('SELECT count(id)' 'from parquet. '文件:// tmp/hello_world_dataset ").collect()
Petastorm特性
petstorm集成了多种功能,以支持自动驾驶算法的训练场景。这包括行过滤、数据分片、变换、访问字段子集以及支持时间序列数据(n-grams)的有效实现。
对于附加上下文,典型数据集的结构包括:
- 在自动驾驶汽车测试过程中收集传感器采集信号的多个柱。这些包括摄像机、激光雷达和雷达。
- 手工生成的标签存储为一行中的字段。
行按时间顺序排序,按运行情况分组。行组大小通常在30到100之间。
并行执行策略
petstorm为并行数据加载和解码操作提供了两种策略:一种基于线程池,另一种基于进程池实现。策略选择取决于读取的数据类型。
通常,当一行包含已编码的高分辨率图像时,应该使用线程池策略。在这种情况下,大部分处理时间都用于通过c++代码解码图像。没有Python全局解释器锁(GIL)被关押在那个时候。
当行大小较小时,进程池策略更合适。在这种情况下,大部分处理都是由纯Python代码完成的。为了克服GIL导致的执行序列化,必须并行运行多个进程。
字格
为了更好地解释环境和/或预测环境中行为者的未来行为,可以使用观察到的环境的动态的模型需要时间上下文。
当底层数据按时间排列时,petstorm可以提供这样的时间上下文。如果从petstorm Reader对象请求一个n-gram,那么随后的行将被分组到单个训练样本中。
下面的二元图显示了长度为3的n-克的分组。反病毒日志#0和反病毒日志#1显示了两个不同的车载传感器数据记录:
注意,n-gram的组不能跨越Parquet行组。在上面的图3中,从行组0生成三个3-g;只有一个来自行组1,另外三个来自行组2。n-g节省了IO和CPU带宽,因为没有磁盘上的数据复制,也没有重复的加载/解码。
n-g是按照它们在数据集中出现的顺序产生的,因此用户需要使数据集中的顺序与他们的访问模式相匹配。
洗牌
如果数据集支持n-gram访问模式,则其行按时间戳排序。Parquet只支持从行组中加载完整数量的行。因此,数据将被加载到高度相关的样本组中(例如,从自动驾驶汽车的摄像头获得的两个随后的相机图像将非常相似)。结果示例之间的高相关性是不可取的,已知会降低训练算法的性能。为了减少相关性,petstorm的特点是洗牌。
下图4展示了《petstorm》中不同的洗牌机制:
petstorm从数据集中的所有行组中随机选择一组行组。解码后的行被放入行变换缓冲区中。从该缓冲区中选择一个随机行并返回给用户。
行谓词(筛选器)
对于在多个实验中被多个研究人员重用的数据集实例,重要的是能够提供有效的机制来选择行的子集。雷竞技是骗人的petstorm支持行谓词。如果可用的话,petstorm行谓词使用Parquet存储分区,并且只有被约束的列从存储中加载。
未来,我们计划将对Parquet谓词下推特性的支持集成到petstorm中,以进一步加快查询速度。
行组索引
petstorm支持将键和一个或多个行组集合之间的映射与数据集一起存储。这种映射有助于快速查找匹配特定条件的行组。在可能使用“行谓词”的行级别需要附加筛选。
用于分布式训练的分片
在分布式培训设置中,每个工作人员通常在数据的一个子集上进行培训。该子集与提供给参与训练的其他机器的子集正交。petstorm支持将数据集读时分片到一个正交样本集。下面的图5演示了petstorm如何在共享数据集上促进分布式训练:
本地缓存
petstorm支持在本地存储上缓存数据。当网络连接缓慢或带宽昂贵时,这就派上了用场。下面的图6描述了这个设置:
在第一个纪元中,从远程存储中读取一组样本,并将它们保存到本地缓存中。在随后的时间段里,所有数据都将从本地缓存中读取。
Petastorm架构
petstorm的设计目标包括:
- 单一数据模式定义驱动数据的编码和解码。
- 高数据加载带宽可用于ML框架和纯Python代码。
- 利用Apache Spark作为分布式集群计算框架来生成数据集。
- 核心petstorm组件的纯Python、ML平台不可知的实现。
- 提供给Tensorflow和PyTorch框架的界面的原生外观和感觉。
下面的图7显示了数据集生成和读取过程中使用的petstorm组件。
- etl包实现了生成数据集的功能。
- 的读者是培训/评估代码使用的主要数据加载引擎。的读者是用纯Python实现的,不依赖于任何ML框架(Tensorflow, Pytorch),可以由纯Python代码实例化和使用。
- petstorm包括Tensorflow和PyTorch适配器,它们为各自的框架提供本机接口。
- 数据集生成和数据加载代码都引用了un缺血。
镶木地板修复
当我们开始使用Apache Parquet时,我们在让Spark用我们的数据编写Parquet数据集方面遇到了一些麻烦。原因主要在于我们的行大小,这些行有几个兆字节的字段。我们的第一个问题是数据集中的行组比预期的要大得多,这导致了内存不足等问题。深入研究代码,我们发现parquet-mr在检查行组是否达到用户设置的目标大小之前,强制行组中的任意最小值为100行。评估项目时,已经有问题提交伴随着把请求,因此我们能够在需要的更改中分叉回购和移植,以获得更合理的行组大小。
尽管我们可以实现更合理的行组大小,但我们注意到,当我们试图创建更小的行组或使用更大的字段时,Spark作业会耗尽内存。通过深入研究结果数据集,当我们添加新字段或减小行组大小时,存储文件元数据的Parquet页脚显著增加。
事实证明,Parquet正在为表示图像或其他多维数组的巨大二进制字段生成统计数据。由于Parquet统计信息将在页脚中存储这些字段的每个行组的最小值和最大值,如果行组的大小减小到足够大,则页脚将变得太大,无法装入内存。看看parquet-mr库,这个问题已经解决了;然而,我们使用的是Spark 2.1.0,它依赖于Parquet 1.8.1。为了解决这个问题,我们升级了Spark发行版,以使用Parquet 1.8.3(带有最小行组大小的修复),并利用了这些改进。
下一个步骤
以下是我们计划在不久的将来整合到petstorm中的一些改进:
减少随机变换的RAM占用
大行组有助于提高IO利用率和数据加载速率。然而,它们也增加了输入训练算法的后续样本之间的相关性。我们正积极致力于改进洗牌机制。
谓词下推支持
Pyarrow很快将支持谓词下推。我们希望利用它进行更快的行过滤。
改进的Spark集成
当从Spark访问petstorm数据集时,一些操作似乎要花费比预期更多的时间或内存。需要对Parquet库代码进行进一步的研究,以理解有效地处理非常大的字段的额外细微差别。
可选存储格式
petstorm抽象了底层存储格式。除了Parquet之外,还可以将其他存储格式集成到petstorm中,从而提供了进一步的实验自由和数据加载性能调优。
我们希望Petastorm将帮助其他人创建可重用的、易于使用的和高性能的数据集。通过开源我们的工作,我们希望听到来自深度学习社区的输入和建议。我们欢迎反馈和贡献:请报告您遇到的任何问题,分享加速,并发送拉请求。
如果你有兴趣与Uber ATG合作,为自动驾驶汽车创建机器学习系统,考虑申请我们团队的一个职位!
订阅我们的通讯以跟上优步工程的最新创新。






