用Dask并行化特征工程!

作者:William Koehrsen

当计算非常缓慢时,需要考虑的最重要的问题是:"当前我们的瓶颈是什么?"一旦你找出了解决方案,接下来的逻辑步骤就是找出如何度过瓶颈期。

用Dask并行化特征工程!


在这里,我们的瓶颈是没有充分利用我们的硬件资源。例如,我们的计算机有8个核心,然而只有一个核心在运行计算。如果我们编写的代码不能"照顾"到我们所现有的全部资源。那么简单地更换一个更大的机器——比如说扩充RAM或核心的容量——是无法解决问题的。因此,我认为最佳的解决方案是重写代码,尽可能有效地利用我们现有的硬件。

在本文中,我们将看到如何重构我们的自动化特征工程代码,以便在所有笔记本的核心上并行运行,从而减少我们的运行计算时间(预估可以减少八倍以上的时间)。我们将使用两个开源库,即用于自动特征工程(https://medium.com/@williamkoehrsen/why-automated-feature-engineering-will-change-the-way-you-do-machine-learning-5c15bf188b96)的Featuretools(https://www.featuretools.com/)和用于Dask里的并行处理工具(https://dask.pydata.org/),然后用它们解决实际问题。

用Dask并行化特征工程!


文中的一些具体的解决方案仅适用于上述问题。但是其中一些通用方法你可以学习用于自己的数据集。

虽然在这里我们将坚持使用一台多核计算机,但在将来,我们将使用相同的方法在多台计算机上运行计算。

完整的代码实现可以在GitHub上的Jupyter笔记本(https://github.com/Featuretools/Automated-Manual-Comparison/blob/master/Loan%20Repayment/notebooks/Featuretools%20on%20Dask.ipynb)中找到。如果你还不熟悉Featuretools,请点击:https://towardsdatascience.com/automated-feature-engineering-in-python-99baf11cc219。在本文,我们的主要关注点是Dask和特征工程的使用,会跳过一些自动化特征工程的细节。

问题:数据太多,时间不够。

在这里,我们将应用自动特征工具解决住宅信贷违约风险的问题(即预测客户是否会按时偿还贷款),我们有大量的数据,这导致我们需要很长的特征计算时间(https://www.kaggle.com/c/home-credit-default-risk)。使用深度特征合成(https://www.featurelabs.com/blog/deep-feature-synthesis/),我们能够从7个数据表、共计5800万行客户信息中自动生成1820个特征,调用这个只有一个核心的函数需要花费25个小时,即使是在一个64 GB内存的EC2实例上也是如此!

谈到我们的EC2实例 ,通常我们的笔记本电脑 有8个核心,为了加快计算速度,我们不需要更多的内存,但我们需要更好的利用这些核心Featuretools确实允许并行处理,方法是在调用深度特征合成时设置n_jobs参数。但是,目前该函数必须将完整的EntitySet发送到机器上的所有核心中 。使用大型EntitySet,如果每个核心的内存耗尽,这可能会产生一些问题。我们的改良方案是使用并行化,从目前的结果看来Dask解决了我们的问题。

解决方案:制造很多小问题

方法是将一个大问题分解成多个较小的问题,然后使用 Dask一次运行多个小问题,每个问题位于不同的核心上。这里最重要的一点是,我们必须确保每个问题 都独立于其他任务,以便它们能够同时运行。因为我们正在为数据集中的每个客户创建特征,所以我们目前的主要任务是为客户创建特征矩阵。

用Dask并行化特征工程!


我们的做法概述如下:

  1. 通过对数据进行划分,将一个大问题转化为许多小问题。

  2. 编写函数,从每个子数据集中生成一个特征矩阵。

  3. 使用dask在所有核心上并行运行步骤2。

最后,我们得到许多较小的特征矩阵,然后我们把它们连接到一个最终的特征矩阵中。同样的方法,将一个大问题分解为多个并行运行的小问题,可以缩放到任意大小的数据集,并分布到其他库中实现计算,例如使用PySPark(http://spark.apache.org/docs/2.2.0/api/python/pyspark.html)的SPark(https://spark.apache.org/)。

无论我们拥有什么资源,我们都希望尽可能有效地利用它们,我们的目的是采用同样的框架处理更大的数据集。

分区数据:划分和征服

我们的第一步是创建原始数据集的小分区,每个分区包含来自七个表的所有信息,用于处理客户信息。然后,我们可以使用每个分区独立计算一组客户信息的特征矩阵。

完成此操作的方法是:获取所有客户信息的列表,将其分解为104个子列表,然后迭代这些子列表,最后将数据细分为只包括子列表中的客户信息,并将结果数据保存到磁盘中。此过程的基本伪代码是:

用Dask并行化特征工程!


用Dask并行化特征工程!


104个分区是根据以下几个准则选择的:

  1. 我们希望至少有和核心一样多的分区,并且这个数目应该是核心数量的倍数。

  2. 每个分区必须小到只能容纳单个核心的内存。

  3. 分区越多,完成每个任务的时间变化就越小。

(这样做额外的优化点是可以减少内存使用。这使我们的整个数据集从4GB缩小到到大约2GB左右。我建议你阅读这篇文章:https://pandas.pydata.org/pandas-docs/stable/categorical.html 。因为只有这样你才能有效地使用它们。

将所有104个分区保存到磁盘需要大约30分钟,但这样的步骤,我们以后不需重复第二次。

用Dask并行化特征工程!


来自分区的实体集

Featuretools中的实体集是一种有用的数据结构,因为它包含多个表及其之间的关系。若要创建一个EntitySet从分区中,我们需要编写一个函数,该函数将从磁盘读取分区,然后生成EntitySet以及它们之间的关系。

此步骤的伪代码是:

用Dask并行化特征工程!


用Dask并行化特征工程!


注意,这个时候函数返回了EntitySet,而不是像对分区的数据那样保存它。的确保存原始数据是解决此问题的一个更好的选择,但是我们可能更希望修改EntitySets (比如添加有趣的值或领域知识特征 ),而不更改原始数据。动态生成EntitySet,然后将其传递到下一阶段:计算特征矩阵。

一个实体集合的特征矩阵

函数feature_matrix_from_entityset完全按照我们当初命名的目的那样做:即接受先前创建的EntitySet(实体集),并使用深度特征合成技术生成一个包含数千个特征的特征矩阵。然后将特征矩阵保存到磁盘中。为了确保每个分区都有相同的特征,我们只生成一次特征定义,然后使用Featuretools函数calculate_feature_matrix。

下面是整个函数(我们传入一个带有EntitySet和分区号的字典,这样我们就可以保存具有唯一名称的函数矩阵):

用Dask并行化特征工程!


用Dask并行化特征工程!


用Dask并行化特征工程!


chunk_size是这个调用中唯一棘手的部分:它用于将特征矩阵计算分解为更小的部分,但是由于我们已经对数据进行了分区,这不再是必要的,只要整个EntitySet能够容纳内存就好。同时我发现,将chunk_size设置为观察时的数量,可以有效的节省时间。

现在我们有了从磁盘上的数据分区到特征矩阵所需要的所有单独的部分。我们之前所做的步骤包含了大部分准备工作,现在让Dask并行运行变的非常简单。

Dask:释放你的机器

Dask是一个并行计算库,它允许我们同时进行多项计算,或者使用一台机器(本地)上的进程/线程,或者使用多台单独的计算机(集群)。对于一台机器,Dask允许我们使用线程或进程并行运行计算。

进程不共享内存并在单个核心上运行,更适合于不需要通信的计算密集型任务。线程共享内存,但在Python中,由于全局解释器锁(GIL)的原因,两个线程不能在同一个程序中同时操作,只有一些操作可以使用线程并行运行。(有关线程/进程的更多信息,请点击:https://medium.com/@bfortuner/python-multithreading-vs-multiprocessing-73072ce5600b)

由于计算特征矩阵属于计算密集型,并且与之对应的每个分区都可以独立完成,所以在本文我们希望使用进程。任务不需要共享内存,因为每个特征矩阵不依赖于其他特征矩阵。在计算机科学方面,通过对数据进行划分,我们解决了我们的问题。不过这是一场"尴尬的"并行(http://www.cs.iusb.edu/~danav/teach/b424/b424_23_embpar.html),因为我们的核心不需要交流。

如果我们使用进程 (按下面的代码 那样做),我们将有8个核心,每个核心分配2GB内存(总共16 GB,具体内存取决于你的笔记本电脑)。

用Dask并行化特征工程!


为了检查一切是否顺利,我们可以导航到localhost:8787,其中Dask已经为我们设置了一个Bokeh仪表板。在核心选项卡上,我们看到8个内存为2GB的核心:

用Dask并行化特征工程!


目前,所有8个核心都处于闲置状态,因为我们没有给它们分配任何事情。下一步是创建一个"Dask包",你可以理解为是Dask分配给核心的任务列表。我们使用db.from_sequence方法和分区路径列表来生成"Dask包"。

用Dask并行化特征工程!


然后,我们将计算任务映射到Dask包上。映射意味着获取一个函数和一个输入列表,并将该函数应用于列表中的每个元素。因为我们首先需要从每个分区创建一个EntitySet(实体集),所以我们将相关的函数映射到"Dask包"上:

用Dask并行化特征工程!


接下来,我们进行另一次映射,这一次是为了生成特征矩阵:

用Dask并行化特征工程!


这段代码将获取第一个映射 的输出(即实体集 ) ,并将其传递给第二个映射。这些步骤实际上并不运行计算,而是列出Dask随后将分配给核心的任务列表。要运行任务并生成特征矩阵,我们调用:

用Dask并行化特征工程!


DASK根据由映射构造的任务图(有向无环图)自动将任务分配给核心。当计算发生时,我们可以在Bokeh仪表板上查看任务图和状态(https://en.wikipedia.org/wiki/Directed_acyclic_graph)。

用Dask并行化特征工程!


上图中,左边的表示entity_set_from_partition函数,右边的是feature_matrix_from_entityset函数。从这个图中,我们可以看到两个函数之间有依赖关系,但每个分区的特征矩阵计算之间没有依赖关系。

Bokeh仪表板上还有许多其他可视化方法,包括任务流(下图左)和操作概要(下图右):

用Dask并行化特征工程!


从任务流中,我们可以看到,所有八个核心同时使用,总共有208项任务要完成。配置文件告诉我们,花费时间最长的操作是计算每个分区的特征矩阵。在我的MacBook上,构建和保存所有104个功能矩阵需要6200秒(1.75小时)。这是一个相当大的改进,这就是重写代码到尽可能高效地使用可用硬件带来的好处。

我们没有更高一级的计算机,而是重写代码,以便最有效地利用我们拥有的资源。然后,如果我们有更高一级的计算机,我们可以使用相同的代码,以进一步减少计算时间。

构造一个特征矩阵

一旦我们有了单独的特征矩阵,如果我们使用增量学习(https://blog.bigml.com/2013/03/12/machine-learning-from-streaming-data-two-problems-two-solutions-two-concerns-and-two-lessons/),我们就可以直接使用它们进行建模。另一种选择是创建一个特征矩阵,可以在纯Python使用pandas完成:

用Dask并行化特征工程!


用Dask并行化特征工程!


单一特征矩阵有350,000行和1,820列,形状与我第一次使用单个核心时的形状相同。

用Dask并行化特征工程!


结论

我们不应该考虑如何获得更好的计算设备,而应该考虑如何尽可能有效地使用我们拥有的硬件。在本文中,我们介绍了如何使用Dask并行代码,它允许我们使用笔记本电脑完成计算,计算速度是单核计算速度的8倍。

我们设计的解决方案主要利用了几个关键概念:

  1. 将问题分解成更小的、独立的块。

  2. 编写函数时一次处理一个块。

  3. 将每个块委托给一个核心并行计算。

现在我们不仅可以利用Featuretools自动特征工程的的速度和建模性能,还可以使用Dask并行地执行计算,并获得更快的结果。此外,我们还可以使用相同的方法来扩展到更大的数据集,从而使我们能够很好地处理任何机器学习问题。


来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/31509949/viewspace-2212324/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/31509949/viewspace-2212324/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值